@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
package/src/native.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
- type ChangedAsset,
2
+ type ArtifactAsset,
3
3
  type CatalogHighWater,
4
4
  INVALID_COHORT_ERROR_MESSAGE,
5
5
  isValidCohort,
6
6
  normalizeCohortValue,
7
7
  type UpdateStatus,
8
8
  type PersistedSelectionReceipt,
9
- } from "@hot-updater/core";
9
+ } from "@hot-updater/protocol";
10
10
  import { NativeEventEmitter, Platform } from "react-native";
11
11
 
12
12
  import { HotUpdaterErrorCode, isHotUpdaterError } from "./error";
@@ -74,7 +74,10 @@ class HotUpdaterSessionState {
74
74
  private readonly defaultChannel: string;
75
75
  private currentChannel: string;
76
76
  private cachedCohort: string | undefined;
77
- private readonly inflightUpdates = new Map<string, Promise<boolean>>();
77
+ private readonly inflightUpdates = new Map<
78
+ string,
79
+ Promise<BundleDelivery | null>
80
+ >();
78
81
  private lastInstalledBundleId: string | null = null;
79
82
  private readonly activeBundleSnapshotCache = new Map<
80
83
  ActiveBundleSnapshotCacheKey,
@@ -103,11 +106,16 @@ class HotUpdaterSessionState {
103
106
  return this.lastInstalledBundleId === bundleId;
104
107
  }
105
108
 
106
- getInflightUpdate(bundleId: string): Promise<boolean> | undefined {
109
+ getInflightUpdate(
110
+ bundleId: string,
111
+ ): Promise<BundleDelivery | null> | undefined {
107
112
  return this.inflightUpdates.get(bundleId);
108
113
  }
109
114
 
110
- trackInflightUpdate(bundleId: string, promise: Promise<boolean>) {
115
+ trackInflightUpdate(
116
+ bundleId: string,
117
+ promise: Promise<BundleDelivery | null>,
118
+ ) {
111
119
  this.inflightUpdates.set(bundleId, promise);
112
120
  }
113
121
 
@@ -234,7 +242,7 @@ const getReloadProcess = (): (() => Promise<void>) | null => {
234
242
  : null;
235
243
  };
236
244
 
237
- export type HotUpdaterProgressArtifactType = "archive" | "diff";
245
+ export type HotUpdaterProgressArtifactType = "diff";
238
246
 
239
247
  export type HotUpdaterDiffFileStatus =
240
248
  | "pending"
@@ -286,18 +294,11 @@ export interface HotUpdaterDiffProgressDetails {
286
294
  files: HotUpdaterDiffFileSnapshot[];
287
295
  }
288
296
 
289
- export type HotUpdaterProgressEvent =
290
- | {
291
- progress: number;
292
- artifactType: "archive";
293
- downloadedBytes?: number;
294
- totalBytes?: number;
295
- }
296
- | {
297
- progress: number;
298
- artifactType: "diff";
299
- details: HotUpdaterDiffProgressDetails;
300
- };
297
+ export type HotUpdaterProgressEvent = {
298
+ progress: number;
299
+ artifactType: "diff";
300
+ details: HotUpdaterDiffProgressDetails;
301
+ };
301
302
 
302
303
  export type HotUpdaterEvent = {
303
304
  onProgress: HotUpdaterProgressEvent;
@@ -424,18 +425,42 @@ export const commitReleaseSelection = async (input: {
424
425
  return ok;
425
426
  };
426
427
 
428
+ /** How native delivered a staged bundle. */
429
+ export type BundleDelivery = {
430
+ /**
431
+ * "patch" when a bsdiff patch produced a file, "manifest" when only the
432
+ * changed files were downloaded, "archive" for the full archive.
433
+ */
434
+ readonly delivery: "patch" | "manifest" | "archive";
435
+ /** A patch was tried, but the file or the archive was downloaded instead. */
436
+ readonly patchFallback: boolean;
437
+ };
438
+
439
+ const readBundleDelivery = (value: unknown): BundleDelivery | null => {
440
+ if (typeof value !== "object" || value === null) return null;
441
+ const { delivery, patchFallback } = value as Record<string, unknown>;
442
+ if (
443
+ delivery !== "patch" &&
444
+ delivery !== "manifest" &&
445
+ delivery !== "archive"
446
+ ) {
447
+ return null;
448
+ }
449
+ return { delivery, patchFallback: patchFallback === true };
450
+ };
451
+
427
452
  /**
428
- * Downloads files and applies them to the app.
453
+ * Stages a bundle for the next launch. Resolves with how native delivered
454
+ * it, or with null when this runtime had already staged it and nothing was
455
+ * downloaded; rejects when the download or install fails.
429
456
  *
430
- * @param {UpdateParams} params - Parameters object required for bundle update
431
- * @returns {Promise<boolean>} Resolves with true if download was successful
432
457
  * @throws {Error} Rejects with error.code from HotUpdaterErrorCode enum and error.message
433
458
  */
434
- export async function updateBundle(params: UpdateParams): Promise<boolean> {
459
+ export async function stageBundle(
460
+ params: UpdateParams,
461
+ ): Promise<BundleDelivery | null> {
435
462
  const updateBundleId = params.bundleId;
436
463
  const status = params.status;
437
- const targetFileUrl = params.fileUrl;
438
-
439
464
  const currentBundleId = status === "UPDATE" ? getFreshBundleId() : undefined;
440
465
 
441
466
  // If native is still on the same bundle we installed in this session,
@@ -446,7 +471,7 @@ export async function updateBundle(params: UpdateParams): Promise<boolean> {
446
471
  sessionState.hasInstalledBundle(updateBundleId) &&
447
472
  currentBundleId === updateBundleId
448
473
  ) {
449
- return true;
474
+ return null;
450
475
  }
451
476
 
452
477
  const shouldSkipCurrentBundleIdCheck =
@@ -467,30 +492,25 @@ export async function updateBundle(params: UpdateParams): Promise<boolean> {
467
492
  const existing = sessionState.getInflightUpdate(updateBundleId);
468
493
  if (existing) return existing;
469
494
 
470
- const targetFileHash = params.fileHash;
471
495
  const targetChannel = params.channel;
472
496
  const targetManifestUrl = params.manifestUrl;
473
497
  const targetManifestFileHash = params.manifestFileHash;
474
- const targetChangedAssets = params.changedAssets;
498
+ const targetAssets = params.assets;
475
499
 
476
500
  const promise = (async () => {
477
501
  try {
478
502
  const selection = params.selection;
479
- const ok = await HotUpdaterNative.updateBundle({
503
+ const staged = await HotUpdaterNative.updateBundle({
480
504
  bundleId: updateBundleId,
481
505
  channel: targetChannel,
482
- changedAssets:
483
- (targetChangedAssets as Record<string, ChangedAsset> | null) ?? null,
484
- fileUrl: targetFileUrl,
485
- fileHash: targetFileHash ?? null,
486
- manifestFileHash: targetManifestFileHash ?? null,
487
- manifestUrl: targetManifestUrl ?? null,
506
+ assets: targetAssets as Record<string, ArtifactAsset>,
507
+ manifestFileHash: targetManifestFileHash,
508
+ manifestUrl: targetManifestUrl,
509
+ ...(params.archiveUrl ? { archiveUrl: params.archiveUrl } : {}),
488
510
  ...(selection === undefined ? {} : { selection }),
489
511
  });
490
- if (ok) {
491
- sessionState.markBundleInstalled(updateBundleId, targetChannel);
492
- }
493
- return ok;
512
+ sessionState.markBundleInstalled(updateBundleId, targetChannel);
513
+ return readBundleDelivery(staged);
494
514
  } finally {
495
515
  sessionState.clearInflightUpdate(updateBundleId);
496
516
  }
@@ -500,6 +520,18 @@ export async function updateBundle(params: UpdateParams): Promise<boolean> {
500
520
  return promise;
501
521
  }
502
522
 
523
+ /**
524
+ * Downloads files and applies them to the app.
525
+ *
526
+ * @param {UpdateParams} params - Parameters object required for bundle update
527
+ * @returns {Promise<boolean>} Resolves with true if download was successful
528
+ * @throws {Error} Rejects with error.code from HotUpdaterErrorCode enum and error.message
529
+ */
530
+ export async function updateBundle(params: UpdateParams): Promise<boolean> {
531
+ await stageBundle(params);
532
+ return true;
533
+ }
534
+
503
535
  /**
504
536
  * Fetches the current app version.
505
537
  */
@@ -705,64 +737,31 @@ export const getInstallId = (): string => {
705
737
  return nativeModule.getInstallId();
706
738
  };
707
739
 
708
- export type PersistedUserIdentity = {
709
- userId?: string;
710
- username?: string;
711
- };
712
-
713
- export const getPersistedUserIdentity = (): PersistedUserIdentity => {
714
- const nativeModule = HotUpdaterNative as typeof HotUpdaterNative & {
715
- getUserId?: () => string | null;
716
- getUsername?: () => string | null;
717
- };
718
-
719
- if (
720
- typeof nativeModule.getUserId !== "function" ||
721
- typeof nativeModule.getUsername !== "function"
722
- ) {
740
+ const requireStorageNativeMethod = <T extends (...args: any[]) => any>(
741
+ name: "getStorageItem" | "setStorageItem",
742
+ ): T => {
743
+ const method = (HotUpdaterNative as unknown as Record<string, unknown>)[name];
744
+ if (typeof method !== "function") {
723
745
  throw new Error(
724
- "[HotUpdater] Native module is missing 'getUserId()' or 'getUsername()'. This JS bundle requires a newer native @hot-updater/react-native SDK. Rebuild and release a new app version before delivering this OTA update.",
746
+ `[HotUpdater] Native module is missing '${name}()'. This JS bundle requires a newer native @hot-updater/react-native SDK. Rebuild and release a new app version before delivering this OTA update.`,
725
747
  );
726
748
  }
727
-
728
- const userId = nativeModule.getUserId();
729
- const username = nativeModule.getUsername();
730
-
731
- return {
732
- ...(userId != null ? { userId } : {}),
733
- ...(username != null ? { username } : {}),
734
- };
749
+ return method.bind(HotUpdaterNative) as T;
735
750
  };
736
751
 
737
- export type SetUserParams = {
738
- userId?: string | number | null;
739
- username?: string | null;
752
+ /** Reads a value from the SDK's native key-value store. */
753
+ export const getStorageItem = (key: string): string | null => {
754
+ const value =
755
+ requireStorageNativeMethod<(key: string) => unknown>("getStorageItem")(key);
756
+ return typeof value === "string" ? value : null;
740
757
  };
741
758
 
742
- export function setUser(params: SetUserParams): void;
743
- export function setUser(params: null): void;
744
- export function setUser(params: SetUserParams | null): void {
745
- const nativeModule = HotUpdaterNative as typeof HotUpdaterNative & {
746
- setUser?: (userId: string | null, username: string | null) => void;
747
- };
748
-
749
- if (typeof nativeModule.setUser !== "function") {
750
- throw new Error(
751
- "[HotUpdater] Native module is missing 'setUser()'. This JS bundle requires a newer native @hot-updater/react-native SDK. Rebuild and release a new app version before delivering this OTA update.",
752
- );
753
- }
754
-
755
- if (params === null) {
756
- nativeModule.setUser(null, null);
757
- return;
758
- }
759
-
760
- const normalizedUserId =
761
- params.userId === null || params.userId === undefined
762
- ? null
763
- : String(params.userId);
764
- nativeModule.setUser(normalizedUserId, params.username ?? null);
765
- }
759
+ /** Writes a value to the SDK's native key-value store; null removes it. */
760
+ export const setStorageItem = (key: string, value: string | null): void => {
761
+ requireStorageNativeMethod<(key: string, value: string | null) => void>(
762
+ "setStorageItem",
763
+ )(key, value);
764
+ };
766
765
 
767
766
  /**
768
767
  * Result returned by notifyAppReady()
@@ -784,12 +783,13 @@ export type NotifyAppReadyResult =
784
783
  toReleaseId?: string;
785
784
  };
786
785
 
787
- export type NotifyAppReadyInsightsEvent = {
786
+ /** An update applied or recovered at this launch, as native persisted it. */
787
+ export type LaunchTransition = {
788
788
  type: "UPDATE_APPLIED" | "RECOVERED";
789
789
  fromBundleId: string;
790
790
  toBundleId: string;
791
- fromReleaseId?: string | null;
792
- toReleaseId?: string | null;
791
+ fromReleaseId: string | null;
792
+ toReleaseId: string | null;
793
793
  updateStrategy: PersistedUpdateStrategy;
794
794
  };
795
795
 
@@ -892,9 +892,9 @@ const getNotifyAppReadyTransition = (
892
892
  return null;
893
893
  };
894
894
 
895
- const getNotifyAppReadyInsightsEvent = (
895
+ const getLaunchTransition = (
896
896
  result: RawNotifyAppReadyResult,
897
- ): NotifyAppReadyInsightsEvent | null => {
897
+ ): LaunchTransition | null => {
898
898
  const transition = getNotifyAppReadyTransition(result);
899
899
 
900
900
  if (!transition || !isPersistedUpdateStrategy(result.updateStrategy)) {
@@ -905,12 +905,8 @@ const getNotifyAppReadyInsightsEvent = (
905
905
  type: transition.status,
906
906
  fromBundleId: transition.fromBundleId,
907
907
  toBundleId: transition.toBundleId,
908
- ...(transition.fromReleaseId === null
909
- ? {}
910
- : { fromReleaseId: transition.fromReleaseId }),
911
- ...(transition.toReleaseId === null
912
- ? {}
913
- : { toReleaseId: transition.toReleaseId }),
908
+ fromReleaseId: transition.fromReleaseId,
909
+ toReleaseId: transition.toReleaseId,
914
910
  updateStrategy: result.updateStrategy,
915
911
  };
916
912
  };
@@ -939,14 +935,14 @@ const normalizeNotifyAppReadyResult = (
939
935
 
940
936
  export const readNotifyAppReady = (): {
941
937
  result: NotifyAppReadyResult;
942
- insightsEvent: NotifyAppReadyInsightsEvent | null;
938
+ transition: LaunchTransition | null;
943
939
  pending: boolean;
944
940
  } => {
945
941
  const rawResult = readRawNotifyAppReadyResult();
946
942
 
947
943
  return {
948
944
  result: normalizeNotifyAppReadyResult(rawResult),
949
- insightsEvent: getNotifyAppReadyInsightsEvent(rawResult),
945
+ transition: getLaunchTransition(rawResult),
950
946
  pending: rawResult.status === "PENDING",
951
947
  };
952
948
  };
@@ -0,0 +1,47 @@
1
+ import { createPluginHost } from "@hot-updater/protocol";
2
+ import { Platform } from "react-native";
3
+
4
+ import {
5
+ getAppVersion,
6
+ getBundleId,
7
+ getChannel,
8
+ getCohort,
9
+ getFingerprintHash,
10
+ getInstallId,
11
+ getMinBundleId,
12
+ getStorageItem,
13
+ setStorageItem,
14
+ } from "./native";
15
+ import { HOT_UPDATER_SDK_VERSION } from "./sdkVersion";
16
+
17
+ export type { PluginHostConfig } from "@hot-updater/protocol";
18
+
19
+ /** The app's plugin host: plugins read the native module and fetch the configured server. */
20
+ export const {
21
+ configurePlugins,
22
+ hasPluginHook,
23
+ buildPluginEvent,
24
+ dispatchPluginHook,
25
+ emitPluginHook,
26
+ } = createPluginHost({
27
+ fetch: (url, init) => fetch(url, init),
28
+ get platform() {
29
+ return Platform.OS === "android" ? "android" : "ios";
30
+ },
31
+ get isDebugBuild() {
32
+ return typeof __DEV__ !== "undefined" && __DEV__;
33
+ },
34
+ get sdkVersion() {
35
+ return HOT_UPDATER_SDK_VERSION;
36
+ },
37
+ getInstallId: () => getInstallId(),
38
+ getAppVersion: () => getAppVersion(),
39
+ getMinBundleId: () => getMinBundleId(),
40
+ getBundleId: () => getBundleId(),
41
+ getChannel: () => getChannel(),
42
+ getCohort: () => getCohort(),
43
+ getFingerprintHash: () => getFingerprintHash(),
44
+ getStorageItem: (key) => getStorageItem(key),
45
+ setStorageItem: (key, value) => setStorageItem(key, value),
46
+ now: () => Date.now(),
47
+ });
@@ -1,17 +1,16 @@
1
1
  import {
2
- MAX_TARGET_COHORTS_PER_RELEASE,
3
- MAX_COMPILED_CATALOG_BYTES,
4
- MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE,
5
- NUMERIC_COHORT_SIZE,
6
- isUUIDv7,
7
- parseReleaseCatalogScopeKey,
2
+ type ExpectedReleaseCatalogScope,
3
+ getUtf8ByteLength,
4
+ MAX_RELEASE_CATALOG_WIRE_BYTES,
5
+ parseReleaseCatalog,
8
6
  type ReleaseCatalog,
9
- } from "@hot-updater/core";
7
+ } from "@hot-updater/protocol";
8
+
9
+ import { fetchUpdateResponse, type UpdateRequest } from "./fetchUpdateResponse";
10
+ import { InvalidUpdateResponseError, UpdateHttpError } from "./updateError";
10
11
 
11
12
  const CACHE_FORMAT_VERSION = "1";
12
13
  const MAX_ETAG_BYTES = 1024;
13
- export const MAX_RELEASE_CATALOG_WIRE_BYTES =
14
- MAX_COMPILED_CATALOG_BYTES * 2 + 4 * 1024;
15
14
  export const MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES =
16
15
  MAX_RELEASE_CATALOG_WIRE_BYTES + MAX_ETAG_BYTES + 3;
17
16
 
@@ -46,137 +45,13 @@ let lastParsedCache:
46
45
  })
47
46
  | null = null;
48
47
 
49
- export type ExpectedReleaseCatalogScope = {
50
- readonly channelKey: string;
51
- readonly platform: "ios" | "android";
52
- } & (
53
- | {
54
- readonly strategy: "APP_VERSION";
55
- readonly fingerprintHash?: never;
56
- }
57
- | {
58
- readonly strategy: "FINGERPRINT";
59
- readonly fingerprintHash: string;
60
- }
61
- );
62
-
63
48
  type FetchReleaseCatalogInput = {
64
49
  readonly baseURL: string;
65
50
  readonly expectedScope: ExpectedReleaseCatalogScope;
66
51
  readonly requestHeaders?: Record<string, string>;
67
52
  readonly requestTimeout?: number;
68
53
  readonly url: string;
69
- };
70
-
71
- const getUtf8ByteLength = (value: string): number => {
72
- let bytes = 0;
73
- for (let index = 0; index < value.length; index += 1) {
74
- const code = value.charCodeAt(index);
75
- if (code <= 0x7f) {
76
- bytes += 1;
77
- } else if (code <= 0x7ff) {
78
- bytes += 2;
79
- } else if (
80
- code >= 0xd800 &&
81
- code <= 0xdbff &&
82
- index + 1 < value.length &&
83
- value.charCodeAt(index + 1) >= 0xdc00 &&
84
- value.charCodeAt(index + 1) <= 0xdfff
85
- ) {
86
- bytes += 4;
87
- index += 1;
88
- } else {
89
- bytes += 3;
90
- }
91
- }
92
- return bytes;
93
- };
94
-
95
- const isStringArray = (value: unknown): value is readonly string[] =>
96
- Array.isArray(value) && value.every((entry) => typeof entry === "string");
97
-
98
- const isValidReleaseDescriptor = (value: unknown): boolean => {
99
- if (value === null || typeof value !== "object") return false;
100
- const descriptor = value as Record<string, unknown>;
101
-
102
- return (
103
- isUUIDv7(descriptor.releaseId) &&
104
- (descriptor.kind === "BUNDLE" || descriptor.kind === "EMBEDDED") &&
105
- ((descriptor.kind === "BUNDLE" &&
106
- typeof descriptor.bundleId === "string") ||
107
- (descriptor.kind === "EMBEDDED" && descriptor.bundleId === null)) &&
108
- Number.isSafeInteger(descriptor.rolloutCohortCount) &&
109
- (descriptor.rolloutCohortCount as number) >= 0 &&
110
- (descriptor.rolloutCohortCount as number) <= NUMERIC_COHORT_SIZE &&
111
- isStringArray(descriptor.targetCohorts) &&
112
- descriptor.targetCohorts.length <= MAX_TARGET_COHORTS_PER_RELEASE &&
113
- typeof descriptor.shouldForceUpdate === "boolean" &&
114
- (descriptor.message === null || typeof descriptor.message === "string")
115
- );
116
- };
117
-
118
- export const hasExpectedReleaseCatalogScope = (
119
- catalog: ReleaseCatalog,
120
- expected: ExpectedReleaseCatalogScope,
121
- ): boolean => {
122
- try {
123
- const parsed = parseReleaseCatalogScopeKey(catalog.scopeKey);
124
- return (
125
- parsed.channelKey === expected.channelKey &&
126
- parsed.platform === expected.platform &&
127
- parsed.strategy === expected.strategy &&
128
- (parsed.strategy === "APP_VERSION" ||
129
- (expected.strategy === "FINGERPRINT" &&
130
- parsed.fingerprintHash === expected.fingerprintHash))
131
- );
132
- } catch {
133
- return false;
134
- }
135
- };
136
-
137
- const parseValidatedCatalog = (
138
- value: string,
139
- expectedScope: ExpectedReleaseCatalogScope,
140
- ): ReleaseCatalog | null => {
141
- if (getUtf8ByteLength(value) > MAX_RELEASE_CATALOG_WIRE_BYTES) return null;
142
-
143
- try {
144
- const catalog = JSON.parse(value) as Partial<ReleaseCatalog>;
145
- if (
146
- catalog.schemaVersion !== 1 ||
147
- typeof catalog.catalogId !== "string" ||
148
- catalog.catalogId.length === 0 ||
149
- typeof catalog.scopeKey !== "string" ||
150
- !Number.isSafeInteger(catalog.generation) ||
151
- (catalog.generation ?? 0) < 1 ||
152
- typeof catalog.catalogHash !== "string" ||
153
- !/^sha256:[0-9a-f]{64}$/.test(catalog.catalogHash) ||
154
- catalog.fallbackPolicy !== "BUILTIN_IF_ACTIVE_INELIGIBLE" ||
155
- !Array.isArray(catalog.releases) ||
156
- !catalog.releases.every(isValidReleaseDescriptor) ||
157
- (catalog.rollbackReleases !== undefined &&
158
- (!Array.isArray(catalog.rollbackReleases) ||
159
- !catalog.rollbackReleases.every(isValidReleaseDescriptor)))
160
- ) {
161
- return null;
162
- }
163
- if (
164
- !hasExpectedReleaseCatalogScope(catalog as ReleaseCatalog, expectedScope)
165
- ) {
166
- return null;
167
- }
168
- const distinctTargetCohorts = new Set(
169
- [...catalog.releases, ...(catalog.rollbackReleases ?? [])].flatMap(
170
- (release) => release.targetCohorts,
171
- ),
172
- );
173
- if (distinctTargetCohorts.size > MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE) {
174
- return null;
175
- }
176
- return catalog as ReleaseCatalog;
177
- } catch {
178
- return null;
179
- }
54
+ readonly onResponse?: UpdateRequest["onResponse"];
180
55
  };
181
56
 
182
57
  const isValidETag = (value: string | null): value is string =>
@@ -214,7 +89,7 @@ const parseCache = (
214
89
  const etag = value.slice(versionEnd + 1, etagEnd);
215
90
  const body = value.slice(etagEnd + 1);
216
91
  if (!isValidETag(etag)) return null;
217
- const catalog = parseValidatedCatalog(body, expectedScope);
92
+ const catalog = parseReleaseCatalog(body, expectedScope);
218
93
  if (catalog === null) return null;
219
94
  const parsed = { catalog, etag, partition, serialized: value };
220
95
  lastParsedCache = parsed;
@@ -240,36 +115,24 @@ export const createReleaseCatalogCachePartition = (
240
115
  version: 2,
241
116
  });
242
117
 
243
- const fetchCatalogResponse = async (
118
+ const isEmptyCatalogResponse = (response: Response) =>
119
+ response.status === 404 &&
120
+ response.headers.get("x-hot-updater-catalog")?.trim().toLowerCase() ===
121
+ "none";
122
+
123
+ const fetchCatalogResponse = (
244
124
  input: FetchReleaseCatalogInput,
245
125
  etag?: string,
246
- ): Promise<Response> => {
247
- const controller = new AbortController();
248
- const timeoutId = setTimeout(
249
- () => controller.abort(),
250
- input.requestTimeout ?? 5000,
251
- );
252
-
253
- try {
254
- const headers = new Headers(input.requestHeaders);
255
- headers.set("Accept", "application/json");
256
- if (etag === undefined) {
257
- headers.delete("If-None-Match");
258
- } else {
259
- headers.set("If-None-Match", etag);
260
- }
261
- return await fetch(input.url, {
262
- headers,
263
- signal: controller.signal,
264
- });
265
- } catch (error: unknown) {
266
- if (error instanceof Error && error.name === "AbortError") {
267
- throw new Error("Request timed out");
268
- }
269
- throw error;
270
- } finally {
271
- clearTimeout(timeoutId);
272
- }
126
+ ) => {
127
+ const headers = new Headers(input.requestHeaders);
128
+ headers.set("Accept", "application/json");
129
+ if (etag === undefined) headers.delete("If-None-Match");
130
+ else headers.set("If-None-Match", etag);
131
+ return fetchUpdateResponse({
132
+ ...input,
133
+ resource: "catalog",
134
+ requestHeaders: Object.fromEntries(headers.entries()),
135
+ });
273
136
  };
274
137
 
275
138
  const readValidatedCache = async (
@@ -287,24 +150,28 @@ const readValidatedCache = async (
287
150
  };
288
151
 
289
152
  const consumeSuccessfulResponse = async (
290
- response: Response,
153
+ { response, body }: Awaited<ReturnType<typeof fetchCatalogResponse>>,
291
154
  input: FetchReleaseCatalogInput,
292
155
  partition: string,
293
- ): Promise<ReleaseCatalog> => {
156
+ ): Promise<ReleaseCatalog | null> => {
157
+ // The server marks the 404 of a scope that has no catalog yet: no update,
158
+ // not a failure. Any other 404 is a wrong baseURL or route.
159
+ if (isEmptyCatalogResponse(response)) {
160
+ return null;
161
+ }
294
162
  if (response.status !== 200) {
295
- throw new Error(response.statusText);
163
+ throw new UpdateHttpError(response.status, response.statusText);
296
164
  }
297
165
 
298
- const body = await response.text();
299
- const catalog = parseValidatedCatalog(body, input.expectedScope);
166
+ const catalog = parseReleaseCatalog(body!, input.expectedScope);
300
167
  if (catalog === null) {
301
168
  await removeNativeReleaseCatalogCache(partition);
302
- throw new Error("Received an invalid Release catalog");
169
+ throw new InvalidUpdateResponseError("Received an invalid Release catalog");
303
170
  }
304
171
 
305
172
  const etag = response.headers.get("etag");
306
173
  if (isValidETag(etag)) {
307
- const serialized = serializeCache(etag, body);
174
+ const serialized = serializeCache(etag, body!);
308
175
  if (getUtf8ByteLength(serialized) > MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES) {
309
176
  await removeNativeReleaseCatalogCache(partition);
310
177
  return catalog;
@@ -323,14 +190,18 @@ const consumeSuccessfulResponse = async (
323
190
  return catalog;
324
191
  };
325
192
 
193
+ /**
194
+ * Fetches a scope's catalog, or null when the server answers that the scope
195
+ * has none (a 404 with `x-hot-updater-catalog: none`).
196
+ */
326
197
  export const fetchReleaseCatalogWithCache = async (
327
198
  input: FetchReleaseCatalogInput,
328
- ): Promise<ReleaseCatalog> => {
199
+ ): Promise<ReleaseCatalog | null> => {
329
200
  const partition = createReleaseCatalogCachePartition(input);
330
201
  const cached = await readValidatedCache(partition, input.expectedScope);
331
202
  const response = await fetchCatalogResponse(input, cached?.etag);
332
203
 
333
- if (response.status === 304) {
204
+ if (response.response.status === 304) {
334
205
  if (cached !== null) return cached.catalog;
335
206
 
336
207
  const repairResponse = await fetchCatalogResponse(input);
package/src/sdkVersion.ts CHANGED
@@ -1 +1 @@
1
- export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.4";
1
+ export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.40";