@hot-updater/react-native 1.0.0-rc.2 → 1.0.0-rc.21

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 (291) hide show
  1. package/android/proguard-rules.pro +17 -2
  2. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  3. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +954 -837
  4. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +15 -11
  5. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  6. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  7. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +43 -31
  8. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
  9. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  10. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  11. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  12. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  13. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  14. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  15. package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
  16. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  17. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  18. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  19. package/android/src/newarch/HotUpdater.kt +7 -9
  20. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  21. package/android/src/oldarch/HotUpdater.kt +7 -9
  22. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  23. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  24. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  25. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  26. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +950 -1112
  27. package/ios/HotUpdater/Internal/BundleMetadata.swift +8 -53
  28. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  29. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  30. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  31. package/ios/HotUpdater/Internal/HotUpdater.mm +13 -45
  32. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +190 -73
  33. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  34. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  35. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  36. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  37. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  38. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  39. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  40. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  41. package/lib/commonjs/appReady.js +111 -0
  42. package/lib/commonjs/appReady.js.map +1 -0
  43. package/lib/commonjs/checkForUpdate.js +125 -70
  44. package/lib/commonjs/checkForUpdate.js.map +1 -1
  45. package/lib/commonjs/clientPlugin.js +13 -0
  46. package/lib/commonjs/clientPlugin.js.map +1 -0
  47. package/lib/commonjs/error.js +4 -13
  48. package/lib/commonjs/error.js.map +1 -1
  49. package/lib/commonjs/fetchJSON.js +5 -2
  50. package/lib/commonjs/fetchJSON.js.map +1 -1
  51. package/lib/commonjs/httpClient.js +55 -92
  52. package/lib/commonjs/httpClient.js.map +1 -1
  53. package/lib/commonjs/index.js +68 -20
  54. package/lib/commonjs/index.js.map +1 -1
  55. package/lib/commonjs/native.js +82 -62
  56. package/lib/commonjs/native.js.map +1 -1
  57. package/lib/commonjs/pluginHost.js +44 -0
  58. package/lib/commonjs/pluginHost.js.map +1 -0
  59. package/lib/commonjs/releaseCatalogCache.js +20 -61
  60. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  61. package/lib/commonjs/sdkVersion.js +1 -1
  62. package/lib/commonjs/sdkVersion.js.map +1 -1
  63. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  64. package/lib/commonjs/store.js +3 -10
  65. package/lib/commonjs/store.js.map +1 -1
  66. package/lib/commonjs/updateError.js +132 -0
  67. package/lib/commonjs/updateError.js.map +1 -0
  68. package/lib/commonjs/wrap.js +44 -27
  69. package/lib/commonjs/wrap.js.map +1 -1
  70. package/lib/module/appReady.js +103 -0
  71. package/lib/module/appReady.js.map +1 -0
  72. package/lib/module/checkForUpdate.js +118 -65
  73. package/lib/module/checkForUpdate.js.map +1 -1
  74. package/lib/module/clientPlugin.js +4 -0
  75. package/lib/module/clientPlugin.js.map +1 -0
  76. package/lib/module/error.js +4 -13
  77. package/lib/module/error.js.map +1 -1
  78. package/lib/module/fetchJSON.js +3 -1
  79. package/lib/module/fetchJSON.js.map +1 -1
  80. package/lib/module/httpClient.js +53 -90
  81. package/lib/module/httpClient.js.map +1 -1
  82. package/lib/module/index.js +55 -22
  83. package/lib/module/index.js.map +1 -1
  84. package/lib/module/native.js +74 -54
  85. package/lib/module/native.js.map +1 -1
  86. package/lib/module/pluginHost.js +35 -0
  87. package/lib/module/pluginHost.js.map +1 -0
  88. package/lib/module/releaseCatalogCache.js +16 -56
  89. package/lib/module/releaseCatalogCache.js.map +1 -1
  90. package/lib/module/sdkVersion.js +1 -1
  91. package/lib/module/sdkVersion.js.map +1 -1
  92. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  93. package/lib/module/store.js +3 -10
  94. package/lib/module/store.js.map +1 -1
  95. package/lib/module/updateError.js +126 -0
  96. package/lib/module/updateError.js.map +1 -0
  97. package/lib/module/wrap.js +45 -28
  98. package/lib/module/wrap.js.map +1 -1
  99. package/lib/typescript/commonjs/appReady.d.ts +23 -0
  100. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  102. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  104. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/error.d.ts +4 -13
  106. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/fetchJSON.d.ts +3 -0
  108. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/httpClient.d.ts +3 -32
  110. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/index.d.ts +7 -12
  112. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/native.d.ts +33 -23
  114. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  116. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +6 -14
  118. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  120. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +42 -47
  122. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/store.d.ts +1 -3
  124. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/types.d.ts +1 -1
  126. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  128. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  131. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  132. package/lib/typescript/module/appReady.d.ts +23 -0
  133. package/lib/typescript/module/appReady.d.ts.map +1 -0
  134. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  135. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  136. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  137. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  138. package/lib/typescript/module/error.d.ts +4 -13
  139. package/lib/typescript/module/error.d.ts.map +1 -1
  140. package/lib/typescript/module/fetchJSON.d.ts +3 -0
  141. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  142. package/lib/typescript/module/httpClient.d.ts +3 -32
  143. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  144. package/lib/typescript/module/index.d.ts +7 -12
  145. package/lib/typescript/module/index.d.ts.map +1 -1
  146. package/lib/typescript/module/native.d.ts +33 -23
  147. package/lib/typescript/module/native.d.ts.map +1 -1
  148. package/lib/typescript/module/pluginHost.d.ts +4 -0
  149. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  150. package/lib/typescript/module/releaseCatalogCache.d.ts +6 -14
  151. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  152. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  153. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  154. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +42 -47
  155. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  156. package/lib/typescript/module/store.d.ts +1 -3
  157. package/lib/typescript/module/store.d.ts.map +1 -1
  158. package/lib/typescript/module/types.d.ts +1 -1
  159. package/lib/typescript/module/types.d.ts.map +1 -1
  160. package/lib/typescript/module/updateError.d.ts +26 -0
  161. package/lib/typescript/module/updateError.d.ts.map +1 -0
  162. package/lib/typescript/module/wrap.d.ts.map +1 -1
  163. package/lib/typescript/module/wrap.types.d.ts +6 -7
  164. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  165. package/package.json +7 -4
  166. package/src/appReady.ts +149 -0
  167. package/src/checkForUpdate.ts +152 -89
  168. package/src/clientPlugin.ts +17 -0
  169. package/src/error.ts +4 -14
  170. package/src/fetchJSON.ts +5 -1
  171. package/src/httpClient.ts +78 -157
  172. package/src/index.ts +87 -29
  173. package/src/native.ts +109 -104
  174. package/src/pluginHost.ts +45 -0
  175. package/src/releaseCatalogCache.ts +26 -140
  176. package/src/sdkVersion.ts +1 -1
  177. package/src/specs/NativeHotUpdater.ts +41 -48
  178. package/src/store.ts +2 -23
  179. package/src/types.ts +1 -1
  180. package/src/updateError.ts +153 -0
  181. package/src/wrap.tsx +59 -28
  182. package/src/wrap.types.ts +6 -7
  183. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  184. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  185. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  186. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  187. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  188. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  189. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  190. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  191. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  192. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  193. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  194. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  195. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  196. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  197. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  198. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  199. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  200. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  201. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  202. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  203. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  204. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  205. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  206. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -604
  207. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  208. package/lib/commonjs/httpClient.spec.js +0 -102
  209. package/lib/commonjs/httpClient.spec.js.map +0 -1
  210. package/lib/commonjs/index.spec.js +0 -298
  211. package/lib/commonjs/index.spec.js.map +0 -1
  212. package/lib/commonjs/iosInterop.spec.js +0 -18
  213. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  214. package/lib/commonjs/native.spec.js +0 -795
  215. package/lib/commonjs/native.spec.js.map +0 -1
  216. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  217. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  218. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  219. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  220. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  221. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  222. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  223. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  224. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  225. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  226. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  227. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  228. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  229. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  230. package/lib/commonjs/store.notification.spec.js +0 -76
  231. package/lib/commonjs/store.notification.spec.js.map +0 -1
  232. package/lib/commonjs/store.spec.js +0 -316
  233. package/lib/commonjs/store.spec.js.map +0 -1
  234. package/lib/commonjs/wrap.spec.js +0 -296
  235. package/lib/commonjs/wrap.spec.js.map +0 -1
  236. package/lib/module/catalogCacheNative.spec.js +0 -31
  237. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  238. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -603
  239. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  240. package/lib/module/httpClient.spec.js +0 -102
  241. package/lib/module/httpClient.spec.js.map +0 -1
  242. package/lib/module/index.spec.js +0 -297
  243. package/lib/module/index.spec.js.map +0 -1
  244. package/lib/module/iosInterop.spec.js +0 -18
  245. package/lib/module/iosInterop.spec.js.map +0 -1
  246. package/lib/module/native.spec.js +0 -794
  247. package/lib/module/native.spec.js.map +0 -1
  248. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  249. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  250. package/lib/module/notifyAppReadyInsights.js +0 -128
  251. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  252. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  253. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  254. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  255. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  256. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  257. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  258. package/lib/module/releaseCatalogCache.spec.js +0 -195
  259. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  260. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  261. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  262. package/lib/module/store.notification.spec.js +0 -77
  263. package/lib/module/store.notification.spec.js.map +0 -1
  264. package/lib/module/store.spec.js +0 -315
  265. package/lib/module/store.spec.js.map +0 -1
  266. package/lib/module/wrap.spec.js +0 -295
  267. package/lib/module/wrap.spec.js.map +0 -1
  268. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  269. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  270. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  271. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  272. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  273. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  274. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  275. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  276. package/src/catalogCacheNative.spec.ts +0 -63
  277. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -596
  278. package/src/httpClient.spec.ts +0 -132
  279. package/src/index.spec.ts +0 -331
  280. package/src/iosInterop.spec.ts +0 -30
  281. package/src/native.spec.ts +0 -855
  282. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  283. package/src/notifyAppReadyInsights.spec.ts +0 -243
  284. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  285. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  286. package/src/notifyAppReadyInsights.ts +0 -194
  287. package/src/releaseCatalogCache.spec.ts +0 -282
  288. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  289. package/src/store.notification.spec.ts +0 -92
  290. package/src/store.spec.ts +0 -372
  291. package/src/wrap.spec.ts +0 -367
@@ -21,6 +21,7 @@ data class BundleMetadata(
21
21
  val pendingUpdateStrategy: String? = null,
22
22
  val pendingTransition: PendingSelectionTransition? = null,
23
23
  val verificationPending: Boolean = false,
24
+ val launchInProgress: Boolean = false,
24
25
  val highestSeenCatalogs: Map<String, CatalogHighWater> = emptyMap(),
25
26
  val currentSelectionContexts: Map<String, String> = emptyMap(),
26
27
  val updatedAt: Long = System.currentTimeMillis(),
@@ -71,6 +72,7 @@ data class BundleMetadata(
71
72
  pendingTransition =
72
73
  json.optJSONObject("pendingTransition")?.let(PendingSelectionTransition::fromJson),
73
74
  verificationPending = json.optBoolean("verificationPending", false),
75
+ launchInProgress = json.optBoolean("launchInProgress", false),
74
76
  highestSeenCatalogs = highestSeenCatalogs,
75
77
  currentSelectionContexts = currentSelectionContexts,
76
78
  updatedAt = json.optLong("updatedAt", System.currentTimeMillis()),
@@ -121,6 +123,7 @@ data class BundleMetadata(
121
123
  put("pendingUpdateStrategy", pendingUpdateStrategy ?: JSONObject.NULL)
122
124
  put("pendingTransition", pendingTransition?.toJson() ?: JSONObject.NULL)
123
125
  put("verificationPending", verificationPending)
126
+ put("launchInProgress", launchInProgress)
124
127
  put(
125
128
  "highestSeenCatalogs",
126
129
  JSONObject().apply {
@@ -394,6 +397,13 @@ data class PendingCrashRecovery(
394
397
  val shouldRollback: Boolean,
395
398
  ) {
396
399
  companion object {
400
+ internal fun loadFromFile(file: File): PendingCrashRecovery? =
401
+ try {
402
+ fromJson(JSONObject(file.readText()))
403
+ } catch (_: Exception) {
404
+ null
405
+ }
406
+
397
407
  fun fromJson(json: JSONObject): PendingCrashRecovery =
398
408
  PendingCrashRecovery(
399
409
  launchedBundleId =
@@ -469,21 +479,17 @@ data class LaunchReport(
469
479
  }
470
480
  }
471
481
 
482
+ /**
483
+ * The persisted install id. See [InstallIdentityService].
484
+ */
472
485
  data class InstallationIdentity(
473
486
  val installId: String,
474
- val userId: String? = null,
475
- val username: String? = null,
476
487
  ) {
477
488
  companion object {
478
489
  private const val TAG = "InstallationIdentity"
479
- const val IDENTITY_FILENAME = "identity.json"
490
+ const val IDENTITY_FILENAME = "install-identity.json"
480
491
 
481
- fun fromJson(json: JSONObject): InstallationIdentity =
482
- InstallationIdentity(
483
- installId = json.getString("installId"),
484
- userId = json.optNullableString("userId"),
485
- username = json.optNullableString("username"),
486
- )
492
+ fun fromJson(json: JSONObject): InstallationIdentity = InstallationIdentity(installId = json.getString("installId"))
487
493
 
488
494
  fun loadFromFile(file: File): InstallationIdentity? =
489
495
  try {
@@ -501,8 +507,6 @@ data class InstallationIdentity(
501
507
  fun toJson(): JSONObject =
502
508
  JSONObject().apply {
503
509
  put("installId", installId)
504
- put("userId", userId ?: JSONObject.NULL)
505
- put("username", username ?: JSONObject.NULL)
506
510
  }
507
511
 
508
512
  fun saveToFile(file: File): Boolean =
@@ -2,6 +2,51 @@ package com.hotupdater
2
2
 
3
3
  import android.content.Context
4
4
  import java.io.File
5
+ import java.io.FileOutputStream
6
+ import java.io.IOException
7
+ import java.io.OutputStream
8
+
9
+ /**
10
+ * An I/O failure writing to local storage, as opposed to a failure reading or
11
+ * decoding the data being written.
12
+ */
13
+ class LocalStorageException(
14
+ cause: IOException,
15
+ ) : IOException(cause.message, cause)
16
+
17
+ /**
18
+ * An output stream to a local file whose I/O failures, including opening the
19
+ * file, surface as [LocalStorageException].
20
+ */
21
+ internal class LocalStorageOutputStream private constructor(
22
+ private val output: OutputStream,
23
+ ) : OutputStream() {
24
+ companion object {
25
+ fun open(file: File): LocalStorageOutputStream = LocalStorageOutputStream(storageOperation { FileOutputStream(file) })
26
+ }
27
+
28
+ override fun write(byte: Int) = storageOperation { output.write(byte) }
29
+
30
+ override fun write(
31
+ buffer: ByteArray,
32
+ offset: Int,
33
+ length: Int,
34
+ ) = storageOperation { output.write(buffer, offset, length) }
35
+
36
+ override fun flush() = storageOperation { output.flush() }
37
+
38
+ override fun close() = storageOperation { output.close() }
39
+ }
40
+
41
+ /** Runs a local file operation, reporting its I/O failure as [LocalStorageException]. */
42
+ internal inline fun <T> storageOperation(block: () -> T): T =
43
+ try {
44
+ block()
45
+ } catch (error: LocalStorageException) {
46
+ throw error
47
+ } catch (error: IOException) {
48
+ throw LocalStorageException(error)
49
+ }
5
50
 
6
51
  /**
7
52
  * Interface for file system operations
@@ -1,41 +1,275 @@
1
1
  package com.hotupdater
2
2
 
3
+ import java.io.IOException
4
+ import java.io.InterruptedIOException
5
+ import java.net.MalformedURLException
6
+ import java.net.SocketException
7
+ import java.net.UnknownHostException
8
+ import javax.net.ssl.SSLException
9
+
10
+ /**
11
+ * Where an update failed: [DOWNLOAD] covers transferring and verifying the
12
+ * update's files, [INSTALL] producing the bundle on disk.
13
+ * Values match `UpdateErrorStage` in packages/react-native/src/clientPlugin.ts.
14
+ */
15
+ enum class UpdateFailureStage(
16
+ val value: String,
17
+ ) {
18
+ DOWNLOAD("download"),
19
+ INSTALL("install"),
20
+ }
21
+
22
+ /**
23
+ * Why an update failed.
24
+ * Values match `UpdateErrorReason` in packages/react-native/src/clientPlugin.ts.
25
+ */
26
+ enum class UpdateFailureReason(
27
+ val value: String,
28
+ ) {
29
+ NETWORK("network"),
30
+ HTTP("http"),
31
+ INVALID_RESPONSE("invalid_response"),
32
+ HASH_MISMATCH("hash_mismatch"),
33
+ SIGNATURE("signature"),
34
+ PATCH("patch"),
35
+ EXTRACT("extract"),
36
+ STORAGE("storage"),
37
+ UNKNOWN("unknown"),
38
+ }
39
+
40
+ /** What was being fetched or applied when an update failed. */
41
+ enum class UpdateFailureResource(
42
+ val value: String,
43
+ ) {
44
+ MANIFEST("manifest"),
45
+ FILE("file"),
46
+ PATCH("patch"),
47
+ ARCHIVE("archive"),
48
+ }
49
+
50
+ /** Why no HTTP response arrived, for a [UpdateFailureReason.NETWORK] failure. */
51
+ enum class UpdateFailureTransport(
52
+ val value: String,
53
+ ) {
54
+ TIMEOUT("timeout"),
55
+ DNS("dns"),
56
+ TLS("tls"),
57
+ CONNECTION("connection"),
58
+ OFFLINE("offline"),
59
+ CANCELLED("cancelled"),
60
+ }
61
+
62
+ /**
63
+ * The classification an `updateBundle` rejection carries as `userInfo`.
64
+ * [httpStatus] and [originCode] are reported only for
65
+ * [UpdateFailureReason.HTTP], and [transport] only for
66
+ * [UpdateFailureReason.NETWORK].
67
+ */
68
+ data class UpdateFailure(
69
+ val stage: UpdateFailureStage,
70
+ val reason: UpdateFailureReason,
71
+ val httpStatus: Int? = null,
72
+ val resource: UpdateFailureResource? = null,
73
+ val transport: UpdateFailureTransport? = null,
74
+ /** The storage origin's XML error `<Code>`, such as `AccessDenied`. */
75
+ val originCode: String? = null,
76
+ ) {
77
+ /**
78
+ * The rejection's `userInfo`:
79
+ * `{ stage, reason, resource?, httpStatus?, transport?, originCode? }`.
80
+ */
81
+ fun toUserInfo(): Map<String, Any> =
82
+ buildMap {
83
+ put("stage", stage.value)
84
+ put("reason", reason.value)
85
+ resource?.let { put("resource", it.value) }
86
+ if (reason == UpdateFailureReason.HTTP) {
87
+ httpStatus?.let { put("httpStatus", it) }
88
+ originCode?.let { put("originCode", it) }
89
+ }
90
+ if (reason == UpdateFailureReason.NETWORK) {
91
+ transport?.let { put("transport", it.value) }
92
+ }
93
+ }
94
+
95
+ companion object {
96
+ fun download(reason: UpdateFailureReason) = UpdateFailure(UpdateFailureStage.DOWNLOAD, reason)
97
+
98
+ fun install(reason: UpdateFailureReason) = UpdateFailure(UpdateFailureStage.INSTALL, reason)
99
+
100
+ fun http(
101
+ status: Int,
102
+ originCode: String? = null,
103
+ ) = UpdateFailure(UpdateFailureStage.DOWNLOAD, UpdateFailureReason.HTTP, status, originCode = originCode)
104
+
105
+ /**
106
+ * Classifies the error a download returned. OkHttp rejects a URL it
107
+ * cannot request with IllegalArgumentException, an invalid response.
108
+ * [isOffline] is asked only after a failed host lookup.
109
+ */
110
+ fun ofDownloadError(
111
+ error: Throwable?,
112
+ isOffline: () -> Boolean = { false },
113
+ ): UpdateFailure =
114
+ when (error) {
115
+ is HttpStatusException -> {
116
+ http(error.statusCode, error.originCode)
117
+ }
118
+
119
+ is LocalStorageException -> {
120
+ install(UpdateFailureReason.STORAGE)
121
+ }
122
+
123
+ is MalformedURLException -> {
124
+ download(UpdateFailureReason.INVALID_RESPONSE)
125
+ }
126
+
127
+ // The body ended early; the connection itself did not fail.
128
+ is IncompleteDownloadException -> {
129
+ download(UpdateFailureReason.NETWORK)
130
+ }
131
+
132
+ is IOException -> {
133
+ UpdateFailure(
134
+ UpdateFailureStage.DOWNLOAD,
135
+ UpdateFailureReason.NETWORK,
136
+ transport = transportOf(error, isOffline),
137
+ )
138
+ }
139
+
140
+ is IllegalArgumentException -> {
141
+ download(UpdateFailureReason.INVALID_RESPONSE)
142
+ }
143
+
144
+ else -> {
145
+ download(UpdateFailureReason.UNKNOWN)
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Why a request got no response, or null when the error does not say.
151
+ * An [InterruptedIOException] is a socket or OkHttp call timeout, a
152
+ * [SocketException] a refused, unreachable, or reset connection, and
153
+ * OkHttp reports a canceled call as an IOException "Canceled".
154
+ */
155
+ private fun transportOf(
156
+ error: IOException,
157
+ isOffline: () -> Boolean,
158
+ ): UpdateFailureTransport? =
159
+ when {
160
+ error is SSLException -> UpdateFailureTransport.TLS
161
+ error is InterruptedIOException -> UpdateFailureTransport.TIMEOUT
162
+ error is UnknownHostException -> if (isOffline()) UpdateFailureTransport.OFFLINE else UpdateFailureTransport.DNS
163
+ error is SocketException -> UpdateFailureTransport.CONNECTION
164
+ error.message == "Canceled" -> UpdateFailureTransport.CANCELLED
165
+ else -> null
166
+ }
167
+
168
+ /** Classifies a failed hash or signature check of a downloaded file. */
169
+ fun ofVerificationError(error: Throwable?): UpdateFailure =
170
+ when (error) {
171
+ is SignatureVerificationException.FileHashMismatch -> download(UpdateFailureReason.HASH_MISMATCH)
172
+ is SignatureVerificationException.FileReadFailed -> install(UpdateFailureReason.STORAGE)
173
+ else -> download(UpdateFailureReason.SIGNATURE)
174
+ }
175
+
176
+ /**
177
+ * Classifies an error the pipeline did not anticipate: a malformed URL
178
+ * is an invalid response, any other I/O error a local storage failure,
179
+ * and anything else unknown in [stage].
180
+ */
181
+ fun ofUnexpectedError(
182
+ error: Throwable,
183
+ stage: UpdateFailureStage,
184
+ ): UpdateFailure =
185
+ when (error) {
186
+ is MalformedURLException -> download(UpdateFailureReason.INVALID_RESPONSE)
187
+ is IOException -> install(UpdateFailureReason.STORAGE)
188
+ else -> UpdateFailure(stage, UpdateFailureReason.UNKNOWN)
189
+ }
190
+ }
191
+ }
192
+
193
+ /**
194
+ * The Release catalog selection changed while its bundle was being installed.
195
+ * The update was superseded, so this rejection is not an update failure.
196
+ */
197
+ class StaleReleaseSelectionException : IllegalStateException("Release catalog selection is stale")
198
+
3
199
  /**
4
200
  * Exception class for Hot Updater errors
5
201
  * Matches error codes defined in packages/react-native/src/errors.ts
202
+ *
203
+ * [failure] classifies an update failure for JS. It is null for a rejection
204
+ * that is not an update failure, such as a bundle in the crashed history.
6
205
  */
7
206
  class HotUpdaterException(
8
207
  val code: String,
9
208
  message: String,
10
209
  cause: Throwable? = null,
210
+ val failure: UpdateFailure? = null,
11
211
  ) : Exception(message, cause) {
212
+ /**
213
+ * This failure with [resource] as what was being fetched or applied,
214
+ * unless it already names one or is not an update failure.
215
+ */
216
+ fun withResource(resource: UpdateFailureResource?): HotUpdaterException {
217
+ val failure = failure
218
+ if (resource == null || failure == null || failure.resource != null) {
219
+ return this
220
+ }
221
+ return HotUpdaterException(code, message.orEmpty(), cause, failure.copy(resource = resource)).also {
222
+ it.stackTrace = stackTrace
223
+ }
224
+ }
225
+
12
226
  companion object {
227
+ private val invalidResponse = UpdateFailure.download(UpdateFailureReason.INVALID_RESPONSE)
228
+ private val signatureFailure = UpdateFailure.download(UpdateFailureReason.SIGNATURE)
229
+ private val storageFailure = UpdateFailure.install(UpdateFailureReason.STORAGE)
230
+
13
231
  // Parameter validation errors
14
232
  fun missingBundleId() =
15
233
  HotUpdaterException(
16
234
  "MISSING_BUNDLE_ID",
17
235
  "Missing or empty 'bundleId'",
236
+ failure = invalidResponse,
18
237
  )
19
238
 
20
- fun invalidFileUrl() =
239
+ fun invalidManifestParams() =
21
240
  HotUpdaterException(
22
- "INVALID_FILE_URL",
23
- "Invalid 'fileUrl' provided",
241
+ "INVALID_MANIFEST",
242
+ "Manifest URL, hash, and assets are required",
243
+ failure = invalidResponse.copy(resource = UpdateFailureResource.MANIFEST),
24
244
  )
25
245
 
246
+ fun invalidFileUrl(
247
+ parameter: String,
248
+ url: String,
249
+ resource: UpdateFailureResource,
250
+ ) = HotUpdaterException(
251
+ "INVALID_FILE_URL",
252
+ "Invalid '$parameter' provided: $url",
253
+ failure = invalidResponse.copy(resource = resource),
254
+ )
255
+
26
256
  // Bundle storage errors
27
257
  fun directoryCreationFailed() =
28
258
  HotUpdaterException(
29
259
  "DIRECTORY_CREATION_FAILED",
30
260
  "Failed to create bundle directory",
261
+ failure = storageFailure,
31
262
  )
32
263
 
33
- fun downloadFailed(cause: Throwable? = null) =
34
- HotUpdaterException(
35
- "DOWNLOAD_FAILED",
36
- "Failed to download bundle",
37
- cause,
38
- )
264
+ fun downloadFailed(
265
+ cause: Throwable? = null,
266
+ failure: UpdateFailure = UpdateFailure.ofDownloadError(cause),
267
+ ) = HotUpdaterException(
268
+ "DOWNLOAD_FAILED",
269
+ "Failed to download bundle",
270
+ cause,
271
+ failure,
272
+ )
39
273
 
40
274
  fun incompleteDownload(
41
275
  expectedSize: Long,
@@ -43,19 +277,14 @@ class HotUpdaterException(
43
277
  ) = HotUpdaterException(
44
278
  "INCOMPLETE_DOWNLOAD",
45
279
  "Download incomplete: received $actualSize bytes, expected $expectedSize bytes",
280
+ failure = UpdateFailure.download(UpdateFailureReason.NETWORK),
46
281
  )
47
282
 
48
- fun extractionFormatError(cause: Throwable? = null) =
49
- HotUpdaterException(
50
- "EXTRACTION_FORMAT_ERROR",
51
- "The downloaded bundle file is not a valid compressed archive",
52
- cause,
53
- )
54
-
55
283
  fun invalidBundle() =
56
284
  HotUpdaterException(
57
285
  "INVALID_BUNDLE",
58
286
  "Bundle missing required platform files (index.ios.bundle or index.android.bundle)",
287
+ failure = invalidResponse,
59
288
  )
60
289
 
61
290
  fun insufficientDiskSpace(
@@ -64,19 +293,24 @@ class HotUpdaterException(
64
293
  ) = HotUpdaterException(
65
294
  "INSUFFICIENT_DISK_SPACE",
66
295
  "Insufficient disk space: need $required bytes, available $available bytes",
296
+ failure = storageFailure,
67
297
  )
68
298
 
69
- fun signatureVerificationFailed(cause: Throwable? = null) =
70
- HotUpdaterException(
71
- "SIGNATURE_VERIFICATION_FAILED",
72
- "Bundle signature verification failed",
73
- cause,
74
- )
299
+ fun signatureVerificationFailed(
300
+ cause: Throwable? = null,
301
+ failure: UpdateFailure = UpdateFailure.ofVerificationError(cause),
302
+ ) = HotUpdaterException(
303
+ "SIGNATURE_VERIFICATION_FAILED",
304
+ "Bundle signature verification failed",
305
+ cause,
306
+ failure,
307
+ )
75
308
 
76
309
  fun moveOperationFailed() =
77
310
  HotUpdaterException(
78
311
  "MOVE_OPERATION_FAILED",
79
312
  "Failed to move bundle files",
313
+ failure = storageFailure,
80
314
  )
81
315
 
82
316
  fun bundleInCrashedHistory(bundleId: String) =
@@ -90,30 +324,35 @@ class HotUpdaterException(
90
324
  HotUpdaterException(
91
325
  "PUBLIC_KEY_NOT_CONFIGURED",
92
326
  "Public key not configured for signature verification",
327
+ failure = signatureFailure,
93
328
  )
94
329
 
95
330
  fun invalidPublicKeyFormat() =
96
331
  HotUpdaterException(
97
332
  "INVALID_PUBLIC_KEY_FORMAT",
98
333
  "Invalid public key format",
334
+ failure = signatureFailure,
99
335
  )
100
336
 
101
337
  fun fileHashMismatch() =
102
338
  HotUpdaterException(
103
339
  "FILE_HASH_MISMATCH",
104
340
  "File hash verification failed",
341
+ failure = UpdateFailure.download(UpdateFailureReason.HASH_MISMATCH),
105
342
  )
106
343
 
107
344
  fun fileReadFailed() =
108
345
  HotUpdaterException(
109
346
  "FILE_READ_FAILED",
110
347
  "Failed to read file for verification",
348
+ failure = storageFailure,
111
349
  )
112
350
 
113
351
  fun unsignedNotAllowed() =
114
352
  HotUpdaterException(
115
353
  "UNSIGNED_NOT_ALLOWED",
116
354
  "Unsigned bundles are not allowed",
355
+ failure = signatureFailure,
117
356
  )
118
357
 
119
358
  fun securityFrameworkError(cause: Throwable? = null) =
@@ -121,6 +360,7 @@ class HotUpdaterException(
121
360
  "SECURITY_FRAMEWORK_ERROR",
122
361
  "Security framework error occurred",
123
362
  cause,
363
+ failure = signatureFailure,
124
364
  )
125
365
 
126
366
  // Internal errors
@@ -130,5 +370,30 @@ class HotUpdaterException(
130
370
  "An unknown error occurred",
131
371
  cause,
132
372
  )
373
+
374
+ /**
375
+ * A local storage failure while producing the bundle. It keeps the
376
+ * `UNKNOWN_ERROR` code these failures have always been rejected with.
377
+ */
378
+ fun storageFailed(message: String) =
379
+ HotUpdaterException(
380
+ "UNKNOWN_ERROR",
381
+ message,
382
+ failure = storageFailure,
383
+ )
384
+
385
+ /**
386
+ * Wraps an error the update pipeline did not anticipate in the
387
+ * `UNKNOWN_ERROR` rejection it has always produced, adding [failure].
388
+ */
389
+ fun unexpected(
390
+ cause: Throwable,
391
+ failure: UpdateFailure,
392
+ ) = HotUpdaterException(
393
+ "UNKNOWN_ERROR",
394
+ cause.message ?: "An unknown error occurred",
395
+ cause,
396
+ failure,
397
+ )
133
398
  }
134
399
  }
@@ -18,6 +18,8 @@ class HotUpdaterImpl {
18
18
  private val preferences: PreferencesService
19
19
  private val recoveryManager: HotUpdaterRecoveryManager
20
20
  private val releaseCatalogCache: ReleaseCatalogCacheService
21
+ private val installIdentity: InstallIdentityService
22
+ private val keyValueStorage: KeyValueStorageService
21
23
  private var currentLaunchSelection: LaunchSelection? = null
22
24
 
23
25
  /**
@@ -36,6 +38,9 @@ class HotUpdaterImpl {
36
38
  ReleaseCatalogCacheService(
37
39
  File(this.context.noBackupFilesDir, "hot-updater-release-catalog-cache"),
38
40
  )
41
+ this.installIdentity = InstallIdentityService.create(this.context)
42
+ this.keyValueStorage = KeyValueStorageService.create(this.context)
43
+ PreviousProcessExit.initialize(this.context)
39
44
  }
40
45
 
41
46
  /**
@@ -83,14 +88,12 @@ class HotUpdaterImpl {
83
88
  val fileSystem = FileManagerService(appContext)
84
89
  val preferences = createPreferences(appContext)
85
90
  val downloadService = OkHttpDownloadService()
86
- val decompressService = DecompressService()
87
91
  val isolationKey = getIsolationKey(appContext)
88
92
 
89
93
  return BundleFileStorageService(
90
94
  appContext,
91
95
  fileSystem,
92
96
  downloadService,
93
- decompressService,
94
97
  preferences,
95
98
  isolationKey,
96
99
  )
@@ -268,36 +271,37 @@ class HotUpdaterImpl {
268
271
  fun getJSBundleFile(): String = prepareLaunchIfNeeded().bundleUrl
269
272
 
270
273
  /**
271
- * Updates the bundle from the specified URL
274
+ * Updates the bundle from its manifest and complete asset descriptors.
272
275
  * @param bundleId ID of the bundle to update
273
- * @param fileUrl URL of the bundle file to download (or null to reset)
274
- * @param fileHash Combined hash string for verification (sig:<signature> or <hex_hash>)
276
+ * @param manifestUrl URL of the target manifest
277
+ * @param manifestFileHash Hash or signature used to verify the target manifest
278
+ * @param assets Complete target asset descriptor map
275
279
  * @param progressCallback Callback for download progress updates
280
+ * @return how the staged bundle arrived
276
281
  * @throws HotUpdaterException if the update fails
277
282
  */
278
283
  suspend fun updateBundle(
279
284
  bundleId: String,
280
- fileUrl: String?,
281
- fileHash: String?,
282
- manifestUrl: String?,
283
- manifestFileHash: String?,
284
- changedAssets: Map<String, ChangedAssetDescriptor>?,
285
+ manifestUrl: String,
286
+ manifestFileHash: String,
287
+ assets: Map<String, ChangedAssetDescriptor>,
285
288
  channel: String?,
286
289
  selection: PersistedSelection? = null,
290
+ archiveUrl: String? = null,
287
291
  progressCallback: (UpdateProgressPayload) -> Unit,
288
- ) {
292
+ ): UpdateBundleResult {
289
293
  if (selection != null && !bundleStorage.stageReleaseSelection(selection)) {
290
- throw IllegalStateException("Release catalog selection is stale")
294
+ throw StaleReleaseSelectionException()
291
295
  }
292
- bundleStorage.updateBundle(
293
- bundleId,
294
- fileUrl,
295
- fileHash,
296
- manifestUrl,
297
- manifestFileHash,
298
- changedAssets,
299
- progressCallback,
300
- )
296
+ val result =
297
+ bundleStorage.updateBundle(
298
+ bundleId,
299
+ manifestUrl,
300
+ manifestFileHash,
301
+ assets,
302
+ archiveUrl,
303
+ progressCallback,
304
+ )
301
305
 
302
306
  if (!channel.isNullOrEmpty()) {
303
307
  if (channel == getDefaultChannel()) {
@@ -306,6 +310,7 @@ class HotUpdaterImpl {
306
310
  preferences.setItem(CHANNEL_STORAGE_KEY, channel)
307
311
  }
308
312
  }
313
+ return result
309
314
  }
310
315
 
311
316
  /**
@@ -374,7 +379,15 @@ class HotUpdaterImpl {
374
379
  }
375
380
  }
376
381
 
377
- fun notifyAppReady(): Map<String, Any?> = bundleStorage.notifyAppReady()
382
+ /**
383
+ * The launch report for this process, with `previousProcessExit` when
384
+ * Android reports why the previous main process exited.
385
+ */
386
+ fun notifyAppReady(): Map<String, Any?> {
387
+ val report = bundleStorage.notifyAppReady()
388
+ val previousProcessExit = PreviousProcessExit.get() ?: return report
389
+ return report + ("previousProcessExit" to previousProcessExit)
390
+ }
378
391
 
379
392
  fun acceptReleaseCatalog(
380
393
  catalogId: String,
@@ -423,17 +436,17 @@ class HotUpdaterImpl {
423
436
 
424
437
  suspend fun commitReleaseSelection(selection: PersistedSelection): Boolean = bundleStorage.commitReleaseSelection(selection)
425
438
 
426
- fun getInstallId(): String = bundleStorage.getInstallId()
427
-
428
- fun getUserId(): String? = bundleStorage.getUserId()
439
+ fun getInstallId(): String = installIdentity.getInstallId()
429
440
 
430
- fun getUsername(): String? = bundleStorage.getUsername()
441
+ /** Reads a value from the client plugins' key-value store. */
442
+ fun getStorageItem(key: String): String? = keyValueStorage.getItem(key)
431
443
 
432
- fun setUser(
433
- userId: String?,
434
- username: String?,
444
+ /** Writes a value to the client plugins' key-value store; null removes the key. */
445
+ fun setStorageItem(
446
+ key: String,
447
+ value: String?,
435
448
  ) {
436
- bundleStorage.setUser(userId, username)
449
+ keyValueStorage.setItem(key, value)
437
450
  }
438
451
 
439
452
  /**
@@ -467,7 +480,6 @@ class HotUpdaterImpl {
467
480
 
468
481
  /**
469
482
  * Gets the current launched bundle ID.
470
- * Reads manifest.json first and falls back to the legacy BUNDLE_ID file.
471
483
  * Built-in bundle fallback is handled in JS.
472
484
  */
473
485
  fun getBundleId(): String? {