@hot-updater/react-native 1.0.0-rc.17 → 1.0.0-rc.21

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 (240) hide show
  1. package/lib/commonjs/checkForUpdate.js +7 -9
  2. package/lib/commonjs/checkForUpdate.js.map +1 -1
  3. package/lib/commonjs/clientPlugin.js +7 -43
  4. package/lib/commonjs/clientPlugin.js.map +1 -1
  5. package/lib/commonjs/httpClient.js +7 -9
  6. package/lib/commonjs/httpClient.js.map +1 -1
  7. package/lib/commonjs/index.js +10 -0
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/native.js +4 -4
  10. package/lib/commonjs/native.js.map +1 -1
  11. package/lib/commonjs/pluginHost.js +26 -190
  12. package/lib/commonjs/pluginHost.js.map +1 -1
  13. package/lib/commonjs/releaseCatalogCache.js +7 -59
  14. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  15. package/lib/commonjs/sdkVersion.js +1 -1
  16. package/lib/module/checkForUpdate.js +1 -3
  17. package/lib/module/checkForUpdate.js.map +1 -1
  18. package/lib/module/clientPlugin.js +1 -41
  19. package/lib/module/clientPlugin.js.map +1 -1
  20. package/lib/module/httpClient.js +1 -3
  21. package/lib/module/httpClient.js.map +1 -1
  22. package/lib/module/index.js +2 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/native.js +1 -1
  25. package/lib/module/native.js.map +1 -1
  26. package/lib/module/pluginHost.js +22 -187
  27. package/lib/module/pluginHost.js.map +1 -1
  28. package/lib/module/releaseCatalogCache.js +3 -54
  29. package/lib/module/releaseCatalogCache.js.map +1 -1
  30. package/lib/module/sdkVersion.js +1 -1
  31. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/clientPlugin.d.ts +1 -184
  33. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/httpClient.d.ts +1 -2
  35. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/index.d.ts +1 -0
  37. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/native.d.ts +1 -1
  39. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/pluginHost.d.ts +3 -28
  41. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +1 -13
  43. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  45. package/lib/typescript/commonjs/types.d.ts +1 -1
  46. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/wrap.types.d.ts +2 -2
  48. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  49. package/lib/typescript/module/clientPlugin.d.ts +1 -184
  50. package/lib/typescript/module/clientPlugin.d.ts.map +1 -1
  51. package/lib/typescript/module/httpClient.d.ts +1 -2
  52. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  53. package/lib/typescript/module/index.d.ts +1 -0
  54. package/lib/typescript/module/index.d.ts.map +1 -1
  55. package/lib/typescript/module/native.d.ts +1 -1
  56. package/lib/typescript/module/native.d.ts.map +1 -1
  57. package/lib/typescript/module/pluginHost.d.ts +3 -28
  58. package/lib/typescript/module/pluginHost.d.ts.map +1 -1
  59. package/lib/typescript/module/releaseCatalogCache.d.ts +1 -13
  60. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  61. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  62. package/lib/typescript/module/types.d.ts +1 -1
  63. package/lib/typescript/module/types.d.ts.map +1 -1
  64. package/lib/typescript/module/wrap.types.d.ts +2 -2
  65. package/package.json +7 -16
  66. package/src/checkForUpdate.ts +4 -6
  67. package/src/clientPlugin.ts +17 -231
  68. package/src/httpClient.ts +4 -4
  69. package/src/index.ts +7 -0
  70. package/src/native.ts +1 -1
  71. package/src/pluginHost.ts +24 -241
  72. package/src/releaseCatalogCache.ts +7 -136
  73. package/src/sdkVersion.ts +1 -1
  74. package/src/types.ts +1 -1
  75. package/src/wrap.types.ts +2 -2
  76. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  77. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -2236
  78. package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +0 -220
  79. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -264
  80. package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +0 -97
  81. package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +0 -71
  82. package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +0 -106
  83. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  84. package/android/src/test/java/com/hotupdater/NoBackupContext.kt +0 -11
  85. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -397
  86. package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +0 -44
  87. package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +0 -57
  88. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  89. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  90. package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +0 -206
  91. package/lib/commonjs/appReady.spec.js +0 -360
  92. package/lib/commonjs/appReady.spec.js.map +0 -1
  93. package/lib/commonjs/appReady.test-utils.js +0 -25
  94. package/lib/commonjs/appReady.test-utils.js.map +0 -1
  95. package/lib/commonjs/baseURL.js +0 -16
  96. package/lib/commonjs/baseURL.js.map +0 -1
  97. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  98. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  99. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -936
  100. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  101. package/lib/commonjs/entryImportGraph.spec.js +0 -50
  102. package/lib/commonjs/entryImportGraph.spec.js.map +0 -1
  103. package/lib/commonjs/httpClient.spec.js +0 -171
  104. package/lib/commonjs/httpClient.spec.js.map +0 -1
  105. package/lib/commonjs/index.spec.js +0 -395
  106. package/lib/commonjs/index.spec.js.map +0 -1
  107. package/lib/commonjs/iosInterop.spec.js +0 -18
  108. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  109. package/lib/commonjs/native.spec.js +0 -910
  110. package/lib/commonjs/native.spec.js.map +0 -1
  111. package/lib/commonjs/pluginHost.spec.js +0 -292
  112. package/lib/commonjs/pluginHost.spec.js.map +0 -1
  113. package/lib/commonjs/plugins/insights/eventId.js +0 -129
  114. package/lib/commonjs/plugins/insights/eventId.js.map +0 -1
  115. package/lib/commonjs/plugins/insights/eventId.spec.js +0 -47
  116. package/lib/commonjs/plugins/insights/eventId.spec.js.map +0 -1
  117. package/lib/commonjs/plugins/insights/index.js +0 -272
  118. package/lib/commonjs/plugins/insights/index.js.map +0 -1
  119. package/lib/commonjs/plugins/insights/insights.spec.js +0 -640
  120. package/lib/commonjs/plugins/insights/insights.spec.js.map +0 -1
  121. package/lib/commonjs/plugins/insights/sender.js +0 -128
  122. package/lib/commonjs/plugins/insights/sender.js.map +0 -1
  123. package/lib/commonjs/plugins/insights/sender.spec.js +0 -265
  124. package/lib/commonjs/plugins/insights/sender.spec.js.map +0 -1
  125. package/lib/commonjs/plugins/insights/state.js +0 -145
  126. package/lib/commonjs/plugins/insights/state.js.map +0 -1
  127. package/lib/commonjs/releaseCatalogCache.spec.js +0 -213
  128. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  129. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  130. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  131. package/lib/commonjs/store.notification.spec.js +0 -84
  132. package/lib/commonjs/store.notification.spec.js.map +0 -1
  133. package/lib/commonjs/store.spec.js +0 -182
  134. package/lib/commonjs/store.spec.js.map +0 -1
  135. package/lib/commonjs/updateError.spec.js +0 -149
  136. package/lib/commonjs/updateError.spec.js.map +0 -1
  137. package/lib/commonjs/wrap.progress.spec.js +0 -195
  138. package/lib/commonjs/wrap.progress.spec.js.map +0 -1
  139. package/lib/commonjs/wrap.spec.js +0 -232
  140. package/lib/commonjs/wrap.spec.js.map +0 -1
  141. package/lib/module/appReady.spec.js +0 -359
  142. package/lib/module/appReady.spec.js.map +0 -1
  143. package/lib/module/appReady.test-utils.js +0 -19
  144. package/lib/module/appReady.test-utils.js.map +0 -1
  145. package/lib/module/baseURL.js +0 -11
  146. package/lib/module/baseURL.js.map +0 -1
  147. package/lib/module/catalogCacheNative.spec.js +0 -31
  148. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  149. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -935
  150. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  151. package/lib/module/entryImportGraph.spec.js +0 -50
  152. package/lib/module/entryImportGraph.spec.js.map +0 -1
  153. package/lib/module/httpClient.spec.js +0 -171
  154. package/lib/module/httpClient.spec.js.map +0 -1
  155. package/lib/module/index.spec.js +0 -394
  156. package/lib/module/index.spec.js.map +0 -1
  157. package/lib/module/iosInterop.spec.js +0 -18
  158. package/lib/module/iosInterop.spec.js.map +0 -1
  159. package/lib/module/native.spec.js +0 -909
  160. package/lib/module/native.spec.js.map +0 -1
  161. package/lib/module/pluginHost.spec.js +0 -291
  162. package/lib/module/pluginHost.spec.js.map +0 -1
  163. package/lib/module/plugins/insights/eventId.js +0 -122
  164. package/lib/module/plugins/insights/eventId.js.map +0 -1
  165. package/lib/module/plugins/insights/eventId.spec.js +0 -47
  166. package/lib/module/plugins/insights/eventId.spec.js.map +0 -1
  167. package/lib/module/plugins/insights/index.js +0 -268
  168. package/lib/module/plugins/insights/index.js.map +0 -1
  169. package/lib/module/plugins/insights/insights.spec.js +0 -639
  170. package/lib/module/plugins/insights/insights.spec.js.map +0 -1
  171. package/lib/module/plugins/insights/sender.js +0 -123
  172. package/lib/module/plugins/insights/sender.js.map +0 -1
  173. package/lib/module/plugins/insights/sender.spec.js +0 -265
  174. package/lib/module/plugins/insights/sender.spec.js.map +0 -1
  175. package/lib/module/plugins/insights/state.js +0 -139
  176. package/lib/module/plugins/insights/state.js.map +0 -1
  177. package/lib/module/releaseCatalogCache.spec.js +0 -213
  178. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  179. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  180. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  181. package/lib/module/store.notification.spec.js +0 -85
  182. package/lib/module/store.notification.spec.js.map +0 -1
  183. package/lib/module/store.spec.js +0 -181
  184. package/lib/module/store.spec.js.map +0 -1
  185. package/lib/module/updateError.spec.js +0 -149
  186. package/lib/module/updateError.spec.js.map +0 -1
  187. package/lib/module/wrap.progress.spec.js +0 -196
  188. package/lib/module/wrap.progress.spec.js.map +0 -1
  189. package/lib/module/wrap.spec.js +0 -233
  190. package/lib/module/wrap.spec.js.map +0 -1
  191. package/lib/typescript/commonjs/appReady.test-utils.d.ts +0 -9
  192. package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +0 -1
  193. package/lib/typescript/commonjs/baseURL.d.ts +0 -4
  194. package/lib/typescript/commonjs/baseURL.d.ts.map +0 -1
  195. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +0 -18
  196. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +0 -1
  197. package/lib/typescript/commonjs/plugins/insights/index.d.ts +0 -40
  198. package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +0 -1
  199. package/lib/typescript/commonjs/plugins/insights/sender.d.ts +0 -74
  200. package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +0 -1
  201. package/lib/typescript/commonjs/plugins/insights/state.d.ts +0 -46
  202. package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +0 -1
  203. package/lib/typescript/module/appReady.test-utils.d.ts +0 -9
  204. package/lib/typescript/module/appReady.test-utils.d.ts.map +0 -1
  205. package/lib/typescript/module/baseURL.d.ts +0 -4
  206. package/lib/typescript/module/baseURL.d.ts.map +0 -1
  207. package/lib/typescript/module/plugins/insights/eventId.d.ts +0 -18
  208. package/lib/typescript/module/plugins/insights/eventId.d.ts.map +0 -1
  209. package/lib/typescript/module/plugins/insights/index.d.ts +0 -40
  210. package/lib/typescript/module/plugins/insights/index.d.ts.map +0 -1
  211. package/lib/typescript/module/plugins/insights/sender.d.ts +0 -74
  212. package/lib/typescript/module/plugins/insights/sender.d.ts.map +0 -1
  213. package/lib/typescript/module/plugins/insights/state.d.ts +0 -46
  214. package/lib/typescript/module/plugins/insights/state.d.ts.map +0 -1
  215. package/plugins/insights/package.json +0 -8
  216. package/src/appReady.spec.ts +0 -393
  217. package/src/appReady.test-utils.ts +0 -31
  218. package/src/baseURL.ts +0 -15
  219. package/src/catalogCacheNative.spec.ts +0 -63
  220. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -966
  221. package/src/entryImportGraph.spec.ts +0 -65
  222. package/src/httpClient.spec.ts +0 -216
  223. package/src/index.spec.ts +0 -434
  224. package/src/iosInterop.spec.ts +0 -30
  225. package/src/native.spec.ts +0 -978
  226. package/src/pluginHost.spec.ts +0 -297
  227. package/src/plugins/insights/eventId.spec.ts +0 -60
  228. package/src/plugins/insights/eventId.ts +0 -171
  229. package/src/plugins/insights/index.ts +0 -367
  230. package/src/plugins/insights/insights.spec.ts +0 -732
  231. package/src/plugins/insights/sender.spec.ts +0 -287
  232. package/src/plugins/insights/sender.ts +0 -221
  233. package/src/plugins/insights/state.ts +0 -184
  234. package/src/releaseCatalogCache.spec.ts +0 -304
  235. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  236. package/src/store.notification.spec.ts +0 -102
  237. package/src/store.spec.ts +0 -215
  238. package/src/updateError.spec.ts +0 -144
  239. package/src/wrap.progress.spec.ts +0 -232
  240. package/src/wrap.spec.ts +0 -262
@@ -1,304 +0,0 @@
1
- import {
2
- createReleaseCatalogScopeKey,
3
- encodeChannelKey,
4
- MAX_COMPILED_CATALOG_BYTES,
5
- type ReleaseCatalog,
6
- } from "@hot-updater/core";
7
- import { beforeEach, describe, expect, it, vi } from "vitest";
8
-
9
- import {
10
- createReleaseCatalogCachePartition,
11
- fetchReleaseCatalogWithCache,
12
- MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES,
13
- MAX_RELEASE_CATALOG_WIRE_BYTES,
14
- } from "./releaseCatalogCache";
15
-
16
- const cache = vi.hoisted(() => new Map<string, string>());
17
- const nativeMocks = vi.hoisted(() => ({
18
- read: vi.fn(async (partition: string) => cache.get(partition) ?? null),
19
- remove: vi.fn(async (partition: string) => {
20
- cache.delete(partition);
21
- }),
22
- write: vi.fn(async (partition: string, payload: string) => {
23
- cache.set(partition, payload);
24
- return true;
25
- }),
26
- }));
27
-
28
- vi.mock("./catalogCacheNative", () => ({
29
- readNativeReleaseCatalogCache: nativeMocks.read,
30
- removeNativeReleaseCatalogCache: nativeMocks.remove,
31
- writeNativeReleaseCatalogCache: nativeMocks.write,
32
- }));
33
-
34
- const CATALOG_ID = "project-a";
35
- const CHANNEL_KEY = encodeChannelKey("production");
36
- const SCOPE_KEY = createReleaseCatalogScopeKey({
37
- channelKey: CHANNEL_KEY,
38
- platform: "ios",
39
- strategy: "APP_VERSION",
40
- });
41
- const URL = `https://updates.example.com/release-catalogs/app-version/ios/${CHANNEL_KEY}/1.0.0`;
42
- const EXPECTED_SCOPE = {
43
- channelKey: CHANNEL_KEY,
44
- platform: "ios",
45
- strategy: "APP_VERSION",
46
- } as const;
47
-
48
- const catalog: ReleaseCatalog = {
49
- catalogId: CATALOG_ID,
50
- catalogHash: `sha256:${"a".repeat(64)}`,
51
- fallbackPolicy: "BUILTIN_IF_ACTIVE_INELIGIBLE",
52
- generation: 1,
53
- releases: [
54
- {
55
- bundleId: "00000000-0000-7000-8000-000000000002",
56
- kind: "BUNDLE",
57
- message: null,
58
- releaseId: "00000000-0000-7000-8000-000000000001",
59
- rolloutCohortCount: 1000,
60
- shouldForceUpdate: false,
61
- targetCohorts: [],
62
- },
63
- ],
64
- schemaVersion: 1,
65
- scopeKey: SCOPE_KEY,
66
- };
67
-
68
- const input = (
69
- overrides: Partial<Parameters<typeof fetchReleaseCatalogWithCache>[0]> = {},
70
- ) => ({
71
- baseURL: "https://updates.example.com",
72
- expectedScope: EXPECTED_SCOPE,
73
- requestHeaders: { "X-API-Key": "api-key-a" },
74
- url: URL,
75
- ...overrides,
76
- });
77
-
78
- const response = (status: number, body?: string, etag?: string) =>
79
- new Response(body, {
80
- headers: etag === undefined ? undefined : { ETag: etag },
81
- status,
82
- statusText: status === 200 ? "OK" : "Request failed",
83
- });
84
-
85
- const requestHeadersAt = (
86
- fetchMock: ReturnType<typeof vi.fn>,
87
- index: number,
88
- ) => {
89
- const request = fetchMock.mock.calls[index]?.[1] as RequestInit | undefined;
90
- return new Headers(request?.headers);
91
- };
92
-
93
- describe("Release Catalog persistent cache", () => {
94
- beforeEach(() => {
95
- cache.clear();
96
- vi.clearAllMocks();
97
- });
98
-
99
- it("persists a validated 200 and reuses it across resolver instances on 304", async () => {
100
- const fetchMock = vi
101
- .fn()
102
- .mockResolvedValueOnce(response(200, JSON.stringify(catalog), '"v1"'))
103
- .mockResolvedValueOnce(response(304));
104
- vi.stubGlobal("fetch", fetchMock);
105
-
106
- await expect(fetchReleaseCatalogWithCache(input())).resolves.toEqual(
107
- catalog,
108
- );
109
- expect(nativeMocks.write).toHaveBeenCalledOnce();
110
-
111
- const parseSpy = vi.spyOn(JSON, "parse");
112
- await expect(fetchReleaseCatalogWithCache(input())).resolves.toEqual(
113
- catalog,
114
- );
115
- expect(requestHeadersAt(fetchMock, 1).get("if-none-match")).toBe('"v1"');
116
- expect(parseSpy).not.toHaveBeenCalled();
117
- expect(fetchMock).toHaveBeenCalledTimes(2);
118
- });
119
-
120
- it("repairs an invalid cache after an unsatisfiable 304 with one unconditional request", async () => {
121
- const partition = createReleaseCatalogCachePartition(input());
122
- cache.set(partition, "invalid-envelope");
123
- const fetchMock = vi
124
- .fn()
125
- .mockResolvedValueOnce(response(304))
126
- .mockResolvedValueOnce(response(200, JSON.stringify(catalog), '"v2"'));
127
- vi.stubGlobal("fetch", fetchMock);
128
-
129
- await expect(fetchReleaseCatalogWithCache(input())).resolves.toEqual(
130
- catalog,
131
- );
132
-
133
- expect(nativeMocks.remove).toHaveBeenCalledWith(partition);
134
- expect(fetchMock).toHaveBeenCalledTimes(2);
135
- expect(requestHeadersAt(fetchMock, 0).has("if-none-match")).toBe(false);
136
- expect(requestHeadersAt(fetchMock, 1).has("if-none-match")).toBe(false);
137
- expect(cache.get(partition)).toContain('"v2"');
138
- });
139
-
140
- it("isolates base URL, scope, and case-insensitive API-key partitions", async () => {
141
- const betaScope = createReleaseCatalogScopeKey({
142
- channelKey: encodeChannelKey("beta"),
143
- platform: "ios",
144
- strategy: "APP_VERSION",
145
- });
146
- const fetchMock = vi
147
- .fn()
148
- .mockResolvedValueOnce(response(200, JSON.stringify(catalog), '"v1"'))
149
- .mockResolvedValueOnce(response(304))
150
- .mockResolvedValueOnce(response(200, JSON.stringify(catalog), '"key-b"'))
151
- .mockResolvedValueOnce(
152
- response(
153
- 200,
154
- JSON.stringify({ ...catalog, scopeKey: betaScope }),
155
- '"beta"',
156
- ),
157
- );
158
- vi.stubGlobal("fetch", fetchMock);
159
-
160
- await fetchReleaseCatalogWithCache(input());
161
- await fetchReleaseCatalogWithCache(
162
- input({ requestHeaders: { "x-api-key": "api-key-a" } }),
163
- );
164
- await fetchReleaseCatalogWithCache(
165
- input({ requestHeaders: { "x-api-key": "api-key-b" } }),
166
- );
167
- await fetchReleaseCatalogWithCache(
168
- input({
169
- baseURL: "https://other.example.com",
170
- expectedScope: {
171
- channelKey: encodeChannelKey("beta"),
172
- platform: "ios",
173
- strategy: "APP_VERSION",
174
- },
175
- url: "https://other.example.com/release-catalogs/app-version/ios/YmV0YQ/1.0.0",
176
- }),
177
- );
178
-
179
- expect(requestHeadersAt(fetchMock, 1).get("if-none-match")).toBe('"v1"');
180
- expect(requestHeadersAt(fetchMock, 2).has("if-none-match")).toBe(false);
181
- expect(requestHeadersAt(fetchMock, 3).has("if-none-match")).toBe(false);
182
- expect(cache.size).toBe(3);
183
- });
184
-
185
- it.each([401, 403, 500])(
186
- "does not use or repair cached data after HTTP %s",
187
- async (status) => {
188
- const fetchMock = vi
189
- .fn()
190
- .mockResolvedValueOnce(response(200, JSON.stringify(catalog), '"v1"'))
191
- .mockResolvedValueOnce(response(status));
192
- vi.stubGlobal("fetch", fetchMock);
193
- await fetchReleaseCatalogWithCache(input());
194
-
195
- await expect(fetchReleaseCatalogWithCache(input())).rejects.toThrow(
196
- "Request failed",
197
- );
198
- expect(fetchMock).toHaveBeenCalledTimes(2);
199
- },
200
- );
201
-
202
- it("reads a 404 the server marks x-hot-updater-catalog: none as no catalog", async () => {
203
- const fetchMock = vi.fn().mockResolvedValueOnce(
204
- new Response(null, {
205
- headers: { "x-hot-updater-catalog": "none" },
206
- status: 404,
207
- }),
208
- );
209
- vi.stubGlobal("fetch", fetchMock);
210
-
211
- await expect(fetchReleaseCatalogWithCache(input())).resolves.toBeNull();
212
- expect(nativeMocks.write).not.toHaveBeenCalled();
213
- });
214
-
215
- it("throws the HTTP status of any other 404", async () => {
216
- vi.stubGlobal("fetch", vi.fn().mockResolvedValueOnce(response(404)));
217
-
218
- await expect(fetchReleaseCatalogWithCache(input())).rejects.toMatchObject({
219
- name: "UpdateHttpError",
220
- status: 404,
221
- });
222
- });
223
-
224
- it("does not use cached data as a fallback on network failure", async () => {
225
- const fetchMock = vi
226
- .fn()
227
- .mockResolvedValueOnce(response(200, JSON.stringify(catalog), '"v1"'))
228
- .mockRejectedValueOnce(new Error("offline"));
229
- vi.stubGlobal("fetch", fetchMock);
230
- await fetchReleaseCatalogWithCache(input());
231
-
232
- await expect(fetchReleaseCatalogWithCache(input())).rejects.toThrow(
233
- "offline",
234
- );
235
- expect(fetchMock).toHaveBeenCalledTimes(2);
236
- });
237
-
238
- it("rejects and never persists a catalog with the wrong canonical scope", async () => {
239
- const fetchMock = vi
240
- .fn()
241
- .mockResolvedValueOnce(
242
- response(
243
- 200,
244
- JSON.stringify({ ...catalog, scopeKey: "wrong-scope" }),
245
- '"wrong"',
246
- ),
247
- );
248
- vi.stubGlobal("fetch", fetchMock);
249
-
250
- await expect(fetchReleaseCatalogWithCache(input())).rejects.toThrow(
251
- "invalid Release catalog",
252
- );
253
- expect(nativeMocks.write).not.toHaveBeenCalled();
254
- });
255
-
256
- it("rejects a catalog with no persisted identity", async () => {
257
- const fetchMock = vi
258
- .fn()
259
- .mockResolvedValueOnce(
260
- response(
261
- 200,
262
- JSON.stringify({ ...catalog, catalogId: "" }),
263
- '"missing-identity"',
264
- ),
265
- );
266
- vi.stubGlobal("fetch", fetchMock);
267
-
268
- await expect(fetchReleaseCatalogWithCache(input())).rejects.toThrow(
269
- "invalid Release catalog",
270
- );
271
- expect(nativeMocks.write).not.toHaveBeenCalled();
272
- });
273
-
274
- it("accepts a legal compiled-boundary catalog plus its wire envelope", async () => {
275
- const template = {
276
- ...catalog,
277
- releases: [{ ...catalog.releases[0]!, message: "" }],
278
- };
279
- const templateBody = JSON.stringify(template);
280
- const padding = "x".repeat(
281
- MAX_COMPILED_CATALOG_BYTES + 1 - templateBody.length,
282
- );
283
- const body = JSON.stringify({
284
- ...template,
285
- releases: [{ ...template.releases[0]!, message: padding }],
286
- });
287
- expect(body.length).toBe(MAX_COMPILED_CATALOG_BYTES + 1);
288
- expect(body.length).toBeLessThanOrEqual(MAX_RELEASE_CATALOG_WIRE_BYTES);
289
- const fetchMock = vi
290
- .fn()
291
- .mockResolvedValueOnce(response(200, body, '"boundary"'));
292
- vi.stubGlobal("fetch", fetchMock);
293
-
294
- await expect(fetchReleaseCatalogWithCache(input())).resolves.toEqual(
295
- JSON.parse(body),
296
- );
297
-
298
- const persisted = nativeMocks.write.mock.calls[0]?.[1];
299
- expect(persisted).toBeDefined();
300
- expect(new TextEncoder().encode(persisted).byteLength).toBeLessThanOrEqual(
301
- MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES,
302
- );
303
- });
304
- });
@@ -1,80 +0,0 @@
1
- import {
2
- createReleaseCatalogScopeKey,
3
- encodeChannelKey,
4
- type ReleaseCatalog,
5
- } from "@hot-updater/core";
6
- import { beforeEach, describe, expect, it, vi } from "vitest";
7
-
8
- const nativeModule = vi.hoisted(
9
- () => ({}) as Record<string, ReturnType<typeof vi.fn>>,
10
- );
11
-
12
- vi.mock("./specs/NativeHotUpdater", () => ({ default: nativeModule }));
13
-
14
- import { fetchReleaseCatalogWithCache } from "./releaseCatalogCache";
15
-
16
- const catalogId = "project-a";
17
- const scopeKey = createReleaseCatalogScopeKey({
18
- channelKey: encodeChannelKey("production"),
19
- platform: "ios",
20
- strategy: "APP_VERSION",
21
- });
22
- const input = {
23
- baseURL: "https://updates.example.com",
24
- expectedScope: {
25
- channelKey: encodeChannelKey("production"),
26
- platform: "ios",
27
- strategy: "APP_VERSION",
28
- } as const,
29
- requestHeaders: { "x-api-key": "key" },
30
- url: "https://updates.example.com/catalog",
31
- };
32
- const catalog: ReleaseCatalog = {
33
- catalogId,
34
- catalogHash: `sha256:${"a".repeat(64)}`,
35
- fallbackPolicy: "BUILTIN_IF_ACTIVE_INELIGIBLE",
36
- generation: 1,
37
- releases: [],
38
- schemaVersion: 1,
39
- scopeKey,
40
- };
41
-
42
- describe("Release Catalog native cache contract", () => {
43
- beforeEach(() => {
44
- for (const key of Object.keys(nativeModule)) delete nativeModule[key];
45
- });
46
-
47
- it("fails before fetching when the v1 native cache API is missing", async () => {
48
- const fetchMock = vi.fn();
49
- vi.stubGlobal("fetch", fetchMock);
50
-
51
- await expect(fetchReleaseCatalogWithCache(input)).rejects.toThrow(
52
- "Native module is missing 'getReleaseCatalogCache()'. Rebuild the native app before using Release catalogs.",
53
- );
54
- expect(fetchMock).not.toHaveBeenCalled();
55
- });
56
-
57
- it("uses the network when native cache I/O fails", async () => {
58
- nativeModule.getReleaseCatalogCache = vi
59
- .fn()
60
- .mockRejectedValue(new Error("read"));
61
- nativeModule.setReleaseCatalogCache = vi
62
- .fn()
63
- .mockRejectedValue(new Error("write"));
64
- nativeModule.removeReleaseCatalogCache = vi
65
- .fn()
66
- .mockRejectedValue(new Error("remove"));
67
- const fetchMock = vi.fn().mockResolvedValue(
68
- new Response(JSON.stringify(catalog), {
69
- headers: { ETag: '"v1"' },
70
- status: 200,
71
- }),
72
- );
73
- vi.stubGlobal("fetch", fetchMock);
74
-
75
- await expect(fetchReleaseCatalogWithCache(input)).resolves.toEqual(catalog);
76
- expect(fetchMock).toHaveBeenCalledOnce();
77
- expect(nativeModule.getReleaseCatalogCache).toHaveBeenCalledOnce();
78
- expect(nativeModule.setReleaseCatalogCache).toHaveBeenCalledOnce();
79
- });
80
- });
@@ -1,102 +0,0 @@
1
- // @vitest-environment jsdom
2
-
3
- import { cleanup, render } from "@testing-library/react";
4
- import { act, createElement, useLayoutEffect, useState } from "react";
5
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
-
7
- const listeners = vi.hoisted(
8
- () => new Map<string, (event: Record<string, unknown>) => void>(),
9
- );
10
-
11
- vi.mock("./native", () => ({
12
- addListener: vi.fn(
13
- (eventName: string, listener: (event: Record<string, unknown>) => void) => {
14
- listeners.set(eventName, listener);
15
- return () => {
16
- listeners.delete(eventName);
17
- };
18
- },
19
- ),
20
- }));
21
-
22
- describe("hotUpdaterStore notification scheduling", () => {
23
- beforeEach(() => {
24
- listeners.clear();
25
- vi.resetModules();
26
- });
27
-
28
- afterEach(() => {
29
- cleanup();
30
- vi.unstubAllGlobals();
31
- });
32
-
33
- it("keeps progress notifications from exceeding React's maximum update depth", async () => {
34
- // Given
35
- const frameCallbacks: ((timestamp: number) => void)[] = [];
36
- vi.stubGlobal(
37
- "requestAnimationFrame",
38
- (callback: (timestamp: number) => void) => {
39
- frameCallbacks.push(callback);
40
- return frameCallbacks.length;
41
- },
42
- );
43
- const { hotUpdaterStore } = await import("./store");
44
- const emitProgress = listeners.get("onProgress");
45
- const Consumer = () => {
46
- const [progress, setProgress] = useState(0);
47
-
48
- useLayoutEffect(() => {
49
- const unsubscribe = hotUpdaterStore.subscribe(() => {
50
- setProgress(hotUpdaterStore.getSnapshot().progress);
51
- });
52
- return () => {
53
- unsubscribe();
54
- };
55
- }, []);
56
-
57
- useLayoutEffect(() => {
58
- if (progress >= 1) {
59
- return;
60
- }
61
-
62
- const nextProgress = Math.min(1, progress + 0.01);
63
- emitProgress?.({
64
- artifactType: "diff",
65
- details: {
66
- completedFilesCount: nextProgress === 1 ? 1 : 0,
67
- files: [
68
- {
69
- order: 0,
70
- path: "index.ios.bundle",
71
- progress: nextProgress,
72
- status: nextProgress === 1 ? "downloaded" : "downloading",
73
- },
74
- ],
75
- totalFilesCount: 1,
76
- },
77
- progress: nextProgress,
78
- });
79
- }, [progress]);
80
-
81
- return null;
82
- };
83
-
84
- // When
85
- render(createElement(Consumer));
86
- for (
87
- let frame = 0;
88
- frame < 200 && hotUpdaterStore.getSnapshot().progress < 1;
89
- frame++
90
- ) {
91
- const frameCallback = frameCallbacks.shift();
92
- expect(frameCallback).toBeTypeOf("function");
93
- await new Promise<void>((resolve) => setImmediate(resolve));
94
- await act(async () => {
95
- frameCallback?.(0);
96
- });
97
- }
98
-
99
- // Then
100
- expect(hotUpdaterStore.getSnapshot().progress).toBe(1);
101
- });
102
- });
package/src/store.spec.ts DELETED
@@ -1,215 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const listeners = vi.hoisted(
4
- () => new Map<string, (event: Record<string, unknown>) => void>(),
5
- );
6
-
7
- vi.mock("./native", () => ({
8
- addListener: vi.fn(
9
- (eventName: string, listener: (event: Record<string, unknown>) => void) => {
10
- listeners.set(eventName, listener);
11
- return () => {
12
- listeners.delete(eventName);
13
- };
14
- },
15
- ),
16
- }));
17
-
18
- const importStore = async () => {
19
- const { hotUpdaterStore } = await import("./store");
20
- return hotUpdaterStore;
21
- };
22
-
23
- describe("hotUpdaterStore", () => {
24
- beforeEach(() => {
25
- listeners.clear();
26
- vi.resetModules();
27
- });
28
-
29
- it("does not notify subscribers when state values are unchanged", async () => {
30
- const store = await importStore();
31
- const listener = vi.fn();
32
-
33
- const unsubscribe = store.subscribe(listener);
34
-
35
- store.setState({});
36
- store.setState({ progress: 0 });
37
- store.setState({ isUpdateDownloaded: false });
38
-
39
- expect(listener).not.toHaveBeenCalled();
40
-
41
- unsubscribe();
42
- });
43
-
44
- it("stores manifest progress metadata while keeping overall progress", async () => {
45
- const store = await importStore();
46
- const emitProgress = listeners.get("onProgress");
47
-
48
- expect(emitProgress).toBeTypeOf("function");
49
-
50
- emitProgress?.({
51
- artifactType: "diff",
52
- details: {
53
- completedFilesCount: 1,
54
- files: [
55
- {
56
- downloadPath: "index.ios.bundle",
57
- order: 0,
58
- path: "index.ios.bundle",
59
- progress: 1,
60
- status: "downloaded",
61
- },
62
- {
63
- downloadPath: "assets/logo.png",
64
- order: 1,
65
- path: "assets/logo.png",
66
- progress: 0.5,
67
- status: "downloading",
68
- },
69
- ],
70
- totalFilesCount: 2,
71
- },
72
- progress: 0.42,
73
- });
74
-
75
- expect(store.getSnapshot()).toEqual({
76
- artifactType: "diff",
77
- details: {
78
- completedFilesCount: 1,
79
- files: [
80
- {
81
- downloadPath: "index.ios.bundle",
82
- order: 0,
83
- path: "index.ios.bundle",
84
- progress: 1,
85
- status: "downloaded",
86
- },
87
- {
88
- downloadPath: "assets/logo.png",
89
- order: 1,
90
- path: "assets/logo.png",
91
- progress: 0.5,
92
- status: "downloading",
93
- },
94
- ],
95
- totalFilesCount: 2,
96
- },
97
- isUpdateDownloaded: false,
98
- progress: 0.42,
99
- });
100
- });
101
-
102
- it("preserves patch download metadata for manifest diff files", async () => {
103
- const store = await importStore();
104
- const emitProgress = listeners.get("onProgress");
105
-
106
- emitProgress?.({
107
- artifactType: "diff",
108
- details: {
109
- completedFilesCount: 0,
110
- files: [
111
- {
112
- downloadPath: "index.ios.bundle.bsdiff",
113
- order: 0,
114
- path: "index.ios.bundle",
115
- progress: 0.4,
116
- status: "downloading",
117
- },
118
- ],
119
- totalFilesCount: 1,
120
- },
121
- progress: 0.32,
122
- });
123
-
124
- expect(store.getSnapshot().details?.files).toEqual([
125
- {
126
- downloadPath: "index.ios.bundle.bsdiff",
127
- downloadedBytes: undefined,
128
- order: 0,
129
- path: "index.ios.bundle",
130
- progress: 0.4,
131
- status: "downloading",
132
- totalBytes: undefined,
133
- },
134
- ]);
135
- });
136
-
137
- it("stores diff snapshot transitions from downloading to downloaded", async () => {
138
- const store = await importStore();
139
- const emitProgress = listeners.get("onProgress");
140
-
141
- emitProgress?.({
142
- artifactType: "diff",
143
- details: {
144
- completedFilesCount: 0,
145
- files: [
146
- {
147
- order: 0,
148
- path: "index.ios.bundle",
149
- progress: 0.6,
150
- status: "downloading",
151
- },
152
- {
153
- order: 1,
154
- path: "assets/logo.png",
155
- progress: 0,
156
- status: "pending",
157
- },
158
- ],
159
- totalFilesCount: 2,
160
- },
161
- progress: 0.4,
162
- });
163
-
164
- emitProgress?.({
165
- artifactType: "diff",
166
- details: {
167
- completedFilesCount: 1,
168
- files: [
169
- {
170
- downloadPath: "index.ios.bundle",
171
- order: 0,
172
- path: "index.ios.bundle",
173
- progress: 1,
174
- status: "downloaded",
175
- },
176
- {
177
- downloadPath: "assets/logo.png",
178
- order: 1,
179
- path: "assets/logo.png",
180
- progress: 0,
181
- status: "pending",
182
- },
183
- ],
184
- totalFilesCount: 2,
185
- },
186
- progress: 0.6,
187
- });
188
-
189
- expect(store.getSnapshot()).toEqual({
190
- artifactType: "diff",
191
- details: {
192
- completedFilesCount: 1,
193
- files: [
194
- {
195
- downloadPath: "index.ios.bundle",
196
- order: 0,
197
- path: "index.ios.bundle",
198
- progress: 1,
199
- status: "downloaded",
200
- },
201
- {
202
- downloadPath: "assets/logo.png",
203
- order: 1,
204
- path: "assets/logo.png",
205
- progress: 0,
206
- status: "pending",
207
- },
208
- ],
209
- totalFilesCount: 2,
210
- },
211
- isUpdateDownloaded: false,
212
- progress: 0.6,
213
- });
214
- });
215
- });