Skip to content

Commit c8f339d

Browse files
committed
Copy files from PoC
1 parent dcddbea commit c8f339d

File tree

7 files changed

+306
-3
lines changed

7 files changed

+306
-3
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#pragma once
2+
#include <assert.h>
3+
4+
#if defined(__APPLE__) || defined(__ANDROID__)
5+
#include <dlfcn.h>
6+
#include <stdio.h>
7+
8+
struct PosixLoader {
9+
using Module = void *;
10+
using Symbol = void *;
11+
12+
static Module loadLibrary(const char *filePath) {
13+
assert(NULL != filePath);
14+
Module result = dlopen(filePath, RTLD_NOW | RTLD_LOCAL);
15+
if (NULL == result) {
16+
fprintf(stderr, "NapiHost: Failed to load library '%s': %s", filePath,
17+
dlerror());
18+
}
19+
return result;
20+
}
21+
22+
static Symbol getSymbol(Module library, const char *name) {
23+
assert(NULL != library);
24+
assert(NULL != name);
25+
Symbol result = dlsym(library, name);
26+
// if (NULL == result) {
27+
// NSLog(@"NapiHost: Cannot find '%s' symbol!", name);
28+
// }
29+
return result;
30+
}
31+
32+
static void unloadLibrary(Module library) {
33+
if (NULL != library) {
34+
dlclose(library);
35+
}
36+
}
37+
};
38+
#endif
39+
40+
#if defined(_WIN32)
41+
struct Win32Loader {
42+
using Module = HMODULE;
43+
using Symbol = void *;
44+
45+
static Module loadLibrary(const char *filePath) {
46+
assert(NULL != filePath);
47+
Module result = LoadLibrary(filePath);
48+
if (NULL == result) {
49+
// TODO: Handle the error case... call GetLastError() that gives us error
50+
// code as DWORD
51+
}
52+
return result;
53+
}
54+
55+
static Symbol getSymbol(Module library, const char *name) {
56+
assert(NULL != library);
57+
assert(NULL != name);
58+
Symbol result = GetProcAddress(library, name);
59+
if (NULL == result) {
60+
// TODO: Handle the error case... call GetLastError() that gives us error
61+
// code as DWORD
62+
}
63+
return result;
64+
}
65+
66+
static void unloadLibrary(Module library) {
67+
if (NULL != library) {
68+
FreeLibrary(library);
69+
}
70+
}
71+
};
72+
73+
struct WinRTLoader {
74+
using Module = HMODULE;
75+
using Symbol = void *;
76+
77+
static Module loadLibrary(const char *filePath) {
78+
assert(NULL != filePath);
79+
Module result = LoadPackagedLibrary(filePath);
80+
if (NULL == result) {
81+
// TODO: Handle the error case... call GetLastError() that gives us error
82+
// code as DWORD
83+
}
84+
return result;
85+
}
86+
87+
static Symbol getSymbol(Module library, const char *name) {
88+
assert(NULL != library);
89+
assert(NULL != name);
90+
Symbol result = GetProcAddress(library, name);
91+
if (NULL == result) {
92+
// TODO: Handle the error case... call GetLastError() that gives us error
93+
// code as DWORD
94+
}
95+
return result;
96+
}
97+
98+
static void unloadLibrary(Module library) {
99+
if (NULL != library) {
100+
FreeLibrary(library);
101+
}
102+
}
103+
};
104+
#endif
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#include "CxxNodeApiHostModule.hpp"
2+
3+
using namespace facebook;
4+
5+
extern napi_status
6+
hermes_create_napi_env(::hermes::vm::Runtime &runtime, bool isInspectable,
7+
std::shared_ptr<jsi::PreparedScriptStore> scriptCache,
8+
const ::hermes::vm::RuntimeConfig &runtimeConfig,
9+
napi_env *env);
10+
11+
namespace callstack::nodeapihost {
12+
13+
CxxNodeApiHostModule::CxxNodeApiHostModule(
14+
std::shared_ptr<react::CallInvoker> jsInvoker)
15+
: TurboModule(CxxNodeApiHostModule::kModuleName, jsInvoker) {
16+
methodMap_["requireNodeAddon"] =
17+
MethodMetadata{1, &CxxNodeApiHostModule::requireNodeAddon};
18+
}
19+
20+
jsi::Value
21+
CxxNodeApiHostModule::requireNodeAddon(jsi::Runtime &rt,
22+
react::TurboModule &turboModule,
23+
const jsi::Value args[], size_t count) {
24+
auto &thisModule = static_cast<CxxNodeApiHostModule &>(turboModule);
25+
if (1 == count && args[0].isString()) {
26+
return thisModule.requireNodeAddon(rt, args[0].asString(rt));
27+
}
28+
return jsi::Value::undefined();
29+
}
30+
31+
jsi::Value CxxNodeApiHostModule::requireNodeAddon(jsi::Runtime &rt,
32+
const jsi::String path) {
33+
const std::string pathStr = path.utf8(rt);
34+
35+
// Check if this module has been loaded already, if not then load it...
36+
if (nodeAddons_.end() == nodeAddons_.find(pathStr)) {
37+
NodeAddon &addon = nodeAddons_[pathStr];
38+
if (!loadNodeAddon(addon, pathStr)) {
39+
return jsi::Value::undefined();
40+
}
41+
}
42+
43+
// Library has been loaded, make sure that the "exports" was populated.
44+
// If not, then just call the "napi_register_module_v1" function...
45+
NodeAddon &addon = nodeAddons_[pathStr];
46+
if (NULL == addon.cachedExports) {
47+
if (!initializeNodeModule(napiEnv_, addon)) {
48+
return jsi::Value::undefined();
49+
}
50+
}
51+
52+
// Look the exports up (using JSI) and return it...
53+
return rt.global().getProperty(rt, addon.generatedName.data());
54+
}
55+
56+
bool CxxNodeApiHostModule::loadNodeAddon(NodeAddon &addon,
57+
const std::string &path) const {
58+
typename LoaderPolicy::Symbol registratorFn = NULL;
59+
typename LoaderPolicy::Module library =
60+
LoaderPolicy::loadLibrary(path.c_str());
61+
if (NULL != library) {
62+
addon.moduleHandle = library;
63+
registratorFn = LoaderPolicy::getSymbol(library, "napi_register_module_v1");
64+
if (NULL != registratorFn) {
65+
addon.registerFn = (napi_addon_register_func)registratorFn;
66+
}
67+
}
68+
return NULL != registratorFn;
69+
}
70+
71+
bool CxxNodeApiHostModule::initializeNodeModule(jsi::Runtime &rt,
72+
NodeAddon &addon) {
73+
// We should check if the module has already been registered
74+
assert(NULL != addon.moduleHandle);
75+
assert(NULL != addon.registerFn);
76+
napi_status status = napi_ok;
77+
napi_value &exports = addon.cachedExports;
78+
79+
// Create the "exports" object
80+
status = napi_create_object(napiEnv_, &exports);
81+
if (napi_ok != status) {
82+
return false;
83+
}
84+
85+
// Call the addon registration function to populate the "exports" object
86+
addon.registerFn(napiEnv_, exports);
87+
88+
// Instead of using random numbers to avoid name clashes, we just use the
89+
// pointer address of the loaded module
90+
addon.generatedName.resize(32, '\0');
91+
snprintf(addon.generatedName.data(), addon.generatedName.size(),
92+
"RN$NodeAddon_%lX", (uintptr_t)addon.moduleHandle);
93+
94+
napi_value global;
95+
napi_get_global(napiEnv_, &global);
96+
napi_set_named_property(napiEnv_, global, addon.generatedName.data(),
97+
addon.cachedExports);
98+
return true;
99+
}
100+
101+
} // namespace callstack::nodeapihost
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#pragma once
2+
3+
#include <ReactCommon/TurboModule.h>
4+
#include <jsi/jsi.h>
5+
#include <node_api.h>
6+
7+
#include "AddonLoaders.hpp"
8+
9+
namespace callstack::nodeapihost {
10+
11+
class JSI_EXPORT CxxNodeApiHostModule : public facebook::react::TurboModule {
12+
public:
13+
static constexpr std::string kModuleName = "NodeApiHost";
14+
15+
CxxNodeApiHostModule(std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
16+
17+
static facebook::jsi::Value
18+
requireNodeAddon(facebook::jsi::Runtime &rt,
19+
facebook::react::TurboModule &turboModule,
20+
const facebook::jsi::Value args[], size_t count);
21+
facebook::jsi::Value requireNodeAddon(facebook::jsi::Runtime &rt,
22+
const facebook::jsi::String path);
23+
24+
protected:
25+
struct NodeAddon {
26+
void *moduleHandle;
27+
napi_addon_register_func registerFn;
28+
napi_value cachedExports;
29+
std::string generatedName;
30+
};
31+
std::unordered_map<std::string, NodeAddon> nodeAddons_;
32+
napi_env napiEnv_{};
33+
using LoaderPolicy = PosixLoader; // FIXME: HACK: This is temporary workaround
34+
// for my lazyness (work on iOS and Android)
35+
36+
bool loadNodeAddon(NodeAddon &addon, const std::string &path) const;
37+
bool initializeNodeModule(facebook::jsi::Runtime &rt, NodeAddon &addon);
38+
};
39+
40+
} // namespace callstack::nodeapihost
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#import "CxxNodeApiHostModule.hpp"
2+
3+
#define USE_CXX_TURBO_MODULE_UTILS 0
4+
#if defined(__has_include)
5+
#if __has_include(<ReactCommon/CxxTurboModuleUtils.h>)
6+
#undef USE_CXX_TURBO_MODULE_UTILS
7+
#define USE_CXX_TURBO_MODULE_UTILS 1
8+
#endif
9+
#endif
10+
11+
#if USE_CXX_TURBO_MODULE_UTILS
12+
#import <ReactCommon/CxxTurboModuleUtils.h>
13+
@interface NodeApiHost : NSObject
14+
#else
15+
#import <ReactCommon/RCTTurboModule.h>
16+
@interface NodeApiHost : NSObject <RCTBridgeModule, RCTTurboModule>
17+
#endif // USE_CXX_TURBO_MODULE_UTILS
18+
19+
@end
20+
21+
@implementation NodeApiHost
22+
#if USE_CXX_TURBO_MODULE_UTILS
23+
+ (void)load {
24+
facebook::react::registerCxxModuleToGlobalModuleMap(
25+
callstack::nodeapihost::CxxNodeApiHostModule::kModuleName,
26+
[](std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
27+
return std::make_shared<callstack::nodeapihost::CxxNodeApiHostModule>(
28+
jsInvoker);
29+
});
30+
}
31+
#else
32+
RCT_EXPORT_MODULE()
33+
34+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
35+
(const facebook::react::ObjCTurboModule::InitParams &)params {
36+
return std::make_shared<callstack::nodeapihost::CxxNodeApiHostModule>(
37+
params.jsInvoker);
38+
}
39+
#endif // USE_CXX_TURBO_MODULE_UTILS
40+
41+
@end

packages/react-native-node-api-modules/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,14 @@
5454
},
5555
"peerDependencies": {
5656
"react-native": "0.79.1"
57+
},
58+
"codegenConfig": {
59+
"name": "NodeApiHostSpec",
60+
"type": "modules",
61+
"jsSrcsDir": "src/host",
62+
"outputDir": {
63+
"ios": "ios/generated",
64+
"android": "android/generated"
65+
}
5766
}
5867
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { TurboModule } from "react-native";
2+
import { TurboModuleRegistry } from "react-native";
3+
4+
export interface Spec extends TurboModule {
5+
requireNodeAddon(path: string): void;
6+
}
7+
8+
export default TurboModuleRegistry.getEnforcing<Spec>("NodeApiHost");

packages/react-native-node-api-modules/tsconfig.react-native.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"noEmit": false,
77
"outDir": "dist",
88
"rootDir": "src",
9-
"types": ["react-native"],
9+
"types": ["react-native"]
1010
},
11-
"include": ["src/index.ts"]
12-
}
11+
"include": ["src/index.ts", "src/host"]
12+
}

0 commit comments

Comments
 (0)