@react-native-native/nativ-fabric 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/NativFabric.podspec +41 -0
  2. package/android/build.gradle +128 -0
  3. package/android/src/main/AndroidManifest.xml +2 -0
  4. package/android/src/main/cpp/CMakeLists.txt +59 -0
  5. package/android/src/main/cpp/NativBindingsInstaller.cpp +393 -0
  6. package/android/src/main/cpp/NativRuntime.cpp +508 -0
  7. package/android/src/main/java/com/nativfabric/ComposeHost.kt +26 -0
  8. package/android/src/main/java/com/nativfabric/NativContainerPackage.kt +35 -0
  9. package/android/src/main/java/com/nativfabric/NativContainerView.kt +51 -0
  10. package/android/src/main/java/com/nativfabric/NativContainerViewManager.kt +62 -0
  11. package/android/src/main/java/com/nativfabric/NativRuntime.kt +201 -0
  12. package/android/src/main/java/com/nativfabric/NativRuntimeModule.kt +37 -0
  13. package/android/src/main/java/com/nativfabric/compose/ComposeWrappers.kt +45 -0
  14. package/app.plugin.js +159 -0
  15. package/expo-module.config.json +6 -0
  16. package/ios/NativContainerComponentView.mm +137 -0
  17. package/ios/NativRuntime.h +36 -0
  18. package/ios/NativRuntime.mm +549 -0
  19. package/metro/Nativ.h +126 -0
  20. package/metro/compilers/android-compiler.js +339 -0
  21. package/metro/compilers/dylib-compiler.js +474 -0
  22. package/metro/compilers/kotlin-compiler.js +632 -0
  23. package/metro/compilers/rust-compiler.js +722 -0
  24. package/metro/compilers/static-compiler.js +1118 -0
  25. package/metro/compilers/swift-compiler.js +363 -0
  26. package/metro/extractors/cpp-ast-extractor.js +126 -0
  27. package/metro/extractors/kotlin-extractor.js +125 -0
  28. package/metro/extractors/rust-extractor.js +118 -0
  29. package/metro/index.js +236 -0
  30. package/metro/transformer.js +649 -0
  31. package/metro/utils/bridge-generator.js +50 -0
  32. package/metro/utils/compile-commands.js +104 -0
  33. package/metro/utils/cpp-daemon.js +344 -0
  34. package/metro/utils/dts-generator.js +32 -0
  35. package/metro/utils/include-resolver.js +73 -0
  36. package/metro/utils/kotlin-daemon.js +394 -0
  37. package/metro/utils/type-mapper.js +63 -0
  38. package/package.json +43 -0
  39. package/react-native.config.js +13 -0
  40. package/src/NativContainerNativeComponent.ts +9 -0
  41. package/src/NativeNativRuntime.ts +8 -0
  42. package/src/index.ts +4 -0
@@ -0,0 +1,41 @@
1
+ Pod::Spec.new do |s|
2
+ s.name = 'NativFabric'
3
+ s.version = '0.1.0'
4
+ s.summary = 'React Native Native — native component rendering + JSI runtime'
5
+ s.author = 'Kim Brandwijk'
6
+ s.homepage = 'https://react-native-native.github.io'
7
+ s.license = { :type => 'MIT' }
8
+ s.platforms = { :ios => '15.1' }
9
+ s.source = { git: '' }
10
+ s.static_framework = true
11
+
12
+ s.dependency 'React-Core'
13
+ s.dependency 'React-RCTFabric'
14
+ s.dependency 'React-Fabric'
15
+ s.dependency 'React-jsi'
16
+ s.dependency 'React-utils'
17
+ s.dependency 'React-graphics'
18
+ s.dependency 'React-rendererdebug'
19
+ s.dependency 'ReactCommon/turbomodule/core'
20
+ s.dependency 'ReactCodegen'
21
+ s.dependency 'Yoga'
22
+
23
+ s.pod_target_xcconfig = {
24
+ 'DEFINES_MODULE' => 'YES',
25
+ 'OTHER_LDFLAGS' => '$(inherited) -lc++',
26
+ 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20',
27
+ 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers/Public/React-Fabric" ' \
28
+ '"$(PODS_ROOT)/Headers/Public/React-graphics" ' \
29
+ '"$(PODS_ROOT)/Headers/Public/ReactCodegen" ' \
30
+ '"$(PODS_ROOT)/Headers/Private/React-Core" ' \
31
+ '"$(PODS_ROOT)/Headers/Private/Yoga" ' \
32
+ '"${PODS_TARGET_SRCROOT}/ios/generated"',
33
+ }
34
+
35
+ s.source_files = [
36
+ "ios/NativContainerComponentView.mm",
37
+ "ios/NativRuntime.h",
38
+ "ios/NativRuntime.mm",
39
+ "ios/generated/**/*.{h,cpp}",
40
+ ]
41
+ end
@@ -0,0 +1,128 @@
1
+ buildscript {
2
+ ext.NativFabric = [
3
+ kotlinVersion: "2.1.20",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ targetSdkVersion: 36
7
+ ]
8
+
9
+ ext.getExtOrDefault = { prop ->
10
+ if (rootProject.ext.has(prop)) {
11
+ return rootProject.ext.get(prop)
12
+ }
13
+ return NativFabric[prop]
14
+ }
15
+
16
+ repositories {
17
+ google()
18
+ mavenCentral()
19
+ }
20
+
21
+ dependencies {
22
+ classpath "com.android.tools.build:gradle:8.7.2"
23
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
24
+ classpath "org.jetbrains.kotlin:compose-compiler-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
25
+ }
26
+ }
27
+
28
+ apply plugin: "com.android.library"
29
+ apply plugin: "kotlin-android"
30
+ apply plugin: "org.jetbrains.kotlin.plugin.compose"
31
+ apply plugin: "com.facebook.react"
32
+
33
+ android {
34
+ namespace "com.nativfabric"
35
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
36
+
37
+ defaultConfig {
38
+ minSdkVersion getExtOrDefault("minSdkVersion")
39
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
40
+
41
+ externalNativeBuild {
42
+ cmake {
43
+ cppFlags "-std=c++17"
44
+ arguments "-DANDROID_STL=c++_shared"
45
+ }
46
+ }
47
+ }
48
+
49
+ externalNativeBuild {
50
+ cmake {
51
+ path "src/main/cpp/CMakeLists.txt"
52
+ }
53
+ }
54
+
55
+ buildFeatures {
56
+ buildConfig true
57
+ prefab true
58
+ }
59
+
60
+ buildTypes {
61
+ release {
62
+ minifyEnabled false
63
+ // Pass NATIV_RELEASE and app root to CMake
64
+ externalNativeBuild {
65
+ cmake {
66
+ arguments "-DNATIV_RELEASE=1", "-DNATIV_APP_ROOT=${rootProject.projectDir.parentFile.absolutePath}"
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ compileOptions {
73
+ sourceCompatibility JavaVersion.VERSION_17
74
+ targetCompatibility JavaVersion.VERSION_17
75
+ }
76
+
77
+ kotlinOptions {
78
+ jvmTarget = "17"
79
+ }
80
+
81
+ sourceSets {
82
+ main {
83
+ java.srcDirs += ['src/main/java']
84
+ }
85
+ // Production: include generated Kotlin wrappers compiled by Gradle (not DexClassLoader)
86
+ release {
87
+ java.srcDirs += ["${rootProject.projectDir.parentFile.absolutePath}/.nativ/generated/kotlin-src"]
88
+ }
89
+ }
90
+ }
91
+
92
+ // ─── Static compiler: generate bridges + compile Rust for release ─────
93
+ // Runs before Kotlin/CMake compilation so generated sources are available.
94
+ tasks.register("buildNativBridges", Exec) {
95
+ def projectRoot = rootProject.projectDir.parentFile.absolutePath
96
+ def compilerScript = file("${projectDir}/../metro/compilers/static-compiler.js").absolutePath
97
+
98
+ commandLine "node", compilerScript,
99
+ "--platform", "android",
100
+ "--root", projectRoot
101
+
102
+ // Only run for release builds
103
+ onlyIf { gradle.startParameter.taskNames.any { it.toLowerCase().contains("release") } }
104
+ }
105
+
106
+ // Wire into release build: run before Kotlin compile and CMake configure
107
+ afterEvaluate {
108
+ tasks.matching { it.name == "compileReleaseKotlin" }.configureEach {
109
+ dependsOn "buildNativBridges"
110
+ }
111
+ tasks.matching { it.name.startsWith("configureCMake") && it.name.contains("Rel") }.configureEach {
112
+ dependsOn "buildNativBridges"
113
+ }
114
+ }
115
+
116
+ repositories {
117
+ google()
118
+ mavenCentral()
119
+ }
120
+
121
+ dependencies {
122
+ implementation "com.facebook.react:react-android"
123
+ implementation "com.facebook.fbjni:fbjni"
124
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:${getExtOrDefault('kotlinVersion')}"
125
+ implementation "androidx.compose.ui:ui:1.7.0"
126
+ implementation "androidx.compose.material3:material3:1.3.0"
127
+ implementation "androidx.compose.ui:ui-tooling-preview:1.7.0"
128
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,59 @@
1
+ cmake_minimum_required(VERSION 3.13)
2
+ project(nativruntime)
3
+
4
+ set(CMAKE_CXX_STANDARD 20)
5
+
6
+ # Map ANDROID_ABI to prefab triple
7
+ if(ANDROID_ABI STREQUAL "arm64-v8a")
8
+ set(_TRIPLE "aarch64-linux-android")
9
+ elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
10
+ set(_TRIPLE "arm-linux-androideabi")
11
+ elseif(ANDROID_ABI STREQUAL "x86_64")
12
+ set(_TRIPLE "x86_64-linux-android")
13
+ elseif(ANDROID_ABI STREQUAL "x86")
14
+ set(_TRIPLE "i686-linux-android")
15
+ endif()
16
+
17
+ # Include prefab Config files
18
+ list(GET CMAKE_FIND_ROOT_PATH 0 _PREFAB_ROOT)
19
+ set(_PREFAB "${_PREFAB_ROOT}/lib/${_TRIPLE}/cmake")
20
+ include("${_PREFAB}/fbjni/fbjniConfig.cmake")
21
+ include("${_PREFAB}/ReactAndroid/ReactAndroidConfig.cmake")
22
+
23
+ add_library(nativruntime SHARED
24
+ NativRuntime.cpp
25
+ NativBindingsInstaller.cpp
26
+ )
27
+
28
+ target_link_libraries(nativruntime
29
+ fbjni::fbjni
30
+ ReactAndroid::jsi
31
+ ReactAndroid::reactnative
32
+ android
33
+ log
34
+ )
35
+
36
+ # Production: statically link user native code into libnativruntime.so.
37
+ # NATIV_APP_ROOT is passed from build.gradle — the user's project root (parent of android/).
38
+ if(NATIV_RELEASE)
39
+ target_compile_definitions(nativruntime PRIVATE NATIV_RELEASE=1)
40
+
41
+ file(GLOB_RECURSE NATIV_USER_SOURCES
42
+ "${NATIV_APP_ROOT}/.nativ/generated/bridges/android/*.cpp"
43
+ "${NATIV_APP_ROOT}/.nativ/generated/bridges/android/*.c"
44
+ )
45
+ if(NATIV_USER_SOURCES)
46
+ target_sources(nativruntime PRIVATE ${NATIV_USER_SOURCES})
47
+ endif()
48
+
49
+ # Include paths: user project root + Nativ.h location
50
+ target_include_directories(nativruntime PRIVATE
51
+ "${NATIV_APP_ROOT}"
52
+ "${CMAKE_SOURCE_DIR}/../../../../metro"
53
+ )
54
+
55
+ set(NATIV_RUST_LIB "${NATIV_APP_ROOT}/.nativ/generated/release/${ANDROID_ABI}/libnativ_user.a")
56
+ if(EXISTS ${NATIV_RUST_LIB})
57
+ target_link_libraries(nativruntime -Wl,--whole-archive ${NATIV_RUST_LIB} -Wl,--no-whole-archive)
58
+ endif()
59
+ endif()
@@ -0,0 +1,393 @@
1
+ /// NativBindingsInstaller — installs global.__nativ via TurboModuleWithJSIBindings.
2
+ /// Self-contained in nativ-fabric. Self-contained.
3
+
4
+ #include <fbjni/fbjni.h>
5
+ #include <jsi/jsi.h>
6
+ #include <ReactCommon/BindingsInstallerHolder.h>
7
+ #include <ReactCommon/CallInvoker.h>
8
+ #include <string>
9
+ #include <thread>
10
+ #include <android/log.h>
11
+ #include <dlfcn.h>
12
+
13
+ using namespace facebook;
14
+
15
+ #define NATIV_LOG(...) __android_log_print(ANDROID_LOG_INFO, "NativRuntime", __VA_ARGS__)
16
+
17
+ // Defined in NativRuntime.cpp (same .so)
18
+ extern "C" const char* nativ_call_sync(const char*, const char*, const char*);
19
+ typedef void (*NativAsyncFn)(const char*, void (*)(const char*), void (*)(const char*, const char*));
20
+ extern "C" NativAsyncFn nativ_get_async_fn(const char*, const char*);
21
+
22
+ static jsi::Runtime* g_runtime = nullptr;
23
+ static std::shared_ptr<react::CallInvoker> g_callInvoker = nullptr;
24
+
25
+ static void installNativ(jsi::Runtime &rt) {
26
+ auto nativ = jsi::Object(rt);
27
+
28
+ // __nativ.callSync(moduleId, fnName, argsJson) → string
29
+ using CallSyncFn = const char* (*)(const char*, const char*, const char*);
30
+ nativ.setProperty(rt, "callSync", jsi::Function::createFromHostFunction(
31
+ rt, jsi::PropNameID::forAscii(rt, "callSync"), 3,
32
+ [](jsi::Runtime &rt, const jsi::Value &,
33
+ const jsi::Value *args, size_t count) -> jsi::Value {
34
+ if (count < 3) return jsi::Value::null();
35
+
36
+ auto moduleId = args[0].getString(rt).utf8(rt);
37
+ auto fnName = args[1].getString(rt).utf8(rt);
38
+ auto argsJson = args[2].getString(rt).utf8(rt);
39
+
40
+ // Try C registry first (Rust/C++ .so modules)
41
+ const char* result = nativ_call_sync(moduleId.c_str(), fnName.c_str(), argsJson.c_str());
42
+ if (result) return jsi::Value(rt, jsi::String::createFromUtf8(rt, result));
43
+
44
+ // Fall back to Kotlin dispatch (.dex modules)
45
+ JNIEnv *env = jni::Environment::current();
46
+ if (env && env->PushLocalFrame(16) == 0) {
47
+ jclass rtClass = env->FindClass("com/nativfabric/NativRuntime");
48
+ if (rtClass) {
49
+ jfieldID instField = env->GetStaticFieldID(rtClass, "INSTANCE", "Lcom/nativfabric/NativRuntime;");
50
+ jobject rtInst = instField ? env->GetStaticObjectField(rtClass, instField) : nullptr;
51
+ if (rtInst) {
52
+ jmethodID callKt = env->GetMethodID(rtClass, "callKotlin",
53
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
54
+ if (callKt) {
55
+ jstring jMod = env->NewStringUTF(moduleId.c_str());
56
+ jstring jFn = env->NewStringUTF(fnName.c_str());
57
+ jstring jArgs = env->NewStringUTF(argsJson.c_str());
58
+ auto jResult = (jstring)env->CallObjectMethod(rtInst, callKt, jMod, jFn, jArgs);
59
+ if (jResult && !env->ExceptionCheck()) {
60
+ const char* chars = env->GetStringUTFChars(jResult, nullptr);
61
+ auto jsResult = jsi::String::createFromUtf8(rt, chars);
62
+ env->ReleaseStringUTFChars(jResult, chars);
63
+ env->PopLocalFrame(nullptr);
64
+ return jsi::Value(rt, jsResult);
65
+ }
66
+ if (env->ExceptionCheck()) env->ExceptionClear();
67
+ }
68
+ }
69
+ } else { env->ExceptionClear(); }
70
+ env->PopLocalFrame(nullptr);
71
+ }
72
+ return jsi::Value::null();
73
+ }));
74
+
75
+ // __nativ.callAsync(moduleId, fnName, argsJson) → Promise
76
+ nativ.setProperty(rt, "callAsync", jsi::Function::createFromHostFunction(
77
+ rt, jsi::PropNameID::forAscii(rt, "callAsync"), 3,
78
+ [](jsi::Runtime &rt, const jsi::Value &,
79
+ const jsi::Value *args, size_t count) -> jsi::Value {
80
+ if (count < 3) return jsi::Value::null();
81
+
82
+ auto moduleId = args[0].getString(rt).utf8(rt);
83
+ auto fnName = args[1].getString(rt).utf8(rt);
84
+ auto argsJson = args[2].getString(rt).utf8(rt);
85
+
86
+ auto asyncFn = nativ_get_async_fn(moduleId.c_str(), fnName.c_str());
87
+ if (!asyncFn) {
88
+ throw jsi::JSError(rt, "Unknown Nativ async function: " + moduleId + "::" + fnName);
89
+ }
90
+
91
+ auto Promise = rt.global().getPropertyAsFunction(rt, "Promise");
92
+ auto executor = jsi::Function::createFromHostFunction(
93
+ rt, jsi::PropNameID::forAscii(rt, "executor"), 2,
94
+ [asyncFn, argsJson](jsi::Runtime &rt, const jsi::Value &,
95
+ const jsi::Value *pargs, size_t) -> jsi::Value {
96
+
97
+ auto resolve = std::make_shared<jsi::Function>(pargs[0].asObject(rt).asFunction(rt));
98
+ auto reject = std::make_shared<jsi::Function>(pargs[1].asObject(rt).asFunction(rt));
99
+ auto invoker = g_callInvoker;
100
+
101
+ // Same thread_local context pattern as iOS
102
+ struct AsyncCtx {
103
+ std::shared_ptr<jsi::Function> resolve;
104
+ std::shared_ptr<jsi::Function> reject;
105
+ std::shared_ptr<react::CallInvoker> invoker;
106
+ };
107
+ static thread_local AsyncCtx* _asyncCtx = nullptr;
108
+ auto ctx = new AsyncCtx{resolve, reject, invoker};
109
+
110
+ // Dispatch to background thread
111
+ std::thread([asyncFn, argsJson, ctx]() {
112
+ _asyncCtx = ctx;
113
+
114
+ asyncFn(
115
+ argsJson.c_str(),
116
+ // resolve
117
+ [](const char* result) {
118
+ auto* c = _asyncCtx;
119
+ if (!c) return;
120
+ _asyncCtx = nullptr;
121
+ auto resultStr = std::string(result ? result : "null");
122
+ auto res = c->resolve;
123
+ auto inv = c->invoker;
124
+ delete c;
125
+ if (inv) {
126
+ inv->invokeAsync([res, resultStr]() {
127
+ auto &rt = *g_runtime;
128
+ auto json = rt.global().getPropertyAsObject(rt, "JSON");
129
+ auto parse = json.getPropertyAsFunction(rt, "parse");
130
+ auto parsed = parse.call(rt, jsi::String::createFromUtf8(rt, resultStr));
131
+ res->call(rt, std::move(parsed));
132
+ });
133
+ }
134
+ },
135
+ // reject
136
+ [](const char* code, const char* msg) {
137
+ auto* c = _asyncCtx;
138
+ if (!c) return;
139
+ _asyncCtx = nullptr;
140
+ auto msgStr = std::string(msg ? msg : "Unknown error");
141
+ auto rej = c->reject;
142
+ auto inv = c->invoker;
143
+ delete c;
144
+ if (inv) {
145
+ inv->invokeAsync([rej, msgStr]() {
146
+ auto &rt = *g_runtime;
147
+ rej->call(rt, jsi::String::createFromUtf8(rt, msgStr));
148
+ });
149
+ }
150
+ });
151
+ }).detach();
152
+
153
+ return jsi::Value::undefined();
154
+ });
155
+
156
+ return Promise.callAsConstructor(rt, executor);
157
+ }));
158
+
159
+ // __nativ.setComponentProps(componentId, props)
160
+ nativ.setProperty(rt, "setComponentProps", jsi::Function::createFromHostFunction(
161
+ rt, jsi::PropNameID::forAscii(rt, "setComponentProps"), 2,
162
+ [](jsi::Runtime &rt, const jsi::Value &,
163
+ const jsi::Value *args, size_t count) -> jsi::Value {
164
+ if (count < 2 || !args[0].isString()) return jsi::Value::undefined();
165
+ JNIEnv *env = jni::Environment::current();
166
+ if (!env) return jsi::Value::undefined();
167
+ if (env->PushLocalFrame(128) != 0) return jsi::Value::undefined();
168
+
169
+ auto componentId = args[0].getString(rt).utf8(rt);
170
+
171
+ jclass hmClass = env->FindClass("java/util/HashMap");
172
+ if (!hmClass) { env->PopLocalFrame(nullptr); return jsi::Value::undefined(); }
173
+ jmethodID hmInit = env->GetMethodID(hmClass, "<init>", "()V");
174
+ jmethodID hmPut = env->GetMethodID(hmClass, "put",
175
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
176
+
177
+ jobject strings = env->NewObject(hmClass, hmInit);
178
+ jobject numbers = env->NewObject(hmClass, hmInit);
179
+ jobject bools = env->NewObject(hmClass, hmInit);
180
+
181
+ jclass dblClass = env->FindClass("java/lang/Double");
182
+ jmethodID dblOf = env->GetStaticMethodID(dblClass, "valueOf", "(D)Ljava/lang/Double;");
183
+ jclass boolClass = env->FindClass("java/lang/Boolean");
184
+ jmethodID boolOf = env->GetStaticMethodID(boolClass, "valueOf", "(Z)Ljava/lang/Boolean;");
185
+
186
+ if (args[1].isObject()) {
187
+ auto obj = args[1].asObject(rt);
188
+ auto names = obj.getPropertyNames(rt);
189
+ for (size_t i = 0; i < names.size(rt); i++) {
190
+ auto propName = names.getValueAtIndex(rt, i).getString(rt).utf8(rt);
191
+ if (propName.empty()) continue;
192
+ auto val = obj.getProperty(rt, propName.c_str());
193
+ jstring jKey = env->NewStringUTF(propName.c_str());
194
+ if (!jKey) continue;
195
+
196
+ if (val.isString()) {
197
+ auto sval = val.getString(rt).utf8(rt);
198
+ jstring jVal = env->NewStringUTF(sval.c_str());
199
+ if (jVal) env->CallObjectMethod(strings, hmPut, jKey, jVal);
200
+ } else if (val.isNumber()) {
201
+ jobject jVal = env->CallStaticObjectMethod(dblClass, dblOf, val.getNumber());
202
+ env->CallObjectMethod(numbers, hmPut, jKey, jVal);
203
+ } else if (val.isBool()) {
204
+ jobject jVal = env->CallStaticObjectMethod(boolClass, boolOf, (jboolean)val.getBool());
205
+ env->CallObjectMethod(bools, hmPut, jKey, jVal);
206
+ }
207
+ }
208
+ }
209
+
210
+ jclass rtClass = env->FindClass("com/nativfabric/NativRuntime");
211
+ if (rtClass) {
212
+ jfieldID instField = env->GetStaticFieldID(rtClass, "INSTANCE",
213
+ "Lcom/nativfabric/NativRuntime;");
214
+ jobject rtInst = instField ? env->GetStaticObjectField(rtClass, instField) : nullptr;
215
+
216
+ jclass psClass = env->FindClass("com/nativfabric/NativRuntime$PropsSnapshot");
217
+ if (psClass && rtInst) {
218
+ jmethodID psInit = env->GetMethodID(psClass, "<init>",
219
+ "(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V");
220
+ jobject snapshot = env->NewObject(psClass, psInit, strings, numbers, bools);
221
+
222
+ jmethodID setProps = env->GetMethodID(rtClass, "setComponentProps",
223
+ "(Ljava/lang/String;Lcom/nativfabric/NativRuntime$PropsSnapshot;)V");
224
+ jstring jCompId = env->NewStringUTF(componentId.c_str());
225
+ if (jCompId && snapshot && setProps) {
226
+ env->CallVoidMethod(rtInst, setProps, jCompId, snapshot);
227
+ }
228
+ }
229
+ }
230
+
231
+ if (env->ExceptionCheck()) env->ExceptionClear();
232
+ env->PopLocalFrame(nullptr);
233
+ return jsi::Value::undefined();
234
+ }));
235
+
236
+ #ifndef NATIV_RELEASE
237
+ // __nativ.loadDylib(url) — dev only, downloads .so/.dex from Metro
238
+ nativ.setProperty(rt, "loadDylib", jsi::Function::createFromHostFunction(
239
+ rt, jsi::PropNameID::forAscii(rt, "loadDylib"), 1,
240
+ [](jsi::Runtime &rt, const jsi::Value &,
241
+ const jsi::Value *args, size_t count) -> jsi::Value {
242
+ if (count < 1 || !args[0].isString()) return jsi::Value(false);
243
+ auto url = args[0].getString(rt).utf8(rt);
244
+ bool isDex = url.find(".dex") != std::string::npos;
245
+
246
+ JNIEnv *env = jni::Environment::current();
247
+ if (!env) return jsi::Value(false);
248
+ if (env->PushLocalFrame(64) != 0) return jsi::Value(false);
249
+
250
+ // Download via java.net.URL
251
+ jclass urlClass = env->FindClass("java/net/URL");
252
+ jmethodID urlInit = env->GetMethodID(urlClass, "<init>", "(Ljava/lang/String;)V");
253
+ jmethodID openStream = env->GetMethodID(urlClass, "openStream", "()Ljava/io/InputStream;");
254
+ jstring jUrl = env->NewStringUTF(url.c_str());
255
+ jobject urlObj = env->NewObject(urlClass, urlInit, jUrl);
256
+ if (env->ExceptionCheck()) { env->ExceptionClear(); env->PopLocalFrame(nullptr); return jsi::Value(false); }
257
+
258
+ jobject stream = env->CallObjectMethod(urlObj, openStream);
259
+ if (env->ExceptionCheck() || !stream) { env->ExceptionClear(); env->PopLocalFrame(nullptr); return jsi::Value(false); }
260
+
261
+ // Read into ByteArrayOutputStream
262
+ jclass isClass = env->FindClass("java/io/InputStream");
263
+ jclass baosClass = env->FindClass("java/io/ByteArrayOutputStream");
264
+ jobject baos = env->NewObject(baosClass, env->GetMethodID(baosClass, "<init>", "()V"));
265
+ jbyteArray buf = env->NewByteArray(8192);
266
+ jmethodID readM = env->GetMethodID(isClass, "read", "([B)I");
267
+ jmethodID writeM = env->GetMethodID(baosClass, "write", "([BII)V");
268
+ while (true) {
269
+ jint n = env->CallIntMethod(stream, readM, buf);
270
+ if (n <= 0) break;
271
+ env->CallVoidMethod(baos, writeM, buf, 0, n);
272
+ }
273
+ env->CallVoidMethod(stream, env->GetMethodID(isClass, "close", "()V"));
274
+ auto bytes = (jbyteArray)env->CallObjectMethod(baos, env->GetMethodID(baosClass, "toByteArray", "()[B"));
275
+ jsize len = env->GetArrayLength(bytes);
276
+
277
+ // Write to temp file
278
+ jclass fileClass = env->FindClass("java/io/File");
279
+ jstring prefix = env->NewStringUTF("nativ_");
280
+ jstring suffix = env->NewStringUTF(isDex ? ".dex" : ".so");
281
+ jobject tmpFile = env->CallStaticObjectMethod(fileClass,
282
+ env->GetStaticMethodID(fileClass, "createTempFile", "(Ljava/lang/String;Ljava/lang/String;)Ljava/io/File;"),
283
+ prefix, suffix);
284
+ if (env->ExceptionCheck()) { env->ExceptionClear(); env->PopLocalFrame(nullptr); return jsi::Value(false); }
285
+
286
+ jclass fosClass = env->FindClass("java/io/FileOutputStream");
287
+ jobject fos = env->NewObject(fosClass, env->GetMethodID(fosClass, "<init>", "(Ljava/io/File;)V"), tmpFile);
288
+ env->CallVoidMethod(fos, env->GetMethodID(fosClass, "write", "([B)V"), bytes);
289
+ env->CallVoidMethod(fos, env->GetMethodID(fosClass, "close", "()V"));
290
+
291
+ auto jPath = (jstring)env->CallObjectMethod(tmpFile,
292
+ env->GetMethodID(fileClass, "getAbsolutePath", "()Ljava/lang/String;"));
293
+ const char *pathChars = env->GetStringUTFChars(jPath, nullptr);
294
+ std::string filePath(pathChars);
295
+ env->ReleaseStringUTFChars(jPath, pathChars);
296
+
297
+ bool ok = false;
298
+ if (isDex) {
299
+ auto lastSlash = url.rfind('/');
300
+ auto dotDex = url.rfind(".dex");
301
+ std::string moduleId = (lastSlash != std::string::npos && dotDex != std::string::npos)
302
+ ? url.substr(lastSlash + 1, dotDex - lastSlash - 1) : "";
303
+ if (moduleId.substr(0, 7) == "nativ_") moduleId = moduleId.substr(7);
304
+ auto lastUs = moduleId.rfind('_');
305
+ if (lastUs != std::string::npos && moduleId.length() - lastUs - 1 == 8) {
306
+ moduleId = moduleId.substr(0, lastUs);
307
+ }
308
+
309
+ jclass rtClass = env->FindClass("com/nativfabric/NativRuntime");
310
+ jfieldID instField = env->GetStaticFieldID(rtClass, "INSTANCE", "Lcom/nativfabric/NativRuntime;");
311
+ jobject rtInst = instField ? env->GetStaticObjectField(rtClass, instField) : nullptr;
312
+ if (rtInst) {
313
+ jmethodID loadDex = env->GetMethodID(rtClass, "loadDex", "(Ljava/lang/String;Ljava/lang/String;)Z");
314
+ jstring jFilePath = env->NewStringUTF(filePath.c_str());
315
+ jstring jModuleId = env->NewStringUTF(moduleId.c_str());
316
+ ok = env->CallBooleanMethod(rtInst, loadDex, jFilePath, jModuleId);
317
+ if (env->ExceptionCheck()) { env->ExceptionClear(); ok = false; }
318
+ }
319
+ } else {
320
+ env->CallBooleanMethod(tmpFile,
321
+ env->GetMethodID(fileClass, "setExecutable", "(Z)Z"), (jboolean)true);
322
+ void *handle = dlopen(filePath.c_str(), RTLD_NOW);
323
+ ok = (handle != nullptr);
324
+ if (ok) {
325
+ using InitFn = void (*)(void*);
326
+ void *rtLib = dlopen("libnativruntime.so", RTLD_NOW | RTLD_NOLOAD);
327
+ if (rtLib) {
328
+ auto setLib = (InitFn)dlsym(handle, "nativ_set_runtime_lib");
329
+ if (setLib) setLib(rtLib);
330
+ auto initFn = (InitFn)dlsym(handle, "nativ_init");
331
+ if (initFn) {
332
+ void *regFn = dlsym(rtLib, "nativ_register_sync");
333
+ if (regFn) initFn(regFn);
334
+ }
335
+ auto renderInitFn = (InitFn)dlsym(handle, "nativ_init_render");
336
+ if (renderInitFn) {
337
+ void *renderRegFn = dlsym(rtLib, "nativ_register_render");
338
+ if (renderRegFn) renderInitFn(renderRegFn);
339
+ }
340
+ }
341
+ } else {
342
+ NATIV_LOG("loadDylib: dlopen failed: %s", dlerror());
343
+ }
344
+ }
345
+
346
+ NATIV_LOG("loadDylib: %s → %s (%d bytes)", url.c_str(), ok ? "OK" : "FAIL", (int)len);
347
+ env->PopLocalFrame(nullptr);
348
+ return jsi::Value(ok);
349
+ }));
350
+ #endif // !NATIV_RELEASE
351
+
352
+ // ABI target — used by JS shim to request correct dylib from Metro
353
+ #if defined(__aarch64__)
354
+ nativ.setProperty(rt, "target", jsi::String::createFromUtf8(rt, "arm64-v8a"));
355
+ #elif defined(__arm__)
356
+ nativ.setProperty(rt, "target", jsi::String::createFromUtf8(rt, "armeabi-v7a"));
357
+ #elif defined(__x86_64__)
358
+ nativ.setProperty(rt, "target", jsi::String::createFromUtf8(rt, "x86_64"));
359
+ #elif defined(__i386__)
360
+ nativ.setProperty(rt, "target", jsi::String::createFromUtf8(rt, "x86"));
361
+ #endif
362
+
363
+ rt.global().setProperty(rt, "__nativ", std::move(nativ));
364
+ NATIV_LOG("global.__nativ installed via TurboModuleWithJSIBindings");
365
+ }
366
+
367
+ // ─── JNI BindingsInstallerHolder for TurboModuleWithJSIBindings ────────
368
+
369
+ struct NativRuntimeJSIBindings : public jni::JavaClass<NativRuntimeJSIBindings> {
370
+ static constexpr const char *kJavaDescriptor = "Lcom/nativfabric/NativRuntimeModule;";
371
+
372
+ static void registerNatives() {
373
+ javaClassLocal()->registerNatives({
374
+ makeNativeMethod("getBindingsInstaller", NativRuntimeJSIBindings::getBindingsInstaller),
375
+ });
376
+ }
377
+
378
+ static jni::local_ref<react::BindingsInstallerHolder::javaobject>
379
+ getBindingsInstaller(jni::alias_ref<NativRuntimeJSIBindings> /*jobj*/) {
380
+ return react::BindingsInstallerHolder::newObjectCxxArgs(
381
+ [](jsi::Runtime &runtime, const std::shared_ptr<react::CallInvoker> &callInvoker) {
382
+ g_callInvoker = callInvoker;
383
+ g_runtime = &runtime;
384
+ installNativ(runtime);
385
+ });
386
+ }
387
+ };
388
+
389
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
390
+ return jni::initialize(vm, [] {
391
+ NativRuntimeJSIBindings::registerNatives();
392
+ });
393
+ }