|
| 1 | +#include "AddonRegistry.hpp" |
| 2 | +#include <cctype> // for std::isalnum |
| 3 | + |
| 4 | +#ifndef NODE_API_DEFAULT_MODULE_API_VERSION |
| 5 | +#define NODE_API_DEFAULT_MODULE_API_VERSION 8 |
| 6 | +#endif |
| 7 | + |
| 8 | +using namespace facebook; |
| 9 | + |
| 10 | +namespace { |
| 11 | +napi_status napi_emplace_named_property_object(napi_env env, |
| 12 | + napi_value object, |
| 13 | + const char *utf8Name, |
| 14 | + napi_value *outObject) { |
| 15 | + bool propertyFound = false; |
| 16 | + napi_status status = napi_has_named_property(env, object, utf8Name, &propertyFound); |
| 17 | + assert(napi_ok == status); |
| 18 | + |
| 19 | + assert(nullptr != outObject); |
| 20 | + if (propertyFound) { |
| 21 | + status = napi_get_named_property(env, object, utf8Name, outObject); |
| 22 | + } else { |
| 23 | + // Need to create it first |
| 24 | + status = napi_create_object(env, outObject); |
| 25 | + assert(napi_ok == status); |
| 26 | + |
| 27 | + status = napi_set_named_property(env, object, utf8Name, *outObject); |
| 28 | + } |
| 29 | + |
| 30 | + return status; |
| 31 | +} |
| 32 | + |
| 33 | +void sanitizeLibraryNameInplace(std::string &name) { |
| 34 | +#if USING_PATCHED_BABEL_PLUGIN |
| 35 | + // Strip the extension (if present) |
| 36 | + // NOTE: This is needed when working with updated Babel plugin |
| 37 | + if (auto pos = name.find(".node"); std::string::npos != pos) { |
| 38 | + name = name.substr(0, pos); |
| 39 | + } |
| 40 | +#endif |
| 41 | + |
| 42 | + for (char &c : name) { |
| 43 | + if (!std::isalnum(c)) { |
| 44 | + c = '-'; |
| 45 | + } |
| 46 | + } |
| 47 | +} |
| 48 | +} // namespace |
| 49 | + |
| 50 | +namespace callstack::nodeapihost { |
| 51 | + |
| 52 | +AddonRegistry::NodeAddon& AddonRegistry::loadAddon(std::string packageName, |
| 53 | + std::string subpath) { |
| 54 | + const std::string fqan = packageName + subpath.substr(1); |
| 55 | + auto [it, inserted] = |
| 56 | + trackedAddons_.try_emplace(fqan, NodeAddon(packageName, subpath)); |
| 57 | + NodeAddon &addon = it->second; |
| 58 | + |
| 59 | + sanitizeLibraryNameInplace(packageName); |
| 60 | + sanitizeLibraryNameInplace(subpath); |
| 61 | + const std::string libraryName = packageName + subpath; |
| 62 | + |
| 63 | + if (inserted || !it->second.isLoaded()) { |
| 64 | +#if defined(__APPLE__) |
| 65 | + const std::string libraryPath = "@rpath/" + libraryName + ".framework/" + libraryName; |
| 66 | + tryLoadAddonAsDynamicLib(addon, libraryPath); |
| 67 | +#elif defined(__ANDROID__) |
| 68 | + const std::string libraryPath = "lib" + libraryName + ".so"; |
| 69 | + tryLoadAddonAsDynamicLib(addon, libraryPath); |
| 70 | +#else |
| 71 | + abort(); |
| 72 | +#endif |
| 73 | + } |
| 74 | + |
| 75 | + return addon; |
| 76 | +} |
| 77 | + |
| 78 | +bool AddonRegistry::tryLoadAddonAsDynamicLib(NodeAddon &addon, const std::string &path) { |
| 79 | + // Load addon as dynamic library |
| 80 | + typename LoaderPolicy::Module library = LoaderPolicy::loadLibrary(path.c_str()); |
| 81 | + if (nullptr != library) { |
| 82 | + // pending addon remains empty, we should look for the symbols... |
| 83 | + typename LoaderPolicy::Symbol initFn = LoaderPolicy::getSymbol(library, "napi_register_module_v1"); |
| 84 | + if (nullptr != initFn) { |
| 85 | + addon.initFun_ = (napi_addon_register_func)initFn; |
| 86 | + addon.moduleApiVersion_ = NODE_API_DEFAULT_MODULE_API_VERSION; |
| 87 | + // This solves https://github.com/callstackincubator/react-native-node-api-modules/issues/4 |
| 88 | + typename LoaderPolicy::Symbol getVersionFn = LoaderPolicy::getSymbol(library, "node_api_module_get_api_version_v1"); |
| 89 | + if (nullptr != getVersionFn) { |
| 90 | + addon.moduleApiVersion_ = ((node_api_addon_get_api_version_func)getVersionFn)(); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + if (nullptr != addon.initFun_) { |
| 95 | + addon.moduleHandle_ = (void *)library; |
| 96 | + addon.loadedFilePath_ = path; |
| 97 | + } |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +jsi::Value AddonRegistry::instantiateAddonInRuntime(jsi::Runtime &rt, NodeAddon &addon) { |
| 102 | + // We should check if the module has already been initialized |
| 103 | + assert(true == addon.isLoaded()); |
| 104 | + assert(addon.moduleApiVersion_ > 0 && addon.moduleApiVersion_ <= 10); |
| 105 | + |
| 106 | + napi_status status = napi_ok; |
| 107 | + napi_env env = reinterpret_cast<napi_env>(rt.createNodeApiEnv(addon.moduleApiVersion_)); |
| 108 | + |
| 109 | + // Create the "exports" object |
| 110 | + napi_value exports; |
| 111 | + status = napi_create_object(env, &exports); |
| 112 | + assert(napi_ok == status); |
| 113 | + |
| 114 | + // Call the addon init function to populate the "exports" object |
| 115 | + // Allowing it to replace the value entirely by its return value |
| 116 | + // TODO: Check the return value (see Node.js specs) |
| 117 | + exports = addon.initFun_(env, exports); |
| 118 | + |
| 119 | + // "Compute" the Fully Qualified Addon Path |
| 120 | + const std::string fqap = addon.packageName_ + addon.subpath_.substr(1); |
| 121 | + |
| 122 | + { |
| 123 | + napi_value descriptor; |
| 124 | + status = createAddonDescriptor(env, exports, &descriptor); |
| 125 | + assert(napi_ok == status); |
| 126 | + |
| 127 | + napi_value global; |
| 128 | + napi_get_global(env, &global); |
| 129 | + assert(napi_ok == status); |
| 130 | + |
| 131 | + status = storeAddonByFullPath(env, global, fqap, descriptor); |
| 132 | + assert(napi_ok == status); |
| 133 | + } |
| 134 | + |
| 135 | + return lookupAddonByFullPath(rt, fqap); |
| 136 | +} |
| 137 | + |
| 138 | +napi_status AddonRegistry::createAddonDescriptor(napi_env env, napi_value exports, napi_value *outDescriptor) { |
| 139 | + // Create the descriptor object |
| 140 | + assert(nullptr != outDescriptor); |
| 141 | + napi_status status = napi_create_object(env, outDescriptor); |
| 142 | + |
| 143 | + // Point the `env` property to the current `napi_env` |
| 144 | + if (napi_ok == status) { |
| 145 | + napi_value env_value; |
| 146 | + status = napi_create_external(env, env, nullptr, nullptr, &env_value); |
| 147 | + if (napi_ok == status) { |
| 148 | + status = napi_set_named_property(env, *outDescriptor, "env", env_value); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + // Cache the addons exports in descriptor's `exports` property |
| 153 | + if (napi_ok == status) { |
| 154 | + status = napi_set_named_property(env, *outDescriptor, "exports", exports); |
| 155 | + } |
| 156 | + |
| 157 | + return status; |
| 158 | +} |
| 159 | + |
| 160 | +napi_status AddonRegistry::storeAddonByFullPath(napi_env env, napi_value global, const std::string &fqap, napi_value descriptor) { |
| 161 | + // Get the internal registry object |
| 162 | + napi_value registryObject; |
| 163 | + napi_status status = napi_emplace_named_property_object(env, global, kInternalRegistryKey, ®istryObject); |
| 164 | + assert(napi_ok == status); |
| 165 | + |
| 166 | + status = napi_set_named_property(env, registryObject, fqap.c_str(), descriptor); |
| 167 | + return status; |
| 168 | +} |
| 169 | + |
| 170 | +jsi::Value AddonRegistry::lookupAddonByFullPath(jsi::Runtime &rt, const std::string &fqap) { |
| 171 | + // Get the internal registry object |
| 172 | + jsi::Object global = rt.global(); |
| 173 | + if (!global.hasProperty(rt, kInternalRegistryKey)) { |
| 174 | + // Create it first |
| 175 | + jsi::Object registryObject = jsi::Object(rt); |
| 176 | + global.setProperty(rt, kInternalRegistryKey, registryObject); |
| 177 | + } |
| 178 | + jsi::Value registryValue = global.getProperty(rt, kInternalRegistryKey); |
| 179 | + jsi::Object registryObject = registryValue.asObject(rt); |
| 180 | + |
| 181 | + // Lookup by addon path |
| 182 | + jsi::Value addonValue(nullptr); |
| 183 | + if (registryObject.hasProperty(rt, fqap.c_str())) { |
| 184 | + addonValue = registryObject.getProperty(rt, fqap.c_str()); |
| 185 | + } |
| 186 | + return addonValue; |
| 187 | +} |
| 188 | + |
| 189 | +} // namespace callstack::nodeapihost |
0 commit comments