@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
@@ -4,6 +4,18 @@
4
4
  #import <React/RCTReloadCommand.h>
5
5
  #import <React/RCTLog.h>
6
6
 
7
+ #ifdef RCT_NEW_ARCH_ENABLED
8
+ #import <React/RCTFabricSurface.h>
9
+ #import <React/RCTSurfacePresenter.h>
10
+ #else
11
+ #import <React/RCTBridge+Private.h>
12
+ #import <React/RCTModuleData.h>
13
+ #endif
14
+
15
+ #include <atomic>
16
+ #include <cstdint>
17
+ #include <cstring>
18
+ #include <mutex>
7
19
  #include <fcntl.h>
8
20
  #include <limits.h>
9
21
  #include <signal.h>
@@ -22,9 +34,10 @@ typedef NSObject<HotUpdaterConfigInterface> HotUpdaterConfig;
22
34
  #import "HotUpdater-Swift.h"
23
35
  #endif
24
36
 
37
+ // The process's impl, which HotUpdaterInstance uses too.
25
38
  static HotUpdaterImpl *HotUpdaterMakeImpl(void)
26
39
  {
27
- return [[HotUpdaterImpl alloc] init];
40
+ return [HotUpdaterNativeUpdateBridge sharedImpl];
28
41
  }
29
42
 
30
43
  static NSString *HotUpdaterAppVersion(void)
@@ -36,6 +49,12 @@ static HotUpdaterConfig *HotUpdaterSharedConfig(void)
36
49
  {
37
50
  return [HotUpdaterConfig shared];
38
51
  }
52
+
53
+ // The class methods share the selectors of HotUpdaterNativeUpdatesInterface.
54
+ static Class HotUpdaterNativeUpdates(void)
55
+ {
56
+ return [HotUpdaterNativeUpdateBridge class];
57
+ }
39
58
  #endif
40
59
 
41
60
  @interface HotUpdater (InternalSharedImpl)
@@ -43,14 +62,75 @@ static HotUpdaterConfig *HotUpdaterSharedConfig(void)
43
62
  @end
44
63
 
45
64
  extern "C" NSString *HotUpdaterBuildMinBundleId(void);
65
+ extern "C" NSString *HotUpdaterBuildFingerprintHash(void);
46
66
 
47
67
  namespace {
48
68
  constexpr size_t kHotUpdaterMaxBundleIdLength = 128;
49
69
  const int kHotUpdaterSignals[] = {SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP};
50
70
 
51
- char gHotUpdaterCrashMarkerPath[PATH_MAX] = {0};
52
- char gHotUpdaterBundleId[kHotUpdaterMaxBundleIdLength] = {0};
53
- volatile sig_atomic_t gHotUpdaterShouldRollback = 0;
71
+ // One slot per instance whose launch a native crash may count against, slot 0
72
+ // being main's (HotUpdaterRecoveryCoordinator in Swift assigns them and keeps
73
+ // the rule below in step). A slot's marker path is set once and never moves.
74
+ // An instance beyond the last slot gets no marker from a native crash; its
75
+ // unfinished launch still rolls back through launchInProgress.
76
+ constexpr int kHotUpdaterCrashSlotCount = 8;
77
+ // Every slot whose launch is unverified writes its marker.
78
+ constexpr sig_atomic_t kHotUpdaterUnattributedCrash = -1;
79
+
80
+ struct HotUpdaterCrashSlotData {
81
+ // Empty while the slot is free.
82
+ char markerPath[PATH_MAX];
83
+ char bundleId[kHotUpdaterMaxBundleIdLength];
84
+ // The launch started and its first content hasn't appeared.
85
+ int shouldRollback;
86
+ };
87
+
88
+ // A sequence lock: a crash on any thread may read a slot while another thread
89
+ // rewrites it. The writer makes the sequence odd while it copies, and the
90
+ // handler keeps a copy only when the sequence was even and unchanged across
91
+ // it.
92
+ // The published copy of HotUpdaterCrashSlotData. Every field is atomic,
93
+ // copied with relaxed loads and stores, so a reader racing a writer reads
94
+ // defined (if mixed) values, which the sequence then rejects.
95
+ struct HotUpdaterAtomicCrashSlotData {
96
+ std::atomic<char> markerPath[PATH_MAX];
97
+ std::atomic<char> bundleId[kHotUpdaterMaxBundleIdLength];
98
+ std::atomic<int> shouldRollback;
99
+ };
100
+
101
+ struct HotUpdaterCrashSlotBuffer {
102
+ std::atomic<uint32_t> sequence;
103
+ HotUpdaterAtomicCrashSlotData data;
104
+ };
105
+
106
+ // Two buffers per slot: the writer fills the one not published, then
107
+ // publishes it. A crash during a rewrite, even on the writer's own thread,
108
+ // still reads the previous whole copy.
109
+ // `published`: bit 0 is the published buffer, bit 1 says anything was
110
+ // published at all (a freshly started slot has neither).
111
+ constexpr uint32_t kPublishedIndexMask = 1U;
112
+ constexpr uint32_t kPublishedOnceFlag = 2U;
113
+
114
+ struct HotUpdaterCrashSlot {
115
+ std::atomic<uint32_t> published;
116
+ HotUpdaterCrashSlotBuffer buffers[2];
117
+ };
118
+ // C++11, as old React Native builds compile it: the handler needs a
119
+ // lock-free sequence.
120
+ static_assert(ATOMIC_INT_LOCK_FREE == 2 && sizeof(uint32_t) == sizeof(int), "the crash handler needs a lock-free sequence");
121
+ static_assert(ATOMIC_CHAR_LOCK_FREE == 2, "the crash handler needs lock-free slot bytes");
122
+
123
+
124
+ // Zero-initialized: static storage. Every slot starts free.
125
+ HotUpdaterCrashSlot gHotUpdaterCrashSlots[kHotUpdaterCrashSlotCount];
126
+ // The sequence lock takes one writer at a time; every rewrite of a slot holds
127
+ // this lock. Never taken by the signal handler.
128
+ std::mutex gHotUpdaterCrashSlotWriters;
129
+ // A JavaScript fatal about to crash the process is one instance's: only its
130
+ // slot writes (none when it has no slot). Otherwise kHotUpdaterUnattributedCrash.
131
+ // Lock-free (the static_assert above), so the handler on any thread reads a
132
+ // whole value published by another.
133
+ std::atomic<int> gHotUpdaterAttributedSlot(kHotUpdaterUnattributedCrash);
54
134
  struct sigaction gHotUpdaterPreviousActions[NSIG];
55
135
  __weak RCTBridge *gHotUpdaterBridge = nil;
56
136
 
@@ -110,13 +190,147 @@ void HotUpdaterSafeCopy(char *destination, size_t destinationSize, const char *s
110
190
  destination[index] = '\0';
111
191
  }
112
192
 
113
- void HotUpdaterWriteCrashMarker()
193
+ void HotUpdaterStoreBytes(std::atomic<char> *destination, const char *source, size_t size)
194
+ {
195
+ for (size_t index = 0; index < size; ++index) {
196
+ destination[index].store(source[index], std::memory_order_relaxed);
197
+ }
198
+ }
199
+
200
+ void HotUpdaterLoadBytes(const std::atomic<char> *source, char *destination, size_t size)
201
+ {
202
+ for (size_t index = 0; index < size; ++index) {
203
+ destination[index] = source[index].load(std::memory_order_relaxed);
204
+ }
205
+ }
206
+
207
+ // The strings first, the flag last with release: a reader that loads the flag
208
+ // with acquire and finds it asking for rollback sees the whole strings, even
209
+ // when it interrupts this copy (see HotUpdaterReadFirstCrashSlotWrite).
210
+ void HotUpdaterStoreCrashSlotData(HotUpdaterAtomicCrashSlotData &destination, const HotUpdaterCrashSlotData &source)
114
211
  {
115
- if (gHotUpdaterCrashMarkerPath[0] == '\0') {
212
+ HotUpdaterStoreBytes(destination.markerPath, source.markerPath, sizeof(source.markerPath));
213
+ HotUpdaterStoreBytes(destination.bundleId, source.bundleId, sizeof(source.bundleId));
214
+ destination.shouldRollback.store(source.shouldRollback, std::memory_order_release);
215
+ }
216
+
217
+ void HotUpdaterLoadCrashSlotData(const HotUpdaterAtomicCrashSlotData &source, HotUpdaterCrashSlotData &destination)
218
+ {
219
+ HotUpdaterLoadBytes(source.markerPath, destination.markerPath, sizeof(destination.markerPath));
220
+ HotUpdaterLoadBytes(source.bundleId, destination.bundleId, sizeof(destination.bundleId));
221
+ destination.shouldRollback = source.shouldRollback.load(std::memory_order_relaxed);
222
+ // A copy taken mid-rewrite is rejected by the sequence, but the strings must
223
+ // still end inside their buffers.
224
+ destination.markerPath[sizeof(destination.markerPath) - 1] = '\0';
225
+ destination.bundleId[sizeof(destination.bundleId) - 1] = '\0';
226
+ }
227
+
228
+ constexpr int kHotUpdaterKeepShouldRollback = -1;
229
+
230
+ // Rewrites a slot; a null path or bundle id, or kHotUpdaterKeepShouldRollback,
231
+ // keeps the current value. The new fields are prepared first, so the window a
232
+ // crash skips stays one copy long.
233
+ void HotUpdaterRewriteCrashSlot(HotUpdaterCrashSlot &slot,
234
+ const char *markerPath,
235
+ const char *bundleId,
236
+ int shouldRollback)
237
+ {
238
+ std::lock_guard<std::mutex> writer(gHotUpdaterCrashSlotWriters);
239
+ const uint32_t state = slot.published.load(std::memory_order_relaxed);
240
+ const uint32_t current = state & kPublishedIndexMask;
241
+ HotUpdaterCrashSlotData published;
242
+ // The writers are serialized, so the published buffer is stable here.
243
+ HotUpdaterLoadCrashSlotData(slot.buffers[current].data, published);
244
+ HotUpdaterCrashSlotData next = published;
245
+ if (markerPath) {
246
+ HotUpdaterSafeCopy(next.markerPath, sizeof(next.markerPath), markerPath);
247
+ }
248
+ if (bundleId) {
249
+ HotUpdaterSafeCopy(next.bundleId, sizeof(next.bundleId), bundleId);
250
+ }
251
+ if (shouldRollback != kHotUpdaterKeepShouldRollback) {
252
+ next.shouldRollback = shouldRollback != 0 ? 1 : 0;
253
+ }
254
+
255
+ if ((state & kPublishedOnceFlag) != 0 && std::strcmp(published.markerPath, next.markerPath) == 0 &&
256
+ std::strcmp(published.bundleId, next.bundleId) == 0) {
257
+ // Same marker and bundle, only the flag changes (a launch charged or
258
+ // verified): flip it in the published buffer with one atomic store, so
259
+ // no crash ever falls inside a copy and reads the old flag.
260
+ slot.buffers[current].data.shouldRollback.store(next.shouldRollback, std::memory_order_release);
116
261
  return;
117
262
  }
118
263
 
119
- int fd = open(gHotUpdaterCrashMarkerPath, O_WRONLY | O_CREAT | O_TRUNC, 0644);
264
+ HotUpdaterCrashSlotBuffer &buffer = slot.buffers[current ^ 1U];
265
+ const uint32_t sequence = buffer.sequence.load(std::memory_order_relaxed);
266
+ buffer.sequence.store(sequence + 1, std::memory_order_relaxed);
267
+ std::atomic_thread_fence(std::memory_order_release);
268
+ HotUpdaterStoreCrashSlotData(buffer.data, next);
269
+ buffer.sequence.store(sequence + 2, std::memory_order_release);
270
+ slot.published.store((current ^ 1U) | kPublishedOnceFlag, std::memory_order_release);
271
+ }
272
+
273
+ // Copies a consistent snapshot of the slot, or returns false while it is
274
+ // being rewritten.
275
+ bool HotUpdaterReadCrashSlotBuffer(const HotUpdaterCrashSlotBuffer &buffer, HotUpdaterCrashSlotData &snapshot)
276
+ {
277
+ const uint32_t before = buffer.sequence.load(std::memory_order_acquire);
278
+ if ((before & 1U) != 0) {
279
+ return false;
280
+ }
281
+ HotUpdaterLoadCrashSlotData(buffer.data, snapshot);
282
+ std::atomic_thread_fence(std::memory_order_acquire);
283
+ return buffer.sequence.load(std::memory_order_relaxed) == before;
284
+ }
285
+
286
+ // The slot's first write, before anything was published, into the buffer
287
+ // that was all zero: whole, or interrupted (a crash on the writer's own
288
+ // thread, or another thread's crash) once its flag asks for rollback, since
289
+ // the strings were stored before it. Nothing to write from otherwise.
290
+ bool HotUpdaterReadFirstCrashSlotWrite(const HotUpdaterCrashSlotBuffer &buffer, HotUpdaterCrashSlotData &snapshot)
291
+ {
292
+ if (HotUpdaterReadCrashSlotBuffer(buffer, snapshot)) {
293
+ return snapshot.markerPath[0] != '\0';
294
+ }
295
+ const int shouldRollback = buffer.data.shouldRollback.load(std::memory_order_acquire);
296
+ if (shouldRollback == 0) {
297
+ // Not asking for rollback yet: a marker would change nothing.
298
+ return false;
299
+ }
300
+ HotUpdaterLoadBytes(buffer.data.markerPath, snapshot.markerPath, sizeof(snapshot.markerPath));
301
+ HotUpdaterLoadBytes(buffer.data.bundleId, snapshot.bundleId, sizeof(snapshot.bundleId));
302
+ snapshot.markerPath[sizeof(snapshot.markerPath) - 1] = '\0';
303
+ snapshot.bundleId[sizeof(snapshot.bundleId) - 1] = '\0';
304
+ snapshot.shouldRollback = shouldRollback;
305
+ return snapshot.markerPath[0] != '\0';
306
+ }
307
+
308
+ // A snapshot to write the slot's marker from, or false when there is none.
309
+ // Before anything was published, only the first write is read, never the
310
+ // unwritten published buffer. After, only the published buffer, retried while
311
+ // it is rewritten.
312
+ bool HotUpdaterReadCrashSlot(const HotUpdaterCrashSlot &slot, HotUpdaterCrashSlotData &snapshot)
313
+ {
314
+ for (int attempt = 0; attempt < 4; ++attempt) {
315
+ const uint32_t state = slot.published.load(std::memory_order_acquire);
316
+ const uint32_t published = state & kPublishedIndexMask;
317
+ if ((state & kPublishedOnceFlag) == 0) {
318
+ return HotUpdaterReadFirstCrashSlotWrite(slot.buffers[published ^ 1U], snapshot);
319
+ }
320
+ if (HotUpdaterReadCrashSlotBuffer(slot.buffers[published], snapshot)) {
321
+ return true;
322
+ }
323
+ }
324
+ return false;
325
+ }
326
+
327
+ void HotUpdaterWriteCrashMarker(const HotUpdaterCrashSlotData &slot)
328
+ {
329
+ if (slot.markerPath[0] == '\0') {
330
+ return;
331
+ }
332
+
333
+ int fd = open(slot.markerPath, O_WRONLY | O_CREAT | O_TRUNC, 0644);
120
334
  if (fd < 0) {
121
335
  return;
122
336
  }
@@ -128,11 +342,11 @@ void HotUpdaterWriteCrashMarker()
128
342
  constexpr char suffix[] = "}\n";
129
343
 
130
344
  write(fd, prefix, sizeof(prefix) - 1);
131
- if (gHotUpdaterBundleId[0] != '\0') {
132
- write(fd, gHotUpdaterBundleId, HotUpdaterSafeStringLength(gHotUpdaterBundleId, kHotUpdaterMaxBundleIdLength));
345
+ if (slot.bundleId[0] != '\0') {
346
+ write(fd, slot.bundleId, HotUpdaterSafeStringLength(slot.bundleId, kHotUpdaterMaxBundleIdLength));
133
347
  }
134
348
  write(fd, middle, sizeof(middle) - 1);
135
- if (gHotUpdaterShouldRollback != 0) {
349
+ if (slot.shouldRollback != 0) {
136
350
  write(fd, trueLiteral, sizeof(trueLiteral) - 1);
137
351
  } else {
138
352
  write(fd, falseLiteral, sizeof(falseLiteral) - 1);
@@ -141,6 +355,28 @@ void HotUpdaterWriteCrashMarker()
141
355
  close(fd);
142
356
  }
143
357
 
358
+ // Main's slot (0) writes once main launched, as it always has, with
359
+ // `shouldRollback: false` after its first content. A named slot writes only
360
+ // while its launch is unverified (started, first content not yet shown). While
361
+ // a JavaScript fatal of one instance takes the process down, only its slot
362
+ // writes.
363
+ void HotUpdaterWriteCrashMarkers()
364
+ {
365
+ const int attributedSlot = gHotUpdaterAttributedSlot.load(std::memory_order_acquire);
366
+ for (int index = 0; index < kHotUpdaterCrashSlotCount; ++index) {
367
+ if (attributedSlot != kHotUpdaterUnattributedCrash && attributedSlot != index) {
368
+ continue;
369
+ }
370
+ HotUpdaterCrashSlotData slot;
371
+ if (!HotUpdaterReadCrashSlot(gHotUpdaterCrashSlots[index], slot) || slot.markerPath[0] == '\0') {
372
+ continue;
373
+ }
374
+ if (index == 0 || slot.shouldRollback != 0) {
375
+ HotUpdaterWriteCrashMarker(slot);
376
+ }
377
+ }
378
+ }
379
+
144
380
  void HotUpdaterSignalHandler(int signum, siginfo_t *info, void *context);
145
381
 
146
382
  void HotUpdaterForwardToPreviousHandler(int signum, siginfo_t *info, void *context)
@@ -172,29 +408,64 @@ void HotUpdaterForwardToPreviousHandler(int signum, siginfo_t *info, void *conte
172
408
 
173
409
  void HotUpdaterSignalHandler(int signum, siginfo_t *info, void *context)
174
410
  {
175
- HotUpdaterWriteCrashMarker();
411
+ HotUpdaterWriteCrashMarkers();
176
412
  HotUpdaterForwardToPreviousHandler(signum, info, context);
177
413
  }
178
414
  } // namespace
179
415
 
180
- extern "C" void HotUpdaterInstallSignalHandlers(NSString *crashMarkerPath)
416
+ // Once per process. Called on the main thread.
417
+ extern "C" void HotUpdaterInstallCrashSignalHandlers(void)
181
418
  {
182
- HotUpdaterSafeCopy(gHotUpdaterCrashMarkerPath, sizeof(gHotUpdaterCrashMarkerPath), crashMarkerPath.UTF8String ?: "");
183
-
184
- struct sigaction action {};
185
- action.sa_sigaction = HotUpdaterSignalHandler;
186
- action.sa_flags = SA_SIGINFO | SA_ONSTACK;
187
- sigemptyset(&action.sa_mask);
419
+ // Once per process, whichever instance asks first: a second sigaction
420
+ // would record this handler as its own previous action and recurse.
421
+ static dispatch_once_t onceToken;
422
+ dispatch_once(&onceToken, ^{
423
+
424
+ struct sigaction action {};
425
+ action.sa_sigaction = HotUpdaterSignalHandler;
426
+ action.sa_flags = SA_SIGINFO | SA_ONSTACK;
427
+ sigemptyset(&action.sa_mask);
428
+
429
+ for (int signum : kHotUpdaterSignals) {
430
+ sigaction(signum, &action, &gHotUpdaterPreviousActions[signum]);
431
+ }
432
+ });
433
+ }
188
434
 
189
- for (int signum : kHotUpdaterSignals) {
190
- sigaction(signum, &action, &gHotUpdaterPreviousActions[signum]);
435
+ extern "C" void HotUpdaterUpdateCrashSlot(NSInteger index,
436
+ NSString *crashMarkerPath,
437
+ NSString * _Nullable bundleId,
438
+ BOOL shouldRollback)
439
+ {
440
+ if (index < 0 || index >= kHotUpdaterCrashSlotCount) {
441
+ return;
191
442
  }
443
+ HotUpdaterRewriteCrashSlot(gHotUpdaterCrashSlots[index],
444
+ crashMarkerPath.UTF8String ?: "",
445
+ bundleId.UTF8String ?: "",
446
+ shouldRollback ? 1 : 0);
447
+ }
448
+
449
+ // kHotUpdaterUnattributedCrash, a slot index, or any other negative value for
450
+ // an instance without a slot (then a native crash writes no marker).
451
+ extern "C" void HotUpdaterAttributeCrashToSlot(NSInteger index)
452
+ {
453
+ gHotUpdaterAttributedSlot.store(static_cast<int>(index), std::memory_order_release);
454
+ }
455
+
456
+ // Legacy entry points of the single main slot.
457
+ extern "C" void HotUpdaterInstallSignalHandlers(NSString *crashMarkerPath)
458
+ {
459
+ HotUpdaterRewriteCrashSlot(gHotUpdaterCrashSlots[0],
460
+ crashMarkerPath.UTF8String ?: "",
461
+ nullptr,
462
+ kHotUpdaterKeepShouldRollback);
463
+ HotUpdaterInstallCrashSignalHandlers();
192
464
  }
193
465
 
194
466
  extern "C" void HotUpdaterUpdateSignalLaunchState(NSString * _Nullable bundleId, BOOL shouldRollback)
195
467
  {
196
- HotUpdaterSafeCopy(gHotUpdaterBundleId, sizeof(gHotUpdaterBundleId), bundleId.UTF8String ?: "");
197
- gHotUpdaterShouldRollback = shouldRollback ? 1 : 0;
468
+ HotUpdaterRewriteCrashSlot(gHotUpdaterCrashSlots[0], nullptr, bundleId.UTF8String ?: "", shouldRollback ? 1 : 0);
198
469
  }
199
470
 
200
471
  extern "C" BOOL HotUpdaterPerformRecoveryReload(void)
@@ -205,7 +476,7 @@ extern "C" BOOL HotUpdaterPerformRecoveryReload(void)
205
476
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
206
477
  [impl resetLaunchPreparation];
207
478
 
208
- NSURL *bundleURL = [impl bundleURLWithBundle:[NSBundle mainBundle]];
479
+ NSURL *bundleURL = [impl bundleURLWithBundle:HotUpdaterSharedConfig().bundle];
209
480
  if (!bundleURL) {
210
481
  RCTLogWarn(@"[HotUpdater.mm] Failed to resolve bundle URL for recovery reload");
211
482
  return;
@@ -243,10 +514,67 @@ extern "C" void *HotUpdaterCopyMinBundleId(void)
243
514
  return (__bridge_retained void *)HotUpdaterGetMinBundleId();
244
515
  }
245
516
 
517
+ // The fingerprint the build embedded, or NULL for an app without one.
518
+ extern "C" void *HotUpdaterCopyBuildFingerprintHash(void)
519
+ {
520
+ NSString *fingerprintHash = HotUpdaterBuildFingerprintHash();
521
+ return fingerprintHash.length > 0
522
+ ? (__bridge_retained void *)fingerprintHash
523
+ : NULL;
524
+ }
525
+
246
526
 
247
527
  // Define Notification names used for observing Swift Core
248
528
  NSNotificationName const HotUpdaterUpdateProgressDidChangeNotification = @"HotUpdaterUpdateProgressDidChange";
249
529
 
530
+ #pragma mark - Runtime Attribution
531
+
532
+ // Crash recovery (HotUpdaterRecoveryHub in Swift) asks which instance a view,
533
+ // a surface, or a failing JavaScript thread belongs to. Each React Native
534
+ // runtime has its own module, which knows its instance once its bundle URL is
535
+ // known, and its runtime's surface presenter.
536
+
537
+ // Posted once a runtime's module knows its instance, so surfaces that started
538
+ // before it existed can be attributed.
539
+ static NSString *const HotUpdaterRuntimeDidResolveNotification = @"HotUpdaterRuntimeDidResolve";
540
+ static NSString *const HotUpdaterRuntimeModuleKey = @"HotUpdater.runtimeModule";
541
+ // The bridgeless runtime's JavaScript thread, where RCTFatal reports its
542
+ // JavaScript fatal errors.
543
+ static NSString *const HotUpdaterRuntimeThreadName = @"com.facebook.react.runtime.JavaScript";
544
+
545
+ #ifndef RCT_NEW_ARCH_ENABLED
546
+ // Old architecture: the context of the ExceptionsManager's method queue, where
547
+ // RCTFatal reports that bridge's JavaScript fatals.
548
+ static char kHotUpdaterRuntimeQueueKey;
549
+
550
+ static void HotUpdaterReleaseRuntimeQueueReference(void *reference)
551
+ {
552
+ CFRelease(reference);
553
+ }
554
+ #endif
555
+
556
+ // What a JavaScript thread's dictionary, or an old-architecture runtime's
557
+ // ExceptionsManager queue, holds: its runtime's module, weakly.
558
+ @interface HotUpdaterRuntimeModuleReference : NSObject
559
+ @property (nonatomic, weak) HotUpdater *module;
560
+ @end
561
+
562
+ @implementation HotUpdaterRuntimeModuleReference
563
+ @end
564
+
565
+ static id HotUpdaterRuntimeIdentityOfModule(HotUpdater *module);
566
+
567
+ // Every module that joined a runtime, weakly.
568
+ static NSHashTable<HotUpdater *> *HotUpdaterRuntimeModules(void)
569
+ {
570
+ static NSHashTable<HotUpdater *> *modules;
571
+ static dispatch_once_t onceToken;
572
+ dispatch_once(&onceToken, ^{
573
+ modules = [NSHashTable weakObjectsHashTable];
574
+ });
575
+ return modules;
576
+ }
577
+
250
578
  @interface HotUpdaterRecoverySignalBridge : NSObject
251
579
  @end
252
580
 
@@ -265,14 +593,23 @@ NSNotificationName const HotUpdaterUpdateProgressDidChangeNotification = @"HotUp
265
593
  @end
266
594
 
267
595
  @interface HotUpdater () <RCTInitializing>
596
+ - (HotUpdaterImpl *)resolvedInstanceImpl;
597
+ - (NSURL *)runtimeBundleURL;
598
+ - (BOOL)isRuntimeOfSurfacePresenter:(id)surfacePresenter rootTag:(NSNumber *)rootTag;
268
599
  @end
269
600
 
270
601
  @implementation HotUpdater {
271
602
  bool hasListeners;
603
+ // The impl of this module's React Native runtime, once its bundle URL is known.
604
+ HotUpdaterImpl *_instanceImpl;
605
+ // Bridgeless: this runtime's surface presenter, which knows every surface
606
+ // of this runtime by its root tag.
607
+ __weak id _surfacePresenter;
272
608
  }
273
609
 
274
610
  @synthesize bridge = _bridge;
275
611
  @synthesize moduleRegistry = _moduleRegistry;
612
+ @synthesize bundleManager = _bundleManager;
276
613
 
277
614
  + (BOOL)requiresMainQueueSetup {
278
615
  return YES;
@@ -299,7 +636,12 @@ NSNotificationName const HotUpdaterUpdateProgressDidChangeNotification = @"HotUp
299
636
  - (void)setBridge:(RCTBridge *)bridge
300
637
  {
301
638
  [super setBridge:bridge];
302
- gHotUpdaterBridge = bridge;
639
+ // Main's recovery reload reloads this bridge: only main's runtime sets
640
+ // it, never a named runtime's that started after main's. With main
641
+ // only, every bridge is main's, as before.
642
+ if (!bridge || [HotUpdaterNativeUpdates() implForBundleURL:bridge.bundleURL] == [HotUpdater sharedImpl]) {
643
+ gHotUpdaterBridge = bridge;
644
+ }
303
645
  [self configureExceptionsManagerWithBridge:bridge];
304
646
  }
305
647
 
@@ -329,7 +671,44 @@ NSNotificationName const HotUpdaterUpdateProgressDidChangeNotification = @"HotUp
329
671
 
330
672
  id exceptionsManager = [bridge moduleForClass:[RCTExceptionsManager class]];
331
673
  [self applyExceptionsManagerReloadLimit:exceptionsManager];
674
+ #ifndef RCT_NEW_ARCH_ENABLED
675
+ [self noteRuntimeQueuesOfBridge:bridge];
676
+ #endif
677
+ }
678
+
679
+ #ifndef RCT_NEW_ARCH_ENABLED
680
+ // Old architecture: RCTFatal reports a JavaScript fatal on the
681
+ // ExceptionsManager's method queue, and an error of the bridge itself on its
682
+ // JavaScript thread. Both belong to this module's bridge, so both remember
683
+ // this module, which tells crash recovery whose fatal it is.
684
+ - (void)noteRuntimeQueuesOfBridge:(RCTBridge *)bridge
685
+ {
686
+ HotUpdaterRuntimeModuleReference *reference = [HotUpdaterRuntimeModuleReference new];
687
+ reference.module = self;
688
+
689
+ #pragma clang diagnostic push
690
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
691
+ // Modules get the batched bridge, which keeps the module data.
692
+ RCTBridge *batchedBridge = [bridge respondsToSelector:@selector(moduleDataForName:)] ? bridge : bridge.batchedBridge;
693
+ RCTModuleData *exceptionsManager = [batchedBridge respondsToSelector:@selector(moduleDataForName:)]
694
+ ? [batchedBridge moduleDataForName:@"ExceptionsManager"]
695
+ : nil;
696
+ dispatch_queue_t queue = exceptionsManager.methodQueue;
697
+ #pragma clang diagnostic pop
698
+ // Only a queue of this bridge alone.
699
+ if (queue && queue != dispatch_get_main_queue() && queue != RCTJSThread) {
700
+ dispatch_queue_set_specific(queue,
701
+ &kHotUpdaterRuntimeQueueKey,
702
+ (__bridge_retained void *)reference,
703
+ HotUpdaterReleaseRuntimeQueueReference);
704
+ }
705
+ if ([batchedBridge respondsToSelector:@selector(dispatchBlock:queue:)]) {
706
+ [batchedBridge dispatchBlock:^{
707
+ [NSThread currentThread].threadDictionary[HotUpdaterRuntimeModuleKey] = reference;
708
+ } queue:RCTJSThread];
709
+ }
332
710
  }
711
+ #endif
333
712
 
334
713
  - (void)configureExceptionsManagerWithModuleRegistry:(id)moduleRegistry
335
714
  {
@@ -384,6 +763,132 @@ RCT_EXPORT_MODULE();
384
763
  return _sharedImpl;
385
764
  }
386
765
 
766
+ #pragma mark - Instance Routing
767
+
768
+ // The impl of the instance whose bundle this module's React Native runtime
769
+ // runs. Each runtime has its own module, and JavaScript never names its
770
+ // instance, so the runtime's bundle URL tells them apart. Resolved on first
771
+ // use: the URL isn't set yet in -init, and React Native also creates a module
772
+ // that never joins a runtime.
773
+ - (HotUpdaterImpl *)instanceImpl {
774
+ @synchronized (self) {
775
+ if (_instanceImpl) {
776
+ return _instanceImpl;
777
+ }
778
+ }
779
+
780
+ #ifdef RCT_NEW_ARCH_ENABLED
781
+ NSURL *bundleURL = self.bundleManager.bundleURL;
782
+ #else
783
+ NSURL *bundleURL = self.bridge.bundleURL;
784
+ #endif
785
+ if (!bundleURL) {
786
+ // Not cached: the next call resolves again once the URL is set.
787
+ return [HotUpdater sharedImpl];
788
+ }
789
+
790
+ HotUpdaterImpl *impl = [HotUpdaterNativeUpdates() implForBundleURL:bundleURL];
791
+ BOOL resolvedNow = NO;
792
+ @synchronized (self) {
793
+ if (!_instanceImpl) {
794
+ _instanceImpl = impl;
795
+ resolvedNow = YES;
796
+ }
797
+ impl = _instanceImpl;
798
+ }
799
+ if (resolvedNow) {
800
+ [[NSNotificationCenter defaultCenter] postNotificationName:HotUpdaterRuntimeDidResolveNotification
801
+ object:self
802
+ userInfo:@{
803
+ @"impl" : impl,
804
+ @"bundleURL" : bundleURL,
805
+ @"runtime" : HotUpdaterRuntimeIdentityOfModule(self) ?: self,
806
+ }];
807
+ }
808
+ return impl;
809
+ }
810
+
811
+ // The impl once this runtime's bundle URL is known, else nil: attribution
812
+ // must not take instanceImpl's fallback to main for a runtime it can't tell.
813
+ - (HotUpdaterImpl *)resolvedInstanceImpl {
814
+ @synchronized (self) {
815
+ if (_instanceImpl) {
816
+ return _instanceImpl;
817
+ }
818
+ }
819
+ return [self runtimeBundleURL] ? [self instanceImpl] : nil;
820
+ }
821
+
822
+ // The bundle this module's runtime runs, which tells an error of an earlier
823
+ // launch's runtime from one of the instance's current launch.
824
+ - (NSURL *)runtimeBundleURL {
825
+ #ifdef RCT_NEW_ARCH_ENABLED
826
+ return self.bundleManager.bundleURL;
827
+ #else
828
+ return self.bridge.bundleURL;
829
+ #endif
830
+ }
831
+
832
+ // RCTInstance hands each module it creates its runtime's surface presenter.
833
+ - (void)setSurfacePresenter:(id)surfacePresenter {
834
+ @synchronized (self) {
835
+ _surfacePresenter = surfacePresenter;
836
+ }
837
+ NSHashTable<HotUpdater *> *modules = HotUpdaterRuntimeModules();
838
+ @synchronized (modules) {
839
+ [modules addObject:self];
840
+ }
841
+ }
842
+
843
+ // Whether `surfacePresenter` is this runtime's, or, without one, whether
844
+ // this runtime's presenter has the surface of `rootTag`. Root tags are unique
845
+ // in the process, so no other runtime's presenter has it.
846
+ - (BOOL)isRuntimeOfSurfacePresenter:(id)surfacePresenter rootTag:(NSNumber *)rootTag {
847
+ #ifdef RCT_NEW_ARCH_ENABLED
848
+ RCTSurfacePresenter *presenter = nil;
849
+ @synchronized (self) {
850
+ presenter = _surfacePresenter;
851
+ }
852
+ if (!presenter) {
853
+ return NO;
854
+ }
855
+ if (surfacePresenter) {
856
+ return presenter == surfacePresenter;
857
+ }
858
+ return rootTag && [presenter surfaceForRootTag:rootTag.integerValue] != nil;
859
+ #else
860
+ return NO;
861
+ #endif
862
+ }
863
+
864
+ // React Native asks for the module's JSI object on the runtime's JavaScript
865
+ // thread, which is where RCTFatal reports that runtime's JavaScript fatals.
866
+ - (void)noteRuntimeThread {
867
+ NSThread *thread = [NSThread currentThread];
868
+ if (![thread.name isEqualToString:HotUpdaterRuntimeThreadName]) {
869
+ return;
870
+ }
871
+ HotUpdaterRuntimeModuleReference *reference = [HotUpdaterRuntimeModuleReference new];
872
+ reference.module = self;
873
+ thread.threadDictionary[HotUpdaterRuntimeModuleKey] = reference;
874
+ // Resolves now, while the bundle URL is known, for the surfaces that
875
+ // started before this module existed.
876
+ [self resolvedInstanceImpl];
877
+ }
878
+
879
+ // Reloading reloads main's React host or the whole app, so a runtime of a
880
+ // named instance refuses it until each React host can reload its own.
881
+ - (BOOL)rejectReloadOfNamedInstance:(RCTPromiseRejectBlock)reject {
882
+ if ([self instanceImpl] == [HotUpdater sharedImpl]) {
883
+ return NO;
884
+ }
885
+ reject(@"RELOAD_UNSUPPORTED",
886
+ @"HotUpdater.reload() and HotUpdater.reloadProcess() are not supported in a React Native runtime of a named "
887
+ @"HotUpdaterInstance yet. Reload its React Native host from native code.",
888
+ nil);
889
+ return YES;
890
+ }
891
+
387
892
  #pragma mark - React Native Constants (Keep getMinBundleId, delegate others)
388
893
 
389
894
  // Returns the bundle ID of the bundle this build ships (MIN_BUNDLE_ID)
@@ -395,31 +900,34 @@ RCT_EXPORT_MODULE();
395
900
  return @{
396
901
  @"MIN_BUNDLE_ID": [self getMinBundleId] ?: [NSNull null],
397
902
  @"APP_VERSION": HotUpdaterAppVersion() ?: [NSNull null],
398
- @"CHANNEL": [[HotUpdater sharedImpl] getChannel] ?: [NSNull null],
399
- @"DEFAULT_CHANNEL": [[HotUpdater sharedImpl] getDefaultChannel] ?: [NSNull null],
400
- @"FINGERPRINT_HASH": [[HotUpdater sharedImpl] getFingerprintHash] ?: [NSNull null]
903
+ @"CHANNEL": [[self instanceImpl] getChannel] ?: [NSNull null],
904
+ @"DEFAULT_CHANNEL": [[self instanceImpl] getDefaultChannel] ?: [NSNull null],
905
+ @"FINGERPRINT_HASH": [[self instanceImpl] getFingerprintHash] ?: [NSNull null]
401
906
  };
402
907
  }
403
908
 
404
909
 
405
910
  // Get bundleURL with default bundle using singleton
406
911
  + (NSURL *)bundleURL {
407
- return [[HotUpdater sharedImpl] bundleURLWithBundle:[NSBundle mainBundle]];
912
+ return [self bundleURLWithBundle:[NSBundle mainBundle]];
408
913
  }
409
914
 
410
915
  // Get bundleURL with specific bundle using singleton
411
916
  + (NSURL *)bundleURLWithBundle:(NSBundle *)bundle {
917
+ // Record the bundle before the first sharedImpl, whose storage isolation
918
+ // key reads the bundle's HOT_UPDATER_* Info.plist keys.
919
+ HotUpdaterSharedConfig().bundle = bundle;
412
920
  return [[HotUpdater sharedImpl] bundleURLWithBundle:bundle];
413
921
  }
414
922
 
415
923
  // Get bundleURL with default bundle using instance impl
416
924
  - (NSURL *)bundleURL {
417
- return [[HotUpdater sharedImpl] bundleURLWithBundle:[NSBundle mainBundle]];
925
+ return [HotUpdater bundleURLWithBundle:[NSBundle mainBundle]];
418
926
  }
419
927
 
420
928
  // Get bundleURL with specific bundle using instance impl
421
929
  - (NSURL *)bundleURLWithBundle:(NSBundle *)bundle {
422
- return [[HotUpdater sharedImpl] bundleURLWithBundle:bundle];
930
+ return [HotUpdater bundleURLWithBundle:bundle];
423
931
  }
424
932
 
425
933
  + (void)configureWithFingerprintHash:(NSString *)fingerprintHash
@@ -434,6 +942,9 @@ RCT_EXPORT_MODULE();
434
942
 
435
943
  - (void)handleDownloadProgress:(NSNotification *)notification {
436
944
  if (!hasListeners) return;
945
+ // Every impl posts its progress here; this runtime's JavaScript gets
946
+ // only its own instance's.
947
+ if (notification.object != [self instanceImpl]) return;
437
948
 
438
949
  NSDictionary *userInfo = notification.userInfo;
439
950
  NSNumber *progressNum = userInfo[@"progress"];
@@ -478,11 +989,14 @@ RCT_EXPORT_MODULE();
478
989
  - (void)reload:(RCTPromiseResolveBlock)resolve
479
990
  reject:(RCTPromiseRejectBlock)reject {
480
991
  RCTLogInfo(@"[HotUpdater.mm] HotUpdater requested a reload");
992
+ if ([self rejectReloadOfNamedInstance:reject]) {
993
+ return;
994
+ }
481
995
  dispatch_async(dispatch_get_main_queue(), ^{
482
996
  @try {
483
997
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
484
998
  [impl resetLaunchPreparation];
485
- NSURL *bundleURL = [impl bundleURLWithBundle:[NSBundle mainBundle]];
999
+ NSURL *bundleURL = [impl bundleURLWithBundle:HotUpdaterSharedConfig().bundle];
486
1000
  RCTLogInfo(@"[HotUpdater.mm] Reloading with bundle URL: %@", bundleURL);
487
1001
  if (bundleURL) {
488
1002
  RCTReloadCommandSetBundleURL(bundleURL);
@@ -543,7 +1057,7 @@ RCT_EXPORT_MODULE();
543
1057
  paramDict[@"requestTimeout"] = @(params.requestTimeout().value());
544
1058
  }
545
1059
 
546
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1060
+ HotUpdaterImpl *impl = [self instanceImpl];
547
1061
  [impl updateBundle:paramDict resolver:resolve rejecter:reject];
548
1062
  }
549
1063
 
@@ -583,63 +1097,63 @@ RCT_EXPORT_MODULE();
583
1097
  paramDict[@"selection"] = params.selection();
584
1098
  }
585
1099
 
586
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1100
+ HotUpdaterImpl *impl = [self instanceImpl];
587
1101
  [impl installArtifact:paramDict resolver:resolve rejecter:reject];
588
1102
  }
589
1103
 
590
1104
  - (NSNumber *)acceptReleaseCatalog:(NSDictionary *)params {
591
- return @([[HotUpdater sharedImpl] acceptReleaseCatalog:params]);
1105
+ return @([[self instanceImpl] acceptReleaseCatalog:params]);
592
1106
  }
593
1107
 
594
1108
  - (NSDictionary *)getActiveUpdateState {
595
- return [[HotUpdater sharedImpl] getActiveUpdateState];
1109
+ return [[self instanceImpl] getActiveUpdateState];
596
1110
  }
597
1111
 
598
1112
  - (void)getReleaseCatalogCache:(NSString *)partition
599
1113
  resolve:(RCTPromiseResolveBlock)resolve
600
1114
  reject:(RCTPromiseRejectBlock)reject {
601
- [[HotUpdater sharedImpl] getReleaseCatalogCache:partition resolver:resolve rejecter:reject];
1115
+ [[self instanceImpl] getReleaseCatalogCache:partition resolver:resolve rejecter:reject];
602
1116
  }
603
1117
 
604
1118
  - (void)setReleaseCatalogCache:(NSString *)partition
605
1119
  payload:(NSString *)payload
606
1120
  resolve:(RCTPromiseResolveBlock)resolve
607
1121
  reject:(RCTPromiseRejectBlock)reject {
608
- [[HotUpdater sharedImpl] setReleaseCatalogCache:partition payload:payload resolver:resolve rejecter:reject];
1122
+ [[self instanceImpl] setReleaseCatalogCache:partition payload:payload resolver:resolve rejecter:reject];
609
1123
  }
610
1124
 
611
1125
  - (void)removeReleaseCatalogCache:(NSString *)partition
612
1126
  resolve:(RCTPromiseResolveBlock)resolve
613
1127
  reject:(RCTPromiseRejectBlock)reject {
614
- [[HotUpdater sharedImpl] removeReleaseCatalogCache:partition resolver:resolve rejecter:reject];
1128
+ [[self instanceImpl] removeReleaseCatalogCache:partition resolver:resolve rejecter:reject];
615
1129
  }
616
1130
 
617
1131
  - (NSNumber *)isReleaseSelectionCurrent:(NSDictionary *)params {
618
- return @([[HotUpdater sharedImpl] isReleaseSelectionCurrent:params]);
1132
+ return @([[self instanceImpl] isReleaseSelectionCurrent:params]);
619
1133
  }
620
1134
 
621
1135
  - (void)commitReleaseSelection:(NSDictionary *)params
622
1136
  resolve:(RCTPromiseResolveBlock)resolve
623
1137
  reject:(RCTPromiseRejectBlock)reject {
624
- [[HotUpdater sharedImpl] commitReleaseSelection:params resolver:resolve rejecter:reject];
1138
+ [[self instanceImpl] commitReleaseSelection:params resolver:resolve rejecter:reject];
625
1139
  }
626
1140
 
627
1141
  - (NSDictionary *)notifyAppReady {
628
1142
  NSLog(@"[HotUpdater.mm] notifyAppReady called");
629
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1143
+ HotUpdaterImpl *impl = [self instanceImpl];
630
1144
  return [impl notifyAppReady];
631
1145
  }
632
1146
 
633
1147
  - (NSArray<NSString *> *)getCrashHistory {
634
1148
  NSLog(@"[HotUpdater.mm] getCrashHistory called");
635
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1149
+ HotUpdaterImpl *impl = [self instanceImpl];
636
1150
  NSArray<NSString *> *crashHistory = [impl getCrashHistory];
637
1151
  return crashHistory ?: @[];
638
1152
  }
639
1153
 
640
1154
  - (NSNumber *)clearCrashHistory {
641
1155
  NSLog(@"[HotUpdater.mm] clearCrashHistory called");
642
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1156
+ HotUpdaterImpl *impl = [self instanceImpl];
643
1157
  BOOL result = [impl clearCrashHistory];
644
1158
  return @(result);
645
1159
  }
@@ -650,18 +1164,22 @@ RCT_EXPORT_MODULE();
650
1164
  }
651
1165
 
652
1166
  - (NSString * _Nullable)getStorageItem:(NSString *)key {
653
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1167
+ HotUpdaterImpl *impl = [self instanceImpl];
654
1168
  return [impl getStorageItem:key];
655
1169
  }
656
1170
 
657
1171
  - (void)setStorageItem:(NSString *)key value:(NSString * _Nullable)value {
658
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1172
+ HotUpdaterImpl *impl = [self instanceImpl];
659
1173
  [impl setStorageItem:key value:value];
660
1174
  }
661
1175
 
1176
+ - (NSString * _Nullable)takeNativeUpdateEvents {
1177
+ return [HotUpdaterNativeUpdates() takeEventsWithImpl:[self instanceImpl]];
1178
+ }
1179
+
662
1180
  - (NSString * _Nullable)getBaseURL {
663
1181
  NSLog(@"[HotUpdater.mm] getBaseURL called");
664
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1182
+ HotUpdaterImpl *impl = [self instanceImpl];
665
1183
  return [impl getBaseURL];
666
1184
  }
667
1185
 
@@ -677,21 +1195,22 @@ RCT_EXPORT_MODULE();
677
1195
 
678
1196
  - (NSString * _Nullable)getBundleId {
679
1197
  NSLog(@"[HotUpdater.mm] getBundleId called");
680
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1198
+ HotUpdaterImpl *impl = [self instanceImpl];
681
1199
  return [impl getBundleId];
682
1200
  }
683
1201
 
684
1202
  - (NSDictionary<NSString *, id> *)getManifest {
685
1203
  NSLog(@"[HotUpdater.mm] getManifest called");
686
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1204
+ HotUpdaterImpl *impl = [self instanceImpl];
687
1205
  NSDictionary<NSString *, id> *manifest = [impl getManifest];
688
1206
  return manifest ?: @{};
689
1207
  }
690
1208
 
691
1209
  - (void)resetChannel:(RCTPromiseResolveBlock)resolve
692
1210
  reject:(RCTPromiseRejectBlock)reject {
693
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
694
- [impl resetChannel:resolve rejecter:reject];
1211
+ [HotUpdaterNativeUpdates() resetChannelWithImpl:[self instanceImpl]
1212
+ resolver:resolve
1213
+ rejecter:reject];
695
1214
  }
696
1215
 
697
1216
  - (facebook::react::ModuleConstants<JS::NativeHotUpdater::Constants::Builder>)constantsToExport {
@@ -699,7 +1218,7 @@ RCT_EXPORT_MODULE();
699
1218
  }
700
1219
 
701
1220
  - (facebook::react::ModuleConstants<JS::NativeHotUpdater::Constants::Builder>)getConstants {
702
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1221
+ HotUpdaterImpl *impl = [self instanceImpl];
703
1222
  return facebook::react::typedConstants<JS::NativeHotUpdater::Constants::Builder>({
704
1223
  .MIN_BUNDLE_ID = [self getMinBundleId],
705
1224
  .APP_VERSION = HotUpdaterAppVersion(),
@@ -716,11 +1235,14 @@ RCT_EXPORT_MODULE();
716
1235
  RCT_EXPORT_METHOD(reload:(RCTPromiseResolveBlock)resolve
717
1236
  reject:(RCTPromiseRejectBlock)reject) {
718
1237
  RCTLogInfo(@"[HotUpdater.mm] HotUpdater requested a reload");
1238
+ if ([self rejectReloadOfNamedInstance:reject]) {
1239
+ return;
1240
+ }
719
1241
  dispatch_async(dispatch_get_main_queue(), ^{
720
1242
  @try {
721
1243
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
722
1244
  [impl resetLaunchPreparation];
723
- NSURL *bundleURL = [impl bundleURLWithBundle:[NSBundle mainBundle]];
1245
+ NSURL *bundleURL = [impl bundleURLWithBundle:HotUpdaterSharedConfig().bundle];
724
1246
  RCTLogInfo(@"[HotUpdater.mm] Reloading with bundle URL: %@", bundleURL);
725
1247
  if (bundleURL) {
726
1248
  RCTReloadCommandSetBundleURL(bundleURL);
@@ -748,7 +1270,7 @@ RCT_EXPORT_METHOD(updateBundle:(NSDictionary *)params
748
1270
  resolve:(RCTPromiseResolveBlock)resolve
749
1271
  reject:(RCTPromiseRejectBlock)reject) {
750
1272
  NSLog(@"[HotUpdater.mm] updateBundle called. params: %@", params);
751
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1273
+ HotUpdaterImpl *impl = [self instanceImpl];
752
1274
  [impl updateBundle:params resolver:resolve rejecter:reject];
753
1275
  }
754
1276
 
@@ -756,63 +1278,63 @@ RCT_EXPORT_METHOD(installArtifact:(NSDictionary *)params
756
1278
  resolve:(RCTPromiseResolveBlock)resolve
757
1279
  reject:(RCTPromiseRejectBlock)reject) {
758
1280
  NSLog(@"[HotUpdater.mm] installArtifact called.");
759
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1281
+ HotUpdaterImpl *impl = [self instanceImpl];
760
1282
  [impl installArtifact:params resolver:resolve rejecter:reject];
761
1283
  }
762
1284
 
763
1285
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(acceptReleaseCatalog:(NSDictionary *)params) {
764
- return @([[HotUpdater sharedImpl] acceptReleaseCatalog:params]);
1286
+ return @([[self instanceImpl] acceptReleaseCatalog:params]);
765
1287
  }
766
1288
 
767
1289
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getActiveUpdateState) {
768
- return [[HotUpdater sharedImpl] getActiveUpdateState];
1290
+ return [[self instanceImpl] getActiveUpdateState];
769
1291
  }
770
1292
 
771
1293
  RCT_EXPORT_METHOD(getReleaseCatalogCache:(NSString *)partition
772
1294
  resolve:(RCTPromiseResolveBlock)resolve
773
1295
  reject:(RCTPromiseRejectBlock)reject) {
774
- [[HotUpdater sharedImpl] getReleaseCatalogCache:partition resolver:resolve rejecter:reject];
1296
+ [[self instanceImpl] getReleaseCatalogCache:partition resolver:resolve rejecter:reject];
775
1297
  }
776
1298
 
777
1299
  RCT_EXPORT_METHOD(setReleaseCatalogCache:(NSString *)partition
778
1300
  payload:(NSString *)payload
779
1301
  resolve:(RCTPromiseResolveBlock)resolve
780
1302
  reject:(RCTPromiseRejectBlock)reject) {
781
- [[HotUpdater sharedImpl] setReleaseCatalogCache:partition payload:payload resolver:resolve rejecter:reject];
1303
+ [[self instanceImpl] setReleaseCatalogCache:partition payload:payload resolver:resolve rejecter:reject];
782
1304
  }
783
1305
 
784
1306
  RCT_EXPORT_METHOD(removeReleaseCatalogCache:(NSString *)partition
785
1307
  resolve:(RCTPromiseResolveBlock)resolve
786
1308
  reject:(RCTPromiseRejectBlock)reject) {
787
- [[HotUpdater sharedImpl] removeReleaseCatalogCache:partition resolver:resolve rejecter:reject];
1309
+ [[self instanceImpl] removeReleaseCatalogCache:partition resolver:resolve rejecter:reject];
788
1310
  }
789
1311
 
790
1312
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isReleaseSelectionCurrent:(NSDictionary *)params) {
791
- return @([[HotUpdater sharedImpl] isReleaseSelectionCurrent:params]);
1313
+ return @([[self instanceImpl] isReleaseSelectionCurrent:params]);
792
1314
  }
793
1315
 
794
1316
  RCT_EXPORT_METHOD(commitReleaseSelection:(NSDictionary *)params
795
1317
  resolve:(RCTPromiseResolveBlock)resolve
796
1318
  reject:(RCTPromiseRejectBlock)reject) {
797
- [[HotUpdater sharedImpl] commitReleaseSelection:params resolver:resolve rejecter:reject];
1319
+ [[self instanceImpl] commitReleaseSelection:params resolver:resolve rejecter:reject];
798
1320
  }
799
1321
 
800
1322
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(notifyAppReady) {
801
1323
  NSLog(@"[HotUpdater.mm] notifyAppReady called");
802
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1324
+ HotUpdaterImpl *impl = [self instanceImpl];
803
1325
  return [impl notifyAppReady];
804
1326
  }
805
1327
 
806
1328
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCrashHistory) {
807
1329
  NSLog(@"[HotUpdater.mm] getCrashHistory called");
808
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1330
+ HotUpdaterImpl *impl = [self instanceImpl];
809
1331
  NSArray<NSString *> *crashHistory = [impl getCrashHistory];
810
1332
  return crashHistory ?: @[];
811
1333
  }
812
1334
 
813
1335
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(clearCrashHistory) {
814
1336
  NSLog(@"[HotUpdater.mm] clearCrashHistory called");
815
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1337
+ HotUpdaterImpl *impl = [self instanceImpl];
816
1338
  BOOL result = [impl clearCrashHistory];
817
1339
  return @(result);
818
1340
  }
@@ -823,18 +1345,22 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getInstallId) {
823
1345
  }
824
1346
 
825
1347
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getStorageItem:(NSString *)key) {
826
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1348
+ HotUpdaterImpl *impl = [self instanceImpl];
827
1349
  return [impl getStorageItem:key];
828
1350
  }
829
1351
 
830
1352
  RCT_EXPORT_METHOD(setStorageItem:(NSString *)key value:(NSString * _Nullable)value) {
831
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1353
+ HotUpdaterImpl *impl = [self instanceImpl];
832
1354
  [impl setStorageItem:key value:value];
833
1355
  }
834
1356
 
1357
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(takeNativeUpdateEvents) {
1358
+ return [HotUpdaterNativeUpdates() takeEventsWithImpl:[self instanceImpl]];
1359
+ }
1360
+
835
1361
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBaseURL) {
836
1362
  NSLog(@"[HotUpdater.mm] getBaseURL called");
837
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1363
+ HotUpdaterImpl *impl = [self instanceImpl];
838
1364
  return [impl getBaseURL];
839
1365
  }
840
1366
 
@@ -850,21 +1376,22 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCohort) {
850
1376
 
851
1377
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBundleId) {
852
1378
  NSLog(@"[HotUpdater.mm] getBundleId called");
853
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1379
+ HotUpdaterImpl *impl = [self instanceImpl];
854
1380
  return [impl getBundleId];
855
1381
  }
856
1382
 
857
1383
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getManifest) {
858
1384
  NSLog(@"[HotUpdater.mm] getManifest called");
859
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
1385
+ HotUpdaterImpl *impl = [self instanceImpl];
860
1386
  NSDictionary<NSString *, id> *manifest = [impl getManifest];
861
1387
  return manifest ?: @{};
862
1388
  }
863
1389
 
864
1390
  RCT_EXPORT_METHOD(resetChannel:(RCTPromiseResolveBlock)resolve
865
1391
  reject:(RCTPromiseRejectBlock)reject) {
866
- HotUpdaterImpl *impl = [HotUpdater sharedImpl];
867
- [impl resetChannel:resolve rejecter:reject];
1392
+ [HotUpdaterNativeUpdates() resetChannelWithImpl:[self instanceImpl]
1393
+ resolver:resolve
1394
+ rejecter:reject];
868
1395
  }
869
1396
 
870
1397
  - (NSDictionary *)constantsToExport {
@@ -883,9 +1410,232 @@ RCT_EXPORT_METHOD(resetChannel:(RCTPromiseResolveBlock)resolve
883
1410
 
884
1411
  #ifdef RCT_NEW_ARCH_ENABLED
885
1412
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
1413
+ [self noteRuntimeThread];
886
1414
  return std::make_shared<facebook::react::NativeHotUpdaterSpecJSI>(params);
887
1415
  }
888
1416
  #endif
889
1417
 
890
1418
 
891
1419
  @end
1420
+
1421
+ #pragma mark - Runtime Attribution (C)
1422
+
1423
+ #ifdef RCT_NEW_ARCH_ENABLED
1424
+ // The bundle URL an RCTHost or RCTInstance runs, which neither exposes:
1425
+ // read through KVC from what RN 0.85 keeps (RCTHost's `_bundleURL` and
1426
+ // `_bundleManager`, RCTInstance's `_bridgeModuleDecorator.bundleManager`).
1427
+ // Nil when another React Native version keeps it elsewhere.
1428
+ static NSURL *HotUpdaterBundleURLOfRuntime(id object)
1429
+ {
1430
+ for (NSString *keyPath in @[ @"bundleURL", @"bundleManager.bundleURL", @"bridgeModuleDecorator.bundleManager.bundleURL" ]) {
1431
+ @try {
1432
+ id value = [object valueForKeyPath:keyPath];
1433
+ if ([value isKindOfClass:[NSURL class]]) {
1434
+ return value;
1435
+ }
1436
+ } @catch (NSException *exception) {
1437
+ // Not a key of this object.
1438
+ }
1439
+ }
1440
+ return nil;
1441
+ }
1442
+ #endif
1443
+
1444
+ // The impl whose runtime `object` belongs to: a Fabric surface or root view,
1445
+ // an RCTInstance or RCTHost (by its surface presenter), an old-architecture
1446
+ // root view or bridge (by its bundle URL). Nil when no runtime claims it.
1447
+ // `runtimeBundleURL`, when given, receives the bundle the runtime runs.
1448
+ // The object that stands for one React Native runtime, which crash recovery
1449
+ // keeps the launch it runs by: the module (bridgeless), or the bridge the app
1450
+ // created (old architecture; a module, a root view and a load failure each
1451
+ // reach it, and its batched bridge points back to it).
1452
+ static id HotUpdaterRuntimeIdentityOfBridge(id bridge)
1453
+ {
1454
+ if ([bridge respondsToSelector:NSSelectorFromString(@"parentBridge")]) {
1455
+ id parent = [bridge valueForKey:@"parentBridge"];
1456
+ if (parent) {
1457
+ return parent;
1458
+ }
1459
+ }
1460
+ return bridge;
1461
+ }
1462
+
1463
+ static id HotUpdaterRuntimeIdentityOfModule(HotUpdater *module)
1464
+ {
1465
+ #ifdef RCT_NEW_ARCH_ENABLED
1466
+ return module;
1467
+ #else
1468
+ return HotUpdaterRuntimeIdentityOfBridge(module.bridge);
1469
+ #endif
1470
+ }
1471
+
1472
+ // `runtimeIdentity`, when given, receives the runtime's identity (see
1473
+ // HotUpdaterRuntimeIdentityOfBridge) when one told the runtime apart.
1474
+ static HotUpdaterImpl *HotUpdaterImplForRootObject(id object, NSURL **runtimeBundleURL, id *runtimeIdentity = nullptr)
1475
+ {
1476
+ if (!object) {
1477
+ return nil;
1478
+ }
1479
+
1480
+ #ifdef RCT_NEW_ARCH_ENABLED
1481
+ id surfacePresenter = nil;
1482
+ NSNumber *rootTag = nil;
1483
+ if ([object respondsToSelector:@selector(surfacePresenter)]) {
1484
+ surfacePresenter = [object valueForKey:@"surfacePresenter"];
1485
+ } else if ([object respondsToSelector:NSSelectorFromString(@"rootViewTag")]) {
1486
+ id tag = [object valueForKey:@"rootViewTag"];
1487
+ rootTag = [tag isKindOfClass:[NSNumber class]] ? tag : nil;
1488
+ } else if ([object isKindOfClass:[UIView class]] &&
1489
+ [object isKindOfClass:NSClassFromString(@"RCTRootComponentView")]) {
1490
+ rootTag = @(((UIView *)object).tag);
1491
+ }
1492
+ if (surfacePresenter || rootTag) {
1493
+ NSHashTable<HotUpdater *> *table = HotUpdaterRuntimeModules();
1494
+ NSArray<HotUpdater *> *modules;
1495
+ @synchronized (table) {
1496
+ modules = table.allObjects;
1497
+ }
1498
+ for (HotUpdater *module in modules) {
1499
+ if ([module isRuntimeOfSurfacePresenter:surfacePresenter rootTag:rootTag]) {
1500
+ HotUpdaterImpl *impl = [module resolvedInstanceImpl];
1501
+ if (impl) {
1502
+ if (runtimeBundleURL) {
1503
+ *runtimeBundleURL = [module runtimeBundleURL];
1504
+ }
1505
+ if (runtimeIdentity) {
1506
+ *runtimeIdentity = HotUpdaterRuntimeIdentityOfModule(module);
1507
+ }
1508
+ return impl;
1509
+ }
1510
+ }
1511
+ }
1512
+ if (!surfacePresenter) {
1513
+ return nil;
1514
+ }
1515
+ // An RCTInstance or RCTHost whose bundle failed to load before its
1516
+ // runtime created a module: its bundle URL tells whose it is.
1517
+ NSURL *bundleURL = HotUpdaterBundleURLOfRuntime(object);
1518
+ if (!bundleURL) {
1519
+ return nil;
1520
+ }
1521
+ if (runtimeBundleURL) {
1522
+ *runtimeBundleURL = bundleURL;
1523
+ }
1524
+ return [HotUpdaterNativeUpdates() implForBundleURL:bundleURL];
1525
+ }
1526
+ #endif
1527
+
1528
+ RCTBridge *bridge = nil;
1529
+ if ([object isKindOfClass:[RCTBridge class]]) {
1530
+ bridge = object;
1531
+ } else if ([object respondsToSelector:@selector(bridge)]) {
1532
+ id candidate = [object valueForKey:@"bridge"];
1533
+ bridge = [candidate isKindOfClass:[RCTBridge class]] ? candidate : nil;
1534
+ }
1535
+ NSURL *bundleURL = bridge.bundleURL;
1536
+ if (!bundleURL) {
1537
+ return nil;
1538
+ }
1539
+ if (runtimeBundleURL) {
1540
+ *runtimeBundleURL = bundleURL;
1541
+ }
1542
+ if (runtimeIdentity) {
1543
+ *runtimeIdentity = HotUpdaterRuntimeIdentityOfBridge(bridge);
1544
+ }
1545
+ return [HotUpdaterNativeUpdates() implForBundleURL:bundleURL];
1546
+ }
1547
+
1548
+ extern "C" void *HotUpdaterCopyImplForRootObject(id object)
1549
+ {
1550
+ HotUpdaterImpl *impl = HotUpdaterImplForRootObject(object, nullptr);
1551
+ return impl ? (__bridge_retained void *)impl : NULL;
1552
+ }
1553
+
1554
+ // The bundle the runtime `object` belongs to runs, or NULL.
1555
+ extern "C" void *HotUpdaterCopyBundleURLForRootObject(id object)
1556
+ {
1557
+ NSURL *bundleURL = nil;
1558
+ HotUpdaterImplForRootObject(object, &bundleURL);
1559
+ return bundleURL ? (__bridge_retained void *)bundleURL : NULL;
1560
+ }
1561
+
1562
+ // The identity of the runtime `object` belongs to, or NULL when none told it
1563
+ // apart. Crash recovery keeps the launch each runtime runs by it.
1564
+ extern "C" void *HotUpdaterCopyRuntimeIdentityForRootObject(id object)
1565
+ {
1566
+ id identity = nil;
1567
+ HotUpdaterImplForRootObject(object, nullptr, &identity);
1568
+ return identity ? (__bridge_retained void *)identity : NULL;
1569
+ }
1570
+
1571
+ // The impl of the runtime whose JavaScript thread, or old-architecture
1572
+ // ExceptionsManager queue, calls this, or NULL.
1573
+ extern "C" void *HotUpdaterCopyRuntimeImpl(void)
1574
+ {
1575
+ HotUpdaterRuntimeModuleReference *reference =
1576
+ [NSThread currentThread].threadDictionary[HotUpdaterRuntimeModuleKey];
1577
+ #ifndef RCT_NEW_ARCH_ENABLED
1578
+ if (!reference) {
1579
+ reference = (__bridge HotUpdaterRuntimeModuleReference *)dispatch_get_specific(&kHotUpdaterRuntimeQueueKey);
1580
+ }
1581
+ #endif
1582
+ HotUpdaterImpl *impl = [reference.module resolvedInstanceImpl];
1583
+ return impl ? (__bridge_retained void *)impl : NULL;
1584
+ }
1585
+
1586
+ // The identity of the runtime whose JavaScript thread, or old-architecture
1587
+ // ExceptionsManager queue, calls this, or NULL.
1588
+ extern "C" void *HotUpdaterCopyRuntimeIdentity(void)
1589
+ {
1590
+ HotUpdaterRuntimeModuleReference *reference =
1591
+ [NSThread currentThread].threadDictionary[HotUpdaterRuntimeModuleKey];
1592
+ #ifndef RCT_NEW_ARCH_ENABLED
1593
+ if (!reference) {
1594
+ reference = (__bridge HotUpdaterRuntimeModuleReference *)dispatch_get_specific(&kHotUpdaterRuntimeQueueKey);
1595
+ }
1596
+ #endif
1597
+ HotUpdater *module = reference.module;
1598
+ id identity = module ? HotUpdaterRuntimeIdentityOfModule(module) : nil;
1599
+ return identity ? (__bridge_retained void *)identity : NULL;
1600
+ }
1601
+
1602
+ // The bundle the runtime whose JavaScript thread, or old-architecture
1603
+ // ExceptionsManager queue, calls this runs, or NULL.
1604
+ extern "C" void *HotUpdaterCopyRuntimeBundleURL(void)
1605
+ {
1606
+ HotUpdaterRuntimeModuleReference *reference =
1607
+ [NSThread currentThread].threadDictionary[HotUpdaterRuntimeModuleKey];
1608
+ #ifndef RCT_NEW_ARCH_ENABLED
1609
+ if (!reference) {
1610
+ reference = (__bridge HotUpdaterRuntimeModuleReference *)dispatch_get_specific(&kHotUpdaterRuntimeQueueKey);
1611
+ }
1612
+ #endif
1613
+ NSURL *bundleURL = [reference.module runtimeBundleURL];
1614
+ return bundleURL ? (__bridge_retained void *)bundleURL : NULL;
1615
+ }
1616
+
1617
+ // Whether a started view is shown: an old-architecture root view in a window,
1618
+ // or a running Fabric surface whose view is in a window. A surface's `view`
1619
+ // getter would create its view, so this reads the one it already has.
1620
+ extern "C" BOOL HotUpdaterIsViewShown(id object)
1621
+ {
1622
+ if ([object isKindOfClass:[UIView class]]) {
1623
+ return ((UIView *)object).window != nil;
1624
+ }
1625
+ #ifdef RCT_NEW_ARCH_ENABLED
1626
+ if ([object isKindOfClass:[RCTFabricSurface class]]) {
1627
+ RCTFabricSurface *surface = object;
1628
+ if (surface.stage != RCTSurfaceStageRunning) {
1629
+ return NO;
1630
+ }
1631
+ id view = nil;
1632
+ @try {
1633
+ view = [surface valueForKey:@"_view"];
1634
+ } @catch (NSException *exception) {
1635
+ return NO;
1636
+ }
1637
+ return [view isKindOfClass:[UIView class]] && ((UIView *)view).window != nil;
1638
+ }
1639
+ #endif
1640
+ return NO;
1641
+ }