@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
@@ -1,6 +1,7 @@
1
1
  package com.hotupdater
2
2
 
3
3
  import android.app.ActivityOptions
4
+ import android.app.Application
4
5
  import android.content.Context
5
6
  import android.content.Intent
6
7
  import android.os.Process
@@ -18,6 +19,9 @@ class HotUpdaterImpl {
18
19
  private val preferences: PreferencesService
19
20
  private val recoveryManager: HotUpdaterRecoveryManager
20
21
  private val releaseCatalogCache: ReleaseCatalogCacheService
22
+ private val installIdentity: InstallIdentityService
23
+ private val keyValueStorage: KeyValueStorageService
24
+ private val activityStartGate: ActivityStartGate
21
25
  private var currentLaunchSelection: LaunchSelection? = null
22
26
 
23
27
  /**
@@ -31,11 +35,14 @@ class HotUpdaterImpl {
31
35
  this.context = context.applicationContext
32
36
  this.bundleStorage = bundleStorage
33
37
  this.preferences = preferences
38
+ this.activityStartGate = ActivityStartGate(this.context as? Application)
34
39
  this.recoveryManager = HotUpdaterRecoveryManager(this.context)
35
40
  this.releaseCatalogCache =
36
41
  ReleaseCatalogCacheService(
37
42
  File(this.context.noBackupFilesDir, "hot-updater-release-catalog-cache"),
38
43
  )
44
+ this.installIdentity = InstallIdentityService.create(this.context)
45
+ this.keyValueStorage = KeyValueStorageService.create(this.context)
39
46
  }
40
47
 
41
48
  /**
@@ -83,14 +90,12 @@ class HotUpdaterImpl {
83
90
  val fileSystem = FileManagerService(appContext)
84
91
  val preferences = createPreferences(appContext)
85
92
  val downloadService = OkHttpDownloadService()
86
- val decompressService = DecompressService()
87
93
  val isolationKey = getIsolationKey(appContext)
88
94
 
89
95
  return BundleFileStorageService(
90
96
  appContext,
91
97
  fileSystem,
92
98
  downloadService,
93
- decompressService,
94
99
  preferences,
95
100
  isolationKey,
96
101
  )
@@ -268,36 +273,37 @@ class HotUpdaterImpl {
268
273
  fun getJSBundleFile(): String = prepareLaunchIfNeeded().bundleUrl
269
274
 
270
275
  /**
271
- * Updates the bundle from the specified URL
276
+ * Updates the bundle from its manifest and complete asset descriptors.
272
277
  * @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>)
278
+ * @param manifestUrl URL of the target manifest
279
+ * @param manifestFileHash Hash or signature used to verify the target manifest
280
+ * @param assets Complete target asset descriptor map
275
281
  * @param progressCallback Callback for download progress updates
282
+ * @return how the staged bundle arrived
276
283
  * @throws HotUpdaterException if the update fails
277
284
  */
278
285
  suspend fun updateBundle(
279
286
  bundleId: String,
280
- fileUrl: String?,
281
- fileHash: String?,
282
- manifestUrl: String?,
283
- manifestFileHash: String?,
284
- changedAssets: Map<String, ChangedAssetDescriptor>?,
287
+ manifestUrl: String,
288
+ manifestFileHash: String,
289
+ assets: Map<String, ChangedAssetDescriptor>,
285
290
  channel: String?,
286
291
  selection: PersistedSelection? = null,
292
+ archiveUrl: String? = null,
287
293
  progressCallback: (UpdateProgressPayload) -> Unit,
288
- ) {
294
+ ): UpdateBundleResult {
289
295
  if (selection != null && !bundleStorage.stageReleaseSelection(selection)) {
290
- throw IllegalStateException("Release catalog selection is stale")
296
+ throw StaleReleaseSelectionException()
291
297
  }
292
- bundleStorage.updateBundle(
293
- bundleId,
294
- fileUrl,
295
- fileHash,
296
- manifestUrl,
297
- manifestFileHash,
298
- changedAssets,
299
- progressCallback,
300
- )
298
+ val result =
299
+ bundleStorage.updateBundle(
300
+ bundleId,
301
+ manifestUrl,
302
+ manifestFileHash,
303
+ assets,
304
+ archiveUrl,
305
+ progressCallback,
306
+ )
301
307
 
302
308
  if (!channel.isNullOrEmpty()) {
303
309
  if (channel == getDefaultChannel()) {
@@ -306,6 +312,7 @@ class HotUpdaterImpl {
306
312
  preferences.setItem(CHANNEL_STORAGE_KEY, channel)
307
313
  }
308
314
  }
315
+ return result
309
316
  }
310
317
 
311
318
  /**
@@ -423,17 +430,17 @@ class HotUpdaterImpl {
423
430
 
424
431
  suspend fun commitReleaseSelection(selection: PersistedSelection): Boolean = bundleStorage.commitReleaseSelection(selection)
425
432
 
426
- fun getInstallId(): String = bundleStorage.getInstallId()
427
-
428
- fun getUserId(): String? = bundleStorage.getUserId()
433
+ fun getInstallId(): String = installIdentity.getInstallId()
429
434
 
430
- fun getUsername(): String? = bundleStorage.getUsername()
435
+ /** Reads a value from the client plugins' key-value store. */
436
+ fun getStorageItem(key: String): String? = keyValueStorage.getItem(key)
431
437
 
432
- fun setUser(
433
- userId: String?,
434
- username: String?,
438
+ /** Writes a value to the client plugins' key-value store; null removes the key. */
439
+ fun setStorageItem(
440
+ key: String,
441
+ value: String?,
435
442
  ) {
436
- bundleStorage.setUser(userId, username)
443
+ keyValueStorage.setItem(key, value)
437
444
  }
438
445
 
439
446
  /**
@@ -467,7 +474,6 @@ class HotUpdaterImpl {
467
474
 
468
475
  /**
469
476
  * Gets the current launched bundle ID.
470
- * Reads manifest.json first and falls back to the legacy BUNDLE_ID file.
471
477
  * Built-in bundle fallback is handled in JS.
472
478
  */
473
479
  fun getBundleId(): String? {
@@ -505,6 +511,16 @@ class HotUpdaterImpl {
505
511
 
506
512
  val pendingRecovery = recoveryManager.consumePendingCrashRecovery()
507
513
  val selection = bundleStorage.prepareLaunch(pendingRecovery)
514
+ // A headless JS task, such as a background push, loads the bundle without
515
+ // an activity and never renders. Only a launch with an activity may leave
516
+ // an unfinished launch for the next process to roll back.
517
+ activityStartGate.runWhenActivityStarted(
518
+ if (selection.shouldRollbackOnCrash) {
519
+ { bundleStorage.markLaunchStarted(selection.launchedBundleId) }
520
+ } else {
521
+ null
522
+ },
523
+ )
508
524
  recoveryManager.startMonitoring(
509
525
  bundleId = selection.launchedBundleId,
510
526
  shouldRollback = selection.shouldRollbackOnCrash,
@@ -9,6 +9,7 @@ import android.os.Looper
9
9
  import android.os.Process
10
10
  import android.os.SystemClock
11
11
  import android.util.Log
12
+ import androidx.core.util.AtomicFile
12
13
  import com.facebook.react.ReactApplication
13
14
  import com.facebook.react.bridge.JSExceptionHandler
14
15
  import com.facebook.react.bridge.ReactContext
@@ -16,6 +17,7 @@ import com.facebook.react.bridge.ReactMarker
16
17
  import com.facebook.react.bridge.ReactMarkerConstants
17
18
  import org.json.JSONObject
18
19
  import java.io.File
20
+ import java.io.FileOutputStream
19
21
  import java.lang.reflect.Field
20
22
  import kotlin.system.exitProcess
21
23
 
@@ -28,10 +30,7 @@ internal class HotUpdaterRecoveryManager(
28
30
  private val crashMarkerFile = File(bundleStoreDir, CRASH_MARKER_FILENAME)
29
31
  private val watchdogStateFile = File(bundleStoreDir, WATCHDOG_STATE_FILENAME)
30
32
 
31
- private var currentBundleId: String? = null
32
- private var shouldRollbackOnCrash = false
33
- private var isMonitoring = false
34
- private var recoveryRequested = false
33
+ private val launchState = HotUpdaterRecoveryState()
35
34
  private var contentAppearedCallback: ((String?) -> Unit)? = null
36
35
  private var recoveryRestartCallback: (() -> Boolean)? = null
37
36
 
@@ -39,10 +38,7 @@ internal class HotUpdaterRecoveryManager(
39
38
  Runnable {
40
39
  cancelRecoveryWatchdog()
41
40
  Log.d(TAG, "Stopping crash monitoring for current launch")
42
- isMonitoring = false
43
- recoveryRequested = false
44
- shouldRollbackOnCrash = false
45
- currentBundleId = null
41
+ launchState.stop()
46
42
  contentAppearedCallback = null
47
43
  recoveryRestartCallback = null
48
44
  activeManager = null
@@ -52,7 +48,7 @@ internal class HotUpdaterRecoveryManager(
52
48
  private val installJsExceptionHooksRunnable =
53
49
  object : Runnable {
54
50
  override fun run() {
55
- if (!isMonitoring) {
51
+ if (launchState.snapshot() == null) {
56
52
  return
57
53
  }
58
54
 
@@ -74,7 +70,7 @@ internal class HotUpdaterRecoveryManager(
74
70
  }
75
71
 
76
72
  return try {
77
- val recovery = PendingCrashRecovery.fromJson(JSONObject(crashMarkerFile.readText()))
73
+ val recovery = PendingCrashRecovery.loadFromFile(crashMarkerFile) ?: return null
78
74
  Log.d(
79
75
  TAG,
80
76
  "Consumed pending crash marker bundleId=${recovery.launchedBundleId} shouldRollback=${recovery.shouldRollback}",
@@ -96,11 +92,8 @@ internal class HotUpdaterRecoveryManager(
96
92
  ) {
97
93
  crashMarkerFile.parentFile?.mkdirs()
98
94
  contentAppearedCallback = onContentAppeared
99
- currentBundleId = bundleId
100
- shouldRollbackOnCrash = shouldRollback
101
- isMonitoring = true
102
- recoveryRequested = false
103
95
  recoveryRestartCallback = onRecoveryRestartRequested
96
+ launchState.start(bundleId, shouldRollback)
104
97
  activeManager = this
105
98
 
106
99
  ensureExceptionHandlerInstalled()
@@ -122,17 +115,13 @@ internal class HotUpdaterRecoveryManager(
122
115
  }
123
116
 
124
117
  private fun handleContentAppeared() {
125
- if (!isMonitoring) {
126
- return
127
- }
128
-
129
- Log.d(TAG, "First content appeared for bundleId=$currentBundleId")
118
+ val launch = launchState.completeLaunch() ?: return
119
+ Log.d(TAG, "First content appeared for bundleId=${launch.bundleId}")
130
120
  ReactMarker.removeListener(contentAppearedListener)
131
121
  mainHandler.removeCallbacks(installJsExceptionHooksRunnable)
132
122
  mainHandler.removeCallbacks(stopMonitoringRunnable)
133
- contentAppearedCallback?.invoke(currentBundleId)
134
- shouldRollbackOnCrash = false
135
- updateNativeLaunchState(currentBundleId, false)
123
+ contentAppearedCallback?.invoke(launch.bundleId)
124
+ updateNativeLaunchState(launch.bundleId, false)
136
125
  cancelRecoveryWatchdog()
137
126
  mainHandler.postDelayed(stopMonitoringRunnable, MONITORING_GRACE_PERIOD_MS)
138
127
  }
@@ -145,11 +134,10 @@ internal class HotUpdaterRecoveryManager(
145
134
  previousExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
146
135
  Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
147
136
  val manager = activeManager
148
- manager?.writeCrashMarker()
149
-
150
137
  if (manager?.requestAutomaticRecovery() == true) {
151
- Process.killProcess(Process.myPid())
152
- exitProcess(10)
138
+ // The separate restart activity kills this process after it starts.
139
+ // A second fatal error must not kill it while that request is in flight.
140
+ return@setDefaultUncaughtExceptionHandler
153
141
  }
154
142
 
155
143
  previousExceptionHandler?.uncaughtException(thread, throwable)
@@ -161,20 +149,21 @@ internal class HotUpdaterRecoveryManager(
161
149
  exceptionHandlerInstalled = true
162
150
  }
163
151
 
164
- private fun writeCrashMarker() {
165
- if (!isMonitoring) {
166
- return
167
- }
168
-
152
+ private fun writeCrashMarker(launch: HotUpdaterRecoveryState.Launch) {
153
+ val atomicFile = AtomicFile(crashMarkerFile)
154
+ var output: FileOutputStream? = null
169
155
  try {
170
156
  val payload =
171
157
  JSONObject().apply {
172
- put("bundleId", currentBundleId ?: JSONObject.NULL)
173
- put("shouldRollback", shouldRollbackOnCrash)
158
+ put("bundleId", launch.bundleId ?: JSONObject.NULL)
159
+ put("shouldRollback", launch.shouldRollback)
174
160
  }
175
161
  crashMarkerFile.parentFile?.mkdirs()
176
- crashMarkerFile.writeText(payload.toString())
162
+ output = atomicFile.startWrite()
163
+ output.write(payload.toString().toByteArray(Charsets.UTF_8))
164
+ atomicFile.finishWrite(output)
177
165
  } catch (e: Exception) {
166
+ output?.let(atomicFile::failWrite)
178
167
  Log.e(TAG, "Failed to write crash marker", e)
179
168
  }
180
169
  }
@@ -194,35 +183,28 @@ internal class HotUpdaterRecoveryManager(
194
183
  cancelRecoveryWatchdogAlarm(appContext)
195
184
  }
196
185
 
197
- private fun requestAutomaticRecovery(): Boolean {
198
- if (!isMonitoring || !shouldRollbackOnCrash) {
199
- return false
186
+ private fun requestAutomaticRecovery(exception: Exception? = null): Boolean {
187
+ val request = launchState.requestRecovery()
188
+ if (exception != null) {
189
+ Log.e(TAG, "Caught React startup exception for bundleId=${launchState.snapshot()?.bundleId}", exception)
200
190
  }
201
-
202
- synchronized(this) {
203
- if (recoveryRequested) {
204
- return true
205
- }
206
- recoveryRequested = true
191
+ if (request == null) {
192
+ launchState.snapshot()?.let(::writeCrashMarker)
193
+ return false
207
194
  }
208
-
195
+ if (!request.shouldRestart) return true
196
+ writeCrashMarker(request.launch)
209
197
  val started = recoveryRestartCallback?.invoke() == true
198
+ launchState.finishRecoveryRequest(request, started)
210
199
  if (!started) {
211
- synchronized(this) {
212
- recoveryRequested = false
213
- }
214
200
  Log.w(TAG, "Failed to schedule automatic recovery restart")
215
201
  } else {
216
- Log.i(TAG, "Scheduled automatic recovery restart for bundleId=$currentBundleId")
202
+ Log.i(TAG, "Scheduled automatic recovery restart for bundleId=${request.launch.bundleId}")
217
203
  }
218
204
  return started
219
205
  }
220
206
 
221
- private fun handleJavaScriptException(exception: Exception): Boolean {
222
- Log.e(TAG, "Caught React startup exception for bundleId=$currentBundleId", exception)
223
- writeCrashMarker()
224
- return requestAutomaticRecovery()
225
- }
207
+ private fun handleJavaScriptException(exception: Exception): Boolean = requestAutomaticRecovery(exception)
226
208
 
227
209
  private fun ensureNativeSignalHandlerInstalled() {
228
210
  if (signalHandlerInstalled || !loadNativeLibrary()) {
@@ -252,141 +234,11 @@ internal class HotUpdaterRecoveryManager(
252
234
  }
253
235
  }
254
236
 
237
+ // Each architecture's source set reaches its own React instance, so the old
238
+ // architecture's ReactNativeHost is compiled only into old architecture builds.
255
239
  private fun installJavaScriptExceptionHooks() {
256
240
  val application = appContext as? ReactApplication ?: return
257
-
258
- if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
259
- installReactHostExceptionHook(application)
260
- } else {
261
- installLegacyExceptionHook(application)
262
- }
263
- }
264
-
265
- private fun installReactHostExceptionHook(application: ReactApplication) {
266
- val reactHost = getReactHost(application) ?: return
267
- val reactHostDelegate =
268
- findField(reactHost.javaClass, "mReactHostDelegate")?.let { field ->
269
- field.isAccessible = true
270
- field.get(reactHost)
271
- }
272
- ?: findField(reactHost.javaClass, "reactHostDelegate")?.let { field ->
273
- field.isAccessible = true
274
- field.get(reactHost)
275
- }
276
- ?: return
277
-
278
- val delegateIdentity = System.identityHashCode(reactHostDelegate)
279
- if (patchedReactHostDelegateIds.add(delegateIdentity)) {
280
- val exceptionHandlerField = findField(reactHostDelegate.javaClass, "exceptionHandler")
281
- if (exceptionHandlerField == null) {
282
- patchedReactHostDelegateIds.remove(delegateIdentity)
283
- return
284
- }
285
-
286
- exceptionHandlerField.isAccessible = true
287
- @Suppress("UNCHECKED_CAST")
288
- val previousHandler =
289
- exceptionHandlerField.get(reactHostDelegate) as? (Exception) -> Unit
290
-
291
- exceptionHandlerField.set(reactHostDelegate) { exception: Exception ->
292
- if (activeManager?.handleJavaScriptException(exception) != true) {
293
- previousHandler?.invoke(exception) ?: throw exception
294
- }
295
- }
296
- }
297
-
298
- val reactContext =
299
- findMethod(reactHost.javaClass, "getCurrentReactContext")?.invoke(reactHost) as? ReactContext
300
- reactContext?.let { patchReactContextExceptionHandler(it) }
301
- }
302
-
303
- private fun installLegacyExceptionHook(application: ReactApplication) {
304
- val instanceManager = application.reactNativeHost.reactInstanceManager
305
- val managerIdentity = System.identityHashCode(instanceManager)
306
- if (patchedInstanceManagerIds.add(managerIdentity)) {
307
- val exceptionHandlerField = findField(instanceManager.javaClass, "mJSExceptionHandler")
308
- if (exceptionHandlerField == null) {
309
- patchedInstanceManagerIds.remove(managerIdentity)
310
- return
311
- }
312
-
313
- exceptionHandlerField.isAccessible = true
314
- val previousHandler = exceptionHandlerField.get(instanceManager) as? JSExceptionHandler
315
- exceptionHandlerField.set(instanceManager, RecoveryJSExceptionHandler(previousHandler))
316
- }
317
-
318
- instanceManager.currentReactContext?.let { reactContext ->
319
- patchReactContextExceptionHandler(reactContext)
320
- patchCatalystInstanceExceptionHandler(reactContext)
321
- }
322
- }
323
-
324
- private fun patchReactContextExceptionHandler(reactContext: ReactContext) {
325
- val contextIdentity = System.identityHashCode(reactContext)
326
- if (!patchedReactContextIds.add(contextIdentity)) {
327
- return
328
- }
329
-
330
- val previousHandler = reactContext.jsExceptionHandler
331
- if (previousHandler is RecoveryJSExceptionHandler) {
332
- return
333
- }
334
-
335
- reactContext.setJSExceptionHandler(RecoveryJSExceptionHandler(previousHandler))
336
- }
337
-
338
- private fun patchCatalystInstanceExceptionHandler(reactContext: ReactContext) {
339
- val catalystInstance =
340
- try {
341
- reactContext.catalystInstance
342
- } catch (_: Exception) {
343
- null
344
- } ?: return
345
-
346
- val catalystIdentity = System.identityHashCode(catalystInstance)
347
- if (!patchedCatalystInstanceIds.add(catalystIdentity)) {
348
- return
349
- }
350
-
351
- val exceptionHandlerField = findField(catalystInstance.javaClass, "mJSExceptionHandler")
352
- if (exceptionHandlerField == null) {
353
- patchedCatalystInstanceIds.remove(catalystIdentity)
354
- return
355
- }
356
-
357
- exceptionHandlerField.isAccessible = true
358
- val previousHandler = exceptionHandlerField.get(catalystInstance) as? JSExceptionHandler
359
- if (previousHandler is RecoveryJSExceptionHandler) {
360
- return
361
- }
362
- exceptionHandlerField.set(catalystInstance, RecoveryJSExceptionHandler(previousHandler))
363
- }
364
-
365
- private fun getReactHost(application: ReactApplication): Any? =
366
- try {
367
- findMethod(application.javaClass, "getReactHost")?.invoke(application)
368
- } catch (_: Exception) {
369
- null
370
- }
371
-
372
- private fun findMethod(
373
- clazz: Class<*>,
374
- name: String,
375
- ) = runCatching { clazz.getMethod(name) }.getOrNull()
376
-
377
- private fun findField(
378
- clazz: Class<*>,
379
- name: String,
380
- ): Field? {
381
- var current: Class<*>? = clazz
382
- while (true) {
383
- val currentClass = current ?: break
384
- runCatching { currentClass.getDeclaredField(name) }
385
- .getOrNull()
386
- ?.let { return it }
387
- current = currentClass.superclass
388
- }
389
- return null
241
+ JavaScriptExceptionHooks.install(application)
390
242
  }
391
243
 
392
244
  private fun loadNativeLibrary(): Boolean {
@@ -413,11 +265,11 @@ internal class HotUpdaterRecoveryManager(
413
265
  shouldRollback: Boolean,
414
266
  )
415
267
 
416
- private class RecoveryJSExceptionHandler(
268
+ internal class RecoveryJSExceptionHandler(
417
269
  private val previousHandler: JSExceptionHandler?,
418
270
  ) : JSExceptionHandler {
419
271
  override fun handleException(e: Exception) {
420
- if (activeManager?.handleJavaScriptException(e) != true) {
272
+ if (!handleJavaScriptExceptionForRecovery(e)) {
421
273
  previousHandler?.handleException(e) ?: throw e
422
274
  }
423
275
  }
@@ -450,10 +302,40 @@ internal class HotUpdaterRecoveryManager(
450
302
  @Volatile
451
303
  private var activeManager: HotUpdaterRecoveryManager? = null
452
304
 
453
- private val patchedInstanceManagerIds = mutableSetOf<Int>()
454
- private val patchedCatalystInstanceIds = mutableSetOf<Int>()
455
305
  private val patchedReactContextIds = mutableSetOf<Int>()
456
- private val patchedReactHostDelegateIds = mutableSetOf<Int>()
306
+
307
+ /** Hands a JavaScript error to the monitored launch; false when it does not recover it. */
308
+ internal fun handleJavaScriptExceptionForRecovery(exception: Exception): Boolean =
309
+ activeManager?.handleJavaScriptException(exception) == true
310
+
311
+ internal fun patchReactContextExceptionHandler(reactContext: ReactContext) {
312
+ val contextIdentity = System.identityHashCode(reactContext)
313
+ if (!patchedReactContextIds.add(contextIdentity)) {
314
+ return
315
+ }
316
+
317
+ val previousHandler = reactContext.jsExceptionHandler
318
+ if (previousHandler is RecoveryJSExceptionHandler) {
319
+ return
320
+ }
321
+
322
+ reactContext.setJSExceptionHandler(RecoveryJSExceptionHandler(previousHandler))
323
+ }
324
+
325
+ internal fun findField(
326
+ clazz: Class<*>,
327
+ name: String,
328
+ ): Field? {
329
+ var current: Class<*>? = clazz
330
+ while (true) {
331
+ val currentClass = current ?: break
332
+ runCatching { currentClass.getDeclaredField(name) }
333
+ .getOrNull()
334
+ ?.let { return it }
335
+ current = currentClass.superclass
336
+ }
337
+ return null
338
+ }
457
339
 
458
340
  @JvmStatic
459
341
  fun handleRecoveryWatchdog(context: Context) {
@@ -467,7 +349,8 @@ internal class HotUpdaterRecoveryManager(
467
349
  }
468
350
 
469
351
  val crashMarkerFile = File(getBundleStoreDir(appContext), CRASH_MARKER_FILENAME)
470
- if (crashMarkerFile.exists()) {
352
+ val recovery = PendingCrashRecovery.loadFromFile(crashMarkerFile)
353
+ if (recovery?.shouldRollback == true && recovery.launchedBundleId != null) {
471
354
  Log.i(TAG, "Recovery watchdog detected crash marker, relaunching app")
472
355
  watchdogStateFile.delete()
473
356
  cancelRecoveryWatchdogAlarm(appContext)
@@ -0,0 +1,64 @@
1
+ package com.hotupdater
2
+
3
+ internal class HotUpdaterRecoveryState {
4
+ data class Launch(
5
+ val bundleId: String?,
6
+ val shouldRollback: Boolean,
7
+ )
8
+
9
+ data class RecoveryRequest(
10
+ val launch: Launch,
11
+ val shouldRestart: Boolean,
12
+ )
13
+
14
+ private enum class RestartState { NONE, REQUESTED, SCHEDULED, FAILED }
15
+
16
+ private var launch: Launch? = null
17
+ private var restartState = RestartState.NONE
18
+ private var contentAppeared = false
19
+
20
+ @Synchronized
21
+ fun start(
22
+ bundleId: String?,
23
+ shouldRollback: Boolean,
24
+ ) {
25
+ launch = Launch(bundleId, shouldRollback)
26
+ restartState = RestartState.NONE
27
+ contentAppeared = false
28
+ }
29
+
30
+ @Synchronized
31
+ fun snapshot(): Launch? = launch
32
+
33
+ @Synchronized
34
+ fun stop() {
35
+ launch = null
36
+ restartState = RestartState.NONE
37
+ }
38
+
39
+ @Synchronized
40
+ fun completeLaunch(): Launch? {
41
+ val current = launch ?: return null
42
+ if (contentAppeared || restartState != RestartState.NONE) return null
43
+ contentAppeared = true
44
+ launch = current.copy(shouldRollback = false)
45
+ return current
46
+ }
47
+
48
+ @Synchronized
49
+ fun requestRecovery(): RecoveryRequest? {
50
+ val current = launch?.takeIf { it.shouldRollback } ?: return null
51
+ val shouldRestart = restartState == RestartState.NONE || restartState == RestartState.FAILED
52
+ if (shouldRestart) restartState = RestartState.REQUESTED
53
+ return RecoveryRequest(current, shouldRestart)
54
+ }
55
+
56
+ @Synchronized
57
+ fun finishRecoveryRequest(
58
+ request: RecoveryRequest,
59
+ started: Boolean,
60
+ ) {
61
+ if (launch !== request.launch) return
62
+ restartState = if (started) RestartState.SCHEDULED else RestartState.FAILED
63
+ }
64
+ }
@@ -0,0 +1,37 @@
1
+ package com.hotupdater
2
+
3
+ import android.content.Context
4
+ import java.io.File
5
+ import java.util.UUID
6
+
7
+ /**
8
+ * Owns the install id, a random id created once per app installation.
9
+ *
10
+ * The id lives outside the bundle store and outside device backups, so a
11
+ * device restored from a backup gets an id of its own.
12
+ */
13
+ class InstallIdentityService(
14
+ private val identityFile: File,
15
+ ) {
16
+ companion object {
17
+ /** The install id kept in `noBackupFilesDir/hot-updater/install-identity.json`. */
18
+ fun create(context: Context): InstallIdentityService =
19
+ InstallIdentityService(NoBackupStorage.file(context, InstallationIdentity.IDENTITY_FILENAME))
20
+ }
21
+
22
+ private var identity: InstallationIdentity? = null
23
+
24
+ /**
25
+ * Returns the install id, creating and persisting a random one when none
26
+ * is stored. The id stays stable for this process even if persisting fails.
27
+ */
28
+ @Synchronized
29
+ fun getInstallId(): String {
30
+ identity?.let { return it.installId }
31
+ val current =
32
+ InstallationIdentity.loadFromFile(identityFile)
33
+ ?: InstallationIdentity(installId = UUID.randomUUID().toString()).also { it.saveToFile(identityFile) }
34
+ identity = current
35
+ return current.installId
36
+ }
37
+ }