@hot-updater/react-native 1.0.0-rc.1 → 1.0.0-rc.15

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 (227) 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 +651 -637
  4. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +10 -0
  5. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +0 -13
  6. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +10 -14
  7. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
  8. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  9. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +19 -0
  10. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  11. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  12. package/android/src/newarch/HotUpdater.kt +7 -9
  13. package/android/src/newarch/HotUpdaterModule.kt +40 -20
  14. package/android/src/oldarch/HotUpdater.kt +7 -9
  15. package/android/src/oldarch/HotUpdaterModule.kt +40 -20
  16. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +1126 -74
  17. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -5
  18. package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +97 -0
  19. package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +44 -0
  20. package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +206 -0
  21. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  22. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  23. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +773 -1016
  24. package/ios/HotUpdater/Internal/BundleMetadata.swift +5 -0
  25. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  26. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  27. package/ios/HotUpdater/Internal/HotUpdater.mm +5 -27
  28. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +149 -51
  29. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  30. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  31. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +13 -72
  32. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  33. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  34. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  35. package/lib/commonjs/checkForUpdate.js +32 -23
  36. package/lib/commonjs/checkForUpdate.js.map +1 -1
  37. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +97 -28
  38. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  39. package/lib/commonjs/error.js +4 -13
  40. package/lib/commonjs/error.js.map +1 -1
  41. package/lib/commonjs/fetchJSON.js +9 -2
  42. package/lib/commonjs/fetchJSON.js.map +1 -1
  43. package/lib/commonjs/httpClient.js +46 -29
  44. package/lib/commonjs/httpClient.js.map +1 -1
  45. package/lib/commonjs/httpClient.spec.js +57 -5
  46. package/lib/commonjs/httpClient.spec.js.map +1 -1
  47. package/lib/commonjs/index.js +31 -14
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/index.spec.js +56 -11
  50. package/lib/commonjs/index.spec.js.map +1 -1
  51. package/lib/commonjs/native.js +15 -14
  52. package/lib/commonjs/native.js.map +1 -1
  53. package/lib/commonjs/native.spec.js +128 -22
  54. package/lib/commonjs/native.spec.js.map +1 -1
  55. package/lib/commonjs/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +28 -21
  56. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +1 -0
  57. package/lib/commonjs/notifyAppReadyInsights.js +188 -0
  58. package/lib/commonjs/notifyAppReadyInsights.js.map +1 -0
  59. package/lib/commonjs/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +161 -29
  60. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +1 -0
  61. package/lib/commonjs/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
  62. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +1 -0
  63. package/lib/commonjs/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +14 -7
  64. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +1 -0
  65. package/lib/commonjs/sdkVersion.js +1 -1
  66. package/lib/commonjs/sdkVersion.js.map +1 -1
  67. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  68. package/lib/commonjs/store.js +3 -10
  69. package/lib/commonjs/store.js.map +1 -1
  70. package/lib/commonjs/store.notification.spec.js +12 -4
  71. package/lib/commonjs/store.notification.spec.js.map +1 -1
  72. package/lib/commonjs/store.spec.js +2 -136
  73. package/lib/commonjs/store.spec.js.map +1 -1
  74. package/lib/commonjs/wrap.js +11 -12
  75. package/lib/commonjs/wrap.js.map +1 -1
  76. package/lib/commonjs/wrap.spec.js +97 -25
  77. package/lib/commonjs/wrap.spec.js.map +1 -1
  78. package/lib/module/checkForUpdate.js +32 -23
  79. package/lib/module/checkForUpdate.js.map +1 -1
  80. package/lib/module/checkForUpdate.releaseCatalog.spec.js +97 -28
  81. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  82. package/lib/module/error.js +4 -13
  83. package/lib/module/error.js.map +1 -1
  84. package/lib/module/fetchJSON.js +7 -1
  85. package/lib/module/fetchJSON.js.map +1 -1
  86. package/lib/module/httpClient.js +48 -31
  87. package/lib/module/httpClient.js.map +1 -1
  88. package/lib/module/httpClient.spec.js +57 -5
  89. package/lib/module/httpClient.spec.js.map +1 -1
  90. package/lib/module/index.js +32 -15
  91. package/lib/module/index.js.map +1 -1
  92. package/lib/module/index.spec.js +56 -11
  93. package/lib/module/index.spec.js.map +1 -1
  94. package/lib/module/native.js +13 -12
  95. package/lib/module/native.js.map +1 -1
  96. package/lib/module/native.spec.js +128 -22
  97. package/lib/module/native.spec.js.map +1 -1
  98. package/lib/module/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +23 -16
  99. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +1 -0
  100. package/lib/module/notifyAppReadyInsights.js +181 -0
  101. package/lib/module/notifyAppReadyInsights.js.map +1 -0
  102. package/lib/module/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +153 -21
  103. package/lib/module/notifyAppReadyInsights.spec.js.map +1 -0
  104. package/lib/module/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
  105. package/lib/module/notifyAppReadyInsights.test-utils.js.map +1 -0
  106. package/lib/module/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +11 -4
  107. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +1 -0
  108. package/lib/module/sdkVersion.js +1 -1
  109. package/lib/module/sdkVersion.js.map +1 -1
  110. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  111. package/lib/module/store.js +3 -10
  112. package/lib/module/store.js.map +1 -1
  113. package/lib/module/store.notification.spec.js +12 -4
  114. package/lib/module/store.notification.spec.js.map +1 -1
  115. package/lib/module/store.spec.js +2 -136
  116. package/lib/module/store.spec.js.map +1 -1
  117. package/lib/module/wrap.js +11 -12
  118. package/lib/module/wrap.js.map +1 -1
  119. package/lib/module/wrap.spec.js +99 -25
  120. package/lib/module/wrap.spec.js.map +1 -1
  121. package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -3
  122. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/error.d.ts +4 -13
  124. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/fetchJSON.d.ts +4 -0
  126. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/httpClient.d.ts +6 -6
  128. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/index.d.ts +4 -7
  130. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/native.d.ts +5 -9
  132. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +25 -0
  134. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +8 -0
  136. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  138. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +8 -35
  140. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/store.d.ts +1 -3
  142. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/wrap.types.d.ts +5 -8
  144. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  145. package/lib/typescript/module/checkForUpdate.d.ts +2 -3
  146. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  147. package/lib/typescript/module/error.d.ts +4 -13
  148. package/lib/typescript/module/error.d.ts.map +1 -1
  149. package/lib/typescript/module/fetchJSON.d.ts +4 -0
  150. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  151. package/lib/typescript/module/httpClient.d.ts +6 -6
  152. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  153. package/lib/typescript/module/index.d.ts +4 -7
  154. package/lib/typescript/module/index.d.ts.map +1 -1
  155. package/lib/typescript/module/native.d.ts +5 -9
  156. package/lib/typescript/module/native.d.ts.map +1 -1
  157. package/lib/typescript/module/notifyAppReadyInsights.d.ts +25 -0
  158. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +1 -0
  159. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +8 -0
  160. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
  161. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  162. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  163. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +8 -35
  164. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  165. package/lib/typescript/module/store.d.ts +1 -3
  166. package/lib/typescript/module/store.d.ts.map +1 -1
  167. package/lib/typescript/module/wrap.types.d.ts +5 -8
  168. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  169. package/package.json +3 -3
  170. package/src/checkForUpdate.releaseCatalog.spec.ts +137 -61
  171. package/src/checkForUpdate.ts +35 -29
  172. package/src/error.ts +4 -14
  173. package/src/fetchJSON.ts +12 -1
  174. package/src/httpClient.spec.ts +70 -5
  175. package/src/httpClient.ts +78 -64
  176. package/src/index.spec.ts +59 -11
  177. package/src/index.ts +41 -17
  178. package/src/native.spec.ts +133 -22
  179. package/src/native.ts +21 -32
  180. package/src/{notifyAppReadyAnalytics.boundary.spec.ts → notifyAppReadyInsights.boundary.spec.ts} +25 -17
  181. package/src/{notifyAppReadyAnalytics.spec.ts → notifyAppReadyInsights.spec.ts} +141 -22
  182. package/src/{notifyAppReadyAnalytics.test-utils.ts → notifyAppReadyInsights.test-utils.ts} +4 -4
  183. package/src/{notifyAppReadyAnalytics.transport.spec.ts → notifyAppReadyInsights.transport.spec.ts} +13 -5
  184. package/src/notifyAppReadyInsights.ts +293 -0
  185. package/src/sdkVersion.ts +1 -1
  186. package/src/specs/NativeHotUpdater.ts +8 -36
  187. package/src/store.notification.spec.ts +13 -3
  188. package/src/store.spec.ts +0 -157
  189. package/src/store.ts +2 -23
  190. package/src/wrap.spec.ts +109 -31
  191. package/src/wrap.tsx +11 -11
  192. package/src/wrap.types.ts +5 -8
  193. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  194. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  195. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  196. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  197. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  198. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  199. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  200. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  201. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  202. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  203. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  204. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  205. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  206. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  207. package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
  208. package/lib/commonjs/notifyAppReadyAnalytics.js +0 -133
  209. package/lib/commonjs/notifyAppReadyAnalytics.js.map +0 -1
  210. package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +0 -1
  211. package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +0 -1
  212. package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
  213. package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
  214. package/lib/module/notifyAppReadyAnalytics.js +0 -128
  215. package/lib/module/notifyAppReadyAnalytics.js.map +0 -1
  216. package/lib/module/notifyAppReadyAnalytics.spec.js.map +0 -1
  217. package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +0 -1
  218. package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
  219. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts +0 -13
  220. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +0 -1
  221. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
  222. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
  223. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts +0 -13
  224. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +0 -1
  225. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
  226. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
  227. package/src/notifyAppReadyAnalytics.ts +0 -194
@@ -1,6 +1,7 @@
1
1
  import type { ArtifactInfo, ReleaseCatalog } from "@hot-updater/core";
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
 
4
+ import { FetchJSONResponseError } from "./fetchJSON";
4
5
  import { createHttpClient } from "./httpClient";
5
6
 
6
7
  const mocks = vi.hoisted(() => {
@@ -18,12 +19,28 @@ vi.mock("./releaseCatalogCache", () => ({
18
19
  }));
19
20
 
20
21
  vi.mock("./fetchJSON", () => ({
22
+ FetchJSONResponseError: class FetchJSONResponseError extends Error {
23
+ constructor(
24
+ readonly status: number,
25
+ statusText: string,
26
+ ) {
27
+ super(statusText);
28
+ }
29
+ },
21
30
  fetchJSON: mocks.fetchJSON,
22
31
  }));
23
32
 
24
33
  const artifact: ArtifactInfo = {
25
- fileHash: "bundle-hash",
26
- fileUrl: "/storage/bundle.zip",
34
+ artifactProtocolVersion: 1,
35
+ assets: {
36
+ "index.ios.bundle": {
37
+ file: { url: "/storage/index.bundle.br" },
38
+ fileHash: "bundle-hash",
39
+ },
40
+ },
41
+ manifestFileHash: "manifest-hash",
42
+ manifestUrl: "/storage/manifest.json",
43
+ archiveUrl: "/storage/bundle.tar.br",
27
44
  };
28
45
 
29
46
  const catalog: ReleaseCatalog = {
@@ -93,14 +110,24 @@ describe("private HotUpdater HTTP client", () => {
93
110
  }),
94
111
  ).resolves.toEqual({
95
112
  ...artifact,
96
- fileUrl: "https://first.example.com/hot-updater/storage/bundle.zip",
113
+ assets: {
114
+ "index.ios.bundle": {
115
+ file: {
116
+ url: "https://first.example.com/hot-updater/storage/index.bundle.br",
117
+ },
118
+ fileHash: "bundle-hash",
119
+ },
120
+ },
121
+ manifestUrl:
122
+ "https://first.example.com/hot-updater/storage/manifest.json",
123
+ archiveUrl: "https://first.example.com/hot-updater/storage/bundle.tar.br",
97
124
  });
98
125
 
99
126
  expect(resolveBaseURL).toHaveBeenCalledOnce();
100
127
  expect(mocks.fetchJSON).toHaveBeenCalledWith({
101
128
  requestHeaders: undefined,
102
129
  requestTimeout: undefined,
103
- url: "https://first.example.com/hot-updater/artifacts/target/from/current",
130
+ url: "https://first.example.com/hot-updater/artifacts/v1/target/from/current",
104
131
  });
105
132
 
106
133
  await client.createSession();
@@ -110,7 +137,12 @@ describe("private HotUpdater HTTP client", () => {
110
137
  it("rejects non-storage relative artifact URLs", async () => {
111
138
  mocks.fetchJSON.mockResolvedValue({
112
139
  ...artifact,
113
- fileUrl: "/private/bundle.zip",
140
+ assets: {
141
+ "index.ios.bundle": {
142
+ file: { url: "/private/index.bundle.br" },
143
+ fileHash: "bundle-hash",
144
+ },
145
+ },
114
146
  });
115
147
  const session = await createHttpClient(
116
148
  "https://updates.example.com",
@@ -124,6 +156,39 @@ describe("private HotUpdater HTTP client", () => {
124
156
  ).rejects.toThrow("client-relative storage paths");
125
157
  });
126
158
 
159
+ it("rejects a legacy artifact response", async () => {
160
+ mocks.fetchJSON.mockResolvedValue({
161
+ fileHash: "archive-hash",
162
+ fileUrl: "/storage/bundle.zip",
163
+ });
164
+ const session = await createHttpClient(
165
+ "https://updates.example.com",
166
+ ).createSession();
167
+
168
+ await expect(
169
+ session.resolveArtifact({
170
+ currentBundleId: "current",
171
+ targetBundleId: "target",
172
+ }),
173
+ ).rejects.toThrow("does not support artifact protocol 1");
174
+ });
175
+
176
+ it("reports an old server without the v1 endpoint explicitly", async () => {
177
+ mocks.fetchJSON.mockRejectedValue(
178
+ new FetchJSONResponseError(404, "Not Found"),
179
+ );
180
+ const session = await createHttpClient(
181
+ "https://updates.example.com",
182
+ ).createSession();
183
+
184
+ await expect(
185
+ session.resolveArtifact({
186
+ currentBundleId: "current",
187
+ targetBundleId: "target",
188
+ }),
189
+ ).rejects.toThrow("does not support artifact protocol 1");
190
+ });
191
+
127
192
  it("requires a functional baseURL to resolve to a non-empty string", async () => {
128
193
  await expect(createHttpClient(() => "").createSession()).rejects.toThrow(
129
194
  "baseURL function must return a non-empty string",
package/src/httpClient.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import {
2
+ ARTIFACT_PROTOCOL_VERSION,
2
3
  encodeChannelKey,
3
4
  type ArtifactInfo,
4
5
  type ReleaseCatalog,
5
6
  } from "@hot-updater/core";
6
7
  import { canonicalizeAppVersion } from "@hot-updater/plugin-core";
7
8
 
8
- import { fetchJSON } from "./fetchJSON";
9
+ import { fetchJSON, FetchJSONResponseError } from "./fetchJSON";
9
10
  import { fetchReleaseCatalogWithCache } from "./releaseCatalogCache";
10
11
  import { HOT_UPDATER_SDK_VERSION } from "./sdkVersion";
11
12
  import type { HotUpdaterBaseURL } from "./types";
@@ -27,7 +28,7 @@ export interface ArtifactRequest {
27
28
  readonly requestTimeout?: number;
28
29
  }
29
30
 
30
- interface AnalyticsEventCommonParams {
31
+ interface InsightsEventCommonParams {
31
32
  readonly installId: string;
32
33
  readonly userId?: string;
33
34
  readonly username?: string;
@@ -42,30 +43,30 @@ interface AnalyticsEventCommonParams {
42
43
  readonly requestTimeout?: number;
43
44
  }
44
45
 
45
- type AnalyticsTransitionEventParams = AnalyticsEventCommonParams & {
46
- readonly type: "UPDATE_APPLIED" | "RECOVERED" | "RELEASE_ADOPTED";
46
+ type InsightsTransitionEventParams = InsightsEventCommonParams & {
47
+ readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "RECOVERED";
47
48
  readonly fromBundleId: string;
48
49
  readonly toBundleId: string;
49
50
  readonly updateStrategy: "fingerprint" | "appVersion";
50
51
  };
51
52
 
52
- type AnalyticsUnchangedEventParams = AnalyticsEventCommonParams & {
53
+ type InsightsUnchangedEventParams = InsightsEventCommonParams & {
53
54
  readonly type: "UNCHANGED";
54
55
  readonly fromBundleId: null;
55
56
  readonly toBundleId: string;
56
57
  readonly updateStrategy: null;
57
58
  };
58
59
 
59
- export type AnalyticsEventParams =
60
- | AnalyticsTransitionEventParams
61
- | AnalyticsUnchangedEventParams;
60
+ export type InsightsEventParams =
61
+ | InsightsTransitionEventParams
62
+ | InsightsUnchangedEventParams;
62
63
 
63
64
  export interface HotUpdaterHttpSession {
64
65
  fetchReleaseCatalog: (
65
66
  params: ReleaseCatalogRequest,
66
67
  ) => Promise<ReleaseCatalog>;
67
68
  resolveArtifact: (params: ArtifactRequest) => Promise<ArtifactInfo>;
68
- sendAnalyticsEvent: (params: AnalyticsEventParams) => Promise<void>;
69
+ sendInsightsEvent: (params: InsightsEventParams) => Promise<void>;
69
70
  }
70
71
 
71
72
  export interface HotUpdaterHttpClient {
@@ -101,55 +102,57 @@ const resolveArtifactUrls = (
101
102
  info: ArtifactInfo,
102
103
  ): ArtifactInfo => ({
103
104
  ...info,
104
- fileUrl:
105
- info.fileUrl === null ? null : resolveArtifactUrl(baseURL, info.fileUrl),
106
- ...(info.manifestUrl === undefined
107
- ? {}
108
- : {
109
- manifestUrl:
110
- info.manifestUrl === null
111
- ? null
112
- : resolveArtifactUrl(baseURL, info.manifestUrl),
113
- }),
114
- ...(info.changedAssets === undefined
115
- ? {}
116
- : {
117
- changedAssets:
118
- info.changedAssets === null
119
- ? null
120
- : Object.fromEntries(
121
- Object.entries(info.changedAssets).map(([path, asset]) => [
122
- path,
123
- {
124
- ...asset,
125
- ...(asset.file
126
- ? {
127
- file: {
128
- ...asset.file,
129
- url: resolveArtifactUrl(baseURL, asset.file.url),
130
- },
131
- }
132
- : {}),
133
- ...(asset.patch
134
- ? {
135
- patch: {
136
- ...asset.patch,
137
- patchUrl: resolveArtifactUrl(
138
- baseURL,
139
- asset.patch.patchUrl,
140
- ),
141
- },
142
- }
143
- : {}),
144
- },
145
- ]),
146
- ),
147
- }),
105
+ manifestUrl: resolveArtifactUrl(baseURL, info.manifestUrl),
106
+ ...(info.archiveUrl
107
+ ? { archiveUrl: resolveArtifactUrl(baseURL, info.archiveUrl) }
108
+ : {}),
109
+ assets: Object.fromEntries(
110
+ Object.entries(info.assets).map(([path, asset]) => [
111
+ path,
112
+ {
113
+ ...asset,
114
+ file: {
115
+ ...asset.file,
116
+ url: resolveArtifactUrl(baseURL, asset.file.url),
117
+ },
118
+ ...(asset.patch
119
+ ? {
120
+ patch: {
121
+ ...asset.patch,
122
+ patchUrl: resolveArtifactUrl(baseURL, asset.patch.patchUrl),
123
+ },
124
+ }
125
+ : {}),
126
+ },
127
+ ]),
128
+ ),
148
129
  });
149
130
 
150
- const sendAnalyticsEvent = async (
131
+ const requireArtifactProtocolV1 = (info: ArtifactInfo): ArtifactInfo => {
132
+ if (
133
+ info.artifactProtocolVersion !== ARTIFACT_PROTOCOL_VERSION ||
134
+ !info.assets ||
135
+ !info.manifestUrl ||
136
+ !info.manifestFileHash ||
137
+ (info.archiveUrl !== undefined && typeof info.archiveUrl !== "string")
138
+ ) {
139
+ throw new Error(
140
+ `Server does not support artifact protocol ${ARTIFACT_PROTOCOL_VERSION}.`,
141
+ );
142
+ }
143
+ for (const asset of Object.values(info.assets)) {
144
+ if (!asset.file?.url || !asset.fileHash) {
145
+ throw new Error(
146
+ "Artifact protocol 1 requires an original file for every asset.",
147
+ );
148
+ }
149
+ }
150
+ return info;
151
+ };
152
+
153
+ const sendInsightsEvent = async (
151
154
  baseURL: string,
152
- params: AnalyticsEventParams,
155
+ params: InsightsEventParams,
153
156
  ): Promise<void> => {
154
157
  const controller = new AbortController();
155
158
  const timeoutId = setTimeout(() => {
@@ -241,16 +244,27 @@ const createSession = (baseURL: string): HotUpdaterHttpSession => ({
241
244
  });
242
245
  },
243
246
  resolveArtifact: async (params): Promise<ArtifactInfo> => {
244
- const info = await fetchJSON<ArtifactInfo>({
245
- requestHeaders: params.requestHeaders,
246
- requestTimeout: params.requestTimeout,
247
- url: `${baseURL}/artifacts/${encodeURIComponent(
248
- params.targetBundleId,
249
- )}/from/${encodeURIComponent(params.currentBundleId)}`,
250
- });
251
- return resolveArtifactUrls(baseURL, info);
247
+ let info: ArtifactInfo;
248
+ try {
249
+ info = await fetchJSON<ArtifactInfo>({
250
+ requestHeaders: params.requestHeaders,
251
+ requestTimeout: params.requestTimeout,
252
+ url: `${baseURL}/artifacts/v1/${encodeURIComponent(
253
+ params.targetBundleId,
254
+ )}/from/${encodeURIComponent(params.currentBundleId)}`,
255
+ });
256
+ } catch (error) {
257
+ if (error instanceof FetchJSONResponseError && error.status === 404) {
258
+ throw new Error(
259
+ `Server does not support artifact protocol ${ARTIFACT_PROTOCOL_VERSION}.`,
260
+ { cause: error },
261
+ );
262
+ }
263
+ throw error;
264
+ }
265
+ return resolveArtifactUrls(baseURL, requireArtifactProtocolV1(info));
252
266
  },
253
- sendAnalyticsEvent: (params) => sendAnalyticsEvent(baseURL, params),
267
+ sendInsightsEvent: (params) => sendInsightsEvent(baseURL, params),
254
268
  });
255
269
 
256
270
  /** Creates the private HTTP client used by HotUpdater.init and HotUpdater.wrap. */
package/src/index.spec.ts CHANGED
@@ -22,7 +22,7 @@ const mocks = vi.hoisted(() => {
22
22
  })),
23
23
  getAppVersion: vi.fn(() => "1.0.0"),
24
24
  getBaseURL: vi.fn(() => null),
25
- getBundleId: vi.fn(() => "bundle-id"),
25
+ getUpdateId: vi.fn(() => "release-id"),
26
26
  getChannel: vi.fn(() => "production"),
27
27
  getCohort: vi.fn(() => "123"),
28
28
  getCrashHistory: vi.fn(() => []),
@@ -31,7 +31,6 @@ const mocks = vi.hoisted(() => {
31
31
  getInstallId: vi.fn(() => "install-id"),
32
32
  getManifest: vi.fn(() => null),
33
33
  getMinBundleId: vi.fn(() => "min-bundle-id"),
34
- getReleaseId: vi.fn(async () => null),
35
34
  init: vi.fn(),
36
35
  isChannelSwitched: vi.fn(() => false),
37
36
  notifyAppReady: vi.fn(() => ({ status: "UNCHANGED" as const })),
@@ -45,6 +44,8 @@ const mocks = vi.hoisted(() => {
45
44
  };
46
45
  });
47
46
 
47
+ vi.mock("react-native", () => ({ Platform: { OS: "ios" } }));
48
+
48
49
  vi.mock("./httpClient", () => ({
49
50
  createHttpClient: mocks.createHttpClient,
50
51
  }));
@@ -57,9 +58,11 @@ vi.mock("./native", () => ({
57
58
  addListener: mocks.addListener,
58
59
  clearCrashHistory: mocks.clearCrashHistory,
59
60
  getPublicActiveUpdateState: mocks.getActiveUpdateState,
61
+ getActiveUpdateState: mocks.getActiveUpdateState,
62
+ getBundleId: () => "bundle-id",
60
63
  getAppVersion: mocks.getAppVersion,
61
64
  getBaseURL: mocks.getBaseURL,
62
- getBundleId: mocks.getBundleId,
65
+ getUpdateId: mocks.getUpdateId,
63
66
  getChannel: mocks.getChannel,
64
67
  getCohort: mocks.getCohort,
65
68
  getCrashHistory: mocks.getCrashHistory,
@@ -68,7 +71,6 @@ vi.mock("./native", () => ({
68
71
  getInstallId: mocks.getInstallId,
69
72
  getManifest: mocks.getManifest,
70
73
  getMinBundleId: mocks.getMinBundleId,
71
- getReleaseId: mocks.getReleaseId,
72
74
  isChannelSwitched: mocks.isChannelSwitched,
73
75
  notifyAppReady: mocks.notifyAppReady,
74
76
  reload: mocks.reload,
@@ -97,13 +99,21 @@ describe("HotUpdater client initialization", () => {
97
99
  mocks.wrap.mockImplementation((Component: unknown) => Component);
98
100
  });
99
101
 
102
+ it("exposes the console ID through the existing getter only", async () => {
103
+ const HotUpdater = await importHotUpdater();
104
+
105
+ expect(HotUpdater.getBundleId()).toBe("release-id");
106
+ expect(HotUpdater).not.toHaveProperty("getReleaseId");
107
+ expect(HotUpdater).not.toHaveProperty("getUpdateId");
108
+ });
109
+
100
110
  it("initializes a private HTTP client from the required baseURL", async () => {
101
111
  const client = { createSession: vi.fn() };
102
112
  mocks.createHttpClient.mockReturnValue(client as never);
103
113
  const HotUpdater = await importHotUpdater();
104
114
 
105
115
  HotUpdater.init({
106
- analytics: true,
116
+ insights: true,
107
117
  baseURL: "https://updates.example.com",
108
118
  requestHeaders: { Authorization: "Bearer token" },
109
119
  requestTimeout: 1000,
@@ -113,7 +123,7 @@ describe("HotUpdater client initialization", () => {
113
123
  "https://updates.example.com",
114
124
  );
115
125
  expect(mocks.init).toHaveBeenCalledWith({
116
- analytics: true,
126
+ insights: true,
117
127
  client,
118
128
  requestHeaders: { Authorization: "Bearer token" },
119
129
  requestTimeout: 1000,
@@ -130,6 +140,44 @@ describe("HotUpdater client initialization", () => {
130
140
  expect(resolveBaseURL).not.toHaveBeenCalled();
131
141
  });
132
142
 
143
+ it("enables insights by default for init and wrap", async () => {
144
+ const HotUpdater = await importHotUpdater();
145
+
146
+ HotUpdater.init({ baseURL: "https://updates.example.com" });
147
+ HotUpdater.wrap({
148
+ baseURL: "https://updates.example.com",
149
+ updateStrategy: "appVersion",
150
+ });
151
+
152
+ expect(mocks.init).toHaveBeenCalledWith(
153
+ expect.objectContaining({ insights: true }),
154
+ );
155
+ expect(mocks.wrap).toHaveBeenCalledWith(
156
+ expect.objectContaining({ insights: true }),
157
+ );
158
+ });
159
+
160
+ it("preserves an explicit insights opt-out", async () => {
161
+ const HotUpdater = await importHotUpdater();
162
+
163
+ HotUpdater.init({
164
+ baseURL: "https://updates.example.com",
165
+ insights: false,
166
+ });
167
+ HotUpdater.wrap({
168
+ baseURL: "https://updates.example.com",
169
+ insights: false,
170
+ updateStrategy: "appVersion",
171
+ });
172
+
173
+ expect(mocks.init).toHaveBeenCalledWith(
174
+ expect.objectContaining({ insights: false }),
175
+ );
176
+ expect(mocks.wrap).toHaveBeenCalledWith(
177
+ expect.objectContaining({ insights: false }),
178
+ );
179
+ });
180
+
133
181
  it("requires baseURL and rejects the removed resolver shape", async () => {
134
182
  const HotUpdater = await importHotUpdater();
135
183
 
@@ -171,13 +219,13 @@ describe("HotUpdater client initialization", () => {
171
219
  expect(wrapOptions.updateStrategy).toBe("appVersion");
172
220
  });
173
221
 
174
- it("merges settings and carries the analytics gate to checks", async () => {
222
+ it("merges settings and carries the insights gate to checks", async () => {
175
223
  const client = { createSession: vi.fn() };
176
224
  mocks.createHttpClient.mockReturnValue(client as never);
177
225
  const checkOnError = vi.fn();
178
226
  const HotUpdater = await importHotUpdater();
179
227
  HotUpdater.init({
180
- analytics: true,
228
+ insights: true,
181
229
  baseURL: "https://updates.example.com",
182
230
  requestHeaders: { Authorization: "Bearer token" },
183
231
  requestTimeout: 1000,
@@ -190,7 +238,7 @@ describe("HotUpdater client initialization", () => {
190
238
  });
191
239
 
192
240
  expect(mocks.checkForUpdate).toHaveBeenCalledWith({
193
- analytics: true,
241
+ insights: true,
194
242
  client,
195
243
  onError: checkOnError,
196
244
  requestHeaders: {
@@ -208,13 +256,13 @@ describe("HotUpdater client initialization", () => {
208
256
  const HotUpdater = await importHotUpdater();
209
257
 
210
258
  HotUpdater.wrap({
211
- analytics: true,
259
+ insights: true,
212
260
  baseURL: "https://updates.example.com",
213
261
  updateStrategy: "appVersion",
214
262
  });
215
263
 
216
264
  expect(mocks.wrap).toHaveBeenCalledWith({
217
- analytics: true,
265
+ insights: true,
218
266
  client,
219
267
  updateStrategy: "appVersion",
220
268
  });
package/src/index.ts CHANGED
@@ -7,10 +7,12 @@ import { createHttpClient, type HotUpdaterHttpClient } from "./httpClient";
7
7
  import {
8
8
  addListener,
9
9
  getPublicActiveUpdateState,
10
+ getActiveUpdateState,
11
+ getBundleId,
10
12
  clearCrashHistory,
11
13
  getAppVersion,
12
14
  getBaseURL,
13
- getBundleId,
15
+ getUpdateId,
14
16
  getChannel,
15
17
  getCohort,
16
18
  getCrashHistory,
@@ -19,7 +21,6 @@ import {
19
21
  getInstallId,
20
22
  getManifest,
21
23
  getMinBundleId,
22
- getReleaseId,
23
24
  isChannelSwitched,
24
25
  notifyAppReady,
25
26
  reload,
@@ -30,6 +31,7 @@ import {
30
31
  type UpdateParams,
31
32
  updateBundle,
32
33
  } from "./native";
34
+ import { reportBundleDownloaded } from "./notifyAppReadyInsights";
33
35
  import { hotUpdaterStore } from "./store";
34
36
  import {
35
37
  type AutoUpdateOptions,
@@ -101,12 +103,13 @@ function createHotUpdaterClient() {
101
103
 
102
104
  // Global configuration stored from wrap
103
105
  const globalConfig: {
104
- analytics?: boolean;
106
+ insights: boolean;
105
107
  client: HotUpdaterHttpClient | null;
106
108
  requestHeaders?: Record<string, string>;
107
109
  requestTimeout?: number;
108
110
  onError?: (error: unknown) => void;
109
111
  } = {
112
+ insights: true,
110
113
  client: null,
111
114
  };
112
115
 
@@ -158,6 +161,7 @@ function createHotUpdaterClient() {
158
161
  const { baseURL, ...rest } = autoOptions;
159
162
  return {
160
163
  ...rest,
164
+ insights: rest.insights ?? true,
161
165
  client: createHttpClient(baseURL),
162
166
  };
163
167
  }
@@ -177,6 +181,7 @@ function createHotUpdaterClient() {
177
181
  const { baseURL, ...baseURLRest } = rest;
178
182
  return {
179
183
  ...baseURLRest,
184
+ insights: baseURLRest.insights ?? true,
180
185
  client: createHttpClient(baseURL),
181
186
  };
182
187
  }
@@ -188,7 +193,7 @@ function createHotUpdaterClient() {
188
193
  normalizedOptions: InternalInitOptions | InternalWrapOptions,
189
194
  options: HotUpdaterOptions | HotUpdaterInitOptions,
190
195
  ) => {
191
- globalConfig.analytics = normalizedOptions.analytics;
196
+ globalConfig.insights = normalizedOptions.insights ?? true;
192
197
  globalConfig.client = normalizedOptions.client;
193
198
  globalConfig.requestHeaders = options.requestHeaders;
194
199
  globalConfig.requestTimeout = options.requestTimeout;
@@ -333,16 +338,14 @@ function createHotUpdaterClient() {
333
338
  getActiveUpdateState: async () => getPublicActiveUpdateState(),
334
339
 
335
340
  /**
336
- * Fetches the current bundle ID of the app.
341
+ * Returns the selected update ID, matching the ID shown in the console.
342
+ * A staged update can change this ID before the app reloads.
337
343
  */
338
- getBundleId,
344
+ getBundleId: getUpdateId,
339
345
 
340
346
  /** Returns the minimum bundle ID based on the native app build time. */
341
347
  getMinBundleId,
342
348
 
343
- /** Returns the active Release identity, or null for BUILTIN/legacy state. */
344
- getReleaseId,
345
-
346
349
  /**
347
350
  * Fetches the current manifest for the active bundle.
348
351
  */
@@ -448,7 +451,7 @@ function createHotUpdaterClient() {
448
451
 
449
452
  const mergedConfig: InternalCheckForUpdateOptions = {
450
453
  ...config,
451
- analytics: globalConfig.analytics,
454
+ insights: globalConfig.insights,
452
455
  client,
453
456
  requestHeaders: {
454
457
  ...globalConfig.requestHeaders,
@@ -466,8 +469,6 @@ function createHotUpdaterClient() {
466
469
  *
467
470
  * @param {UpdateBundleParams} params - Parameters object required for bundle update
468
471
  * @param {string} params.bundleId - The bundle ID of the app
469
- * @param {string|null} params.fileUrl - The URL of the zip file
470
- *
471
472
  * @returns {Promise<boolean>} Whether the update was successful
472
473
  *
473
474
  * @example
@@ -491,9 +492,34 @@ function createHotUpdaterClient() {
491
492
  * }
492
493
  * ```
493
494
  */
494
- updateBundle: (params: UpdateParams) => {
495
- ensureGlobalClient("updateBundle");
496
- return updateBundle(params);
495
+ updateBundle: async (params: UpdateParams) => {
496
+ const client = ensureGlobalClient("updateBundle");
497
+ const fromBundleId = getBundleId();
498
+ const state = getActiveUpdateState();
499
+ const active = state.activeSelection;
500
+ const downloaded = await updateBundle(params);
501
+ if (downloaded) {
502
+ const selection = getActiveUpdateState().activeSelection;
503
+ await reportBundleDownloaded(
504
+ { ...globalConfig, client },
505
+ {
506
+ fromBundleId,
507
+ fromReleaseId:
508
+ active?.bundleId === fromBundleId
509
+ ? active.releaseId
510
+ : state.stableSelection?.bundleId === fromBundleId
511
+ ? state.stableSelection.releaseId
512
+ : null,
513
+ toBundleId: params.bundleId,
514
+ toReleaseId: selection?.releaseId ?? null,
515
+ channel: params.channel ?? getChannel(),
516
+ updateStrategy: selection?.scopeKey?.startsWith("v1:fingerprint:")
517
+ ? "fingerprint"
518
+ : "appVersion",
519
+ },
520
+ );
521
+ }
522
+ return downloaded;
497
523
  },
498
524
 
499
525
  /**
@@ -507,10 +533,8 @@ function createHotUpdaterClient() {
507
533
  hotUpdaterStore.setState({
508
534
  artifactType: null,
509
535
  details: null,
510
- downloadedBytes: undefined,
511
536
  isUpdateDownloaded: false,
512
537
  progress: 0,
513
- totalBytes: undefined,
514
538
  });
515
539
  }
516
540
  return ok;