@hot-updater/react-native 1.0.0-rc.3 → 1.0.0-rc.31

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 (300) hide show
  1. package/android/proguard-rules.pro +17 -2
  2. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  3. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +954 -837
  4. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +15 -11
  5. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  6. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  7. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +43 -31
  8. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
  9. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  10. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  11. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  12. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  13. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  14. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  15. package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
  16. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  17. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  18. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  19. package/android/src/newarch/HotUpdater.kt +7 -9
  20. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  21. package/android/src/oldarch/HotUpdater.kt +7 -9
  22. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  23. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  24. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  25. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  26. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +950 -1112
  27. package/ios/HotUpdater/Internal/BundleMetadata.swift +8 -53
  28. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  29. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  30. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  31. package/ios/HotUpdater/Internal/HotUpdater.mm +13 -45
  32. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +190 -73
  33. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  34. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  35. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  36. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  37. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  38. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  39. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  40. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  41. package/lib/commonjs/appReady.js +111 -0
  42. package/lib/commonjs/appReady.js.map +1 -0
  43. package/lib/commonjs/checkForUpdate.js +124 -69
  44. package/lib/commonjs/checkForUpdate.js.map +1 -1
  45. package/lib/commonjs/clientPlugin.js +13 -0
  46. package/lib/commonjs/clientPlugin.js.map +1 -0
  47. package/lib/commonjs/error.js +4 -13
  48. package/lib/commonjs/error.js.map +1 -1
  49. package/lib/commonjs/fetchJSON.js +16 -27
  50. package/lib/commonjs/fetchJSON.js.map +1 -1
  51. package/lib/commonjs/fetchUpdateResponse.js +54 -0
  52. package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
  53. package/lib/commonjs/httpClient.js +63 -94
  54. package/lib/commonjs/httpClient.js.map +1 -1
  55. package/lib/commonjs/index.js +71 -18
  56. package/lib/commonjs/index.js.map +1 -1
  57. package/lib/commonjs/native.js +78 -60
  58. package/lib/commonjs/native.js.map +1 -1
  59. package/lib/commonjs/pluginHost.js +44 -0
  60. package/lib/commonjs/pluginHost.js.map +1 -0
  61. package/lib/commonjs/releaseCatalogCache.js +36 -87
  62. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  63. package/lib/commonjs/sdkVersion.js +1 -1
  64. package/lib/commonjs/sdkVersion.js.map +1 -1
  65. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  66. package/lib/commonjs/store.js +3 -10
  67. package/lib/commonjs/store.js.map +1 -1
  68. package/lib/commonjs/updateError.js +132 -0
  69. package/lib/commonjs/updateError.js.map +1 -0
  70. package/lib/commonjs/wrap.js +43 -26
  71. package/lib/commonjs/wrap.js.map +1 -1
  72. package/lib/module/appReady.js +103 -0
  73. package/lib/module/appReady.js.map +1 -0
  74. package/lib/module/checkForUpdate.js +117 -64
  75. package/lib/module/checkForUpdate.js.map +1 -1
  76. package/lib/module/clientPlugin.js +4 -0
  77. package/lib/module/clientPlugin.js.map +1 -0
  78. package/lib/module/error.js +4 -13
  79. package/lib/module/error.js.map +1 -1
  80. package/lib/module/fetchJSON.js +14 -26
  81. package/lib/module/fetchJSON.js.map +1 -1
  82. package/lib/module/fetchUpdateResponse.js +49 -0
  83. package/lib/module/fetchUpdateResponse.js.map +1 -0
  84. package/lib/module/httpClient.js +62 -93
  85. package/lib/module/httpClient.js.map +1 -1
  86. package/lib/module/index.js +58 -20
  87. package/lib/module/index.js.map +1 -1
  88. package/lib/module/native.js +71 -53
  89. package/lib/module/native.js.map +1 -1
  90. package/lib/module/pluginHost.js +35 -0
  91. package/lib/module/pluginHost.js.map +1 -0
  92. package/lib/module/releaseCatalogCache.js +32 -82
  93. package/lib/module/releaseCatalogCache.js.map +1 -1
  94. package/lib/module/sdkVersion.js +1 -1
  95. package/lib/module/sdkVersion.js.map +1 -1
  96. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  97. package/lib/module/store.js +3 -10
  98. package/lib/module/store.js.map +1 -1
  99. package/lib/module/updateError.js +126 -0
  100. package/lib/module/updateError.js.map +1 -0
  101. package/lib/module/wrap.js +43 -26
  102. package/lib/module/wrap.js.map +1 -1
  103. package/lib/typescript/commonjs/appReady.d.ts +23 -0
  104. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  106. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  108. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/error.d.ts +4 -13
  110. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
  112. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
  114. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/httpClient.d.ts +4 -33
  116. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/index.d.ts +4 -8
  118. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/native.d.ts +31 -22
  120. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  122. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
  124. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  126. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +42 -47
  128. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/store.d.ts +1 -3
  130. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/types.d.ts +1 -1
  132. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  134. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  136. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  137. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  138. package/lib/typescript/module/appReady.d.ts +23 -0
  139. package/lib/typescript/module/appReady.d.ts.map +1 -0
  140. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  141. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  142. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  143. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  144. package/lib/typescript/module/error.d.ts +4 -13
  145. package/lib/typescript/module/error.d.ts.map +1 -1
  146. package/lib/typescript/module/fetchJSON.d.ts +5 -5
  147. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  148. package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
  149. package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
  150. package/lib/typescript/module/httpClient.d.ts +4 -33
  151. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  152. package/lib/typescript/module/index.d.ts +4 -8
  153. package/lib/typescript/module/index.d.ts.map +1 -1
  154. package/lib/typescript/module/native.d.ts +31 -22
  155. package/lib/typescript/module/native.d.ts.map +1 -1
  156. package/lib/typescript/module/pluginHost.d.ts +4 -0
  157. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  158. package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
  159. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  160. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  161. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  162. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +42 -47
  163. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  164. package/lib/typescript/module/store.d.ts +1 -3
  165. package/lib/typescript/module/store.d.ts.map +1 -1
  166. package/lib/typescript/module/types.d.ts +1 -1
  167. package/lib/typescript/module/types.d.ts.map +1 -1
  168. package/lib/typescript/module/updateError.d.ts +26 -0
  169. package/lib/typescript/module/updateError.d.ts.map +1 -0
  170. package/lib/typescript/module/wrap.d.ts.map +1 -1
  171. package/lib/typescript/module/wrap.types.d.ts +6 -7
  172. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  173. package/package.json +7 -4
  174. package/src/appReady.ts +149 -0
  175. package/src/checkForUpdate.ts +151 -88
  176. package/src/clientPlugin.ts +18 -0
  177. package/src/error.ts +4 -14
  178. package/src/fetchJSON.ts +14 -30
  179. package/src/fetchUpdateResponse.ts +54 -0
  180. package/src/httpClient.ts +88 -159
  181. package/src/index.ts +90 -24
  182. package/src/native.ts +106 -102
  183. package/src/pluginHost.ts +45 -0
  184. package/src/releaseCatalogCache.ts +43 -172
  185. package/src/sdkVersion.ts +1 -1
  186. package/src/specs/NativeHotUpdater.ts +41 -48
  187. package/src/store.ts +2 -23
  188. package/src/types.ts +1 -1
  189. package/src/updateError.ts +153 -0
  190. package/src/wrap.tsx +57 -26
  191. package/src/wrap.types.ts +6 -7
  192. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  193. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  194. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  195. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  196. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  197. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  198. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  199. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  200. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  201. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  202. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  203. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  204. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  205. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  206. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  207. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  208. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  209. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  210. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  211. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  212. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  213. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  214. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  215. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -615
  216. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  217. package/lib/commonjs/httpClient.spec.js +0 -102
  218. package/lib/commonjs/httpClient.spec.js.map +0 -1
  219. package/lib/commonjs/index.spec.js +0 -302
  220. package/lib/commonjs/index.spec.js.map +0 -1
  221. package/lib/commonjs/iosInterop.spec.js +0 -18
  222. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  223. package/lib/commonjs/native.spec.js +0 -883
  224. package/lib/commonjs/native.spec.js.map +0 -1
  225. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  226. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  227. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  228. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  229. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  230. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  231. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  232. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  233. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  234. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  235. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  236. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  237. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  238. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  239. package/lib/commonjs/store.notification.spec.js +0 -76
  240. package/lib/commonjs/store.notification.spec.js.map +0 -1
  241. package/lib/commonjs/store.spec.js +0 -316
  242. package/lib/commonjs/store.spec.js.map +0 -1
  243. package/lib/commonjs/wrap.spec.js +0 -329
  244. package/lib/commonjs/wrap.spec.js.map +0 -1
  245. package/lib/module/catalogCacheNative.spec.js +0 -31
  246. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  247. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -614
  248. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  249. package/lib/module/httpClient.spec.js +0 -102
  250. package/lib/module/httpClient.spec.js.map +0 -1
  251. package/lib/module/index.spec.js +0 -301
  252. package/lib/module/index.spec.js.map +0 -1
  253. package/lib/module/iosInterop.spec.js +0 -18
  254. package/lib/module/iosInterop.spec.js.map +0 -1
  255. package/lib/module/native.spec.js +0 -882
  256. package/lib/module/native.spec.js.map +0 -1
  257. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  258. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  259. package/lib/module/notifyAppReadyInsights.js +0 -128
  260. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  261. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  262. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  263. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  264. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  265. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  266. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  267. package/lib/module/releaseCatalogCache.spec.js +0 -195
  268. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  269. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  270. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  271. package/lib/module/store.notification.spec.js +0 -77
  272. package/lib/module/store.notification.spec.js.map +0 -1
  273. package/lib/module/store.spec.js +0 -315
  274. package/lib/module/store.spec.js.map +0 -1
  275. package/lib/module/wrap.spec.js +0 -330
  276. package/lib/module/wrap.spec.js.map +0 -1
  277. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  278. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  279. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  280. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  281. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  282. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  283. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  284. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  285. package/src/catalogCacheNative.spec.ts +0 -63
  286. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -611
  287. package/src/httpClient.spec.ts +0 -132
  288. package/src/index.spec.ts +0 -337
  289. package/src/iosInterop.spec.ts +0 -30
  290. package/src/native.spec.ts +0 -945
  291. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  292. package/src/notifyAppReadyInsights.spec.ts +0 -243
  293. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  294. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  295. package/src/notifyAppReadyInsights.ts +0 -194
  296. package/src/releaseCatalogCache.spec.ts +0 -282
  297. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  298. package/src/store.notification.spec.ts +0 -92
  299. package/src/store.spec.ts +0 -372
  300. 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,18 +783,20 @@ 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
 
796
796
  type RawNotifyAppReadyResult = {
797
797
  status?: string;
798
798
  crashedBundleId?: string;
799
+ previousProcessExit?: string;
799
800
  fromReleaseId?: string;
800
801
  fromBundleId?: string;
801
802
  toReleaseId?: string;
@@ -892,9 +893,9 @@ const getNotifyAppReadyTransition = (
892
893
  return null;
893
894
  };
894
895
 
895
- const getNotifyAppReadyInsightsEvent = (
896
+ const getLaunchTransition = (
896
897
  result: RawNotifyAppReadyResult,
897
- ): NotifyAppReadyInsightsEvent | null => {
898
+ ): LaunchTransition | null => {
898
899
  const transition = getNotifyAppReadyTransition(result);
899
900
 
900
901
  if (!transition || !isPersistedUpdateStrategy(result.updateStrategy)) {
@@ -905,12 +906,8 @@ const getNotifyAppReadyInsightsEvent = (
905
906
  type: transition.status,
906
907
  fromBundleId: transition.fromBundleId,
907
908
  toBundleId: transition.toBundleId,
908
- ...(transition.fromReleaseId === null
909
- ? {}
910
- : { fromReleaseId: transition.fromReleaseId }),
911
- ...(transition.toReleaseId === null
912
- ? {}
913
- : { toReleaseId: transition.toReleaseId }),
909
+ fromReleaseId: transition.fromReleaseId,
910
+ toReleaseId: transition.toReleaseId,
914
911
  updateStrategy: result.updateStrategy,
915
912
  };
916
913
  };
@@ -939,14 +936,21 @@ const normalizeNotifyAppReadyResult = (
939
936
 
940
937
  export const readNotifyAppReady = (): {
941
938
  result: NotifyAppReadyResult;
942
- insightsEvent: NotifyAppReadyInsightsEvent | null;
939
+ transition: LaunchTransition | null;
940
+ /** Android 11+: why the previous main process exited, when native knows. */
941
+ previousProcessExit: string | null;
943
942
  pending: boolean;
944
943
  } => {
945
944
  const rawResult = readRawNotifyAppReadyResult();
945
+ const { previousProcessExit } = rawResult;
946
946
 
947
947
  return {
948
948
  result: normalizeNotifyAppReadyResult(rawResult),
949
- insightsEvent: getNotifyAppReadyInsightsEvent(rawResult),
949
+ transition: getLaunchTransition(rawResult),
950
+ previousProcessExit:
951
+ typeof previousProcessExit === "string" && previousProcessExit !== ""
952
+ ? previousProcessExit
953
+ : null,
950
954
  pending: rawResult.status === "PENDING",
951
955
  };
952
956
  };
@@ -0,0 +1,45 @@
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
+ getStorageItem,
12
+ setStorageItem,
13
+ } from "./native";
14
+ import { HOT_UPDATER_SDK_VERSION } from "./sdkVersion";
15
+
16
+ export type { PluginHostConfig } from "@hot-updater/protocol";
17
+
18
+ /** The app's plugin host: plugins read the native module and fetch the configured server. */
19
+ export const {
20
+ configurePlugins,
21
+ hasPluginHook,
22
+ buildPluginEvent,
23
+ dispatchPluginHook,
24
+ emitPluginHook,
25
+ } = createPluginHost({
26
+ fetch: (url, init) => fetch(url, init),
27
+ get platform() {
28
+ return Platform.OS === "android" ? "android" : "ios";
29
+ },
30
+ get isDebugBuild() {
31
+ return typeof __DEV__ !== "undefined" && __DEV__;
32
+ },
33
+ get sdkVersion() {
34
+ return HOT_UPDATER_SDK_VERSION;
35
+ },
36
+ getInstallId: () => getInstallId(),
37
+ getAppVersion: () => getAppVersion(),
38
+ getBundleId: () => getBundleId(),
39
+ getChannel: () => getChannel(),
40
+ getCohort: () => getCohort(),
41
+ getFingerprintHash: () => getFingerprintHash(),
42
+ getStorageItem: (key) => getStorageItem(key),
43
+ setStorageItem: (key, value) => setStorageItem(key, value),
44
+ now: () => Date.now(),
45
+ });
@@ -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.3";
1
+ export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.31";