@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
@@ -1,611 +0,0 @@
1
- import {
2
- createReleaseCatalogScopeKey,
3
- encodeChannelKey,
4
- type PersistedSelectionReceipt,
5
- type ReleaseCatalog,
6
- } from "@hot-updater/core";
7
- import { beforeEach, describe, expect, it, vi } from "vitest";
8
-
9
- import type { HotUpdaterHttpClient } from "./httpClient";
10
-
11
- const MINIMUM_RELEASE_ID = "00000000-0000-7000-8000-000000000001";
12
- const RELEASE_ID = "00000000-0000-7000-8000-000000000002";
13
- const ACTIVE_RELEASE_ID = "00000000-0000-7000-8000-000000000003";
14
- const CURRENT_BUNDLE_ID = "00000000-0000-7001-8000-000000000001";
15
- const TARGET_BUNDLE_ID = "00000000-0000-7001-8000-000000000002";
16
- const CATALOG_ID = "project-a";
17
- const CHANNEL = "production";
18
- const CATALOG_HASH = `sha256:${"a".repeat(64)}`;
19
- const SCOPE_KEY = createReleaseCatalogScopeKey({
20
- channelKey: encodeChannelKey(CHANNEL),
21
- platform: "ios",
22
- strategy: "APP_VERSION",
23
- });
24
-
25
- const mocks = vi.hoisted(() => ({
26
- addListener: vi.fn(() => () => {}),
27
- acceptReleaseCatalog: vi.fn(() => true),
28
- commitReleaseSelection: vi.fn(async () => true),
29
- getActiveUpdateState: vi.fn(() => ({
30
- activeSelection: null as PersistedSelectionReceipt | null,
31
- highestSeenCatalogs: {},
32
- stableSelection: null as PersistedSelectionReceipt | null,
33
- verificationPending: false,
34
- })),
35
- getAppVersion: vi.fn(() => "1.2"),
36
- getBundleId: vi.fn(() => CURRENT_BUNDLE_ID),
37
- getChannel: vi.fn(() => CHANNEL),
38
- getCohort: vi.fn(() => "123"),
39
- getCrashHistory: vi.fn(() => []),
40
- getDefaultChannel: vi.fn(() => CHANNEL),
41
- getFingerprintHash: vi.fn(() => null),
42
- getInstallId: vi.fn(() => "install-id"),
43
- getMinBundleId: vi.fn(() => MINIMUM_RELEASE_ID),
44
- getPersistedUserIdentity: vi.fn(() => ({})),
45
- isChannelSwitched: vi.fn(() => false),
46
- isReleaseSelectionCurrent: vi.fn(() => true),
47
- resetChannel: vi.fn(),
48
- updateBundle: vi.fn(async () => true),
49
- }));
50
-
51
- vi.mock("react-native", () => ({ Platform: { OS: "ios" } }));
52
-
53
- vi.mock("./native", () => mocks);
54
-
55
- const createCatalog = (
56
- overrides: Partial<ReleaseCatalog> = {},
57
- ): ReleaseCatalog => ({
58
- catalogId: CATALOG_ID,
59
- catalogHash: CATALOG_HASH,
60
- fallbackPolicy: "BUILTIN_IF_ACTIVE_INELIGIBLE",
61
- generation: 2,
62
- releases: [
63
- {
64
- bundleId: TARGET_BUNDLE_ID,
65
- kind: "BUNDLE",
66
- message: "Release two",
67
- releaseId: RELEASE_ID,
68
- rolloutCohortCount: 1000,
69
- shouldForceUpdate: false,
70
- targetCohorts: [],
71
- },
72
- ],
73
- schemaVersion: 1,
74
- scopeKey: SCOPE_KEY,
75
- ...overrides,
76
- });
77
-
78
- const createClient = (catalog = createCatalog()) => {
79
- const fetchReleaseCatalog = vi.fn(async () => catalog);
80
- const resolveArtifact = vi.fn(async () => ({
81
- fileHash: "bundle-hash",
82
- fileUrl: "https://updates.example.com/bundle.zip",
83
- }));
84
- const sendInsightsEvent = vi.fn(async () => undefined);
85
- const session = {
86
- fetchReleaseCatalog,
87
- resolveArtifact,
88
- sendInsightsEvent,
89
- };
90
- const client: HotUpdaterHttpClient = {
91
- createSession: vi.fn(async () => session),
92
- };
93
- return { client, fetchReleaseCatalog, resolveArtifact, sendInsightsEvent };
94
- };
95
-
96
- describe("checkForUpdate Release catalog protocol", () => {
97
- beforeEach(() => {
98
- vi.clearAllMocks();
99
- vi.stubGlobal("__DEV__", false);
100
- mocks.acceptReleaseCatalog.mockReturnValue(true);
101
- mocks.getActiveUpdateState.mockReturnValue({
102
- activeSelection: null,
103
- highestSeenCatalogs: {},
104
- stableSelection: null,
105
- verificationPending: false,
106
- });
107
- mocks.isReleaseSelectionCurrent.mockReturnValue(true);
108
- mocks.commitReleaseSelection.mockResolvedValue(true);
109
- mocks.updateBundle.mockResolvedValue(true);
110
- });
111
-
112
- it("selects locally and defers Bundle artifact resolution until install", async () => {
113
- const { checkForUpdate } = await import("./checkForUpdate");
114
- const { client, fetchReleaseCatalog, resolveArtifact } = createClient();
115
-
116
- const result = await checkForUpdate({
117
- client,
118
- updateStrategy: "appVersion",
119
- });
120
-
121
- expect(result).toMatchObject({
122
- id: RELEASE_ID,
123
- releaseId: RELEASE_ID,
124
- transitionKind: "INSTALL",
125
- });
126
- expect(fetchReleaseCatalog).toHaveBeenCalledWith({
127
- appVersion: "1.2.0",
128
- channel: CHANNEL,
129
- fingerprintHash: null,
130
- platform: "ios",
131
- requestHeaders: undefined,
132
- requestTimeout: undefined,
133
- updateStrategy: "appVersion",
134
- });
135
- expect(resolveArtifact).not.toHaveBeenCalled();
136
-
137
- await expect(result?.updateBundle()).resolves.toBe(true);
138
-
139
- expect(resolveArtifact).toHaveBeenCalledWith({
140
- currentBundleId: CURRENT_BUNDLE_ID,
141
- requestHeaders: undefined,
142
- requestTimeout: undefined,
143
- targetBundleId: TARGET_BUNDLE_ID,
144
- });
145
- expect(mocks.updateBundle).toHaveBeenCalledWith(
146
- expect.objectContaining({
147
- bundleId: TARGET_BUNDLE_ID,
148
- selection: expect.objectContaining({
149
- catalogId: CATALOG_ID,
150
- releaseId: RELEASE_ID,
151
- scopeKey: SCOPE_KEY,
152
- }),
153
- }),
154
- );
155
- });
156
-
157
- it("adopts a newer Release for the same Bundle without resolving bytes", async () => {
158
- const active: PersistedSelectionReceipt = {
159
- catalogId: CATALOG_ID,
160
- bundleId: TARGET_BUNDLE_ID,
161
- catalogHash: `sha256:${"b".repeat(64)}`,
162
- channel: CHANNEL,
163
- generation: 1,
164
- kind: "BUNDLE",
165
- releaseId: MINIMUM_RELEASE_ID,
166
- scopeKey: SCOPE_KEY,
167
- selectionContextHash: "old-context",
168
- };
169
- mocks.getBundleId.mockReturnValueOnce(TARGET_BUNDLE_ID);
170
- mocks.getActiveUpdateState.mockReturnValueOnce({
171
- activeSelection: active,
172
- highestSeenCatalogs: {},
173
- stableSelection: active,
174
- verificationPending: false,
175
- });
176
- const { checkForUpdate } = await import("./checkForUpdate");
177
- const forceCatalog = createCatalog({
178
- releases: [
179
- {
180
- ...createCatalog().releases[0]!,
181
- shouldForceUpdate: true,
182
- },
183
- ],
184
- });
185
- const { client, resolveArtifact, sendInsightsEvent } =
186
- createClient(forceCatalog);
187
-
188
- const result = await checkForUpdate({
189
- client,
190
- updateStrategy: "appVersion",
191
- });
192
-
193
- expect(result).toMatchObject({
194
- id: RELEASE_ID,
195
- shouldForceUpdate: false,
196
- transitionKind: "ADOPT_RELEASE",
197
- });
198
- await expect(result?.updateBundle()).resolves.toBe(true);
199
- expect(resolveArtifact).not.toHaveBeenCalled();
200
- expect(mocks.updateBundle).not.toHaveBeenCalled();
201
- expect(sendInsightsEvent).not.toHaveBeenCalled();
202
- expect(mocks.commitReleaseSelection).toHaveBeenCalledWith({
203
- guard: expect.objectContaining({ generation: 2, scopeKey: SCOPE_KEY }),
204
- selection: expect.objectContaining({
205
- bundleId: TARGET_BUNDLE_ID,
206
- releaseId: RELEASE_ID,
207
- }),
208
- });
209
- });
210
-
211
- it("sends RELEASE_ADOPTED only when insights is enabled", async () => {
212
- const active: PersistedSelectionReceipt = {
213
- catalogId: CATALOG_ID,
214
- bundleId: TARGET_BUNDLE_ID,
215
- catalogHash: `sha256:${"b".repeat(64)}`,
216
- channel: CHANNEL,
217
- generation: 1,
218
- kind: "BUNDLE",
219
- releaseId: MINIMUM_RELEASE_ID,
220
- scopeKey: SCOPE_KEY,
221
- selectionContextHash: "old-context",
222
- };
223
- mocks.getBundleId.mockReturnValueOnce(TARGET_BUNDLE_ID);
224
- mocks.getActiveUpdateState.mockReturnValueOnce({
225
- activeSelection: active,
226
- highestSeenCatalogs: {},
227
- stableSelection: active,
228
- verificationPending: false,
229
- });
230
- const { checkForUpdate } = await import("./checkForUpdate");
231
- const { client, sendInsightsEvent } = createClient();
232
-
233
- const result = await checkForUpdate({
234
- insights: true,
235
- client,
236
- updateStrategy: "appVersion",
237
- });
238
- await result?.updateBundle();
239
-
240
- expect(sendInsightsEvent).toHaveBeenCalledWith(
241
- expect.objectContaining({
242
- fromBundleId: TARGET_BUNDLE_ID,
243
- fromReleaseId: MINIMUM_RELEASE_ID,
244
- toBundleId: TARGET_BUNDLE_ID,
245
- toReleaseId: RELEASE_ID,
246
- type: "RELEASE_ADOPTED",
247
- }),
248
- );
249
- });
250
-
251
- it("selects a lower-id Release when explicitly switching scopes", async () => {
252
- const betaChannel = "beta";
253
- const betaScopeKey = createReleaseCatalogScopeKey({
254
- channelKey: encodeChannelKey(betaChannel),
255
- platform: "ios",
256
- strategy: "APP_VERSION",
257
- });
258
- const active: PersistedSelectionReceipt = {
259
- catalogId: CATALOG_ID,
260
- bundleId: CURRENT_BUNDLE_ID,
261
- catalogHash: `sha256:${"b".repeat(64)}`,
262
- channel: CHANNEL,
263
- generation: 1,
264
- kind: "BUNDLE",
265
- releaseId: ACTIVE_RELEASE_ID,
266
- scopeKey: SCOPE_KEY,
267
- selectionContextHash: "old-context",
268
- };
269
- mocks.getActiveUpdateState.mockReturnValueOnce({
270
- activeSelection: active,
271
- highestSeenCatalogs: {},
272
- stableSelection: active,
273
- verificationPending: false,
274
- });
275
- const betaCatalog = createCatalog({ scopeKey: betaScopeKey });
276
- const { checkForUpdate } = await import("./checkForUpdate");
277
- const { client } = createClient(betaCatalog);
278
-
279
- const result = await checkForUpdate({
280
- channel: betaChannel,
281
- client,
282
- updateStrategy: "appVersion",
283
- });
284
-
285
- expect(result).toMatchObject({
286
- id: RELEASE_ID,
287
- releaseId: RELEASE_ID,
288
- status: "UPDATE",
289
- transitionKind: "INSTALL",
290
- });
291
- });
292
-
293
- it("refreshes the same Release receipt when catalog provenance changes", async () => {
294
- const active: PersistedSelectionReceipt = {
295
- catalogId: CATALOG_ID,
296
- bundleId: TARGET_BUNDLE_ID,
297
- catalogHash: `sha256:${"b".repeat(64)}`,
298
- channel: CHANNEL,
299
- generation: 1,
300
- kind: "BUNDLE",
301
- releaseId: RELEASE_ID,
302
- scopeKey: SCOPE_KEY,
303
- selectionContextHash: "old-context",
304
- };
305
- mocks.getBundleId.mockReturnValueOnce(TARGET_BUNDLE_ID);
306
- mocks.getActiveUpdateState.mockReturnValueOnce({
307
- activeSelection: active,
308
- highestSeenCatalogs: {},
309
- stableSelection: active,
310
- verificationPending: false,
311
- });
312
- const { checkForUpdate } = await import("./checkForUpdate");
313
- const { client, resolveArtifact } = createClient();
314
-
315
- const result = await checkForUpdate({
316
- client,
317
- updateStrategy: "appVersion",
318
- });
319
-
320
- expect(result).toMatchObject({
321
- id: RELEASE_ID,
322
- releaseId: RELEASE_ID,
323
- transitionKind: "ADOPT_RELEASE",
324
- });
325
- await expect(result?.updateBundle()).resolves.toBe(true);
326
- expect(resolveArtifact).not.toHaveBeenCalled();
327
- expect(mocks.updateBundle).not.toHaveBeenCalled();
328
- expect(mocks.commitReleaseSelection).toHaveBeenCalledWith({
329
- guard: expect.objectContaining({
330
- catalogHash: CATALOG_HASH,
331
- generation: 2,
332
- scopeKey: SCOPE_KEY,
333
- }),
334
- selection: expect.objectContaining({
335
- bundleId: TARGET_BUNDLE_ID,
336
- catalogHash: CATALOG_HASH,
337
- generation: 2,
338
- releaseId: RELEASE_ID,
339
- }),
340
- });
341
- });
342
-
343
- it("rejects a slow artifact completion after a newer catalog wins", async () => {
344
- mocks.isReleaseSelectionCurrent
345
- .mockReturnValueOnce(true)
346
- .mockReturnValueOnce(false);
347
- const { StaleReleaseCatalogError } = await import("./error");
348
- const { checkForUpdate } = await import("./checkForUpdate");
349
- const { client } = createClient();
350
- const result = await checkForUpdate({
351
- client,
352
- updateStrategy: "appVersion",
353
- });
354
-
355
- await expect(result?.updateBundle()).rejects.toBeInstanceOf(
356
- StaleReleaseCatalogError,
357
- );
358
- expect(mocks.updateBundle).not.toHaveBeenCalled();
359
- });
360
-
361
- it("installs an older enabled Release as a forced rollback", async () => {
362
- const active: PersistedSelectionReceipt = {
363
- catalogId: CATALOG_ID,
364
- bundleId: CURRENT_BUNDLE_ID,
365
- catalogHash: `sha256:${"b".repeat(64)}`,
366
- channel: CHANNEL,
367
- generation: 1,
368
- kind: "BUNDLE",
369
- releaseId: ACTIVE_RELEASE_ID,
370
- scopeKey: SCOPE_KEY,
371
- selectionContextHash: "old-context",
372
- };
373
- mocks.getActiveUpdateState.mockReturnValueOnce({
374
- activeSelection: active,
375
- highestSeenCatalogs: {},
376
- stableSelection: active,
377
- verificationPending: false,
378
- });
379
- const catalog = createCatalog({
380
- rollbackReleases: [
381
- {
382
- bundleId: TARGET_BUNDLE_ID,
383
- kind: "BUNDLE",
384
- message: "Previous Release",
385
- releaseId: RELEASE_ID,
386
- rolloutCohortCount: 0,
387
- shouldForceUpdate: false,
388
- targetCohorts: [],
389
- },
390
- ],
391
- });
392
- const { checkForUpdate } = await import("./checkForUpdate");
393
- const { client } = createClient(catalog);
394
-
395
- const result = await checkForUpdate({
396
- client,
397
- updateStrategy: "appVersion",
398
- });
399
-
400
- expect(result).toMatchObject({
401
- id: RELEASE_ID,
402
- releaseId: RELEASE_ID,
403
- shouldForceUpdate: true,
404
- status: "ROLLBACK",
405
- transitionKind: "INSTALL",
406
- });
407
- await expect(result?.updateBundle()).resolves.toBe(true);
408
- expect(mocks.updateBundle).toHaveBeenCalledWith(
409
- expect.objectContaining({
410
- bundleId: TARGET_BUNDLE_ID,
411
- status: "ROLLBACK",
412
- }),
413
- );
414
- });
415
-
416
- it("uses current Bundle identity when migrating an unauthenticated receipt", async () => {
417
- const migratedCurrentBundleId = "00000000-0000-7001-8000-000000000003";
418
- const active: PersistedSelectionReceipt = {
419
- catalogId: null,
420
- bundleId: migratedCurrentBundleId,
421
- catalogHash: null,
422
- channel: CHANNEL,
423
- generation: null,
424
- kind: "BUNDLE",
425
- releaseId: null,
426
- scopeKey: null,
427
- selectionContextHash: null,
428
- };
429
- mocks.getBundleId.mockReturnValueOnce(migratedCurrentBundleId);
430
- mocks.getActiveUpdateState.mockReturnValueOnce({
431
- activeSelection: active,
432
- highestSeenCatalogs: {},
433
- stableSelection: active,
434
- verificationPending: false,
435
- });
436
- const catalog = createCatalog({
437
- releases: [],
438
- rollbackReleases: [
439
- {
440
- ...createCatalog().releases[0]!,
441
- rolloutCohortCount: 0,
442
- },
443
- ],
444
- });
445
- const { checkForUpdate } = await import("./checkForUpdate");
446
- const { client } = createClient(catalog);
447
-
448
- const result = await checkForUpdate({
449
- client,
450
- updateStrategy: "appVersion",
451
- });
452
-
453
- expect(result).toMatchObject({
454
- id: RELEASE_ID,
455
- shouldForceUpdate: true,
456
- status: "ROLLBACK",
457
- transitionKind: "INSTALL",
458
- });
459
- });
460
-
461
- it.each(["EMBEDDED", "BUILTIN"] as const)(
462
- "returns a forced %s rollback with its console ID or built-in fallback",
463
- async (kind) => {
464
- const active: PersistedSelectionReceipt = {
465
- catalogId: CATALOG_ID,
466
- bundleId: CURRENT_BUNDLE_ID,
467
- catalogHash: `sha256:${"b".repeat(64)}`,
468
- channel: CHANNEL,
469
- generation: 1,
470
- kind: "BUNDLE",
471
- releaseId: MINIMUM_RELEASE_ID,
472
- scopeKey: SCOPE_KEY,
473
- selectionContextHash: "old-context",
474
- };
475
- mocks.getActiveUpdateState.mockReturnValueOnce({
476
- activeSelection: active,
477
- highestSeenCatalogs: {},
478
- stableSelection: active,
479
- verificationPending: false,
480
- });
481
- const { checkForUpdate } = await import("./checkForUpdate");
482
- const { client, resolveArtifact } = createClient(
483
- createCatalog({
484
- releases:
485
- kind === "EMBEDDED"
486
- ? [{ ...createCatalog().releases[0]!, kind, bundleId: null }]
487
- : [],
488
- rollbackReleases: [],
489
- }),
490
- );
491
-
492
- const result = await checkForUpdate({
493
- client,
494
- updateStrategy: "appVersion",
495
- });
496
-
497
- expect(result).toMatchObject({
498
- id: kind === "EMBEDDED" ? RELEASE_ID : MINIMUM_RELEASE_ID,
499
- releaseId: kind === "EMBEDDED" ? RELEASE_ID : null,
500
- shouldForceUpdate: true,
501
- status: "ROLLBACK",
502
- transitionKind: kind === "EMBEDDED" ? "USE_EMBEDDED" : "USE_BUILTIN",
503
- });
504
- await expect(result?.updateBundle()).resolves.toBe(true);
505
- expect(mocks.commitReleaseSelection).toHaveBeenCalledWith(
506
- expect.objectContaining({
507
- selection: expect.objectContaining({
508
- bundleId: MINIMUM_RELEASE_ID,
509
- kind,
510
- releaseId: kind === "EMBEDDED" ? RELEASE_ID : null,
511
- }),
512
- }),
513
- );
514
- expect(resolveArtifact).not.toHaveBeenCalled();
515
- expect(mocks.updateBundle).not.toHaveBeenCalled();
516
- },
517
- );
518
-
519
- it("does not surface a rollback when the app already uses built-in bytes", async () => {
520
- mocks.getBundleId.mockReturnValueOnce(MINIMUM_RELEASE_ID);
521
- const { checkForUpdate } = await import("./checkForUpdate");
522
- const { client } = createClient(
523
- createCatalog({ releases: [], rollbackReleases: [] }),
524
- );
525
-
526
- await expect(
527
- checkForUpdate({ client, updateStrategy: "appVersion" }),
528
- ).resolves.toBeNull();
529
- expect(mocks.commitReleaseSelection).not.toHaveBeenCalled();
530
- });
531
-
532
- it("reports a rejected generation without selecting or resolving artifacts", async () => {
533
- mocks.acceptReleaseCatalog.mockReturnValueOnce(false);
534
- const onError = vi.fn();
535
- const { checkForUpdate } = await import("./checkForUpdate");
536
- const { client, resolveArtifact } = createClient();
537
-
538
- await expect(
539
- checkForUpdate({ client, onError, updateStrategy: "appVersion" }),
540
- ).resolves.toBeNull();
541
-
542
- expect(onError).toHaveBeenCalledWith(
543
- expect.objectContaining({
544
- message: "Rejected a stale or inconsistent Release catalog",
545
- }),
546
- );
547
- expect(resolveArtifact).not.toHaveBeenCalled();
548
- });
549
-
550
- it("rejects mismatched catalog scope before native catalog state mutates", async () => {
551
- const wrongScope = createReleaseCatalogScopeKey({
552
- channelKey: encodeChannelKey("beta"),
553
- platform: "ios",
554
- strategy: "APP_VERSION",
555
- });
556
- const onError = vi.fn();
557
- const { checkForUpdate } = await import("./checkForUpdate");
558
- const { client } = createClient(createCatalog({ scopeKey: wrongScope }));
559
-
560
- await expect(
561
- checkForUpdate({ client, onError, updateStrategy: "appVersion" }),
562
- ).resolves.toBeNull();
563
-
564
- expect(onError).toHaveBeenCalledWith(
565
- expect.objectContaining({
566
- message: "Received an invalid Release catalog",
567
- }),
568
- );
569
- expect(mocks.acceptReleaseCatalog).not.toHaveBeenCalled();
570
- expect(mocks.commitReleaseSelection).not.toHaveBeenCalled();
571
- expect(mocks.updateBundle).not.toHaveBeenCalled();
572
- });
573
-
574
- it("rejects an unexpected Catalog identity before accepting catalog state", async () => {
575
- const active: PersistedSelectionReceipt = {
576
- catalogId: "existing-project",
577
- bundleId: CURRENT_BUNDLE_ID,
578
- catalogHash: `sha256:${"b".repeat(64)}`,
579
- channel: CHANNEL,
580
- generation: 1,
581
- kind: "BUNDLE",
582
- releaseId: ACTIVE_RELEASE_ID,
583
- scopeKey: createReleaseCatalogScopeKey({
584
- channelKey: encodeChannelKey(CHANNEL),
585
- platform: "ios",
586
- strategy: "APP_VERSION",
587
- }),
588
- selectionContextHash: "old-context",
589
- };
590
- mocks.getActiveUpdateState.mockReturnValueOnce({
591
- activeSelection: active,
592
- highestSeenCatalogs: {},
593
- stableSelection: active,
594
- verificationPending: false,
595
- });
596
- const onError = vi.fn();
597
- const { checkForUpdate } = await import("./checkForUpdate");
598
- const { client } = createClient();
599
-
600
- await expect(
601
- checkForUpdate({ client, onError, updateStrategy: "appVersion" }),
602
- ).resolves.toBeNull();
603
-
604
- expect(onError).toHaveBeenCalledWith(
605
- expect.objectContaining({
606
- message: "Release transition rejected: UNSOLICITED_SCOPE",
607
- }),
608
- );
609
- expect(mocks.acceptReleaseCatalog).not.toHaveBeenCalled();
610
- });
611
- });