@hot-updater/react-native 1.0.0-rc.2 → 1.0.0-rc.22

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