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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/android/build.gradle +13 -2
  2. package/android/proguard-rules.pro +8 -2
  3. package/android/src/bridge/BridgeReactInstance.kt +38 -0
  4. package/android/src/bridgeless/BridgeReactInstance.kt +24 -0
  5. package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +94 -0
  6. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  7. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1064 -844
  8. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +50 -11
  9. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  10. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  11. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +46 -30
  12. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +75 -192
  13. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  14. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  15. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  16. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  17. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  18. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  19. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  20. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  21. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  22. package/android/src/newarch/HotUpdater.kt +7 -9
  23. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  24. package/android/src/newarch/JavaScriptExceptionHooks.kt +48 -0
  25. package/android/src/newarch/ReactIntegrationManager.kt +6 -32
  26. package/android/src/oldarch/HotUpdater.kt +7 -9
  27. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  28. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  29. package/android/src/oldarch/JavaScriptExceptionHooks.kt +71 -0
  30. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  31. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  32. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1032 -1116
  33. package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -54
  34. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  35. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  36. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  37. package/ios/HotUpdater/Internal/HotUpdater.mm +32 -67
  38. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +229 -73
  39. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  40. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  41. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  42. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  43. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  44. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  45. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  46. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  47. package/lib/commonjs/appReady.js +102 -0
  48. package/lib/commonjs/appReady.js.map +1 -0
  49. package/lib/commonjs/checkForUpdate.js +120 -68
  50. package/lib/commonjs/checkForUpdate.js.map +1 -1
  51. package/lib/commonjs/clientPlugin.js +13 -0
  52. package/lib/commonjs/clientPlugin.js.map +1 -0
  53. package/lib/commonjs/error.js +4 -13
  54. package/lib/commonjs/error.js.map +1 -1
  55. package/lib/commonjs/fetchJSON.js +16 -27
  56. package/lib/commonjs/fetchJSON.js.map +1 -1
  57. package/lib/commonjs/fetchUpdateResponse.js +60 -0
  58. package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
  59. package/lib/commonjs/httpClient.js +63 -94
  60. package/lib/commonjs/httpClient.js.map +1 -1
  61. package/lib/commonjs/index.js +71 -18
  62. package/lib/commonjs/index.js.map +1 -1
  63. package/lib/commonjs/native.js +74 -60
  64. package/lib/commonjs/native.js.map +1 -1
  65. package/lib/commonjs/pluginHost.js +45 -0
  66. package/lib/commonjs/pluginHost.js.map +1 -0
  67. package/lib/commonjs/releaseCatalogCache.js +36 -87
  68. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  69. package/lib/commonjs/sdkVersion.js +1 -1
  70. package/lib/commonjs/sdkVersion.js.map +1 -1
  71. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  72. package/lib/commonjs/store.js +3 -10
  73. package/lib/commonjs/store.js.map +1 -1
  74. package/lib/commonjs/updateError.js +145 -0
  75. package/lib/commonjs/updateError.js.map +1 -0
  76. package/lib/commonjs/wrap.js +43 -26
  77. package/lib/commonjs/wrap.js.map +1 -1
  78. package/lib/module/appReady.js +95 -0
  79. package/lib/module/appReady.js.map +1 -0
  80. package/lib/module/checkForUpdate.js +113 -63
  81. package/lib/module/checkForUpdate.js.map +1 -1
  82. package/lib/module/clientPlugin.js +4 -0
  83. package/lib/module/clientPlugin.js.map +1 -0
  84. package/lib/module/error.js +4 -13
  85. package/lib/module/error.js.map +1 -1
  86. package/lib/module/fetchJSON.js +14 -26
  87. package/lib/module/fetchJSON.js.map +1 -1
  88. package/lib/module/fetchUpdateResponse.js +55 -0
  89. package/lib/module/fetchUpdateResponse.js.map +1 -0
  90. package/lib/module/httpClient.js +62 -93
  91. package/lib/module/httpClient.js.map +1 -1
  92. package/lib/module/index.js +58 -20
  93. package/lib/module/index.js.map +1 -1
  94. package/lib/module/native.js +67 -53
  95. package/lib/module/native.js.map +1 -1
  96. package/lib/module/pluginHost.js +36 -0
  97. package/lib/module/pluginHost.js.map +1 -0
  98. package/lib/module/releaseCatalogCache.js +32 -82
  99. package/lib/module/releaseCatalogCache.js.map +1 -1
  100. package/lib/module/sdkVersion.js +1 -1
  101. package/lib/module/sdkVersion.js.map +1 -1
  102. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  103. package/lib/module/store.js +3 -10
  104. package/lib/module/store.js.map +1 -1
  105. package/lib/module/updateError.js +139 -0
  106. package/lib/module/updateError.js.map +1 -0
  107. package/lib/module/wrap.js +43 -26
  108. package/lib/module/wrap.js.map +1 -1
  109. package/lib/typescript/commonjs/appReady.d.ts +21 -0
  110. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  112. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  114. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/error.d.ts +4 -13
  116. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
  118. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
  120. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/httpClient.d.ts +4 -33
  122. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/index.d.ts +4 -8
  124. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/native.d.ts +29 -22
  126. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  128. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
  130. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  132. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +35 -47
  134. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  135. package/lib/typescript/commonjs/store.d.ts +1 -3
  136. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/types.d.ts +1 -1
  138. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  140. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  143. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  144. package/lib/typescript/module/appReady.d.ts +21 -0
  145. package/lib/typescript/module/appReady.d.ts.map +1 -0
  146. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  147. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  148. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  149. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  150. package/lib/typescript/module/error.d.ts +4 -13
  151. package/lib/typescript/module/error.d.ts.map +1 -1
  152. package/lib/typescript/module/fetchJSON.d.ts +5 -5
  153. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  154. package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
  155. package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
  156. package/lib/typescript/module/httpClient.d.ts +4 -33
  157. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  158. package/lib/typescript/module/index.d.ts +4 -8
  159. package/lib/typescript/module/index.d.ts.map +1 -1
  160. package/lib/typescript/module/native.d.ts +29 -22
  161. package/lib/typescript/module/native.d.ts.map +1 -1
  162. package/lib/typescript/module/pluginHost.d.ts +4 -0
  163. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  164. package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
  165. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  166. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  167. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  168. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +35 -47
  169. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  170. package/lib/typescript/module/store.d.ts +1 -3
  171. package/lib/typescript/module/store.d.ts.map +1 -1
  172. package/lib/typescript/module/types.d.ts +1 -1
  173. package/lib/typescript/module/types.d.ts.map +1 -1
  174. package/lib/typescript/module/updateError.d.ts +26 -0
  175. package/lib/typescript/module/updateError.d.ts.map +1 -0
  176. package/lib/typescript/module/wrap.d.ts.map +1 -1
  177. package/lib/typescript/module/wrap.types.d.ts +6 -7
  178. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  179. package/package.json +7 -4
  180. package/src/appReady.ts +141 -0
  181. package/src/checkForUpdate.ts +145 -86
  182. package/src/clientPlugin.ts +18 -0
  183. package/src/error.ts +4 -14
  184. package/src/fetchJSON.ts +14 -30
  185. package/src/fetchUpdateResponse.ts +60 -0
  186. package/src/httpClient.ts +88 -159
  187. package/src/index.ts +90 -24
  188. package/src/native.ts +98 -102
  189. package/src/pluginHost.ts +47 -0
  190. package/src/releaseCatalogCache.ts +43 -172
  191. package/src/sdkVersion.ts +1 -1
  192. package/src/specs/NativeHotUpdater.ts +34 -48
  193. package/src/store.ts +2 -23
  194. package/src/types.ts +1 -1
  195. package/src/updateError.ts +167 -0
  196. package/src/wrap.tsx +57 -26
  197. package/src/wrap.types.ts +6 -7
  198. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  199. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  200. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  201. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  202. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  203. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  204. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  205. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  206. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  207. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  208. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  209. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  210. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  211. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  212. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  213. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  214. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  215. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  216. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  217. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  218. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  219. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  220. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  221. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -616
  222. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  223. package/lib/commonjs/httpClient.spec.js +0 -102
  224. package/lib/commonjs/httpClient.spec.js.map +0 -1
  225. package/lib/commonjs/index.spec.js +0 -302
  226. package/lib/commonjs/index.spec.js.map +0 -1
  227. package/lib/commonjs/iosInterop.spec.js +0 -18
  228. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  229. package/lib/commonjs/native.spec.js +0 -883
  230. package/lib/commonjs/native.spec.js.map +0 -1
  231. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  232. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  233. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  234. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  235. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  236. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  237. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  238. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  239. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  240. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  241. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  242. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  243. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  244. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  245. package/lib/commonjs/store.notification.spec.js +0 -76
  246. package/lib/commonjs/store.notification.spec.js.map +0 -1
  247. package/lib/commonjs/store.spec.js +0 -316
  248. package/lib/commonjs/store.spec.js.map +0 -1
  249. package/lib/commonjs/wrap.spec.js +0 -329
  250. package/lib/commonjs/wrap.spec.js.map +0 -1
  251. package/lib/module/catalogCacheNative.spec.js +0 -31
  252. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  253. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -615
  254. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  255. package/lib/module/httpClient.spec.js +0 -102
  256. package/lib/module/httpClient.spec.js.map +0 -1
  257. package/lib/module/index.spec.js +0 -301
  258. package/lib/module/index.spec.js.map +0 -1
  259. package/lib/module/iosInterop.spec.js +0 -18
  260. package/lib/module/iosInterop.spec.js.map +0 -1
  261. package/lib/module/native.spec.js +0 -882
  262. package/lib/module/native.spec.js.map +0 -1
  263. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  264. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  265. package/lib/module/notifyAppReadyInsights.js +0 -128
  266. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  267. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  268. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  269. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  270. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  271. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  272. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  273. package/lib/module/releaseCatalogCache.spec.js +0 -195
  274. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  275. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  276. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  277. package/lib/module/store.notification.spec.js +0 -77
  278. package/lib/module/store.notification.spec.js.map +0 -1
  279. package/lib/module/store.spec.js +0 -315
  280. package/lib/module/store.spec.js.map +0 -1
  281. package/lib/module/wrap.spec.js +0 -330
  282. package/lib/module/wrap.spec.js.map +0 -1
  283. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  284. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  285. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  286. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  287. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  288. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  289. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  290. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  291. package/src/catalogCacheNative.spec.ts +0 -63
  292. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -612
  293. package/src/httpClient.spec.ts +0 -132
  294. package/src/index.spec.ts +0 -337
  295. package/src/iosInterop.spec.ts +0 -30
  296. package/src/native.spec.ts +0 -945
  297. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  298. package/src/notifyAppReadyInsights.spec.ts +0 -243
  299. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  300. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  301. package/src/notifyAppReadyInsights.ts +0 -194
  302. package/src/releaseCatalogCache.spec.ts +0 -282
  303. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  304. package/src/store.notification.spec.ts +0 -92
  305. package/src/store.spec.ts +0 -372
  306. package/src/wrap.spec.ts +0 -406
@@ -1,612 +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("reports same-file selection as UNCHANGED with the selected release identity", 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: null,
243
- updateStrategy: null,
244
- fromReleaseId: MINIMUM_RELEASE_ID,
245
- toBundleId: TARGET_BUNDLE_ID,
246
- toReleaseId: RELEASE_ID,
247
- type: "UNCHANGED",
248
- }),
249
- );
250
- });
251
-
252
- it("selects a lower-id Release when explicitly switching scopes", async () => {
253
- const betaChannel = "beta";
254
- const betaScopeKey = createReleaseCatalogScopeKey({
255
- channelKey: encodeChannelKey(betaChannel),
256
- platform: "ios",
257
- strategy: "APP_VERSION",
258
- });
259
- const active: PersistedSelectionReceipt = {
260
- catalogId: CATALOG_ID,
261
- bundleId: CURRENT_BUNDLE_ID,
262
- catalogHash: `sha256:${"b".repeat(64)}`,
263
- channel: CHANNEL,
264
- generation: 1,
265
- kind: "BUNDLE",
266
- releaseId: ACTIVE_RELEASE_ID,
267
- scopeKey: SCOPE_KEY,
268
- selectionContextHash: "old-context",
269
- };
270
- mocks.getActiveUpdateState.mockReturnValueOnce({
271
- activeSelection: active,
272
- highestSeenCatalogs: {},
273
- stableSelection: active,
274
- verificationPending: false,
275
- });
276
- const betaCatalog = createCatalog({ scopeKey: betaScopeKey });
277
- const { checkForUpdate } = await import("./checkForUpdate");
278
- const { client } = createClient(betaCatalog);
279
-
280
- const result = await checkForUpdate({
281
- channel: betaChannel,
282
- client,
283
- updateStrategy: "appVersion",
284
- });
285
-
286
- expect(result).toMatchObject({
287
- id: RELEASE_ID,
288
- releaseId: RELEASE_ID,
289
- status: "UPDATE",
290
- transitionKind: "INSTALL",
291
- });
292
- });
293
-
294
- it("refreshes the same Release receipt when catalog provenance changes", async () => {
295
- const active: PersistedSelectionReceipt = {
296
- catalogId: CATALOG_ID,
297
- bundleId: TARGET_BUNDLE_ID,
298
- catalogHash: `sha256:${"b".repeat(64)}`,
299
- channel: CHANNEL,
300
- generation: 1,
301
- kind: "BUNDLE",
302
- releaseId: RELEASE_ID,
303
- scopeKey: SCOPE_KEY,
304
- selectionContextHash: "old-context",
305
- };
306
- mocks.getBundleId.mockReturnValueOnce(TARGET_BUNDLE_ID);
307
- mocks.getActiveUpdateState.mockReturnValueOnce({
308
- activeSelection: active,
309
- highestSeenCatalogs: {},
310
- stableSelection: active,
311
- verificationPending: false,
312
- });
313
- const { checkForUpdate } = await import("./checkForUpdate");
314
- const { client, resolveArtifact } = createClient();
315
-
316
- const result = await checkForUpdate({
317
- client,
318
- updateStrategy: "appVersion",
319
- });
320
-
321
- expect(result).toMatchObject({
322
- id: RELEASE_ID,
323
- releaseId: RELEASE_ID,
324
- transitionKind: "ADOPT_RELEASE",
325
- });
326
- await expect(result?.updateBundle()).resolves.toBe(true);
327
- expect(resolveArtifact).not.toHaveBeenCalled();
328
- expect(mocks.updateBundle).not.toHaveBeenCalled();
329
- expect(mocks.commitReleaseSelection).toHaveBeenCalledWith({
330
- guard: expect.objectContaining({
331
- catalogHash: CATALOG_HASH,
332
- generation: 2,
333
- scopeKey: SCOPE_KEY,
334
- }),
335
- selection: expect.objectContaining({
336
- bundleId: TARGET_BUNDLE_ID,
337
- catalogHash: CATALOG_HASH,
338
- generation: 2,
339
- releaseId: RELEASE_ID,
340
- }),
341
- });
342
- });
343
-
344
- it("rejects a slow artifact completion after a newer catalog wins", async () => {
345
- mocks.isReleaseSelectionCurrent
346
- .mockReturnValueOnce(true)
347
- .mockReturnValueOnce(false);
348
- const { StaleReleaseCatalogError } = await import("./error");
349
- const { checkForUpdate } = await import("./checkForUpdate");
350
- const { client } = createClient();
351
- const result = await checkForUpdate({
352
- client,
353
- updateStrategy: "appVersion",
354
- });
355
-
356
- await expect(result?.updateBundle()).rejects.toBeInstanceOf(
357
- StaleReleaseCatalogError,
358
- );
359
- expect(mocks.updateBundle).not.toHaveBeenCalled();
360
- });
361
-
362
- it("installs an older enabled Release as a forced rollback", async () => {
363
- const active: PersistedSelectionReceipt = {
364
- catalogId: CATALOG_ID,
365
- bundleId: CURRENT_BUNDLE_ID,
366
- catalogHash: `sha256:${"b".repeat(64)}`,
367
- channel: CHANNEL,
368
- generation: 1,
369
- kind: "BUNDLE",
370
- releaseId: ACTIVE_RELEASE_ID,
371
- scopeKey: SCOPE_KEY,
372
- selectionContextHash: "old-context",
373
- };
374
- mocks.getActiveUpdateState.mockReturnValueOnce({
375
- activeSelection: active,
376
- highestSeenCatalogs: {},
377
- stableSelection: active,
378
- verificationPending: false,
379
- });
380
- const catalog = createCatalog({
381
- rollbackReleases: [
382
- {
383
- bundleId: TARGET_BUNDLE_ID,
384
- kind: "BUNDLE",
385
- message: "Previous Release",
386
- releaseId: RELEASE_ID,
387
- rolloutCohortCount: 0,
388
- shouldForceUpdate: false,
389
- targetCohorts: [],
390
- },
391
- ],
392
- });
393
- const { checkForUpdate } = await import("./checkForUpdate");
394
- const { client } = createClient(catalog);
395
-
396
- const result = await checkForUpdate({
397
- client,
398
- updateStrategy: "appVersion",
399
- });
400
-
401
- expect(result).toMatchObject({
402
- id: RELEASE_ID,
403
- releaseId: RELEASE_ID,
404
- shouldForceUpdate: true,
405
- status: "ROLLBACK",
406
- transitionKind: "INSTALL",
407
- });
408
- await expect(result?.updateBundle()).resolves.toBe(true);
409
- expect(mocks.updateBundle).toHaveBeenCalledWith(
410
- expect.objectContaining({
411
- bundleId: TARGET_BUNDLE_ID,
412
- status: "ROLLBACK",
413
- }),
414
- );
415
- });
416
-
417
- it("uses current Bundle identity when migrating an unauthenticated receipt", async () => {
418
- const migratedCurrentBundleId = "00000000-0000-7001-8000-000000000003";
419
- const active: PersistedSelectionReceipt = {
420
- catalogId: null,
421
- bundleId: migratedCurrentBundleId,
422
- catalogHash: null,
423
- channel: CHANNEL,
424
- generation: null,
425
- kind: "BUNDLE",
426
- releaseId: null,
427
- scopeKey: null,
428
- selectionContextHash: null,
429
- };
430
- mocks.getBundleId.mockReturnValueOnce(migratedCurrentBundleId);
431
- mocks.getActiveUpdateState.mockReturnValueOnce({
432
- activeSelection: active,
433
- highestSeenCatalogs: {},
434
- stableSelection: active,
435
- verificationPending: false,
436
- });
437
- const catalog = createCatalog({
438
- releases: [],
439
- rollbackReleases: [
440
- {
441
- ...createCatalog().releases[0]!,
442
- rolloutCohortCount: 0,
443
- },
444
- ],
445
- });
446
- const { checkForUpdate } = await import("./checkForUpdate");
447
- const { client } = createClient(catalog);
448
-
449
- const result = await checkForUpdate({
450
- client,
451
- updateStrategy: "appVersion",
452
- });
453
-
454
- expect(result).toMatchObject({
455
- id: RELEASE_ID,
456
- shouldForceUpdate: true,
457
- status: "ROLLBACK",
458
- transitionKind: "INSTALL",
459
- });
460
- });
461
-
462
- it.each(["EMBEDDED", "BUILTIN"] as const)(
463
- "returns a forced %s rollback with its console ID or built-in fallback",
464
- async (kind) => {
465
- const active: PersistedSelectionReceipt = {
466
- catalogId: CATALOG_ID,
467
- bundleId: CURRENT_BUNDLE_ID,
468
- catalogHash: `sha256:${"b".repeat(64)}`,
469
- channel: CHANNEL,
470
- generation: 1,
471
- kind: "BUNDLE",
472
- releaseId: MINIMUM_RELEASE_ID,
473
- scopeKey: SCOPE_KEY,
474
- selectionContextHash: "old-context",
475
- };
476
- mocks.getActiveUpdateState.mockReturnValueOnce({
477
- activeSelection: active,
478
- highestSeenCatalogs: {},
479
- stableSelection: active,
480
- verificationPending: false,
481
- });
482
- const { checkForUpdate } = await import("./checkForUpdate");
483
- const { client, resolveArtifact } = createClient(
484
- createCatalog({
485
- releases:
486
- kind === "EMBEDDED"
487
- ? [{ ...createCatalog().releases[0]!, kind, bundleId: null }]
488
- : [],
489
- rollbackReleases: [],
490
- }),
491
- );
492
-
493
- const result = await checkForUpdate({
494
- client,
495
- updateStrategy: "appVersion",
496
- });
497
-
498
- expect(result).toMatchObject({
499
- id: kind === "EMBEDDED" ? RELEASE_ID : MINIMUM_RELEASE_ID,
500
- releaseId: kind === "EMBEDDED" ? RELEASE_ID : null,
501
- shouldForceUpdate: true,
502
- status: "ROLLBACK",
503
- transitionKind: kind === "EMBEDDED" ? "USE_EMBEDDED" : "USE_BUILTIN",
504
- });
505
- await expect(result?.updateBundle()).resolves.toBe(true);
506
- expect(mocks.commitReleaseSelection).toHaveBeenCalledWith(
507
- expect.objectContaining({
508
- selection: expect.objectContaining({
509
- bundleId: MINIMUM_RELEASE_ID,
510
- kind,
511
- releaseId: kind === "EMBEDDED" ? RELEASE_ID : null,
512
- }),
513
- }),
514
- );
515
- expect(resolveArtifact).not.toHaveBeenCalled();
516
- expect(mocks.updateBundle).not.toHaveBeenCalled();
517
- },
518
- );
519
-
520
- it("does not surface a rollback when the app already uses built-in bytes", async () => {
521
- mocks.getBundleId.mockReturnValueOnce(MINIMUM_RELEASE_ID);
522
- const { checkForUpdate } = await import("./checkForUpdate");
523
- const { client } = createClient(
524
- createCatalog({ releases: [], rollbackReleases: [] }),
525
- );
526
-
527
- await expect(
528
- checkForUpdate({ client, updateStrategy: "appVersion" }),
529
- ).resolves.toBeNull();
530
- expect(mocks.commitReleaseSelection).not.toHaveBeenCalled();
531
- });
532
-
533
- it("reports a rejected generation without selecting or resolving artifacts", async () => {
534
- mocks.acceptReleaseCatalog.mockReturnValueOnce(false);
535
- const onError = vi.fn();
536
- const { checkForUpdate } = await import("./checkForUpdate");
537
- const { client, resolveArtifact } = createClient();
538
-
539
- await expect(
540
- checkForUpdate({ client, onError, updateStrategy: "appVersion" }),
541
- ).resolves.toBeNull();
542
-
543
- expect(onError).toHaveBeenCalledWith(
544
- expect.objectContaining({
545
- message: "Rejected a stale or inconsistent Release catalog",
546
- }),
547
- );
548
- expect(resolveArtifact).not.toHaveBeenCalled();
549
- });
550
-
551
- it("rejects mismatched catalog scope before native catalog state mutates", async () => {
552
- const wrongScope = createReleaseCatalogScopeKey({
553
- channelKey: encodeChannelKey("beta"),
554
- platform: "ios",
555
- strategy: "APP_VERSION",
556
- });
557
- const onError = vi.fn();
558
- const { checkForUpdate } = await import("./checkForUpdate");
559
- const { client } = createClient(createCatalog({ scopeKey: wrongScope }));
560
-
561
- await expect(
562
- checkForUpdate({ client, onError, updateStrategy: "appVersion" }),
563
- ).resolves.toBeNull();
564
-
565
- expect(onError).toHaveBeenCalledWith(
566
- expect.objectContaining({
567
- message: "Received an invalid Release catalog",
568
- }),
569
- );
570
- expect(mocks.acceptReleaseCatalog).not.toHaveBeenCalled();
571
- expect(mocks.commitReleaseSelection).not.toHaveBeenCalled();
572
- expect(mocks.updateBundle).not.toHaveBeenCalled();
573
- });
574
-
575
- it("rejects an unexpected Catalog identity before accepting catalog state", async () => {
576
- const active: PersistedSelectionReceipt = {
577
- catalogId: "existing-project",
578
- bundleId: CURRENT_BUNDLE_ID,
579
- catalogHash: `sha256:${"b".repeat(64)}`,
580
- channel: CHANNEL,
581
- generation: 1,
582
- kind: "BUNDLE",
583
- releaseId: ACTIVE_RELEASE_ID,
584
- scopeKey: createReleaseCatalogScopeKey({
585
- channelKey: encodeChannelKey(CHANNEL),
586
- platform: "ios",
587
- strategy: "APP_VERSION",
588
- }),
589
- selectionContextHash: "old-context",
590
- };
591
- mocks.getActiveUpdateState.mockReturnValueOnce({
592
- activeSelection: active,
593
- highestSeenCatalogs: {},
594
- stableSelection: active,
595
- verificationPending: false,
596
- });
597
- const onError = vi.fn();
598
- const { checkForUpdate } = await import("./checkForUpdate");
599
- const { client } = createClient();
600
-
601
- await expect(
602
- checkForUpdate({ client, onError, updateStrategy: "appVersion" }),
603
- ).resolves.toBeNull();
604
-
605
- expect(onError).toHaveBeenCalledWith(
606
- expect.objectContaining({
607
- message: "Release transition rejected: UNSOLICITED_SCOPE",
608
- }),
609
- );
610
- expect(mocks.acceptReleaseCatalog).not.toHaveBeenCalled();
611
- });
612
- });