@hot-updater/react-native 1.0.0-rc.4 → 1.0.0-rc.41

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 (317) hide show
  1. package/android/build.gradle +13 -2
  2. package/android/proguard-rules.pro +8 -2
  3. package/android/src/bridge/BridgeReactInstance.kt +38 -0
  4. package/android/src/bridgeless/BridgeReactInstance.kt +24 -0
  5. package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +94 -0
  6. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  7. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1064 -844
  8. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +50 -11
  9. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  10. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  11. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +46 -30
  12. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +75 -192
  13. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  14. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  15. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  16. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  17. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  18. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  19. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  20. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  21. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  22. package/android/src/newarch/HotUpdater.kt +7 -9
  23. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  24. package/android/src/newarch/JavaScriptExceptionHooks.kt +48 -0
  25. package/android/src/newarch/ReactIntegrationManager.kt +6 -32
  26. package/android/src/oldarch/HotUpdater.kt +7 -9
  27. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  28. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  29. package/android/src/oldarch/JavaScriptExceptionHooks.kt +71 -0
  30. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  31. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  32. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1032 -1116
  33. package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -54
  34. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  35. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  36. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  37. package/ios/HotUpdater/Internal/HotUpdater.mm +32 -67
  38. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +229 -73
  39. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  40. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  41. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  42. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  43. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  44. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  45. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  46. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  47. package/lib/commonjs/appReady.js +96 -0
  48. package/lib/commonjs/appReady.js.map +1 -0
  49. package/lib/commonjs/checkForUpdate.js +121 -69
  50. package/lib/commonjs/checkForUpdate.js.map +1 -1
  51. package/lib/commonjs/clientPlugin.js +13 -0
  52. package/lib/commonjs/clientPlugin.js.map +1 -0
  53. package/lib/commonjs/error.js +5 -14
  54. package/lib/commonjs/error.js.map +1 -1
  55. package/lib/commonjs/fetchJSON.js +16 -27
  56. package/lib/commonjs/fetchJSON.js.map +1 -1
  57. package/lib/commonjs/fetchUpdateResponse.js +60 -0
  58. package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
  59. package/lib/commonjs/httpClient.js +64 -95
  60. package/lib/commonjs/httpClient.js.map +1 -1
  61. package/lib/commonjs/index.js +355 -324
  62. package/lib/commonjs/index.js.map +1 -1
  63. package/lib/commonjs/init.types.js +6 -0
  64. package/lib/commonjs/init.types.js.map +1 -0
  65. package/lib/commonjs/native.js +77 -63
  66. package/lib/commonjs/native.js.map +1 -1
  67. package/lib/commonjs/pluginHost.js +41 -0
  68. package/lib/commonjs/pluginHost.js.map +1 -0
  69. package/lib/commonjs/releaseCatalogCache.js +36 -87
  70. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  71. package/lib/commonjs/sdkVersion.js +1 -1
  72. package/lib/commonjs/sdkVersion.js.map +1 -1
  73. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  74. package/lib/commonjs/store.js +3 -10
  75. package/lib/commonjs/store.js.map +1 -1
  76. package/lib/commonjs/updateError.js +145 -0
  77. package/lib/commonjs/updateError.js.map +1 -0
  78. package/lib/commonjs/wrap.js +46 -38
  79. package/lib/commonjs/wrap.js.map +1 -1
  80. package/lib/module/appReady.js +90 -0
  81. package/lib/module/appReady.js.map +1 -0
  82. package/lib/module/checkForUpdate.js +114 -64
  83. package/lib/module/checkForUpdate.js.map +1 -1
  84. package/lib/module/clientPlugin.js +4 -0
  85. package/lib/module/clientPlugin.js.map +1 -0
  86. package/lib/module/error.js +5 -14
  87. package/lib/module/error.js.map +1 -1
  88. package/lib/module/fetchJSON.js +14 -26
  89. package/lib/module/fetchJSON.js.map +1 -1
  90. package/lib/module/fetchUpdateResponse.js +55 -0
  91. package/lib/module/fetchUpdateResponse.js.map +1 -0
  92. package/lib/module/httpClient.js +63 -94
  93. package/lib/module/httpClient.js.map +1 -1
  94. package/lib/module/index.js +335 -327
  95. package/lib/module/index.js.map +1 -1
  96. package/lib/module/init.types.js +4 -0
  97. package/lib/module/init.types.js.map +1 -0
  98. package/lib/module/native.js +70 -56
  99. package/lib/module/native.js.map +1 -1
  100. package/lib/module/pluginHost.js +36 -0
  101. package/lib/module/pluginHost.js.map +1 -0
  102. package/lib/module/releaseCatalogCache.js +32 -82
  103. package/lib/module/releaseCatalogCache.js.map +1 -1
  104. package/lib/module/sdkVersion.js +1 -1
  105. package/lib/module/sdkVersion.js.map +1 -1
  106. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  107. package/lib/module/store.js +3 -10
  108. package/lib/module/store.js.map +1 -1
  109. package/lib/module/updateError.js +139 -0
  110. package/lib/module/updateError.js.map +1 -0
  111. package/lib/module/wrap.js +47 -38
  112. package/lib/module/wrap.js.map +1 -1
  113. package/lib/typescript/commonjs/appReady.d.ts +30 -0
  114. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/checkForUpdate.d.ts +20 -6
  116. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  118. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/error.d.ts +5 -14
  120. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
  122. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
  124. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/httpClient.d.ts +5 -34
  126. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/index.d.ts +124 -116
  128. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/init.types.d.ts +21 -0
  130. package/lib/typescript/commonjs/init.types.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/native.d.ts +32 -25
  132. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/pluginHost.d.ts +8 -0
  134. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
  136. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  138. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +35 -47
  140. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/store.d.ts +1 -3
  142. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/types.d.ts +1 -1
  144. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  145. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  146. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  147. package/lib/typescript/commonjs/wrap.d.ts +2 -3
  148. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  149. package/lib/typescript/commonjs/wrap.types.d.ts +13 -41
  150. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  151. package/lib/typescript/module/appReady.d.ts +30 -0
  152. package/lib/typescript/module/appReady.d.ts.map +1 -0
  153. package/lib/typescript/module/checkForUpdate.d.ts +20 -6
  154. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  155. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  156. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  157. package/lib/typescript/module/error.d.ts +5 -14
  158. package/lib/typescript/module/error.d.ts.map +1 -1
  159. package/lib/typescript/module/fetchJSON.d.ts +5 -5
  160. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  161. package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
  162. package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
  163. package/lib/typescript/module/httpClient.d.ts +5 -34
  164. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  165. package/lib/typescript/module/index.d.ts +124 -116
  166. package/lib/typescript/module/index.d.ts.map +1 -1
  167. package/lib/typescript/module/init.types.d.ts +21 -0
  168. package/lib/typescript/module/init.types.d.ts.map +1 -0
  169. package/lib/typescript/module/native.d.ts +32 -25
  170. package/lib/typescript/module/native.d.ts.map +1 -1
  171. package/lib/typescript/module/pluginHost.d.ts +8 -0
  172. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  173. package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
  174. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  175. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  176. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  177. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +35 -47
  178. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  179. package/lib/typescript/module/store.d.ts +1 -3
  180. package/lib/typescript/module/store.d.ts.map +1 -1
  181. package/lib/typescript/module/types.d.ts +1 -1
  182. package/lib/typescript/module/types.d.ts.map +1 -1
  183. package/lib/typescript/module/updateError.d.ts +26 -0
  184. package/lib/typescript/module/updateError.d.ts.map +1 -0
  185. package/lib/typescript/module/wrap.d.ts +2 -3
  186. package/lib/typescript/module/wrap.d.ts.map +1 -1
  187. package/lib/typescript/module/wrap.types.d.ts +13 -41
  188. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  189. package/package.json +8 -4
  190. package/src/appReady.ts +147 -0
  191. package/src/checkForUpdate.ts +156 -89
  192. package/src/clientPlugin.ts +21 -0
  193. package/src/error.ts +5 -15
  194. package/src/fetchJSON.ts +14 -30
  195. package/src/fetchUpdateResponse.ts +60 -0
  196. package/src/httpClient.ts +89 -160
  197. package/src/index.ts +413 -425
  198. package/src/init.types.ts +24 -0
  199. package/src/native.ts +101 -105
  200. package/src/pluginHost.ts +52 -0
  201. package/src/releaseCatalogCache.ts +43 -172
  202. package/src/sdkVersion.ts +1 -1
  203. package/src/specs/NativeHotUpdater.ts +34 -48
  204. package/src/store.ts +2 -23
  205. package/src/types.ts +1 -1
  206. package/src/updateError.ts +167 -0
  207. package/src/wrap.tsx +60 -43
  208. package/src/wrap.types.ts +18 -46
  209. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  210. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  211. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  212. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  213. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  214. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  215. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  216. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  217. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  218. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  219. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  220. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  221. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  222. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  223. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  224. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  225. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  226. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  227. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  228. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  229. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  230. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  231. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  232. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -616
  233. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  234. package/lib/commonjs/httpClient.spec.js +0 -102
  235. package/lib/commonjs/httpClient.spec.js.map +0 -1
  236. package/lib/commonjs/index.spec.js +0 -302
  237. package/lib/commonjs/index.spec.js.map +0 -1
  238. package/lib/commonjs/iosInterop.spec.js +0 -18
  239. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  240. package/lib/commonjs/native.spec.js +0 -883
  241. package/lib/commonjs/native.spec.js.map +0 -1
  242. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  243. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  244. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  245. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  246. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  247. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  248. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  249. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  250. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  251. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  252. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  253. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  254. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  255. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  256. package/lib/commonjs/store.notification.spec.js +0 -76
  257. package/lib/commonjs/store.notification.spec.js.map +0 -1
  258. package/lib/commonjs/store.spec.js +0 -316
  259. package/lib/commonjs/store.spec.js.map +0 -1
  260. package/lib/commonjs/wrap.spec.js +0 -329
  261. package/lib/commonjs/wrap.spec.js.map +0 -1
  262. package/lib/module/catalogCacheNative.spec.js +0 -31
  263. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  264. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -615
  265. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  266. package/lib/module/httpClient.spec.js +0 -102
  267. package/lib/module/httpClient.spec.js.map +0 -1
  268. package/lib/module/index.spec.js +0 -301
  269. package/lib/module/index.spec.js.map +0 -1
  270. package/lib/module/iosInterop.spec.js +0 -18
  271. package/lib/module/iosInterop.spec.js.map +0 -1
  272. package/lib/module/native.spec.js +0 -882
  273. package/lib/module/native.spec.js.map +0 -1
  274. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  275. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  276. package/lib/module/notifyAppReadyInsights.js +0 -128
  277. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  278. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  279. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  280. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  281. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  282. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  283. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  284. package/lib/module/releaseCatalogCache.spec.js +0 -195
  285. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  286. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  287. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  288. package/lib/module/store.notification.spec.js +0 -77
  289. package/lib/module/store.notification.spec.js.map +0 -1
  290. package/lib/module/store.spec.js +0 -315
  291. package/lib/module/store.spec.js.map +0 -1
  292. package/lib/module/wrap.spec.js +0 -330
  293. package/lib/module/wrap.spec.js.map +0 -1
  294. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  295. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  296. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  297. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  298. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  299. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  300. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  301. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  302. package/src/catalogCacheNative.spec.ts +0 -63
  303. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -612
  304. package/src/httpClient.spec.ts +0 -132
  305. package/src/index.spec.ts +0 -337
  306. package/src/iosInterop.spec.ts +0 -30
  307. package/src/native.spec.ts +0 -945
  308. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  309. package/src/notifyAppReadyInsights.spec.ts +0 -243
  310. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  311. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  312. package/src/notifyAppReadyInsights.ts +0 -194
  313. package/src/releaseCatalogCache.spec.ts +0 -282
  314. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  315. package/src/store.notification.spec.ts +0 -92
  316. package/src/store.spec.ts +0 -372
  317. package/src/wrap.spec.ts +0 -406
@@ -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 {
@@ -293,12 +296,44 @@ data class CrashedBundleEntry(
293
296
  }
294
297
  }
295
298
 
299
+ /**
300
+ * A bundle whose launch ended before first content without a crash, waiting
301
+ * for its one retry. Such a launch cannot tell a hang from a user leaving
302
+ * early, so the bundle is offered again instead of entering crash history.
303
+ * A second unfinished launch of it adds it there.
304
+ *
305
+ * [retryReady] turns true once a later launch shows content. The retry
306
+ * starts with the next process, so the session that recovered never reloads
307
+ * into the same bundle.
308
+ */
309
+ data class InterruptedLaunch(
310
+ val bundleId: String,
311
+ val retryReady: Boolean = false,
312
+ ) {
313
+ companion object {
314
+ fun fromJson(json: JSONObject): InterruptedLaunch? =
315
+ json.optNullableString("bundleId")?.let { bundleId ->
316
+ InterruptedLaunch(
317
+ bundleId = bundleId,
318
+ retryReady = json.optBoolean("retryReady", false),
319
+ )
320
+ }
321
+ }
322
+
323
+ fun toJson(): JSONObject =
324
+ JSONObject().apply {
325
+ put("bundleId", bundleId)
326
+ put("retryReady", retryReady)
327
+ }
328
+ }
329
+
296
330
  /**
297
331
  * History of crashed bundles
298
332
  */
299
333
  data class CrashedHistory(
300
334
  val bundles: MutableList<CrashedBundleEntry> = mutableListOf(),
301
335
  val maxHistorySize: Int = DEFAULT_MAX_HISTORY_SIZE,
336
+ var interruptedLaunch: InterruptedLaunch? = null,
302
337
  ) {
303
338
  companion object {
304
339
  private const val TAG = "CrashedHistory"
@@ -314,6 +349,7 @@ data class CrashedHistory(
314
349
  return CrashedHistory(
315
350
  bundles = bundles,
316
351
  maxHistorySize = json.optInt("maxHistorySize", DEFAULT_MAX_HISTORY_SIZE),
352
+ interruptedLaunch = json.optJSONObject("interruptedLaunch")?.let(InterruptedLaunch::fromJson),
317
353
  )
318
354
  }
319
355
 
@@ -339,6 +375,7 @@ data class CrashedHistory(
339
375
  bundles.forEach { bundlesArray.put(it.toJson()) }
340
376
  put("bundles", bundlesArray)
341
377
  put("maxHistorySize", maxHistorySize)
378
+ interruptedLaunch?.let { put("interruptedLaunch", it.toJson()) }
342
379
  }
343
380
 
344
381
  fun saveToFile(file: File): Boolean =
@@ -386,6 +423,7 @@ data class CrashedHistory(
386
423
 
387
424
  fun clear() {
388
425
  bundles.clear()
426
+ interruptedLaunch = null
389
427
  }
390
428
  }
391
429
 
@@ -394,6 +432,13 @@ data class PendingCrashRecovery(
394
432
  val shouldRollback: Boolean,
395
433
  ) {
396
434
  companion object {
435
+ internal fun loadFromFile(file: File): PendingCrashRecovery? =
436
+ try {
437
+ fromJson(JSONObject(file.readText()))
438
+ } catch (_: Exception) {
439
+ null
440
+ }
441
+
397
442
  fun fromJson(json: JSONObject): PendingCrashRecovery =
398
443
  PendingCrashRecovery(
399
444
  launchedBundleId =
@@ -469,21 +514,17 @@ data class LaunchReport(
469
514
  }
470
515
  }
471
516
 
517
+ /**
518
+ * The persisted install id. See [InstallIdentityService].
519
+ */
472
520
  data class InstallationIdentity(
473
521
  val installId: String,
474
- val userId: String? = null,
475
- val username: String? = null,
476
522
  ) {
477
523
  companion object {
478
524
  private const val TAG = "InstallationIdentity"
479
- const val IDENTITY_FILENAME = "identity.json"
525
+ const val IDENTITY_FILENAME = "install-identity.json"
480
526
 
481
- fun fromJson(json: JSONObject): InstallationIdentity =
482
- InstallationIdentity(
483
- installId = json.getString("installId"),
484
- userId = json.optNullableString("userId"),
485
- username = json.optNullableString("username"),
486
- )
527
+ fun fromJson(json: JSONObject): InstallationIdentity = InstallationIdentity(installId = json.getString("installId"))
487
528
 
488
529
  fun loadFromFile(file: File): InstallationIdentity? =
489
530
  try {
@@ -501,8 +542,6 @@ data class InstallationIdentity(
501
542
  fun toJson(): JSONObject =
502
543
  JSONObject().apply {
503
544
  put("installId", installId)
504
- put("userId", userId ?: JSONObject.NULL)
505
- put("username", username ?: JSONObject.NULL)
506
545
  }
507
546
 
508
547
  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
  }