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

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 (306) 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 +102 -0
  48. package/lib/commonjs/appReady.js.map +1 -0
  49. package/lib/commonjs/checkForUpdate.js +120 -68
  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 +4 -13
  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 +63 -94
  60. package/lib/commonjs/httpClient.js.map +1 -1
  61. package/lib/commonjs/index.js +71 -18
  62. package/lib/commonjs/index.js.map +1 -1
  63. package/lib/commonjs/native.js +74 -60
  64. package/lib/commonjs/native.js.map +1 -1
  65. package/lib/commonjs/pluginHost.js +45 -0
  66. package/lib/commonjs/pluginHost.js.map +1 -0
  67. package/lib/commonjs/releaseCatalogCache.js +36 -87
  68. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  69. package/lib/commonjs/sdkVersion.js +1 -1
  70. package/lib/commonjs/sdkVersion.js.map +1 -1
  71. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  72. package/lib/commonjs/store.js +3 -10
  73. package/lib/commonjs/store.js.map +1 -1
  74. package/lib/commonjs/updateError.js +145 -0
  75. package/lib/commonjs/updateError.js.map +1 -0
  76. package/lib/commonjs/wrap.js +43 -26
  77. package/lib/commonjs/wrap.js.map +1 -1
  78. package/lib/module/appReady.js +95 -0
  79. package/lib/module/appReady.js.map +1 -0
  80. package/lib/module/checkForUpdate.js +113 -63
  81. package/lib/module/checkForUpdate.js.map +1 -1
  82. package/lib/module/clientPlugin.js +4 -0
  83. package/lib/module/clientPlugin.js.map +1 -0
  84. package/lib/module/error.js +4 -13
  85. package/lib/module/error.js.map +1 -1
  86. package/lib/module/fetchJSON.js +14 -26
  87. package/lib/module/fetchJSON.js.map +1 -1
  88. package/lib/module/fetchUpdateResponse.js +55 -0
  89. package/lib/module/fetchUpdateResponse.js.map +1 -0
  90. package/lib/module/httpClient.js +62 -93
  91. package/lib/module/httpClient.js.map +1 -1
  92. package/lib/module/index.js +58 -20
  93. package/lib/module/index.js.map +1 -1
  94. package/lib/module/native.js +67 -53
  95. package/lib/module/native.js.map +1 -1
  96. package/lib/module/pluginHost.js +36 -0
  97. package/lib/module/pluginHost.js.map +1 -0
  98. package/lib/module/releaseCatalogCache.js +32 -82
  99. package/lib/module/releaseCatalogCache.js.map +1 -1
  100. package/lib/module/sdkVersion.js +1 -1
  101. package/lib/module/sdkVersion.js.map +1 -1
  102. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  103. package/lib/module/store.js +3 -10
  104. package/lib/module/store.js.map +1 -1
  105. package/lib/module/updateError.js +139 -0
  106. package/lib/module/updateError.js.map +1 -0
  107. package/lib/module/wrap.js +43 -26
  108. package/lib/module/wrap.js.map +1 -1
  109. package/lib/typescript/commonjs/appReady.d.ts +21 -0
  110. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  112. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  114. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/error.d.ts +4 -13
  116. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
  118. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
  120. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/httpClient.d.ts +4 -33
  122. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/index.d.ts +4 -8
  124. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/native.d.ts +29 -22
  126. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  128. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
  130. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  132. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +35 -47
  134. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  135. package/lib/typescript/commonjs/store.d.ts +1 -3
  136. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/types.d.ts +1 -1
  138. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  140. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  143. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  144. package/lib/typescript/module/appReady.d.ts +21 -0
  145. package/lib/typescript/module/appReady.d.ts.map +1 -0
  146. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  147. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  148. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  149. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  150. package/lib/typescript/module/error.d.ts +4 -13
  151. package/lib/typescript/module/error.d.ts.map +1 -1
  152. package/lib/typescript/module/fetchJSON.d.ts +5 -5
  153. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  154. package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
  155. package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
  156. package/lib/typescript/module/httpClient.d.ts +4 -33
  157. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  158. package/lib/typescript/module/index.d.ts +4 -8
  159. package/lib/typescript/module/index.d.ts.map +1 -1
  160. package/lib/typescript/module/native.d.ts +29 -22
  161. package/lib/typescript/module/native.d.ts.map +1 -1
  162. package/lib/typescript/module/pluginHost.d.ts +4 -0
  163. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  164. package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
  165. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  166. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  167. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  168. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +35 -47
  169. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  170. package/lib/typescript/module/store.d.ts +1 -3
  171. package/lib/typescript/module/store.d.ts.map +1 -1
  172. package/lib/typescript/module/types.d.ts +1 -1
  173. package/lib/typescript/module/types.d.ts.map +1 -1
  174. package/lib/typescript/module/updateError.d.ts +26 -0
  175. package/lib/typescript/module/updateError.d.ts.map +1 -0
  176. package/lib/typescript/module/wrap.d.ts.map +1 -1
  177. package/lib/typescript/module/wrap.types.d.ts +6 -7
  178. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  179. package/package.json +7 -4
  180. package/src/appReady.ts +141 -0
  181. package/src/checkForUpdate.ts +145 -86
  182. package/src/clientPlugin.ts +18 -0
  183. package/src/error.ts +4 -14
  184. package/src/fetchJSON.ts +14 -30
  185. package/src/fetchUpdateResponse.ts +60 -0
  186. package/src/httpClient.ts +88 -159
  187. package/src/index.ts +90 -24
  188. package/src/native.ts +98 -102
  189. package/src/pluginHost.ts +47 -0
  190. package/src/releaseCatalogCache.ts +43 -172
  191. package/src/sdkVersion.ts +1 -1
  192. package/src/specs/NativeHotUpdater.ts +34 -48
  193. package/src/store.ts +2 -23
  194. package/src/types.ts +1 -1
  195. package/src/updateError.ts +167 -0
  196. package/src/wrap.tsx +57 -26
  197. package/src/wrap.types.ts +6 -7
  198. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  199. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  200. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  201. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  202. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  203. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  204. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  205. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  206. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  207. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  208. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  209. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  210. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  211. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  212. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  213. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  214. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  215. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  216. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  217. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  218. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  219. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  220. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  221. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -616
  222. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  223. package/lib/commonjs/httpClient.spec.js +0 -102
  224. package/lib/commonjs/httpClient.spec.js.map +0 -1
  225. package/lib/commonjs/index.spec.js +0 -302
  226. package/lib/commonjs/index.spec.js.map +0 -1
  227. package/lib/commonjs/iosInterop.spec.js +0 -18
  228. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  229. package/lib/commonjs/native.spec.js +0 -883
  230. package/lib/commonjs/native.spec.js.map +0 -1
  231. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  232. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  233. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  234. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  235. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  236. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  237. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  238. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  239. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  240. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  241. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  242. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  243. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  244. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  245. package/lib/commonjs/store.notification.spec.js +0 -76
  246. package/lib/commonjs/store.notification.spec.js.map +0 -1
  247. package/lib/commonjs/store.spec.js +0 -316
  248. package/lib/commonjs/store.spec.js.map +0 -1
  249. package/lib/commonjs/wrap.spec.js +0 -329
  250. package/lib/commonjs/wrap.spec.js.map +0 -1
  251. package/lib/module/catalogCacheNative.spec.js +0 -31
  252. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  253. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -615
  254. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  255. package/lib/module/httpClient.spec.js +0 -102
  256. package/lib/module/httpClient.spec.js.map +0 -1
  257. package/lib/module/index.spec.js +0 -301
  258. package/lib/module/index.spec.js.map +0 -1
  259. package/lib/module/iosInterop.spec.js +0 -18
  260. package/lib/module/iosInterop.spec.js.map +0 -1
  261. package/lib/module/native.spec.js +0 -882
  262. package/lib/module/native.spec.js.map +0 -1
  263. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  264. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  265. package/lib/module/notifyAppReadyInsights.js +0 -128
  266. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  267. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  268. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  269. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  270. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  271. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  272. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  273. package/lib/module/releaseCatalogCache.spec.js +0 -195
  274. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  275. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  276. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  277. package/lib/module/store.notification.spec.js +0 -77
  278. package/lib/module/store.notification.spec.js.map +0 -1
  279. package/lib/module/store.spec.js +0 -315
  280. package/lib/module/store.spec.js.map +0 -1
  281. package/lib/module/wrap.spec.js +0 -330
  282. package/lib/module/wrap.spec.js.map +0 -1
  283. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  284. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  285. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  286. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  287. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  288. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  289. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  290. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  291. package/src/catalogCacheNative.spec.ts +0 -63
  292. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -612
  293. package/src/httpClient.spec.ts +0 -132
  294. package/src/index.spec.ts +0 -337
  295. package/src/iosInterop.spec.ts +0 -30
  296. package/src/native.spec.ts +0 -945
  297. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  298. package/src/notifyAppReadyInsights.spec.ts +0 -243
  299. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  300. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  301. package/src/notifyAppReadyInsights.ts +0 -194
  302. package/src/releaseCatalogCache.spec.ts +0 -282
  303. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  304. package/src/store.notification.spec.ts +0 -92
  305. package/src/store.spec.ts +0 -372
  306. package/src/wrap.spec.ts +0 -406
@@ -1,17 +1,24 @@
1
1
  import {
2
2
  authorizeReleaseTransition,
3
+ canonicalizeAppVersion,
3
4
  createReleaseSelectionContextHash,
4
5
  encodeChannelKey,
6
+ type ExpectedReleaseCatalogScope,
7
+ hasExpectedReleaseCatalogScope,
5
8
  selectDesiredRelease,
6
- type ArtifactInfo,
7
9
  type PersistedSelectionReceipt,
8
10
  type ReleaseCatalog,
9
- } from "@hot-updater/core";
10
- import { canonicalizeAppVersion } from "@hot-updater/plugin-core";
11
+ } from "@hot-updater/protocol";
11
12
  import { Platform } from "react-native";
12
13
 
14
+ import { emitAfterAppReady, getRunningReleaseId } from "./appReady";
15
+ import type {
16
+ ReleaseTransitionKind,
17
+ UpdateErrorResource,
18
+ UpdateErrorStage,
19
+ } from "./clientPlugin";
13
20
  import { HotUpdaterError, StaleReleaseCatalogError } from "./error";
14
- import type { HotUpdaterHttpClient, HotUpdaterHttpSession } from "./httpClient";
21
+ import type { HotUpdaterHttpClient } from "./httpClient";
15
22
  import {
16
23
  acceptReleaseCatalog,
17
24
  commitReleaseSelection,
@@ -22,19 +29,16 @@ import {
22
29
  getCohort,
23
30
  getDefaultChannel,
24
31
  getFingerprintHash,
25
- getInstallId,
26
32
  getMinBundleId,
27
33
  getCrashHistory,
28
- getPersistedUserIdentity,
29
34
  isReleaseSelectionCurrent,
30
35
  isChannelSwitched,
31
- updateBundle,
36
+ stageBundle,
32
37
  } from "./native";
33
- import {
34
- hasExpectedReleaseCatalogScope,
35
- type ExpectedReleaseCatalogScope,
36
- } from "./releaseCatalogCache";
37
38
  import { hotUpdaterStore } from "./store";
39
+ import { classifyUpdateError, InvalidUpdateResponseError } from "./updateError";
40
+
41
+ export type { ReleaseTransitionKind };
38
42
 
39
43
  export interface CheckForUpdateOptions {
40
44
  /**
@@ -60,7 +64,7 @@ export interface CheckForUpdateOptions {
60
64
  requestTimeout?: number;
61
65
  }
62
66
 
63
- export type CheckForUpdateResult = ArtifactInfo & {
67
+ export type CheckForUpdateResult = {
64
68
  readonly id: string;
65
69
  readonly message: string | null;
66
70
  readonly rolloutCohortCount: number;
@@ -76,14 +80,7 @@ export type CheckForUpdateResult = ArtifactInfo & {
76
80
  updateBundle: () => Promise<boolean>;
77
81
  };
78
82
 
79
- export type ReleaseTransitionKind =
80
- | "INSTALL"
81
- | "ADOPT_RELEASE"
82
- | "USE_EMBEDDED"
83
- | "USE_BUILTIN";
84
-
85
83
  export interface InternalCheckForUpdateOptions extends CheckForUpdateOptions {
86
- insights?: boolean;
87
84
  client: HotUpdaterHttpClient;
88
85
  }
89
86
 
@@ -113,7 +110,7 @@ const validateCatalog = (
113
110
  !/^sha256:[0-9a-f]{64}$/.test(catalog.catalogHash) ||
114
111
  !hasExpectedReleaseCatalogScope(catalog, expectedScope)
115
112
  ) {
116
- throw new HotUpdaterError("Received an invalid Release catalog");
113
+ throw new InvalidUpdateResponseError("Received an invalid Release catalog");
117
114
  }
118
115
  };
119
116
 
@@ -121,47 +118,48 @@ const resetProgress = () => {
121
118
  hotUpdaterStore.setState({
122
119
  artifactType: null,
123
120
  details: null,
124
- downloadedBytes: undefined,
125
121
  isUpdateDownloaded: false,
126
122
  progress: 0,
127
- totalBytes: undefined,
128
123
  });
129
124
  };
130
125
 
131
- const notifyUnchangedSelection = async (input: {
132
- readonly active: PersistedSelectionReceipt | null;
133
- readonly desired: PersistedSelectionReceipt;
134
- readonly appVersion: string;
135
- readonly cohort: string;
136
- readonly fingerprintHash: string | null;
137
- readonly platform: "ios" | "android";
138
- readonly session: HotUpdaterHttpSession;
139
- readonly requestHeaders?: Record<string, string>;
140
- readonly requestTimeout?: number;
141
- }): Promise<void> => {
142
- const { userId, username } = getPersistedUserIdentity();
143
- try {
144
- await input.session.sendInsightsEvent({
145
- appVersion: input.appVersion,
146
- channel: input.desired.channel,
147
- cohort: input.cohort,
148
- fingerprintHash: input.fingerprintHash,
149
- fromBundleId: null,
150
- fromReleaseId: input.active?.releaseId ?? null,
151
- installId: getInstallId(),
152
- platform: input.platform,
153
- requestHeaders: input.requestHeaders,
154
- requestTimeout: input.requestTimeout,
155
- toBundleId: input.desired.bundleId,
156
- toReleaseId: input.desired.releaseId,
157
- type: "UNCHANGED",
158
- updateStrategy: null,
159
- ...(userId === undefined ? {} : { userId }),
160
- ...(username === undefined ? {} : { username }),
161
- });
162
- } catch (error) {
163
- console.warn("[HotUpdater] Unchanged selection insights failed:", error);
164
- }
126
+ /** The running bundle, the channel checked, and the update's target. */
127
+ type UpdateErrorScope = {
128
+ readonly channel: string;
129
+ readonly bundleId: string;
130
+ readonly updateStrategy: "fingerprint" | "appVersion";
131
+ readonly targetBundleId?: string;
132
+ readonly targetReleaseId?: string | null;
133
+ };
134
+
135
+ /**
136
+ * Tells plugins why an update failed, when it failed as an update. An error
137
+ * the SDK's JavaScript raised happened in `stage`, fetching `resource`.
138
+ */
139
+ export const reportUpdateError = (
140
+ error: unknown,
141
+ stage: UpdateErrorStage,
142
+ resource: UpdateErrorResource | undefined,
143
+ scope: UpdateErrorScope,
144
+ ): void => {
145
+ emitAfterAppReady("onUpdateError", () => {
146
+ const classification = classifyUpdateError(error, stage, resource);
147
+ if (classification === null) return null;
148
+ return {
149
+ ...classification,
150
+ channel: scope.channel,
151
+ bundleId: scope.bundleId,
152
+ releaseId: getRunningReleaseId(scope.bundleId, getChannel()),
153
+ updateStrategy: scope.updateStrategy,
154
+ ...(scope.targetBundleId === undefined
155
+ ? {}
156
+ : { targetBundleId: scope.targetBundleId }),
157
+ ...(scope.targetReleaseId == null
158
+ ? {}
159
+ : { targetReleaseId: scope.targetReleaseId }),
160
+ cause: error,
161
+ };
162
+ });
165
163
  };
166
164
 
167
165
  async function checkForReleaseCatalogUpdate(input: {
@@ -212,12 +210,15 @@ async function checkForReleaseCatalogUpdate(input: {
212
210
  requestTimeout: options.requestTimeout,
213
211
  updateStrategy: options.updateStrategy,
214
212
  });
213
+ // The server says this scope has no catalog yet: no update, not a failure.
214
+ if (catalog === null) return null;
215
215
  validateCatalog(catalog, expectedScope);
216
216
  const catalogId = catalog.catalogId;
217
217
  const scopeKey = catalog.scopeKey;
218
218
 
219
219
  const crashedBundleIds = getCrashHistory();
220
- const active = getActiveUpdateState().activeSelection;
220
+ const activeState = getActiveUpdateState();
221
+ const active = activeState.activeSelection;
221
222
  const explicitScopeSwitch =
222
223
  input.explicitChannel !== undefined &&
223
224
  input.explicitChannel !== input.currentChannel;
@@ -264,7 +265,7 @@ async function checkForReleaseCatalogUpdate(input: {
264
265
  scopeKey,
265
266
  })
266
267
  ) {
267
- throw new HotUpdaterError(
268
+ throw new InvalidUpdateResponseError(
268
269
  "Rejected a stale or inconsistent Release catalog",
269
270
  );
270
271
  }
@@ -328,7 +329,20 @@ async function checkForReleaseCatalogUpdate(input: {
328
329
  scopeKey,
329
330
  selectionContextHash,
330
331
  };
331
- const updateBundleForSelection = async (): Promise<boolean> => {
332
+ const fromReleaseId =
333
+ active?.bundleId === input.currentBundleId
334
+ ? active.releaseId
335
+ : activeState.stableSelection?.bundleId === input.currentBundleId
336
+ ? activeState.stableSelection.releaseId
337
+ : null;
338
+ const errorScope = {
339
+ bundleId: input.currentBundleId,
340
+ channel: input.targetChannel,
341
+ targetBundleId: desired.bundleId,
342
+ targetReleaseId: desired.releaseId,
343
+ updateStrategy: options.updateStrategy,
344
+ };
345
+ const installSelection = async (): Promise<boolean> => {
332
346
  resetProgress();
333
347
  if (!isReleaseSelectionCurrent(guard)) {
334
348
  throw new StaleReleaseCatalogError();
@@ -339,18 +353,14 @@ async function checkForReleaseCatalogUpdate(input: {
339
353
  guard,
340
354
  selection: receipt,
341
355
  });
342
- if (committed && transitionKind === "ADOPT_RELEASE" && options.insights) {
343
- await notifyUnchangedSelection({
344
- active,
345
- appVersion: input.currentAppVersion,
346
- cohort: input.cohort,
347
- desired: receipt,
348
- fingerprintHash: input.fingerprintHash,
349
- platform: input.platform,
350
- requestHeaders: options.requestHeaders,
351
- requestTimeout: options.requestTimeout,
352
- session,
353
- });
356
+ if (committed && transitionKind === "ADOPT_RELEASE") {
357
+ emitAfterAppReady("onUpdateCheck", () => ({
358
+ status: "UNCHANGED",
359
+ channel: receipt.channel,
360
+ bundleId: receipt.bundleId,
361
+ releaseId: receipt.releaseId,
362
+ previousReleaseId: active?.releaseId ?? null,
363
+ }));
354
364
  }
355
365
  return committed;
356
366
  }
@@ -364,33 +374,64 @@ async function checkForReleaseCatalogUpdate(input: {
364
374
  if (!isReleaseSelectionCurrent(guard)) {
365
375
  throw new StaleReleaseCatalogError();
366
376
  }
367
- return updateBundle({
377
+ const delivery = await stageBundle({
378
+ assets: artifact.assets,
368
379
  bundleId: desired.bundleId,
369
- changedAssets: artifact.changedAssets ?? null,
370
380
  channel: input.targetChannel,
371
- fileHash: artifact.fileHash,
372
- fileUrl: artifact.fileUrl,
373
- manifestFileHash: artifact.manifestFileHash ?? null,
374
- manifestUrl: artifact.manifestUrl ?? null,
381
+ manifestFileHash: artifact.manifestFileHash,
382
+ manifestUrl: artifact.manifestUrl,
383
+ ...(artifact.archiveUrl ? { archiveUrl: artifact.archiveUrl } : {}),
375
384
  selection: receipt,
376
385
  shouldSkipCurrentBundleIdCheck: true,
377
386
  status: desired.status,
378
387
  });
388
+ if (delivery !== null && desired.bundleId !== input.currentBundleId) {
389
+ emitAfterAppReady("onBundleDownloaded", () => ({
390
+ channel: input.targetChannel,
391
+ fromBundleId: input.currentBundleId,
392
+ fromReleaseId,
393
+ toBundleId: desired.bundleId,
394
+ toReleaseId: desired.releaseId,
395
+ updateStrategy: options.updateStrategy,
396
+ ...delivery,
397
+ }));
398
+ }
399
+ return true;
400
+ };
401
+ const updateBundleForSelection = async (): Promise<boolean> => {
402
+ try {
403
+ return await installSelection();
404
+ } catch (error) {
405
+ reportUpdateError(error, "download", "artifact", errorScope);
406
+ throw error;
407
+ }
379
408
  };
380
409
 
410
+ const shouldForceUpdate =
411
+ transitionKind === "ADOPT_RELEASE"
412
+ ? false
413
+ : desired.status === "ROLLBACK"
414
+ ? true
415
+ : (release?.shouldForceUpdate ?? false);
416
+ emitAfterAppReady("onUpdateCheck", () => ({
417
+ status: "UPDATE_AVAILABLE",
418
+ channel: input.targetChannel,
419
+ fromBundleId: input.currentBundleId,
420
+ fromReleaseId,
421
+ toBundleId: desired.bundleId,
422
+ toReleaseId: desired.releaseId,
423
+ transitionKind,
424
+ updateStatus: desired.status,
425
+ shouldForceUpdate,
426
+ updateStrategy: options.updateStrategy,
427
+ }));
428
+
381
429
  return {
382
- fileHash: null,
383
- fileUrl: null,
384
430
  id: desired.releaseId ?? desired.bundleId,
385
431
  message: release?.message ?? null,
386
432
  releaseId: desired.releaseId,
387
433
  rolloutCohortCount: release?.rolloutCohortCount ?? 1000,
388
- shouldForceUpdate:
389
- transitionKind === "ADOPT_RELEASE"
390
- ? false
391
- : desired.status === "ROLLBACK"
392
- ? true
393
- : (release?.shouldForceUpdate ?? false),
434
+ shouldForceUpdate,
394
435
  status: desired.status,
395
436
  targetCohorts: release?.targetCohorts ? [...release.targetCohorts] : [],
396
437
  transitionKind,
@@ -439,7 +480,7 @@ export async function checkForUpdate(
439
480
  const fingerprintHash = getFingerprintHash();
440
481
 
441
482
  try {
442
- return await checkForReleaseCatalogUpdate({
483
+ const result = await checkForReleaseCatalogUpdate({
443
484
  cohort,
444
485
  currentAppVersion,
445
486
  currentBundleId,
@@ -453,7 +494,25 @@ export async function checkForUpdate(
453
494
  platform,
454
495
  targetChannel,
455
496
  });
497
+ if (result === null) {
498
+ emitAfterAppReady("onUpdateCheck", () => {
499
+ const releaseId = getRunningReleaseId(currentBundleId, currentChannel);
500
+ return {
501
+ status: "UNCHANGED",
502
+ channel: currentChannel,
503
+ bundleId: currentBundleId,
504
+ releaseId,
505
+ previousReleaseId: releaseId,
506
+ };
507
+ });
508
+ }
509
+ return result;
456
510
  } catch (error) {
511
+ reportUpdateError(error, "check", "catalog", {
512
+ bundleId: currentBundleId,
513
+ channel: targetChannel,
514
+ updateStrategy: options.updateStrategy,
515
+ });
457
516
  options.onError?.(error as Error);
458
517
  return null;
459
518
  }
@@ -0,0 +1,18 @@
1
+ export {
2
+ defineClientPlugin,
3
+ type AppReadyResult,
4
+ type BundleDownloadedInfo,
5
+ type HotUpdaterClientContext,
6
+ type HotUpdaterClientHooks,
7
+ type HotUpdaterClientPlugin,
8
+ type HotUpdaterClientStorage,
9
+ type ReleaseTransitionKind,
10
+ type UpdateCheckResult,
11
+ type UpdateError,
12
+ type UpdateErrorReason,
13
+ type UpdateErrorResource,
14
+ type UpdateErrorStage,
15
+ type UpdateErrorTransport,
16
+ type UpdateHttpResponse,
17
+ type UpdateStrategy,
18
+ } from "@hot-updater/protocol";
package/src/error.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Error Classification:
9
9
  * - Parameter Validation: Invalid or missing function parameters
10
- * - Bundle Storage: Errors during download, extraction, and storage
10
+ * - Bundle Storage: Errors during download, verification, and storage
11
11
  * - Signature Verification: Cryptographic verification failures (collapsed to a single public code)
12
12
  * - Internal: Platform-specific or unexpected errors
13
13
  *
@@ -27,8 +27,7 @@ export enum HotUpdaterErrorCode {
27
27
  MISSING_BUNDLE_ID = "MISSING_BUNDLE_ID",
28
28
 
29
29
  /**
30
- * File URL is invalid or malformed.
31
- * Thrown when fileUrl parameter cannot be parsed as a valid URL.
30
+ * Manifest or asset URL is invalid or malformed.
32
31
  * @retryable false
33
32
  */
34
33
  INVALID_FILE_URL = "INVALID_FILE_URL",
@@ -58,25 +57,16 @@ export enum HotUpdaterErrorCode {
58
57
  */
59
58
  INCOMPLETE_DOWNLOAD = "INCOMPLETE_DOWNLOAD",
60
59
 
61
- /**
62
- * Bundle archive format is invalid or corrupted.
63
- * Thrown when ZIP file has wrong magic bytes, invalid structure, or unsupported format.
64
- * Also thrown for path traversal attempts during extraction.
65
- * @retryable false - Indicates corrupted or malicious bundle
66
- */
67
- EXTRACTION_FORMAT_ERROR = "EXTRACTION_FORMAT_ERROR",
68
-
69
60
  /**
70
61
  * Bundle missing required platform files.
71
- * Thrown when extracted bundle doesn't contain index.android.bundle (Android)
62
+ * Thrown when the staged bundle doesn't contain index.android.bundle (Android)
72
63
  * or main.jsbundle (iOS).
73
64
  * @retryable false - Indicates incorrectly built bundle
74
65
  */
75
66
  INVALID_BUNDLE = "INVALID_BUNDLE",
76
67
 
77
68
  /**
78
- * Insufficient disk space for bundle download and extraction.
79
- * Thrown when available disk space is less than required (file size * 2).
69
+ * Insufficient disk space for bundle staging.
80
70
  * Error message includes required and available bytes.
81
71
  * @retryable false - User must free up disk space
82
72
  */
package/src/fetchJSON.ts CHANGED
@@ -1,33 +1,17 @@
1
- export const fetchJSON = async <T>({
2
- url,
3
- requestHeaders,
4
- requestTimeout = 5000,
5
- }: {
6
- url: string;
7
- requestHeaders?: Record<string, string>;
8
- requestTimeout?: number;
9
- }): Promise<T> => {
10
- const controller = new AbortController();
11
- const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
1
+ import { fetchUpdateResponse, type UpdateRequest } from "./fetchUpdateResponse";
2
+ import { UpdateHttpError } from "./updateError";
12
3
 
13
- try {
14
- const response = await fetch(url, {
15
- headers: {
16
- Accept: "application/json",
17
- ...requestHeaders,
18
- },
19
- signal: controller.signal,
20
- });
21
- if (response.status !== 200) {
22
- throw new Error(response.statusText);
23
- }
24
- return (await response.json()) as T;
25
- } catch (error: unknown) {
26
- if (error instanceof Error && error.name === "AbortError") {
27
- throw new Error("Request timed out");
28
- }
29
- throw error;
30
- } finally {
31
- clearTimeout(timeoutId);
4
+ export class FetchJSONResponseError extends UpdateHttpError {}
5
+
6
+ export const fetchJSON = async <T>(
7
+ input: Omit<UpdateRequest, "resource">,
8
+ ): Promise<T> => {
9
+ const { response, body } = await fetchUpdateResponse({
10
+ ...input,
11
+ resource: "artifact",
12
+ });
13
+ if (response.status !== 200) {
14
+ throw new FetchJSONResponseError(response.status, response.statusText);
32
15
  }
16
+ return JSON.parse(body!) as T;
33
17
  };
@@ -0,0 +1,60 @@
1
+ import type { UpdateHttpResponse } from "@hot-updater/protocol";
2
+
3
+ export interface UpdateRequest {
4
+ readonly url: string;
5
+ readonly resource: UpdateHttpResponse["resource"];
6
+ readonly requestHeaders?: Record<string, string>;
7
+ readonly requestTimeout?: number;
8
+ readonly onResponse?: (response: UpdateHttpResponse) => void;
9
+ }
10
+
11
+ /** Read once for parsing and diagnostics, under the same request timeout. */
12
+ export const fetchUpdateResponse = async ({
13
+ url,
14
+ resource,
15
+ requestHeaders,
16
+ requestTimeout = 5000,
17
+ onResponse,
18
+ }: UpdateRequest): Promise<{ response: Response; body: string | null }> => {
19
+ const controller = new AbortController();
20
+ let timedOut = false;
21
+ const timeoutId = setTimeout(() => {
22
+ timedOut = true;
23
+ controller.abort();
24
+ }, requestTimeout);
25
+ try {
26
+ const headers = new Headers(requestHeaders);
27
+ if (!headers.has("Accept")) headers.set("Accept", "application/json");
28
+ const response = await fetch(url, {
29
+ headers,
30
+ signal: controller.signal,
31
+ });
32
+ let body: string | null = null;
33
+ try {
34
+ body = await response.text();
35
+ } catch (error) {
36
+ // A broken error body must not hide an HTTP error already received.
37
+ if (response.status === 200) throw error;
38
+ } finally {
39
+ onResponse?.({
40
+ resource,
41
+ // React Native URL implementations do not all expose pathname.
42
+ path:
43
+ url.replace(/^https?:\/\/[^/?#]*/i, "").split(/[?#]/, 1)[0] || "/",
44
+ status: response.status,
45
+ body,
46
+ bodyTruncated: false,
47
+ });
48
+ }
49
+ return { response, body };
50
+ } catch (error: unknown) {
51
+ // Whatever the fetch rejects with once its timeout aborts it: an
52
+ // AbortError, or Expo's "fetch failed: FetchRequestCanceledException".
53
+ if (timedOut || (error instanceof Error && error.name === "AbortError")) {
54
+ throw new Error("Request timed out");
55
+ }
56
+ throw error;
57
+ } finally {
58
+ clearTimeout(timeoutId);
59
+ }
60
+ };