@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,882 +0,0 @@
1
- "use strict";
2
-
3
- import { INVALID_COHORT_ERROR_MESSAGE } from "@hot-updater/core";
4
- import { beforeEach, describe, expect, it, vi } from "vitest";
5
- const nativeModuleMock = vi.hoisted(() => {
6
- const getManifest = vi.fn();
7
- const getCrashHistory = vi.fn(() => []);
8
- return {
9
- getActiveUpdateState: vi.fn(),
10
- clearCrashHistory: vi.fn(() => true),
11
- getBaseURL: vi.fn(() => null),
12
- getBundleId: vi.fn(() => "bundle-id"),
13
- getCohort: vi.fn(() => "123"),
14
- getInstallId: vi.fn(() => "install-id"),
15
- getUserId: vi.fn(() => null),
16
- getUsername: vi.fn(() => null),
17
- getManifest,
18
- getCrashHistory,
19
- getConstants: vi.fn(() => ({
20
- APP_VERSION: null,
21
- CHANNEL: "production",
22
- DEFAULT_CHANNEL: "production",
23
- FINGERPRINT_HASH: null,
24
- MIN_BUNDLE_ID: "min-bundle-id"
25
- })),
26
- notifyAppReady: vi.fn(),
27
- reload: vi.fn(),
28
- resetChannel: vi.fn(),
29
- setCohort: vi.fn(),
30
- setUser: vi.fn(),
31
- setBundleURL: vi.fn(),
32
- switchChannel: vi.fn(),
33
- updateBundle: vi.fn()
34
- };
35
- });
36
- vi.mock("react-native", () => ({
37
- NativeEventEmitter: class {
38
- addListener() {
39
- return {
40
- remove: () => {}
41
- };
42
- }
43
- },
44
- Platform: {
45
- OS: "ios"
46
- }
47
- }));
48
- vi.mock("./specs/NativeHotUpdater", () => ({
49
- default: nativeModuleMock
50
- }));
51
- describe("notifyAppReady", () => {
52
- beforeEach(() => {
53
- vi.resetModules();
54
- nativeModuleMock.getActiveUpdateState.mockReset();
55
- nativeModuleMock.getActiveUpdateState.mockReturnValue({
56
- activeSelection: null,
57
- stableSelection: null,
58
- verificationPending: false,
59
- highestSeenCatalogs: {}
60
- });
61
- nativeModuleMock.notifyAppReady.mockReset();
62
- nativeModuleMock.getBaseURL.mockReset();
63
- nativeModuleMock.getBundleId.mockReset();
64
- nativeModuleMock.getCrashHistory.mockReset();
65
- nativeModuleMock.getConstants.mockReturnValue({
66
- APP_VERSION: null,
67
- CHANNEL: "production",
68
- DEFAULT_CHANNEL: "production",
69
- FINGERPRINT_HASH: null,
70
- MIN_BUNDLE_ID: "min-bundle-id"
71
- });
72
- nativeModuleMock.getBundleId.mockReturnValue("bundle-id");
73
- nativeModuleMock.getBaseURL.mockReturnValue(null);
74
- nativeModuleMock.getCrashHistory.mockReturnValue([]);
75
- nativeModuleMock.getCohort.mockReset();
76
- nativeModuleMock.getCohort.mockReturnValue("123");
77
- nativeModuleMock.getInstallId.mockReset();
78
- nativeModuleMock.getInstallId.mockReturnValue("install-id");
79
- nativeModuleMock.getManifest.mockReset();
80
- nativeModuleMock.getManifest.mockReturnValue({
81
- assets: {
82
- "index.android.bundle": {
83
- fileHash: "hash-123"
84
- }
85
- },
86
- bundleId: "bundle-id"
87
- });
88
- nativeModuleMock.resetChannel.mockReset();
89
- nativeModuleMock.setCohort.mockReset();
90
- nativeModuleMock.setUser.mockReset();
91
- nativeModuleMock.updateBundle.mockReset();
92
- });
93
- it("exposes active Release state without internal Catalog identity or history", async () => {
94
- const receipt = {
95
- bundleId: "bundle-123",
96
- releaseId: "release-123",
97
- kind: "BUNDLE",
98
- channel: "production",
99
- catalogId: "private-catalog",
100
- scopeKey: "private-scope",
101
- generation: 10,
102
- catalogHash: "private-hash",
103
- selectionContextHash: "private-context"
104
- };
105
- nativeModuleMock.getActiveUpdateState.mockReturnValue({
106
- activeSelection: receipt,
107
- stableSelection: receipt,
108
- verificationPending: true,
109
- highestSeenCatalogs: {
110
- "private-catalog|private-scope": {
111
- generation: 10,
112
- catalogHash: "private-hash"
113
- }
114
- }
115
- });
116
- const {
117
- getPublicActiveUpdateState
118
- } = await import("./native");
119
- const selection = {
120
- bundleId: "bundle-123",
121
- releaseId: "release-123",
122
- kind: "BUNDLE",
123
- channel: "production"
124
- };
125
- expect(getPublicActiveUpdateState()).toEqual({
126
- activeSelection: selection,
127
- stableSelection: selection,
128
- verificationPending: true
129
- });
130
- });
131
- it("tracks same-file promotions without changing artifact or crash identities", async () => {
132
- const state = {
133
- activeSelection: {
134
- kind: "BUNDLE",
135
- bundleId: "bundle-123",
136
- releaseId: "release-1"
137
- }
138
- };
139
- nativeModuleMock.getActiveUpdateState.mockImplementation(() => state);
140
- nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
141
- nativeModuleMock.getManifest.mockReturnValue({
142
- assets: {}
143
- });
144
- nativeModuleMock.getCrashHistory.mockReturnValue(["bundle-failed"]);
145
- const {
146
- getUpdateId,
147
- getBundleId,
148
- getManifest,
149
- getCrashHistory
150
- } = await import("./native");
151
- expect(getUpdateId()).toBe("release-1");
152
- state.activeSelection.releaseId = "release-2";
153
- expect(getUpdateId()).toBe("release-2");
154
- expect(getBundleId()).toBe("bundle-123");
155
- expect(getManifest().bundleId).toBe("bundle-123");
156
- expect(getCrashHistory()).toEqual(["bundle-failed"]);
157
- });
158
- it.each([[null, "min-bundle-id"], ["legacy-bundle", "legacy-bundle"]])("preserves the fallback ID for native bundle %s", async (bundleId, expected) => {
159
- nativeModuleMock.getBundleId.mockReturnValue(bundleId);
160
- const {
161
- getUpdateId
162
- } = await import("./native");
163
- expect(getUpdateId()).toBe(expected);
164
- });
165
- it("reports a staged update while the manifest still identifies the running file", async () => {
166
- const runningSelection = {
167
- kind: "BUNDLE",
168
- bundleId: "running-file",
169
- releaseId: "running-update"
170
- };
171
- const state = {
172
- activeSelection: runningSelection,
173
- stableSelection: runningSelection
174
- };
175
- nativeModuleMock.getActiveUpdateState.mockImplementation(() => state);
176
- nativeModuleMock.getBundleId.mockReturnValue("running-file");
177
- nativeModuleMock.getManifest.mockReturnValue({
178
- assets: {},
179
- bundleId: "running-file"
180
- });
181
- const {
182
- getUpdateId,
183
- getBundleId,
184
- getManifest
185
- } = await import("./native");
186
- expect(getUpdateId()).toBe("running-update");
187
- state.activeSelection = {
188
- kind: "BUNDLE",
189
- bundleId: "staged-file",
190
- releaseId: "staged-update"
191
- };
192
- expect(getUpdateId()).toBe("staged-update");
193
- expect(getBundleId()).toBe("running-file");
194
- expect(getManifest().bundleId).toBe("running-file");
195
- });
196
- it("reports an explicit built-in rollback by its update ID", async () => {
197
- nativeModuleMock.getActiveUpdateState.mockReturnValue(JSON.stringify({
198
- activeSelection: {
199
- kind: "EMBEDDED",
200
- releaseId: "rollback-1",
201
- bundleId: "min-bundle-id"
202
- }
203
- }));
204
- nativeModuleMock.getBundleId.mockReturnValue(null);
205
- const {
206
- getUpdateId,
207
- getBundleId
208
- } = await import("./native");
209
- expect(getUpdateId()).toBe("rollback-1");
210
- expect(getBundleId()).toBe("min-bundle-id");
211
- });
212
- it("normalizes legacy PROMOTED launch reports to UPDATE_APPLIED", async () => {
213
- nativeModuleMock.notifyAppReady.mockReturnValue(JSON.stringify({
214
- fromBundleId: "bundle-123",
215
- status: "PROMOTED",
216
- toBundleId: "bundle-456"
217
- }));
218
- const {
219
- notifyAppReady
220
- } = await import("./native");
221
- expect(notifyAppReady()).toEqual({
222
- fromBundleId: "bundle-123",
223
- status: "UPDATE_APPLIED",
224
- toBundleId: "bundle-456"
225
- });
226
- expect(nativeModuleMock.notifyAppReady).toHaveBeenCalledWith();
227
- });
228
- it("returns RECOVERED launch reports with directional ids", async () => {
229
- nativeModuleMock.notifyAppReady.mockReturnValue({
230
- fromBundleId: "bundle-123",
231
- status: "RECOVERED",
232
- toBundleId: "bundle-122",
233
- updateStrategy: "appVersion"
234
- });
235
- const {
236
- notifyAppReady,
237
- readNotifyAppReady
238
- } = await import("./native");
239
- expect(notifyAppReady()).toEqual({
240
- fromBundleId: "bundle-123",
241
- status: "RECOVERED",
242
- toBundleId: "bundle-122"
243
- });
244
- expect(readNotifyAppReady()).toEqual({
245
- insightsEvent: {
246
- fromBundleId: "bundle-123",
247
- toBundleId: "bundle-122",
248
- type: "RECOVERED",
249
- updateStrategy: "appVersion"
250
- },
251
- pending: false,
252
- result: {
253
- fromBundleId: "bundle-123",
254
- status: "RECOVERED",
255
- toBundleId: "bundle-122"
256
- }
257
- });
258
- });
259
- it("returns UNCHANGED for incomplete recovery payloads", async () => {
260
- nativeModuleMock.notifyAppReady.mockReturnValue({
261
- fromBundleId: "bundle-123",
262
- status: "RECOVERED",
263
- toBundleId: null,
264
- updateStrategy: "appVersion"
265
- });
266
- const {
267
- notifyAppReady,
268
- readNotifyAppReady
269
- } = await import("./native");
270
- expect(notifyAppReady()).toEqual({
271
- status: "UNCHANGED"
272
- });
273
- expect(readNotifyAppReady()).toEqual({
274
- insightsEvent: null,
275
- pending: false,
276
- result: {
277
- status: "UNCHANGED"
278
- }
279
- });
280
- });
281
- it("returns UNCHANGED when automatic insights metadata is incomplete", async () => {
282
- nativeModuleMock.notifyAppReady.mockReturnValue({
283
- status: "UPDATE_APPLIED"
284
- });
285
- const {
286
- notifyAppReady,
287
- readNotifyAppReady
288
- } = await import("./native");
289
- expect(notifyAppReady()).toEqual({
290
- status: "UNCHANGED"
291
- });
292
- expect(readNotifyAppReady()).toEqual({
293
- insightsEvent: null,
294
- pending: false,
295
- result: {
296
- status: "UNCHANGED"
297
- }
298
- });
299
- });
300
- it("normalizes malformed old-arch notifyAppReady payloads to UNCHANGED", async () => {
301
- nativeModuleMock.notifyAppReady.mockReturnValue("{");
302
- const {
303
- notifyAppReady,
304
- readNotifyAppReady
305
- } = await import("./native");
306
- expect(notifyAppReady()).toEqual({
307
- status: "UNCHANGED"
308
- });
309
- expect(readNotifyAppReady()).toEqual({
310
- insightsEvent: null,
311
- pending: false,
312
- result: {
313
- status: "UNCHANGED"
314
- }
315
- });
316
- });
317
- it("keeps the internal pending state out of the public result", async () => {
318
- nativeModuleMock.notifyAppReady.mockReturnValue({
319
- status: "PENDING"
320
- });
321
- const {
322
- notifyAppReady,
323
- readNotifyAppReady
324
- } = await import("./native");
325
- expect(notifyAppReady()).toEqual({
326
- status: "UNCHANGED"
327
- });
328
- expect(readNotifyAppReady()).toEqual({
329
- insightsEvent: null,
330
- pending: true,
331
- result: {
332
- status: "UNCHANGED"
333
- }
334
- });
335
- });
336
- it("returns the native bundle id when available", async () => {
337
- nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
338
- const {
339
- getBundleId
340
- } = await import("./native");
341
- expect(getBundleId()).toBe("bundle-123");
342
- });
343
- it("throws when native SDK does not expose getBundleId", async () => {
344
- const nativeModule = nativeModuleMock;
345
- const originalGetBundleId = nativeModule.getBundleId;
346
- nativeModule.getBundleId = null;
347
- try {
348
- const {
349
- getBundleId
350
- } = await import("./native");
351
- expect(() => getBundleId()).toThrow("Native module is missing 'getBundleId()'");
352
- } finally {
353
- nativeModule.getBundleId = originalGetBundleId;
354
- }
355
- });
356
- it("falls back to MIN_BUNDLE_ID when native reports an empty bundle id", async () => {
357
- nativeModuleMock.getBundleId.mockReturnValue("");
358
- const {
359
- getBundleId
360
- } = await import("./native");
361
- expect(getBundleId()).toBe("min-bundle-id");
362
- });
363
- it("falls back to MIN_BUNDLE_ID when native bundle id is null", async () => {
364
- nativeModuleMock.getBundleId.mockReturnValue(null);
365
- const {
366
- getBundleId
367
- } = await import("./native");
368
- expect(getBundleId()).toBe("min-bundle-id");
369
- });
370
- it("falls back to MIN_BUNDLE_ID for legacy NIL_UUID bundle ids", async () => {
371
- nativeModuleMock.getBundleId.mockReturnValue("00000000-0000-0000-0000-000000000000");
372
- const {
373
- getBundleId
374
- } = await import("./native");
375
- expect(getBundleId()).toBe("min-bundle-id");
376
- });
377
- it("returns manifest from native objects", async () => {
378
- nativeModuleMock.getManifest.mockReturnValue({
379
- assets: {
380
- "assets/logo.png": {
381
- fileHash: "hash-logo",
382
- signature: "sig-logo"
383
- },
384
- "index.android.bundle": {
385
- fileHash: "hash-bundle"
386
- }
387
- },
388
- bundleId: "bundle-123"
389
- });
390
- const {
391
- getManifest
392
- } = await import("./native");
393
- expect(getManifest()).toEqual({
394
- assets: {
395
- "assets/logo.png": {
396
- fileHash: "hash-logo",
397
- signature: "sig-logo"
398
- },
399
- "index.android.bundle": {
400
- fileHash: "hash-bundle"
401
- }
402
- },
403
- bundleId: "bundle-123"
404
- });
405
- });
406
- it("normalizes legacy manifest asset entries from native objects", async () => {
407
- nativeModuleMock.getManifest.mockReturnValue({
408
- assets: {
409
- "assets/logo.png": "hash-logo"
410
- },
411
- bundleId: "bundle-123"
412
- });
413
- const {
414
- getManifest
415
- } = await import("./native");
416
- expect(getManifest()).toEqual({
417
- assets: {
418
- "assets/logo.png": {
419
- fileHash: "hash-logo"
420
- }
421
- },
422
- bundleId: "bundle-123"
423
- });
424
- });
425
- it("parses manifest from old-arch JSON payloads", async () => {
426
- nativeModuleMock.getManifest.mockReturnValue(JSON.stringify({
427
- assets: {
428
- "assets/logo.png": {
429
- fileHash: "hash-logo"
430
- }
431
- },
432
- bundleId: "bundle-123"
433
- }));
434
- const {
435
- getManifest
436
- } = await import("./native");
437
- expect(getManifest()).toEqual({
438
- assets: {
439
- "assets/logo.png": {
440
- fileHash: "hash-logo"
441
- }
442
- },
443
- bundleId: "bundle-123"
444
- });
445
- });
446
- it("normalizes legacy manifest asset entries from old-arch JSON payloads", async () => {
447
- nativeModuleMock.getManifest.mockReturnValue(JSON.stringify({
448
- assets: {
449
- "assets/logo.png": "hash-logo"
450
- },
451
- bundleId: "bundle-123"
452
- }));
453
- const {
454
- getManifest
455
- } = await import("./native");
456
- expect(getManifest()).toEqual({
457
- assets: {
458
- "assets/logo.png": {
459
- fileHash: "hash-logo"
460
- }
461
- },
462
- bundleId: "bundle-123"
463
- });
464
- });
465
- it("returns an empty-assets manifest for malformed payloads", async () => {
466
- nativeModuleMock.getManifest.mockReturnValue("{");
467
- const {
468
- getManifest
469
- } = await import("./native");
470
- expect(getManifest()).toEqual({
471
- assets: {},
472
- bundleId: "bundle-id"
473
- });
474
- });
475
- it("caches active bundle getters within a JS runtime", async () => {
476
- nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
477
- nativeModuleMock.getManifest.mockReturnValue({
478
- assets: {
479
- "assets/logo.png": {
480
- fileHash: "hash-logo"
481
- }
482
- },
483
- bundleId: "bundle-123"
484
- });
485
- nativeModuleMock.getBaseURL.mockReturnValue("file:///bundle-123");
486
- const {
487
- getBaseURL,
488
- getBundleId,
489
- getManifest
490
- } = await import("./native");
491
- expect(getBundleId()).toBe("bundle-123");
492
- expect(getBundleId()).toBe("bundle-123");
493
- expect(nativeModuleMock.getBundleId).toHaveBeenCalledTimes(1);
494
- const firstManifest = getManifest();
495
- firstManifest.assets["assets/logo.png"] = {
496
- fileHash: "mutated-hash"
497
- };
498
- expect(getManifest()).toEqual({
499
- assets: {
500
- "assets/logo.png": {
501
- fileHash: "hash-logo"
502
- }
503
- },
504
- bundleId: "bundle-123"
505
- });
506
- expect(nativeModuleMock.getManifest).toHaveBeenCalledTimes(1);
507
- expect(getBaseURL()).toBe("file:///bundle-123");
508
- expect(getBaseURL()).toBe("file:///bundle-123");
509
- expect(nativeModuleMock.getBaseURL).toHaveBeenCalledTimes(1);
510
- });
511
- it("uses the launched bundle reported by native after updateBundle succeeds", async () => {
512
- nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
513
- nativeModuleMock.getManifest.mockReturnValue({
514
- assets: {},
515
- bundleId: "bundle-123"
516
- });
517
- nativeModuleMock.getBaseURL.mockReturnValue("file:///bundle-123");
518
- nativeModuleMock.updateBundle.mockResolvedValue(true);
519
- const {
520
- getBaseURL,
521
- getBundleId,
522
- getManifest,
523
- updateBundle
524
- } = await import("./native");
525
- expect(getBundleId()).toBe("bundle-123");
526
- expect(getManifest()).toEqual({
527
- assets: {},
528
- bundleId: "bundle-123"
529
- });
530
- expect(getBaseURL()).toBe("file:///bundle-123");
531
- await updateBundle({
532
- bundleId: "bundle-456",
533
- fileHash: null,
534
- fileUrl: "https://example.com/bundle.zip",
535
- status: "UPDATE"
536
- });
537
- expect(getBundleId()).toBe("bundle-123");
538
- expect(getManifest()).toEqual({
539
- assets: {},
540
- bundleId: "bundle-123"
541
- });
542
- expect(getBaseURL()).toBe("file:///bundle-123");
543
- expect(nativeModuleMock.getBundleId).toHaveBeenCalledTimes(3);
544
- expect(nativeModuleMock.getManifest).toHaveBeenCalledTimes(2);
545
- expect(nativeModuleMock.getBaseURL).toHaveBeenCalledTimes(2);
546
- });
547
- it("reinstalls a session-installed bundle after native resets to built-in", async () => {
548
- nativeModuleMock.getConstants.mockReturnValue({
549
- APP_VERSION: null,
550
- CHANNEL: "production",
551
- DEFAULT_CHANNEL: "production",
552
- FINGERPRINT_HASH: null,
553
- MIN_BUNDLE_ID: "00000000-0000-0000-0000-000000000001"
554
- });
555
- nativeModuleMock.getBundleId.mockReturnValue("00000000-0000-0000-0000-000000000002");
556
- nativeModuleMock.updateBundle.mockResolvedValue(true);
557
- const {
558
- getBundleId,
559
- updateBundle
560
- } = await import("./native");
561
- await updateBundle({
562
- bundleId: "00000000-0000-0000-0000-000000000003",
563
- fileHash: null,
564
- fileUrl: "https://example.com/bundle.zip",
565
- status: "UPDATE"
566
- });
567
- nativeModuleMock.getBundleId.mockReturnValue("00000000-0000-0000-0000-000000000003");
568
- expect(getBundleId()).toBe("00000000-0000-0000-0000-000000000003");
569
- nativeModuleMock.getBundleId.mockReturnValue(null);
570
- await expect(updateBundle({
571
- bundleId: "00000000-0000-0000-0000-000000000003",
572
- fileHash: null,
573
- fileUrl: "https://example.com/bundle.zip",
574
- status: "UPDATE"
575
- })).resolves.toBe(true);
576
- expect(nativeModuleMock.updateBundle).toHaveBeenCalledTimes(2);
577
- });
578
- it("skips a session-installed bundle when native still reports it active", async () => {
579
- nativeModuleMock.getConstants.mockReturnValue({
580
- APP_VERSION: null,
581
- CHANNEL: "production",
582
- DEFAULT_CHANNEL: "production",
583
- FINGERPRINT_HASH: null,
584
- MIN_BUNDLE_ID: "00000000-0000-0000-0000-000000000001"
585
- });
586
- nativeModuleMock.getBundleId.mockReturnValue("00000000-0000-0000-0000-000000000002");
587
- nativeModuleMock.updateBundle.mockResolvedValue(true);
588
- const {
589
- updateBundle
590
- } = await import("./native");
591
- await updateBundle({
592
- bundleId: "00000000-0000-0000-0000-000000000003",
593
- fileHash: null,
594
- fileUrl: "https://example.com/bundle.zip",
595
- status: "UPDATE"
596
- });
597
- nativeModuleMock.getBundleId.mockReturnValue("00000000-0000-0000-0000-000000000003");
598
- await expect(updateBundle({
599
- bundleId: "00000000-0000-0000-0000-000000000003",
600
- fileHash: null,
601
- fileUrl: "https://example.com/bundle.zip",
602
- status: "UPDATE"
603
- })).resolves.toBe(true);
604
- expect(nativeModuleMock.updateBundle).toHaveBeenCalledTimes(1);
605
- });
606
- it("forwards manifest artifact parameters to native updateBundle", async () => {
607
- nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
608
- nativeModuleMock.updateBundle.mockResolvedValue(true);
609
- const {
610
- updateBundle
611
- } = await import("./native");
612
- await updateBundle({
613
- bundleId: "bundle-789",
614
- changedAssets: {
615
- "index.ios.bundle": {
616
- file: {
617
- compression: "br",
618
- url: "https://example.com/files/index.ios.bundle.br"
619
- },
620
- fileHash: "hash-next",
621
- patch: {
622
- algorithm: "bsdiff",
623
- baseBundleId: "bundle-123",
624
- baseFileHash: "hash-prev",
625
- patchFileHash: "hash-patch",
626
- patchUrl: "https://example.com/files/index.ios.bundle.bsdiff"
627
- }
628
- }
629
- },
630
- fileHash: "sig:archive",
631
- fileUrl: "https://example.com/bundle.zip",
632
- manifestFileHash: "sig:manifest",
633
- manifestUrl: "https://example.com/manifest.json",
634
- status: "UPDATE"
635
- });
636
- expect(nativeModuleMock.updateBundle).toHaveBeenCalledWith({
637
- bundleId: "bundle-789",
638
- changedAssets: {
639
- "index.ios.bundle": {
640
- file: {
641
- compression: "br",
642
- url: "https://example.com/files/index.ios.bundle.br"
643
- },
644
- fileHash: "hash-next",
645
- patch: {
646
- algorithm: "bsdiff",
647
- baseBundleId: "bundle-123",
648
- baseFileHash: "hash-prev",
649
- patchFileHash: "hash-patch",
650
- patchUrl: "https://example.com/files/index.ios.bundle.bsdiff"
651
- }
652
- }
653
- },
654
- channel: undefined,
655
- fileHash: "sig:archive",
656
- fileUrl: "https://example.com/bundle.zip",
657
- manifestFileHash: "sig:manifest",
658
- manifestUrl: "https://example.com/manifest.json"
659
- });
660
- });
661
- it("invalidates cached bundle getters after resetChannel succeeds", async () => {
662
- nativeModuleMock.getConstants.mockReturnValue({
663
- APP_VERSION: null,
664
- CHANNEL: "beta",
665
- DEFAULT_CHANNEL: "production",
666
- FINGERPRINT_HASH: null,
667
- MIN_BUNDLE_ID: "min-bundle-id"
668
- });
669
- nativeModuleMock.getBundleId.mockReturnValue("bundle-beta");
670
- nativeModuleMock.getManifest.mockReturnValue({
671
- assets: {},
672
- bundleId: "bundle-beta"
673
- });
674
- nativeModuleMock.getBaseURL.mockReturnValue("file:///bundle-beta");
675
- nativeModuleMock.resetChannel.mockResolvedValue(true);
676
- const {
677
- getBaseURL,
678
- getBundleId,
679
- getManifest,
680
- resetChannel
681
- } = await import("./native");
682
- expect(getBundleId()).toBe("bundle-beta");
683
- expect(getManifest()).toEqual({
684
- assets: {},
685
- bundleId: "bundle-beta"
686
- });
687
- expect(getBaseURL()).toBe("file:///bundle-beta");
688
- nativeModuleMock.getBundleId.mockReturnValue(null);
689
- nativeModuleMock.getManifest.mockReturnValue({});
690
- nativeModuleMock.getBaseURL.mockReturnValue("");
691
- await expect(resetChannel()).resolves.toBe(true);
692
- expect(getBundleId()).toBe("min-bundle-id");
693
- expect(getManifest()).toEqual({
694
- assets: {},
695
- bundleId: "min-bundle-id"
696
- });
697
- expect(getBaseURL()).toBeNull();
698
- expect(nativeModuleMock.getBundleId).toHaveBeenCalledTimes(2);
699
- expect(nativeModuleMock.getManifest).toHaveBeenCalledTimes(2);
700
- expect(nativeModuleMock.getBaseURL).toHaveBeenCalledTimes(2);
701
- });
702
- it("delegates resetChannel to native even when exported channel constants already match", async () => {
703
- nativeModuleMock.getConstants.mockReturnValue({
704
- APP_VERSION: null,
705
- CHANNEL: "beta",
706
- DEFAULT_CHANNEL: "beta",
707
- FINGERPRINT_HASH: null,
708
- MIN_BUNDLE_ID: "min-bundle-id"
709
- });
710
- nativeModuleMock.resetChannel.mockResolvedValue(true);
711
- const {
712
- resetChannel
713
- } = await import("./native");
714
- await expect(resetChannel()).resolves.toBe(true);
715
- expect(nativeModuleMock.resetChannel).toHaveBeenCalledTimes(1);
716
- });
717
- it("parses crash history from legacy JSON payloads", async () => {
718
- nativeModuleMock.getCrashHistory.mockReturnValue(JSON.stringify(["bundle-1", "bundle-2"]));
719
- const {
720
- getCrashHistory
721
- } = await import("./native");
722
- expect(getCrashHistory()).toEqual(["bundle-1", "bundle-2"]);
723
- });
724
- it("falls back to an empty crash history for malformed payloads", async () => {
725
- nativeModuleMock.getCrashHistory.mockReturnValue("{");
726
- const {
727
- getCrashHistory
728
- } = await import("./native");
729
- expect(getCrashHistory()).toEqual([]);
730
- });
731
- it("passes normalized cohort overrides to native", async () => {
732
- const {
733
- setCohort
734
- } = await import("./native");
735
- setCohort(" QA-Group ");
736
- expect(nativeModuleMock.setCohort).toHaveBeenCalledWith("qa-group");
737
- });
738
- it("returns the most recently set cohort before native reads catch up", async () => {
739
- nativeModuleMock.getCohort.mockReturnValue("123");
740
- const {
741
- getCohort,
742
- setCohort
743
- } = await import("./native");
744
- setCohort(" QA-Group ");
745
- expect(getCohort()).toBe("qa-group");
746
- expect(nativeModuleMock.getCohort).not.toHaveBeenCalled();
747
- });
748
- it("throws when attempting to clear the cohort with an empty value", async () => {
749
- const {
750
- setCohort
751
- } = await import("./native");
752
- expect(() => setCohort("")).toThrow(INVALID_COHORT_ERROR_MESSAGE);
753
- expect(nativeModuleMock.setCohort).not.toHaveBeenCalled();
754
- });
755
- it("throws for invalid cohort overrides", async () => {
756
- const {
757
- setCohort
758
- } = await import("./native");
759
- expect(() => setCohort("Bad Cohort")).toThrow(INVALID_COHORT_ERROR_MESSAGE);
760
- expect(nativeModuleMock.setCohort).not.toHaveBeenCalled();
761
- });
762
- it("throws for cohort overrides longer than the limit", async () => {
763
- const {
764
- setCohort
765
- } = await import("./native");
766
- expect(() => setCohort("a".repeat(65))).toThrow(INVALID_COHORT_ERROR_MESSAGE);
767
- expect(nativeModuleMock.setCohort).not.toHaveBeenCalled();
768
- });
769
- it("returns the install id reported by native", async () => {
770
- nativeModuleMock.getInstallId.mockReturnValue("install-123");
771
- const {
772
- getInstallId
773
- } = await import("./native");
774
- expect(getInstallId()).toBe("install-123");
775
- });
776
- it("normalizes an omitted native fingerprint constant to null", async () => {
777
- nativeModuleMock.getConstants.mockReturnValue({
778
- APP_VERSION: null,
779
- CHANNEL: "production",
780
- DEFAULT_CHANNEL: "production",
781
- FINGERPRINT_HASH: undefined,
782
- MIN_BUNDLE_ID: "min-bundle-id"
783
- });
784
- const {
785
- getFingerprintHash
786
- } = await import("./native");
787
- expect(getFingerprintHash()).toBeNull();
788
- });
789
- it("throws when native SDK does not expose getInstallId", async () => {
790
- const nativeModule = nativeModuleMock;
791
- const originalGetInstallId = nativeModule.getInstallId;
792
- nativeModule.getInstallId = null;
793
- try {
794
- const {
795
- getInstallId
796
- } = await import("./native");
797
- expect(() => getInstallId()).toThrow("Native module is missing 'getInstallId()'");
798
- } finally {
799
- nativeModule.getInstallId = originalGetInstallId;
800
- }
801
- });
802
- it("passes nullable user identity through to native", async () => {
803
- const {
804
- setUser
805
- } = await import("./native");
806
- setUser({
807
- userId: "user-123",
808
- username: "alice"
809
- });
810
- setUser({
811
- userId: 42,
812
- username: "bob"
813
- });
814
- setUser({});
815
- setUser(null);
816
- expect(nativeModuleMock.setUser).toHaveBeenNthCalledWith(1, "user-123", "alice");
817
- expect(nativeModuleMock.setUser).toHaveBeenNthCalledWith(2, "42", "bob");
818
- expect(nativeModuleMock.setUser).toHaveBeenNthCalledWith(3, null, null);
819
- expect(nativeModuleMock.setUser).toHaveBeenNthCalledWith(4, null, null);
820
- });
821
- it("reads persisted user identity from native when available", async () => {
822
- nativeModuleMock.getUserId.mockReturnValue("user-123");
823
- nativeModuleMock.getUsername.mockReturnValue("alice");
824
- const {
825
- getPersistedUserIdentity
826
- } = await import("./native");
827
- expect(getPersistedUserIdentity()).toEqual({
828
- userId: "user-123",
829
- username: "alice"
830
- });
831
- });
832
- it("throws when native SDK does not expose persisted user identity getters", async () => {
833
- const nativeModule = nativeModuleMock;
834
- const originalGetUserId = nativeModule.getUserId;
835
- nativeModule.getUserId = null;
836
- try {
837
- const {
838
- getPersistedUserIdentity
839
- } = await import("./native");
840
- expect(() => getPersistedUserIdentity()).toThrow("Native module is missing 'getUserId()' or 'getUsername()'");
841
- } finally {
842
- nativeModule.getUserId = originalGetUserId;
843
- }
844
- });
845
- it("throws when native SDK does not expose setUser", async () => {
846
- const nativeModule = nativeModuleMock;
847
- const originalSetUser = nativeModule.setUser;
848
- nativeModule.setUser = null;
849
- try {
850
- const {
851
- setUser
852
- } = await import("./native");
853
- expect(() => setUser({
854
- userId: "user-123"
855
- })).toThrow("Native module is missing 'setUser()'");
856
- } finally {
857
- nativeModule.setUser = originalSetUser;
858
- }
859
- });
860
- it("returns the cohort reported by native", async () => {
861
- nativeModuleMock.getCohort.mockReturnValue("qa-group");
862
- const {
863
- getCohort
864
- } = await import("./native");
865
- expect(getCohort()).toBe("qa-group");
866
- });
867
- it("normalizes the cohort reported by native", async () => {
868
- nativeModuleMock.getCohort.mockReturnValue(" QA-GROUP ");
869
- const {
870
- getCohort
871
- } = await import("./native");
872
- expect(getCohort()).toBe("qa-group");
873
- });
874
- it("throws when native reports an invalid cohort", async () => {
875
- nativeModuleMock.getCohort.mockReturnValue("1001");
876
- const {
877
- getCohort
878
- } = await import("./native");
879
- expect(() => getCohort()).toThrow(INVALID_COHORT_ERROR_MESSAGE);
880
- });
881
- });
882
- //# sourceMappingURL=native.spec.js.map