@hot-updater/react-native 1.0.0-rc.16 → 1.0.0-rc.17

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 (316) hide show
  1. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +491 -388
  2. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +5 -11
  3. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  4. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +290 -12
  5. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +35 -19
  6. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  7. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  8. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  9. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  10. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +110 -52
  11. package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
  12. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  13. package/android/src/newarch/HotUpdaterModule.kt +74 -69
  14. package/android/src/oldarch/HotUpdaterModule.kt +63 -59
  15. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  16. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +1 -1
  17. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +446 -61
  18. package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +220 -0
  19. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +19 -27
  20. package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +71 -0
  21. package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +106 -0
  22. package/android/src/test/java/com/hotupdater/NoBackupContext.kt +11 -0
  23. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +309 -23
  24. package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +57 -0
  25. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +238 -157
  26. package/ios/HotUpdater/Internal/BundleMetadata.swift +3 -53
  27. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  28. package/ios/HotUpdater/Internal/HotUpdater.mm +8 -18
  29. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +48 -29
  30. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  31. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +108 -1
  32. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  33. package/lib/commonjs/appReady.js +111 -0
  34. package/lib/commonjs/appReady.js.map +1 -0
  35. package/lib/commonjs/appReady.spec.js +360 -0
  36. package/lib/commonjs/appReady.spec.js.map +1 -0
  37. package/lib/commonjs/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
  38. package/lib/commonjs/appReady.test-utils.js.map +1 -0
  39. package/lib/commonjs/baseURL.js +16 -0
  40. package/lib/commonjs/baseURL.js.map +1 -0
  41. package/lib/commonjs/checkForUpdate.js +104 -56
  42. package/lib/commonjs/checkForUpdate.js.map +1 -1
  43. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +337 -74
  44. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  45. package/lib/commonjs/clientPlugin.js +49 -0
  46. package/lib/commonjs/clientPlugin.js.map +1 -0
  47. package/lib/commonjs/entryImportGraph.spec.js +50 -0
  48. package/lib/commonjs/entryImportGraph.spec.js.map +1 -0
  49. package/lib/commonjs/fetchJSON.js +2 -6
  50. package/lib/commonjs/fetchJSON.js.map +1 -1
  51. package/lib/commonjs/httpClient.js +18 -165
  52. package/lib/commonjs/httpClient.js.map +1 -1
  53. package/lib/commonjs/httpClient.spec.js +26 -199
  54. package/lib/commonjs/httpClient.spec.js.map +1 -1
  55. package/lib/commonjs/index.js +49 -36
  56. package/lib/commonjs/index.js.map +1 -1
  57. package/lib/commonjs/index.spec.js +108 -123
  58. package/lib/commonjs/index.spec.js.map +1 -1
  59. package/lib/commonjs/native.js +67 -48
  60. package/lib/commonjs/native.js.map +1 -1
  61. package/lib/commonjs/native.spec.js +80 -58
  62. package/lib/commonjs/native.spec.js.map +1 -1
  63. package/lib/commonjs/pluginHost.js +208 -0
  64. package/lib/commonjs/pluginHost.js.map +1 -0
  65. package/lib/commonjs/pluginHost.spec.js +292 -0
  66. package/lib/commonjs/pluginHost.spec.js.map +1 -0
  67. package/lib/commonjs/plugins/insights/eventId.js +129 -0
  68. package/lib/commonjs/plugins/insights/eventId.js.map +1 -0
  69. package/lib/commonjs/plugins/insights/eventId.spec.js +47 -0
  70. package/lib/commonjs/plugins/insights/eventId.spec.js.map +1 -0
  71. package/lib/commonjs/plugins/insights/index.js +272 -0
  72. package/lib/commonjs/plugins/insights/index.js.map +1 -0
  73. package/lib/commonjs/plugins/insights/insights.spec.js +640 -0
  74. package/lib/commonjs/plugins/insights/insights.spec.js.map +1 -0
  75. package/lib/commonjs/plugins/insights/sender.js +128 -0
  76. package/lib/commonjs/plugins/insights/sender.js.map +1 -0
  77. package/lib/commonjs/plugins/insights/sender.spec.js +265 -0
  78. package/lib/commonjs/plugins/insights/sender.spec.js.map +1 -0
  79. package/lib/commonjs/plugins/insights/state.js +145 -0
  80. package/lib/commonjs/plugins/insights/state.js.map +1 -0
  81. package/lib/commonjs/releaseCatalogCache.js +13 -2
  82. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  83. package/lib/commonjs/releaseCatalogCache.spec.js +18 -0
  84. package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -1
  85. package/lib/commonjs/sdkVersion.js +1 -1
  86. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  87. package/lib/commonjs/updateError.js +132 -0
  88. package/lib/commonjs/updateError.js.map +1 -0
  89. package/lib/commonjs/updateError.spec.js +149 -0
  90. package/lib/commonjs/updateError.spec.js.map +1 -0
  91. package/lib/commonjs/wrap.js +43 -25
  92. package/lib/commonjs/wrap.js.map +1 -1
  93. package/lib/commonjs/wrap.progress.spec.js +195 -0
  94. package/lib/commonjs/wrap.progress.spec.js.map +1 -0
  95. package/lib/commonjs/wrap.spec.js +94 -230
  96. package/lib/commonjs/wrap.spec.js.map +1 -1
  97. package/lib/module/appReady.js +103 -0
  98. package/lib/module/appReady.js.map +1 -0
  99. package/lib/module/appReady.spec.js +359 -0
  100. package/lib/module/appReady.spec.js.map +1 -0
  101. package/lib/module/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
  102. package/lib/module/appReady.test-utils.js.map +1 -0
  103. package/lib/module/baseURL.js +11 -0
  104. package/lib/module/baseURL.js.map +1 -0
  105. package/lib/module/checkForUpdate.js +103 -57
  106. package/lib/module/checkForUpdate.js.map +1 -1
  107. package/lib/module/checkForUpdate.releaseCatalog.spec.js +337 -74
  108. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  109. package/lib/module/clientPlugin.js +44 -0
  110. package/lib/module/clientPlugin.js.map +1 -0
  111. package/lib/module/entryImportGraph.spec.js +50 -0
  112. package/lib/module/entryImportGraph.spec.js.map +1 -0
  113. package/lib/module/fetchJSON.js +2 -6
  114. package/lib/module/fetchJSON.js.map +1 -1
  115. package/lib/module/httpClient.js +18 -165
  116. package/lib/module/httpClient.js.map +1 -1
  117. package/lib/module/httpClient.spec.js +27 -200
  118. package/lib/module/httpClient.spec.js.map +1 -1
  119. package/lib/module/index.js +44 -38
  120. package/lib/module/index.js.map +1 -1
  121. package/lib/module/index.spec.js +108 -123
  122. package/lib/module/index.spec.js.map +1 -1
  123. package/lib/module/native.js +63 -44
  124. package/lib/module/native.js.map +1 -1
  125. package/lib/module/native.spec.js +80 -58
  126. package/lib/module/native.spec.js.map +1 -1
  127. package/lib/module/pluginHost.js +200 -0
  128. package/lib/module/pluginHost.js.map +1 -0
  129. package/lib/module/pluginHost.spec.js +291 -0
  130. package/lib/module/pluginHost.spec.js.map +1 -0
  131. package/lib/module/plugins/insights/eventId.js +122 -0
  132. package/lib/module/plugins/insights/eventId.js.map +1 -0
  133. package/lib/module/plugins/insights/eventId.spec.js +47 -0
  134. package/lib/module/plugins/insights/eventId.spec.js.map +1 -0
  135. package/lib/module/plugins/insights/index.js +268 -0
  136. package/lib/module/plugins/insights/index.js.map +1 -0
  137. package/lib/module/plugins/insights/insights.spec.js +639 -0
  138. package/lib/module/plugins/insights/insights.spec.js.map +1 -0
  139. package/lib/module/plugins/insights/sender.js +123 -0
  140. package/lib/module/plugins/insights/sender.js.map +1 -0
  141. package/lib/module/plugins/insights/sender.spec.js +265 -0
  142. package/lib/module/plugins/insights/sender.spec.js.map +1 -0
  143. package/lib/module/plugins/insights/state.js +139 -0
  144. package/lib/module/plugins/insights/state.js.map +1 -0
  145. package/lib/module/releaseCatalogCache.js +13 -2
  146. package/lib/module/releaseCatalogCache.js.map +1 -1
  147. package/lib/module/releaseCatalogCache.spec.js +18 -0
  148. package/lib/module/releaseCatalogCache.spec.js.map +1 -1
  149. package/lib/module/sdkVersion.js +1 -1
  150. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  151. package/lib/module/updateError.js +126 -0
  152. package/lib/module/updateError.js.map +1 -0
  153. package/lib/module/updateError.spec.js +149 -0
  154. package/lib/module/updateError.spec.js.map +1 -0
  155. package/lib/module/wrap.js +43 -25
  156. package/lib/module/wrap.js.map +1 -1
  157. package/lib/module/wrap.progress.spec.js +196 -0
  158. package/lib/module/wrap.progress.spec.js.map +1 -0
  159. package/lib/module/wrap.spec.js +93 -229
  160. package/lib/module/wrap.spec.js.map +1 -1
  161. package/lib/typescript/commonjs/appReady.d.ts +23 -0
  162. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  163. package/lib/typescript/commonjs/appReady.test-utils.d.ts +9 -0
  164. package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +1 -0
  165. package/lib/typescript/commonjs/baseURL.d.ts +4 -0
  166. package/lib/typescript/commonjs/baseURL.d.ts.map +1 -0
  167. package/lib/typescript/commonjs/checkForUpdate.d.ts +15 -2
  168. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  169. package/lib/typescript/commonjs/clientPlugin.d.ts +185 -0
  170. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  171. package/lib/typescript/commonjs/fetchJSON.d.ts +2 -3
  172. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  173. package/lib/typescript/commonjs/httpClient.d.ts +2 -36
  174. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/index.d.ts +3 -6
  176. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  177. package/lib/typescript/commonjs/native.d.ts +29 -15
  178. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  179. package/lib/typescript/commonjs/pluginHost.d.ts +29 -0
  180. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  181. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +18 -0
  182. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +1 -0
  183. package/lib/typescript/commonjs/plugins/insights/index.d.ts +40 -0
  184. package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +1 -0
  185. package/lib/typescript/commonjs/plugins/insights/sender.d.ts +74 -0
  186. package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +1 -0
  187. package/lib/typescript/commonjs/plugins/insights/state.d.ts +46 -0
  188. package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +1 -0
  189. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +5 -1
  190. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  191. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  192. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +34 -12
  193. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  195. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  196. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  197. package/lib/typescript/commonjs/wrap.types.d.ts +6 -10
  198. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  199. package/lib/typescript/module/appReady.d.ts +23 -0
  200. package/lib/typescript/module/appReady.d.ts.map +1 -0
  201. package/lib/typescript/module/appReady.test-utils.d.ts +9 -0
  202. package/lib/typescript/module/appReady.test-utils.d.ts.map +1 -0
  203. package/lib/typescript/module/baseURL.d.ts +4 -0
  204. package/lib/typescript/module/baseURL.d.ts.map +1 -0
  205. package/lib/typescript/module/checkForUpdate.d.ts +15 -2
  206. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  207. package/lib/typescript/module/clientPlugin.d.ts +185 -0
  208. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  209. package/lib/typescript/module/fetchJSON.d.ts +2 -3
  210. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  211. package/lib/typescript/module/httpClient.d.ts +2 -36
  212. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  213. package/lib/typescript/module/index.d.ts +3 -6
  214. package/lib/typescript/module/index.d.ts.map +1 -1
  215. package/lib/typescript/module/native.d.ts +29 -15
  216. package/lib/typescript/module/native.d.ts.map +1 -1
  217. package/lib/typescript/module/pluginHost.d.ts +29 -0
  218. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  219. package/lib/typescript/module/plugins/insights/eventId.d.ts +18 -0
  220. package/lib/typescript/module/plugins/insights/eventId.d.ts.map +1 -0
  221. package/lib/typescript/module/plugins/insights/index.d.ts +40 -0
  222. package/lib/typescript/module/plugins/insights/index.d.ts.map +1 -0
  223. package/lib/typescript/module/plugins/insights/sender.d.ts +74 -0
  224. package/lib/typescript/module/plugins/insights/sender.d.ts.map +1 -0
  225. package/lib/typescript/module/plugins/insights/state.d.ts +46 -0
  226. package/lib/typescript/module/plugins/insights/state.d.ts.map +1 -0
  227. package/lib/typescript/module/releaseCatalogCache.d.ts +5 -1
  228. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  229. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  230. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +34 -12
  231. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  232. package/lib/typescript/module/updateError.d.ts +26 -0
  233. package/lib/typescript/module/updateError.d.ts.map +1 -0
  234. package/lib/typescript/module/wrap.d.ts.map +1 -1
  235. package/lib/typescript/module/wrap.types.d.ts +6 -10
  236. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  237. package/package.json +14 -2
  238. package/plugins/insights/package.json +8 -0
  239. package/src/appReady.spec.ts +393 -0
  240. package/src/{notifyAppReadyInsights.test-utils.ts → appReady.test-utils.ts} +7 -7
  241. package/src/appReady.ts +149 -0
  242. package/src/baseURL.ts +15 -0
  243. package/src/checkForUpdate.releaseCatalog.spec.ts +370 -76
  244. package/src/checkForUpdate.ts +135 -80
  245. package/src/clientPlugin.ts +231 -0
  246. package/src/entryImportGraph.spec.ts +65 -0
  247. package/src/fetchJSON.ts +3 -10
  248. package/src/httpClient.spec.ts +30 -227
  249. package/src/httpClient.ts +20 -256
  250. package/src/index.spec.ts +117 -93
  251. package/src/index.ts +67 -52
  252. package/src/native.spec.ts +98 -72
  253. package/src/native.ts +93 -77
  254. package/src/pluginHost.spec.ts +297 -0
  255. package/src/pluginHost.ts +262 -0
  256. package/src/plugins/insights/eventId.spec.ts +60 -0
  257. package/src/plugins/insights/eventId.ts +171 -0
  258. package/src/plugins/insights/index.ts +367 -0
  259. package/src/plugins/insights/insights.spec.ts +732 -0
  260. package/src/plugins/insights/sender.spec.ts +287 -0
  261. package/src/plugins/insights/sender.ts +221 -0
  262. package/src/plugins/insights/state.ts +184 -0
  263. package/src/releaseCatalogCache.spec.ts +22 -0
  264. package/src/releaseCatalogCache.ts +19 -4
  265. package/src/sdkVersion.ts +1 -1
  266. package/src/specs/NativeHotUpdater.ts +34 -13
  267. package/src/updateError.spec.ts +144 -0
  268. package/src/updateError.ts +153 -0
  269. package/src/wrap.progress.spec.ts +232 -0
  270. package/src/wrap.spec.ts +100 -283
  271. package/src/wrap.tsx +57 -26
  272. package/src/wrap.types.ts +6 -12
  273. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -225
  274. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  275. package/lib/commonjs/notifyAppReadyInsights.js +0 -188
  276. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  277. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -378
  278. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  279. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  280. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -171
  281. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  282. package/lib/commonjs/uuidv7.js +0 -41
  283. package/lib/commonjs/uuidv7.js.map +0 -1
  284. package/lib/commonjs/uuidv7.spec.js +0 -30
  285. package/lib/commonjs/uuidv7.spec.js.map +0 -1
  286. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -224
  287. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  288. package/lib/module/notifyAppReadyInsights.js +0 -181
  289. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  290. package/lib/module/notifyAppReadyInsights.spec.js +0 -377
  291. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  292. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  293. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -170
  294. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  295. package/lib/module/uuidv7.js +0 -36
  296. package/lib/module/uuidv7.js.map +0 -1
  297. package/lib/module/uuidv7.spec.js +0 -30
  298. package/lib/module/uuidv7.spec.js.map +0 -1
  299. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -25
  300. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  301. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  302. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  303. package/lib/typescript/commonjs/uuidv7.d.ts +0 -10
  304. package/lib/typescript/commonjs/uuidv7.d.ts.map +0 -1
  305. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -25
  306. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  307. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  308. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  309. package/lib/typescript/module/uuidv7.d.ts +0 -10
  310. package/lib/typescript/module/uuidv7.d.ts.map +0 -1
  311. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -242
  312. package/src/notifyAppReadyInsights.spec.ts +0 -362
  313. package/src/notifyAppReadyInsights.transport.spec.ts +0 -196
  314. package/src/notifyAppReadyInsights.ts +0 -293
  315. package/src/uuidv7.spec.ts +0 -25
  316. package/src/uuidv7.ts +0 -38
@@ -479,21 +479,17 @@ data class LaunchReport(
479
479
  }
480
480
  }
481
481
 
482
+ /**
483
+ * The persisted install id. See [InstallIdentityService].
484
+ */
482
485
  data class InstallationIdentity(
483
486
  val installId: String,
484
- val userId: String? = null,
485
- val username: String? = null,
486
487
  ) {
487
488
  companion object {
488
489
  private const val TAG = "InstallationIdentity"
489
- const val IDENTITY_FILENAME = "identity.json"
490
+ const val IDENTITY_FILENAME = "install-identity.json"
490
491
 
491
- fun fromJson(json: JSONObject): InstallationIdentity =
492
- InstallationIdentity(
493
- installId = json.getString("installId"),
494
- userId = json.optNullableString("userId"),
495
- username = json.optNullableString("username"),
496
- )
492
+ fun fromJson(json: JSONObject): InstallationIdentity = InstallationIdentity(installId = json.getString("installId"))
497
493
 
498
494
  fun loadFromFile(file: File): InstallationIdentity? =
499
495
  try {
@@ -511,8 +507,6 @@ data class InstallationIdentity(
511
507
  fun toJson(): JSONObject =
512
508
  JSONObject().apply {
513
509
  put("installId", installId)
514
- put("userId", userId ?: JSONObject.NULL)
515
- put("username", username ?: JSONObject.NULL)
516
510
  }
517
511
 
518
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,35 +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
 
239
+ fun invalidManifestParams() =
240
+ HotUpdaterException(
241
+ "INVALID_MANIFEST",
242
+ "Manifest URL, hash, and assets are required",
243
+ failure = invalidResponse.copy(resource = UpdateFailureResource.MANIFEST),
244
+ )
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
+
20
256
  // Bundle storage errors
21
257
  fun directoryCreationFailed() =
22
258
  HotUpdaterException(
23
259
  "DIRECTORY_CREATION_FAILED",
24
260
  "Failed to create bundle directory",
261
+ failure = storageFailure,
25
262
  )
26
263
 
27
- fun downloadFailed(cause: Throwable? = null) =
28
- HotUpdaterException(
29
- "DOWNLOAD_FAILED",
30
- "Failed to download bundle",
31
- cause,
32
- )
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
+ )
33
273
 
34
274
  fun incompleteDownload(
35
275
  expectedSize: Long,
@@ -37,12 +277,14 @@ class HotUpdaterException(
37
277
  ) = HotUpdaterException(
38
278
  "INCOMPLETE_DOWNLOAD",
39
279
  "Download incomplete: received $actualSize bytes, expected $expectedSize bytes",
280
+ failure = UpdateFailure.download(UpdateFailureReason.NETWORK),
40
281
  )
41
282
 
42
283
  fun invalidBundle() =
43
284
  HotUpdaterException(
44
285
  "INVALID_BUNDLE",
45
286
  "Bundle missing required platform files (index.ios.bundle or index.android.bundle)",
287
+ failure = invalidResponse,
46
288
  )
47
289
 
48
290
  fun insufficientDiskSpace(
@@ -51,19 +293,24 @@ class HotUpdaterException(
51
293
  ) = HotUpdaterException(
52
294
  "INSUFFICIENT_DISK_SPACE",
53
295
  "Insufficient disk space: need $required bytes, available $available bytes",
296
+ failure = storageFailure,
54
297
  )
55
298
 
56
- fun signatureVerificationFailed(cause: Throwable? = null) =
57
- HotUpdaterException(
58
- "SIGNATURE_VERIFICATION_FAILED",
59
- "Bundle signature verification failed",
60
- cause,
61
- )
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
+ )
62
308
 
63
309
  fun moveOperationFailed() =
64
310
  HotUpdaterException(
65
311
  "MOVE_OPERATION_FAILED",
66
312
  "Failed to move bundle files",
313
+ failure = storageFailure,
67
314
  )
68
315
 
69
316
  fun bundleInCrashedHistory(bundleId: String) =
@@ -77,30 +324,35 @@ class HotUpdaterException(
77
324
  HotUpdaterException(
78
325
  "PUBLIC_KEY_NOT_CONFIGURED",
79
326
  "Public key not configured for signature verification",
327
+ failure = signatureFailure,
80
328
  )
81
329
 
82
330
  fun invalidPublicKeyFormat() =
83
331
  HotUpdaterException(
84
332
  "INVALID_PUBLIC_KEY_FORMAT",
85
333
  "Invalid public key format",
334
+ failure = signatureFailure,
86
335
  )
87
336
 
88
337
  fun fileHashMismatch() =
89
338
  HotUpdaterException(
90
339
  "FILE_HASH_MISMATCH",
91
340
  "File hash verification failed",
341
+ failure = UpdateFailure.download(UpdateFailureReason.HASH_MISMATCH),
92
342
  )
93
343
 
94
344
  fun fileReadFailed() =
95
345
  HotUpdaterException(
96
346
  "FILE_READ_FAILED",
97
347
  "Failed to read file for verification",
348
+ failure = storageFailure,
98
349
  )
99
350
 
100
351
  fun unsignedNotAllowed() =
101
352
  HotUpdaterException(
102
353
  "UNSIGNED_NOT_ALLOWED",
103
354
  "Unsigned bundles are not allowed",
355
+ failure = signatureFailure,
104
356
  )
105
357
 
106
358
  fun securityFrameworkError(cause: Throwable? = null) =
@@ -108,6 +360,7 @@ class HotUpdaterException(
108
360
  "SECURITY_FRAMEWORK_ERROR",
109
361
  "Security framework error occurred",
110
362
  cause,
363
+ failure = signatureFailure,
111
364
  )
112
365
 
113
366
  // Internal errors
@@ -117,5 +370,30 @@ class HotUpdaterException(
117
370
  "An unknown error occurred",
118
371
  cause,
119
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
+ )
120
398
  }
121
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
  /**
@@ -272,6 +277,7 @@ class HotUpdaterImpl {
272
277
  * @param manifestFileHash Hash or signature used to verify the target manifest
273
278
  * @param assets Complete target asset descriptor map
274
279
  * @param progressCallback Callback for download progress updates
280
+ * @return how the staged bundle arrived
275
281
  * @throws HotUpdaterException if the update fails
276
282
  */
277
283
  suspend fun updateBundle(
@@ -283,18 +289,19 @@ class HotUpdaterImpl {
283
289
  selection: PersistedSelection? = null,
284
290
  archiveUrl: String? = null,
285
291
  progressCallback: (UpdateProgressPayload) -> Unit,
286
- ) {
292
+ ): UpdateBundleResult {
287
293
  if (selection != null && !bundleStorage.stageReleaseSelection(selection)) {
288
- throw IllegalStateException("Release catalog selection is stale")
294
+ throw StaleReleaseSelectionException()
289
295
  }
290
- bundleStorage.updateBundle(
291
- bundleId,
292
- manifestUrl,
293
- manifestFileHash,
294
- assets,
295
- archiveUrl,
296
- progressCallback,
297
- )
296
+ val result =
297
+ bundleStorage.updateBundle(
298
+ bundleId,
299
+ manifestUrl,
300
+ manifestFileHash,
301
+ assets,
302
+ archiveUrl,
303
+ progressCallback,
304
+ )
298
305
 
299
306
  if (!channel.isNullOrEmpty()) {
300
307
  if (channel == getDefaultChannel()) {
@@ -303,6 +310,7 @@ class HotUpdaterImpl {
303
310
  preferences.setItem(CHANNEL_STORAGE_KEY, channel)
304
311
  }
305
312
  }
313
+ return result
306
314
  }
307
315
 
308
316
  /**
@@ -371,7 +379,15 @@ class HotUpdaterImpl {
371
379
  }
372
380
  }
373
381
 
374
- 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
+ }
375
391
 
376
392
  fun acceptReleaseCatalog(
377
393
  catalogId: String,
@@ -420,17 +436,17 @@ class HotUpdaterImpl {
420
436
 
421
437
  suspend fun commitReleaseSelection(selection: PersistedSelection): Boolean = bundleStorage.commitReleaseSelection(selection)
422
438
 
423
- fun getInstallId(): String = bundleStorage.getInstallId()
424
-
425
- fun getUserId(): String? = bundleStorage.getUserId()
439
+ fun getInstallId(): String = installIdentity.getInstallId()
426
440
 
427
- 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)
428
443
 
429
- fun setUser(
430
- userId: String?,
431
- 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?,
432
448
  ) {
433
- bundleStorage.setUser(userId, username)
449
+ keyValueStorage.setItem(key, value)
434
450
  }
435
451
 
436
452
  /**
@@ -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
+ }