@hot-updater/react-native 1.0.8 → 1.1.1

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 (145) hide show
  1. package/HotUpdater.podspec +14 -2
  2. package/Package.swift +24 -3
  3. package/android/build.gradle +2 -6
  4. package/android/hot-updater-fingerprint.gradle +135 -0
  5. package/android/src/main/cpp/HotUpdaterRecovery.cpp +291 -21
  6. package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +176 -15
  7. package/android/src/main/java/com/hotupdater/ArtifactFileResolver.kt +20 -8
  8. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +145 -31
  9. package/android/src/main/java/com/hotupdater/CohortService.kt +10 -2
  10. package/android/src/main/java/com/hotupdater/FileManagerService.kt +3 -1
  11. package/android/src/main/java/com/hotupdater/HotUpdaterConfig.kt +249 -21
  12. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +8 -4
  13. package/android/src/main/java/com/hotupdater/HotUpdaterHeldContent.kt +92 -0
  14. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +319 -113
  15. package/android/src/main/java/com/hotupdater/HotUpdaterInstance.kt +87 -0
  16. package/android/src/main/java/com/hotupdater/HotUpdaterInstanceEnvironment.kt +323 -0
  17. package/android/src/main/java/com/hotupdater/HotUpdaterMainThread.kt +46 -0
  18. package/android/src/main/java/com/hotupdater/HotUpdaterProcess.kt +30 -0
  19. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryAttribution.kt +493 -0
  20. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +1541 -159
  21. package/android/src/main/java/com/hotupdater/HotUpdaterRecoverySlots.kt +137 -0
  22. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +4 -0
  23. package/android/src/main/java/com/hotupdater/HotUpdaterRuntimeInstance.kt +53 -0
  24. package/android/src/main/java/com/hotupdater/HotUpdaterUpdateCheck.kt +149 -0
  25. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +12 -2
  26. package/android/src/main/java/com/hotupdater/NativeConfigUtils.kt +5 -6
  27. package/android/src/main/java/com/hotupdater/NativeUpdateCheck.kt +172 -0
  28. package/android/src/main/java/com/hotupdater/NativeUpdateChecker.kt +1007 -0
  29. package/android/src/main/java/com/hotupdater/NativeUpdateEventQueue.kt +111 -0
  30. package/android/src/main/java/com/hotupdater/OkHttpUpdateCheckTransport.kt +88 -0
  31. package/android/src/main/java/com/hotupdater/ReleaseCatalog.kt +491 -0
  32. package/android/src/main/java/com/hotupdater/ReleaseCatalogClient.kt +405 -0
  33. package/android/src/main/java/com/hotupdater/ReleaseSelection.kt +474 -0
  34. package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +32 -10
  35. package/android/src/main/java/com/hotupdater/StrictJson.kt +392 -0
  36. package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +24 -9
  37. package/android/src/main/java/com/hotupdater/WeakIdentitySet.kt +76 -0
  38. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BitReader.java +314 -0
  39. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliError.java +47 -0
  40. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliInputStream.java +174 -0
  41. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliRuntimeException.java +21 -0
  42. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Context.java +58 -0
  43. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decode.java +1574 -0
  44. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decoder.java +72 -0
  45. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Dictionary.java +103 -0
  46. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/DictionaryData.java +82 -0
  47. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Huffman.java +143 -0
  48. package/{LICENSE → android/src/main/java/com/hotupdater/vendor/brotli/dec/LICENSE} +6 -20
  49. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/README.md +39 -0
  50. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/State.java +100 -0
  51. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Transform.java +240 -0
  52. package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Utils.java +163 -0
  53. package/android/src/main/resources/META-INF/LICENSE-brotli +19 -0
  54. package/android/src/newarch/HotUpdater.kt +53 -15
  55. package/android/src/newarch/HotUpdaterModule.kt +42 -17
  56. package/android/src/newarch/JavaScriptExceptionHooks.kt +45 -8
  57. package/android/src/oldarch/HotUpdater.kt +51 -12
  58. package/android/src/oldarch/HotUpdaterModule.kt +43 -17
  59. package/android/src/oldarch/HotUpdaterSpec.kt +2 -0
  60. package/android/src/oldarch/JavaScriptExceptionHooks.kt +25 -16
  61. package/ios/HotUpdater/Interface/HotUpdaterInterface.h +13 -0
  62. package/ios/HotUpdater/Internal/ArtifactFileResolver.swift +38 -7
  63. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +10 -4
  64. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +186 -48
  65. package/ios/HotUpdater/Internal/CohortService.swift +8 -0
  66. package/ios/HotUpdater/Internal/HotUpdater.mm +821 -71
  67. package/ios/HotUpdater/Internal/HotUpdaterAsync.swift +7 -0
  68. package/ios/HotUpdater/Internal/HotUpdaterConfig.swift +208 -25
  69. package/ios/HotUpdater/Internal/HotUpdaterFingerprintHash.m +10 -0
  70. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +940 -261
  71. package/ios/HotUpdater/Internal/HotUpdaterInstanceEnvironment.swift +492 -0
  72. package/ios/HotUpdater/Internal/HotUpdaterInstanceRouter.swift +83 -0
  73. package/ios/HotUpdater/Internal/HotUpdaterInterface.swift +28 -1
  74. package/ios/HotUpdater/Internal/HotUpdaterLaunchRecovery.swift +1553 -0
  75. package/ios/HotUpdater/Internal/HotUpdaterNativeUpdates.swift +353 -0
  76. package/ios/HotUpdater/Internal/LaunchPreparationLock.swift +54 -0
  77. package/ios/HotUpdater/Internal/NativeUpdateCheck.swift +2051 -0
  78. package/ios/HotUpdater/Internal/ReleaseCatalog.swift +634 -0
  79. package/ios/HotUpdater/Internal/ReleaseCatalogCacheService.swift +1 -1
  80. package/ios/HotUpdater/Internal/ReleaseSelection.swift +482 -0
  81. package/ios/HotUpdater/Internal/SignatureVerifier.swift +27 -10
  82. package/ios/HotUpdater/Internal/StrictJson.swift +363 -0
  83. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +4 -2
  84. package/ios/HotUpdater/Internal/UpdateFailure.swift +2 -0
  85. package/ios/HotUpdater/Package.swift +11 -0
  86. package/ios/HotUpdater/Public/HotUpdater/HotUpdater.h +4 -0
  87. package/ios/HotUpdater/Public/HotUpdater.h +21 -2
  88. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +66 -0
  89. package/ios/Plugins/HotUpdaterFingerprintPlugin/HotUpdaterFingerprintPlugin.swift +45 -0
  90. package/ios/scripts/generate-fingerprint-hash.zsh +85 -0
  91. package/lib/commonjs/checkForUpdate.js +3 -1
  92. package/lib/commonjs/checkForUpdate.js.map +1 -1
  93. package/lib/commonjs/index.js +9 -12
  94. package/lib/commonjs/index.js.map +1 -1
  95. package/lib/commonjs/native.js +24 -79
  96. package/lib/commonjs/native.js.map +1 -1
  97. package/lib/commonjs/nativeUpdateEvents.js +66 -0
  98. package/lib/commonjs/nativeUpdateEvents.js.map +1 -0
  99. package/lib/commonjs/sdkVersion.js +1 -1
  100. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  101. package/lib/module/checkForUpdate.js +3 -1
  102. package/lib/module/checkForUpdate.js.map +1 -1
  103. package/lib/module/index.js +10 -13
  104. package/lib/module/index.js.map +1 -1
  105. package/lib/module/native.js +19 -73
  106. package/lib/module/native.js.map +1 -1
  107. package/lib/module/nativeUpdateEvents.js +61 -0
  108. package/lib/module/nativeUpdateEvents.js.map +1 -0
  109. package/lib/module/sdkVersion.js +1 -1
  110. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  111. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/index.d.ts +4 -14
  113. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  114. package/lib/typescript/commonjs/native.d.ts +9 -46
  115. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  116. package/lib/typescript/commonjs/nativeUpdateEvents.d.ts +12 -0
  117. package/lib/typescript/commonjs/nativeUpdateEvents.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  119. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +19 -3
  120. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  121. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  122. package/lib/typescript/module/index.d.ts +4 -14
  123. package/lib/typescript/module/index.d.ts.map +1 -1
  124. package/lib/typescript/module/native.d.ts +9 -46
  125. package/lib/typescript/module/native.d.ts.map +1 -1
  126. package/lib/typescript/module/nativeUpdateEvents.d.ts +12 -0
  127. package/lib/typescript/module/nativeUpdateEvents.d.ts.map +1 -0
  128. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  129. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -3
  130. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  131. package/package.json +4 -4
  132. package/src/checkForUpdate.ts +7 -1
  133. package/src/index.ts +9 -17
  134. package/src/native.ts +19 -91
  135. package/src/nativeUpdateEvents.ts +77 -0
  136. package/src/sdkVersion.ts +1 -1
  137. package/src/specs/NativeHotUpdater.ts +20 -3
  138. package/android/libs/README.md +0 -50
  139. package/android/libs/hot-updater-brotli-dec-1.2.0.jar +0 -0
  140. package/android/src/bridge/BridgeReactInstance.kt +0 -38
  141. package/android/src/bridgeless/BridgeReactInstance.kt +0 -24
  142. package/android/src/main/java/com/hotupdater/ContentAppearedLog.kt +0 -31
  143. package/android/src/main/java/com/hotupdater/ReactIntegrationManagerBase.kt +0 -34
  144. package/android/src/newarch/ReactIntegrationManager.kt +0 -212
  145. package/android/src/oldarch/ReactIntegrationManager.kt +0 -120
@@ -0,0 +1,77 @@
1
+ import type {
2
+ BundleDownloadedInfo,
3
+ UpdateCheckResult,
4
+ UpdateError,
5
+ UpdateHttpResponse,
6
+ } from "@hot-updater/protocol";
7
+
8
+ import type { LaunchReporter } from "./appReady";
9
+ import { takeNativeUpdateEvents } from "./native";
10
+
11
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
12
+ value !== null && typeof value === "object" && !Array.isArray(value);
13
+
14
+ /**
15
+ * Reports the update checks native code ran without JavaScript, such as a
16
+ * brownfield app's native `HotUpdaterInstance`, with the catalog and artifact
17
+ * responses they received, to the instance's plugins before this launch's
18
+ * report, which they precede. Native takes and clears them in one step, so
19
+ * each is reported at most once. A native error carries a message instead of
20
+ * the caught error, and each event keeps the time it happened as
21
+ * `occurredAt`.
22
+ */
23
+ export const replayNativeUpdateEvents = (emit: LaunchReporter["emit"]) => {
24
+ let value: string | null;
25
+ try {
26
+ value = takeNativeUpdateEvents();
27
+ } catch {
28
+ return;
29
+ }
30
+ if (value === null) return;
31
+
32
+ let entries: unknown;
33
+ try {
34
+ entries = JSON.parse(value);
35
+ } catch {
36
+ return;
37
+ }
38
+ if (!Array.isArray(entries)) return;
39
+
40
+ for (const entry of entries) {
41
+ if (!isRecord(entry) || !isRecord(entry.payload)) continue;
42
+ const payload =
43
+ typeof entry.occurredAt === "number"
44
+ ? { ...entry.payload, occurredAt: entry.occurredAt }
45
+ : entry.payload;
46
+ switch (entry.name) {
47
+ case "onUpdateCheck":
48
+ emit("onUpdateCheck", () => payload as unknown as UpdateCheckResult);
49
+ break;
50
+ case "onBundleDownloaded":
51
+ emit(
52
+ "onBundleDownloaded",
53
+ () => payload as unknown as BundleDownloadedInfo,
54
+ );
55
+ break;
56
+ case "onHttpResponse":
57
+ emit("onHttpResponse", () => payload as unknown as UpdateHttpResponse);
58
+ break;
59
+ case "onUpdateError": {
60
+ const { message, ...error } = payload;
61
+ emit(
62
+ "onUpdateError",
63
+ () =>
64
+ ({
65
+ ...error,
66
+ cause: new Error(
67
+ typeof message === "string"
68
+ ? message
69
+ : "The native update check failed",
70
+ ),
71
+ }) as unknown as UpdateError,
72
+ );
73
+ break;
74
+ }
75
+ }
76
+ }
77
+ };
package/src/sdkVersion.ts CHANGED
@@ -1 +1 @@
1
- export const HOT_UPDATER_SDK_VERSION = "1.0.8";
1
+ export const HOT_UPDATER_SDK_VERSION = "1.1.1";
@@ -54,11 +54,20 @@ export interface InstallArtifactParams {
54
54
 
55
55
  export interface Spec extends TurboModule {
56
56
  // Methods
57
+ /**
58
+ * Restarts the app process on Android and reloads React Native in place on
59
+ * iOS, so the app loads the current bundle.
60
+ */
57
61
  reload(): Promise<void>;
58
62
  /**
59
- * Android process restart path used by `setReloadBehavior("processRestart")`.
60
- *
61
- * iOS exposes the same method name for API parity, but it behaves the same as `reload()`.
63
+ * @deprecated Kept for JavaScript↔native compatibility within this major
64
+ * version. Released JavaScript calls it on Android, and this SDK's
65
+ * JavaScript still calls it on Android because an older native build's
66
+ * `reload()` reloads React Native in place there. Native `reload()` now
67
+ * restarts the process on Android too: the Kotlin and Objective-C
68
+ * `reloadProcess` implementations run the same code as `reload()`, a
69
+ * process restart on Android and an in-place reload on iOS. The next major
70
+ * version removes it.
62
71
  */
63
72
  reloadProcess(): Promise<void>;
64
73
  /**
@@ -234,6 +243,14 @@ export interface Spec extends TurboModule {
234
243
  */
235
244
  setStorageItem: (key: string, value: string | null) => void;
236
245
 
246
+ /**
247
+ * Takes the events of the update checks native code ran without JavaScript
248
+ * (a native `HotUpdaterInstance`), as a JSON array, and clears them in one
249
+ * step.
250
+ * Returns null when there are none.
251
+ */
252
+ takeNativeUpdateEvents: () => string | null;
253
+
237
254
  // EventEmitter
238
255
  addListener(eventName: string): void;
239
256
  removeListeners(count: number): void;
@@ -1,50 +0,0 @@
1
- # Bundled Libraries
2
-
3
- ## hot-updater-brotli-dec-1.2.0.jar
4
-
5
- **Why bundled:** The official `org.brotli:dec` package on Maven Central is stuck at v0.1.2 (released May 2017) and contains critical security vulnerabilities. Google has not published newer versions to Maven Central despite releasing v1.2.0 with security fixes.
6
-
7
- **Source:** Built from [google/brotli v1.2.0](https://github.com/google/brotli/releases/tag/v1.2.0)
8
-
9
- **Security Fixes:**
10
- - CVE-2020-8927: Buffer overflow in Brotli versions < 1.0.8
11
- - CVE-2025-6176: DoS vulnerability in versions ≤ 1.1.0 (fixed in v1.2.0)
12
-
13
- **Build Command:**
14
- ```bash
15
- git clone --depth 1 --branch v1.2.0 https://github.com/google/brotli.git
16
- cd brotli/java/org/brotli/dec
17
- mvn clean package -DskipTests
18
- cp target/org.brotli.dec-1.2.0-SNAPSHOT.jar <destination>/org.brotli.dec-1.2.0.jar
19
- ```
20
-
21
- The generated jar is then relocated before being bundled:
22
-
23
- ```gradle
24
- relocate "org.brotli.dec", "com.hotupdater.vendor.brotli.dec"
25
- ```
26
-
27
- The relocation keeps Hot Updater's security-fixed Brotli decoder isolated from
28
- apps or frameworks that also depend on `org.brotli:dec`.
29
-
30
- **Verification:**
31
- - **Original SHA256:** `2d12a2d7fb52fd7f944564fb378aaccd41389cf02ea2e5eb6b3a6477188cced8`
32
- - **Relocated SHA256:** `4e9aee81466e21e365fd291bd29ca0540dc174ce64d29a7613ff336e4276bcdf`
33
- - **Size:** 99KB
34
- - **Built:** 2026-02-12
35
- - **Git Commit:** [028fb5a](https://github.com/google/brotli/commit/028fb5a23661f123017c060daa546b55cf4bde29) (v1.2.0 tag)
36
- - **Relocated Package:** `com.hotupdater.vendor.brotli.dec`
37
-
38
- **API Compatibility:**
39
- The v1.2.0 decoder is API compatible with v0.1.2. Hot Updater imports the
40
- relocated package internally, so apps can keep their own `org.brotli:dec`
41
- dependency without duplicate classes:
42
- - Relocated package: `com.hotupdater.vendor.brotli.dec`
43
- - Same class: `BrotliInputStream`
44
- - Same constructor: `BrotliInputStream(InputStream)`
45
-
46
- **Why Not Apache Commons Compress?**
47
- Apache Commons Compress depends on `org.brotli:dec:0.1.2` as an optional dependency, so switching to it would not fix the vulnerability—it would just make it transitive.
48
-
49
- **Future Updates:**
50
- When Google releases new versions of Brotli (e.g., v1.3.0), rebuild this JAR using the same process, relocate it into the Hot Updater vendor package, and update the SHA256 checksum and build date in this file.
@@ -1,38 +0,0 @@
1
- package com.hotupdater
2
-
3
- import com.facebook.react.ReactApplication
4
- import com.facebook.react.bridge.JSBundleLoader
5
-
6
- /**
7
- * React Native 0.81 and older can run the New Architecture on the bridge, which
8
- * only ReactNativeHost reaches. Only builds for those versions compile this file.
9
- */
10
- internal object BridgeReactInstance {
11
- fun setJSBundle(
12
- application: ReactApplication,
13
- bundleLoader: JSBundleLoader?,
14
- ) {
15
- @Suppress("DEPRECATION")
16
- val instanceManager = application.reactNativeHost.reactInstanceManager
17
- val bundleLoaderField = instanceManager::class.java.getDeclaredField("mBundleLoader")
18
- bundleLoaderField.isAccessible = true
19
- bundleLoaderField.set(instanceManager, bundleLoader)
20
- }
21
-
22
- fun reload(application: ReactApplication) {
23
- @Suppress("DEPRECATION")
24
- val reactNativeHost = application.reactNativeHost
25
- try {
26
- reactNativeHost.reactInstanceManager.recreateReactContextInBackground()
27
- } catch (e: Exception) {
28
- val currentActivity = reactNativeHost.reactInstanceManager.currentReactContext?.currentActivity
29
- if (currentActivity == null) {
30
- return
31
- }
32
-
33
- currentActivity.runOnUiThread {
34
- currentActivity.recreate()
35
- }
36
- }
37
- }
38
- }
@@ -1,24 +0,0 @@
1
- package com.hotupdater
2
-
3
- import android.util.Log
4
- import com.facebook.react.ReactApplication
5
- import com.facebook.react.bridge.JSBundleLoader
6
-
7
- /**
8
- * React Native 0.82 and later have no bridge, so an app without a ReactHost has
9
- * no instance to update. Builds for those versions compile this file instead of
10
- * the bridge one, and never reference ReactNativeHost.
11
- */
12
- @Suppress("UNUSED_PARAMETER")
13
- internal object BridgeReactInstance {
14
- fun setJSBundle(
15
- application: ReactApplication,
16
- bundleLoader: JSBundleLoader?,
17
- ) {
18
- Log.d("HotUpdater", "No ReactHost to set the JS bundle on")
19
- }
20
-
21
- fun reload(application: ReactApplication) {
22
- Log.d("HotUpdater", "No ReactHost to reload")
23
- }
24
- }
@@ -1,31 +0,0 @@
1
- package com.hotupdater
2
-
3
- import android.app.Activity
4
- import android.view.View
5
- import android.view.ViewGroup
6
- import com.facebook.react.ReactRootView
7
-
8
- /**
9
- * A New Architecture reload restarts each surface in its existing view, and a
10
- * view logs CONTENT_APPEARED only for its first content (the old architecture
11
- * asks for it again when it runs the app). Asks every React root view
12
- * of the activity to log it again, so the reloaded bundle's first content
13
- * completes its launch, as a cold start does. Otherwise the next process rolls
14
- * the working bundle back as an unfinished launch.
15
- */
16
- internal object ContentAppearedLog {
17
- fun rearm(activity: Activity?) {
18
- activity?.window?.decorView?.let(::rearm)
19
- }
20
-
21
- private fun rearm(view: View) {
22
- if (view is ReactRootView) {
23
- view.setShouldLogContentAppeared(true)
24
- }
25
- if (view is ViewGroup) {
26
- for (index in 0 until view.childCount) {
27
- rearm(view.getChildAt(index))
28
- }
29
- }
30
- }
31
- }
@@ -1,34 +0,0 @@
1
- package com.hotupdater
2
-
3
- import android.app.Application
4
- import android.content.Context
5
- import com.facebook.react.ReactApplication
6
- import com.facebook.react.bridge.JSBundleLoader
7
-
8
- open class ReactIntegrationManagerBase(
9
- private val context: Context,
10
- ) {
11
- fun getJSBundlerLoader(bundleFileUrl: String): JSBundleLoader? {
12
- val bundleLoader: JSBundleLoader?
13
-
14
- if (bundleFileUrl.lowercase().startsWith("assets://")) {
15
- bundleLoader =
16
- JSBundleLoader.createAssetLoader(
17
- context,
18
- bundleFileUrl,
19
- false,
20
- )
21
- } else {
22
- bundleLoader = JSBundleLoader.createFileLoader(bundleFileUrl)
23
- }
24
- return bundleLoader
25
- }
26
-
27
- public fun getReactApplication(application: Application?): ReactApplication {
28
- if (application is ReactApplication) {
29
- return application
30
- } else {
31
- throw IllegalArgumentException("Application does not implement ReactApplication")
32
- }
33
- }
34
- }
@@ -1,212 +0,0 @@
1
- package com.hotupdater
2
-
3
- import android.app.Activity
4
- import android.content.Context
5
- import android.util.Log
6
- import com.facebook.react.ReactApplication
7
- import com.facebook.react.ReactHost
8
- import com.facebook.react.ReactInstanceEventListener
9
- import com.facebook.react.bridge.ReactApplicationContext
10
- import com.facebook.react.bridge.ReactContext
11
- import com.facebook.react.bridge.UiThreadUtil
12
- import com.facebook.react.common.LifecycleState
13
- import kotlinx.coroutines.suspendCancellableCoroutine
14
- import kotlin.coroutines.resume
15
-
16
- class ReactIntegrationManager(
17
- private val context: Context,
18
- ) : ReactIntegrationManagerBase(context) {
19
- /**
20
- * Sets the JS bundle using ReactHost directly
21
- * @param reactHost The ReactHost instance
22
- * @param bundleURL The bundle URL to set
23
- */
24
- private fun setJSBundleInternal(
25
- reactHost: ReactHost,
26
- bundleURL: String,
27
- ) {
28
- try {
29
- // Try both Java and Kotlin field names for compatibility
30
- val reactHostDelegateField =
31
- try {
32
- reactHost::class.java.getDeclaredField("mReactHostDelegate")
33
- } catch (e: NoSuchFieldException) {
34
- try {
35
- reactHost::class.java.getDeclaredField("reactHostDelegate")
36
- } catch (e2: NoSuchFieldException) {
37
- throw RuntimeException("Neither mReactHostDelegate nor reactHostDelegate field found", e2)
38
- }
39
- }
40
-
41
- reactHostDelegateField.isAccessible = true
42
- val reactHostDelegate = reactHostDelegateField.get(reactHost)
43
- // Try _jsBundleLoader first (Expo SDK 55+ ExpoReactHostDelegate uses a computed
44
- // property with a private backing field), then fall back to jsBundleLoader
45
- // (DefaultReactHostDelegate and older Expo versions).
46
- val jsBundleLoaderField =
47
- try {
48
- reactHostDelegate::class.java.getDeclaredField("_jsBundleLoader")
49
- } catch (e: NoSuchFieldException) {
50
- reactHostDelegate::class.java.getDeclaredField("jsBundleLoader")
51
- }
52
- jsBundleLoaderField.isAccessible = true
53
- jsBundleLoaderField.set(reactHostDelegate, getJSBundlerLoader(bundleURL))
54
- } catch (e: Exception) {
55
- Log.d("HotUpdater", "Failed to setJSBundle with ReactHost: ${e.message}")
56
- }
57
- }
58
-
59
- /**
60
- * Reload the React Native application using ReactHost directly.
61
- * @param reactHost The ReactHost instance
62
- */
63
- private suspend fun reloadInternal(reactHost: ReactHost) {
64
- try {
65
- // Ensure initialized; if not, start and wait
66
- waitForReactContextInitialized(reactHost)
67
-
68
- val activity = reactHost.currentReactContext?.currentActivity
69
- if (reactHost.lifecycleState != LifecycleState.RESUMED && activity != null) {
70
- reactHost.onHostResume(activity)
71
- }
72
- rearmContentAppearedAfterReload(reactHost, activity)
73
- reactHost.reload("Requested by HotUpdater")
74
- } catch (e: Exception) {
75
- Log.d("HotUpdater", "Failed to reload with ReactHost: ${e.message}")
76
- }
77
- }
78
-
79
- /**
80
- * Lets the reloaded bundle log its first content (see [ContentAppearedLog]).
81
- *
82
- * The views are re-armed once the new instance exists: by then reload has
83
- * stopped and cleared the old surfaces, so the old bundle cannot complete the
84
- * new bundle's launch, and the new bundle has not mounted anything yet.
85
- */
86
- private fun rearmContentAppearedAfterReload(
87
- reactHost: ReactHost,
88
- activity: Activity?,
89
- ) {
90
- val listener =
91
- object : ReactInstanceEventListener {
92
- override fun onReactContextInitialized(context: ReactContext) {
93
- reactHost.removeReactInstanceEventListener(this)
94
- val rearm = { ContentAppearedLog.rearm(context.currentActivity ?: activity) }
95
- // ReactHostImpl calls this on the UI thread, before it restarts
96
- // the surfaces. Another host may not.
97
- if (UiThreadUtil.isOnUiThread()) rearm() else UiThreadUtil.runOnUiThread(rearm)
98
- }
99
- }
100
- reactHost.addReactInstanceEventListener(listener)
101
- }
102
-
103
- private fun getReactApplication(): ReactApplication? {
104
- if (context is ReactApplicationContext) {
105
- val activity = context.currentActivity
106
- val application = activity?.application
107
- if (application is ReactApplication) {
108
- return application
109
- }
110
- }
111
-
112
- return null
113
- }
114
-
115
- /**
116
- * Sets the JS bundle.
117
- * Priority: ReactHostHolder (if set) > application.reactHost > the bridge (React Native 0.81 and older)
118
- * @param bundleURL The bundle URL to set
119
- */
120
- public fun setJSBundle(bundleURL: String) {
121
- try {
122
- // 1. First, check if ReactHost was set via HotUpdater.setReactHost()
123
- val configuredHost = ReactHostHolder.getReactHost()
124
- if (configuredHost != null) {
125
- setJSBundleInternal(configuredHost, bundleURL)
126
- return
127
- }
128
-
129
- // 2. Try to get ReactApplication from context
130
- val application = getReactApplication()
131
- if (application == null) {
132
- Log.d("HotUpdater", "No ReactHost set and application is not ReactApplication")
133
- return
134
- }
135
-
136
- // 3. Try application's ReactHost (new architecture)
137
- val reactHost = application.reactHost
138
- if (reactHost != null) {
139
- setJSBundleInternal(reactHost, bundleURL)
140
- return
141
- }
142
-
143
- // 4. Fall back to the bridge, which React Native 0.81 and older can run
144
- BridgeReactInstance.setJSBundle(application, getJSBundlerLoader(bundleURL))
145
- } catch (e: Exception) {
146
- Log.d("HotUpdater", "Failed to setJSBundle: ${e.message}")
147
- }
148
- }
149
-
150
- /**
151
- * Reload the React Native application.
152
- * Priority: ReactHostHolder (if set) > application.reactHost > the bridge (React Native 0.81 and older)
153
- */
154
- public suspend fun reload() {
155
- try {
156
- // 1. First, check if ReactHost was set via HotUpdater.setReactHost()
157
- val configuredHost = ReactHostHolder.getReactHost()
158
- if (configuredHost != null) {
159
- reloadInternal(configuredHost)
160
- return
161
- }
162
-
163
- // 2. Try to get ReactApplication from context
164
- val application = getReactApplication()
165
- if (application == null) {
166
- Log.d("HotUpdater", "No ReactHost set and application is not ReactApplication")
167
- return
168
- }
169
-
170
- // 3. Try application's ReactHost (new architecture)
171
- val reactHost = application.reactHost
172
- if (reactHost != null) {
173
- reloadInternal(reactHost)
174
- return
175
- }
176
-
177
- // 4. Fall back to the bridge, which React Native 0.81 and older can run
178
- BridgeReactInstance.reload(application)
179
- } catch (e: Exception) {
180
- Log.d("HotUpdater", "Failed to reload: ${e.message}")
181
- }
182
- }
183
-
184
- /**
185
- * Waits until ReactContext is initialized.
186
- * @return true if ReactContext was already initialized; false if we waited for it.
187
- */
188
- suspend fun waitForReactContextInitialized(reactHost: ReactHost): Boolean {
189
- return try {
190
- // If already initialized, return immediately
191
- if (reactHost.currentReactContext != null) return true
192
-
193
- // Wait for initialization; MainApplication handles starting the host
194
- suspendCancellableCoroutine { continuation ->
195
- val listener =
196
- object : ReactInstanceEventListener {
197
- override fun onReactContextInitialized(context: ReactContext) {
198
- reactHost.removeReactInstanceEventListener(this)
199
- if (continuation.isActive) continuation.resume(Unit)
200
- }
201
- }
202
-
203
- reactHost.addReactInstanceEventListener(listener)
204
- continuation.invokeOnCancellation { reactHost.removeReactInstanceEventListener(listener) }
205
- }
206
- false
207
- } catch (e: Exception) {
208
- Log.d("HotUpdater", "waitForReactContextInitialized failed: ${e.message}")
209
- true
210
- }
211
- }
212
- }
@@ -1,120 +0,0 @@
1
- package com.hotupdater
2
-
3
- import android.content.Context
4
- import android.util.Log
5
- import com.facebook.react.ReactApplication
6
- import com.facebook.react.ReactInstanceEventListener
7
- import com.facebook.react.ReactInstanceManager
8
- import com.facebook.react.bridge.JSBundleLoader
9
- import com.facebook.react.bridge.ReactApplicationContext
10
- import com.facebook.react.bridge.ReactContext
11
- import kotlinx.coroutines.suspendCancellableCoroutine
12
- import java.lang.reflect.Field
13
- import kotlin.coroutines.resume
14
-
15
- class ReactIntegrationManager(
16
- private val context: Context,
17
- ) : ReactIntegrationManagerBase(context) {
18
- private fun getReactApplication(): ReactApplication? {
19
- // 1. Try to get from ReactApplicationContext's current activity
20
- if (context is ReactApplicationContext) {
21
- val activity = context.currentActivity
22
- val application = activity?.application
23
- if (application is ReactApplication) {
24
- return application
25
- }
26
- }
27
-
28
- return null
29
- }
30
-
31
- /**
32
- * Sets the JS bundle.
33
- * Gets ReactApplication from context and uses reactNativeHost.
34
- * @param bundleURL The bundle URL to set
35
- */
36
- public fun setJSBundle(bundleURL: String) {
37
- try {
38
- val application = getReactApplication()
39
- if (application == null) {
40
- Log.d("HotUpdater", "Application is not ReactApplication")
41
- return
42
- }
43
-
44
- val instanceManager = application.reactNativeHost.reactInstanceManager
45
- val bundleLoader: JSBundleLoader? = this.getJSBundlerLoader(bundleURL)
46
- val bundleLoaderField: Field =
47
- instanceManager::class.java.getDeclaredField("mBundleLoader")
48
- bundleLoaderField.isAccessible = true
49
-
50
- if (bundleLoader != null) {
51
- bundleLoaderField.set(instanceManager, bundleLoader)
52
- } else {
53
- bundleLoaderField.set(instanceManager, null)
54
- }
55
- } catch (e: Exception) {
56
- Log.d("HotUpdater", "Failed to setJSBundle: ${e.message}")
57
- }
58
- }
59
-
60
- /**
61
- * Reload the React Native application.
62
- * Gets ReactApplication from context and uses reactNativeHost.
63
- */
64
- public suspend fun reload() {
65
- try {
66
- val application = getReactApplication()
67
- if (application == null) {
68
- Log.d("HotUpdater", "Application is not ReactApplication")
69
- return
70
- }
71
-
72
- val instanceManager = application.reactNativeHost.reactInstanceManager
73
-
74
- // Ensure initialized; if not, start and wait
75
- waitForReactContextInitialized(instanceManager)
76
-
77
- instanceManager.recreateReactContextInBackground()
78
- } catch (e: Exception) {
79
- try {
80
- val application = getReactApplication() ?: return
81
- val instanceManager = application.reactNativeHost.reactInstanceManager
82
- val currentActivity = instanceManager.currentReactContext?.currentActivity
83
- if (currentActivity == null) {
84
- return
85
- }
86
-
87
- currentActivity.runOnUiThread {
88
- currentActivity.recreate()
89
- }
90
- } catch (e2: Exception) {
91
- Log.d("HotUpdater", "Failed to reload: ${e2.message}")
92
- }
93
- }
94
- }
95
-
96
- /**
97
- * Waits until ReactContext is initialized using ReactInstanceManager.
98
- * @param instanceManager The ReactInstanceManager instance
99
- * @return true if ReactContext was already initialized; false if we waited for it.
100
- */
101
- suspend fun waitForReactContextInitialized(instanceManager: ReactInstanceManager): Boolean {
102
- // If already initialized, return immediately and indicate so
103
- if (instanceManager.currentReactContext != null) return true
104
-
105
- // Otherwise, wait for initialization; MainApplication handles starting the instance
106
- suspendCancellableCoroutine { continuation ->
107
- val listener =
108
- object : ReactInstanceEventListener {
109
- override fun onReactContextInitialized(context: ReactContext) {
110
- instanceManager.removeReactInstanceEventListener(this)
111
- if (continuation.isActive) continuation.resume(Unit)
112
- }
113
- }
114
-
115
- instanceManager.addReactInstanceEventListener(listener)
116
- continuation.invokeOnCancellation { instanceManager.removeReactInstanceEventListener(listener) }
117
- }
118
- return false
119
- }
120
- }