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

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 (122) hide show
  1. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1 -1
  2. package/lib/commonjs/checkForUpdate.js +4 -4
  3. package/lib/commonjs/checkForUpdate.js.map +1 -1
  4. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +34 -23
  5. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  6. package/lib/commonjs/httpClient.js +2 -2
  7. package/lib/commonjs/httpClient.js.map +1 -1
  8. package/lib/commonjs/index.js +8 -6
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/index.spec.js +49 -11
  11. package/lib/commonjs/index.spec.js.map +1 -1
  12. package/lib/commonjs/native.js +8 -6
  13. package/lib/commonjs/native.js.map +1 -1
  14. package/lib/commonjs/native.spec.js +107 -6
  15. package/lib/commonjs/native.spec.js.map +1 -1
  16. package/lib/commonjs/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +21 -21
  17. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +1 -0
  18. package/lib/commonjs/{notifyAppReadyAnalytics.js → notifyAppReadyInsights.js} +27 -27
  19. package/lib/commonjs/notifyAppReadyInsights.js.map +1 -0
  20. package/lib/commonjs/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +29 -29
  21. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +1 -0
  22. package/lib/commonjs/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
  23. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +1 -0
  24. package/lib/commonjs/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +7 -7
  25. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +1 -0
  26. package/lib/commonjs/sdkVersion.js +1 -1
  27. package/lib/commonjs/wrap.js +5 -5
  28. package/lib/commonjs/wrap.js.map +1 -1
  29. package/lib/commonjs/wrap.spec.js +58 -25
  30. package/lib/commonjs/wrap.spec.js.map +1 -1
  31. package/lib/module/checkForUpdate.js +4 -4
  32. package/lib/module/checkForUpdate.js.map +1 -1
  33. package/lib/module/checkForUpdate.releaseCatalog.spec.js +34 -23
  34. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  35. package/lib/module/httpClient.js +2 -2
  36. package/lib/module/httpClient.js.map +1 -1
  37. package/lib/module/index.js +9 -7
  38. package/lib/module/index.js.map +1 -1
  39. package/lib/module/index.spec.js +49 -11
  40. package/lib/module/index.spec.js.map +1 -1
  41. package/lib/module/native.js +6 -4
  42. package/lib/module/native.js.map +1 -1
  43. package/lib/module/native.spec.js +107 -6
  44. package/lib/module/native.spec.js.map +1 -1
  45. package/lib/module/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +16 -16
  46. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +1 -0
  47. package/lib/module/{notifyAppReadyAnalytics.js → notifyAppReadyInsights.js} +27 -27
  48. package/lib/module/notifyAppReadyInsights.js.map +1 -0
  49. package/lib/module/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +21 -21
  50. package/lib/module/notifyAppReadyInsights.spec.js.map +1 -0
  51. package/lib/module/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
  52. package/lib/module/notifyAppReadyInsights.test-utils.js.map +1 -0
  53. package/lib/module/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +4 -4
  54. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +1 -0
  55. package/lib/module/sdkVersion.js +1 -1
  56. package/lib/module/wrap.js +4 -4
  57. package/lib/module/wrap.js.map +1 -1
  58. package/lib/module/wrap.spec.js +60 -25
  59. package/lib/module/wrap.spec.js.map +1 -1
  60. package/lib/typescript/commonjs/checkForUpdate.d.ts +1 -1
  61. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/httpClient.d.ts +5 -5
  63. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/index.d.ts +4 -5
  65. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/native.d.ts +4 -3
  67. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  68. package/lib/typescript/{module/notifyAppReadyAnalytics.d.ts → commonjs/notifyAppReadyInsights.d.ts} +2 -2
  69. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +8 -0
  71. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
  72. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  73. package/lib/typescript/commonjs/wrap.types.d.ts +5 -6
  74. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  75. package/lib/typescript/module/checkForUpdate.d.ts +1 -1
  76. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  77. package/lib/typescript/module/httpClient.d.ts +5 -5
  78. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  79. package/lib/typescript/module/index.d.ts +4 -5
  80. package/lib/typescript/module/index.d.ts.map +1 -1
  81. package/lib/typescript/module/native.d.ts +4 -3
  82. package/lib/typescript/module/native.d.ts.map +1 -1
  83. package/lib/typescript/{commonjs/notifyAppReadyAnalytics.d.ts → module/notifyAppReadyInsights.d.ts} +2 -2
  84. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +1 -0
  85. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +8 -0
  86. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
  87. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  88. package/lib/typescript/module/wrap.types.d.ts +5 -6
  89. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  90. package/package.json +2 -2
  91. package/src/checkForUpdate.releaseCatalog.spec.ts +71 -56
  92. package/src/checkForUpdate.ts +5 -9
  93. package/src/httpClient.ts +10 -10
  94. package/src/index.spec.ts +55 -11
  95. package/src/index.ts +10 -10
  96. package/src/native.spec.ts +110 -6
  97. package/src/native.ts +9 -8
  98. package/src/{notifyAppReadyAnalytics.boundary.spec.ts → notifyAppReadyInsights.boundary.spec.ts} +17 -17
  99. package/src/{notifyAppReadyAnalytics.spec.ts → notifyAppReadyInsights.spec.ts} +22 -22
  100. package/src/{notifyAppReadyAnalytics.test-utils.ts → notifyAppReadyInsights.test-utils.ts} +4 -4
  101. package/src/{notifyAppReadyAnalytics.transport.spec.ts → notifyAppReadyInsights.transport.spec.ts} +5 -5
  102. package/src/{notifyAppReadyAnalytics.ts → notifyAppReadyInsights.ts} +33 -33
  103. package/src/sdkVersion.ts +1 -1
  104. package/src/wrap.spec.ts +70 -31
  105. package/src/wrap.tsx +4 -4
  106. package/src/wrap.types.ts +5 -6
  107. package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
  108. package/lib/commonjs/notifyAppReadyAnalytics.js.map +0 -1
  109. package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +0 -1
  110. package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +0 -1
  111. package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
  112. package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
  113. package/lib/module/notifyAppReadyAnalytics.js.map +0 -1
  114. package/lib/module/notifyAppReadyAnalytics.spec.js.map +0 -1
  115. package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +0 -1
  116. package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
  117. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +0 -1
  118. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
  119. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
  120. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +0 -1
  121. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
  122. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
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 })),
@@ -59,7 +58,7 @@ vi.mock("./native", () => ({
59
58
  getPublicActiveUpdateState: mocks.getActiveUpdateState,
60
59
  getAppVersion: mocks.getAppVersion,
61
60
  getBaseURL: mocks.getBaseURL,
62
- getBundleId: mocks.getBundleId,
61
+ getUpdateId: mocks.getUpdateId,
63
62
  getChannel: mocks.getChannel,
64
63
  getCohort: mocks.getCohort,
65
64
  getCrashHistory: mocks.getCrashHistory,
@@ -68,7 +67,6 @@ vi.mock("./native", () => ({
68
67
  getInstallId: mocks.getInstallId,
69
68
  getManifest: mocks.getManifest,
70
69
  getMinBundleId: mocks.getMinBundleId,
71
- getReleaseId: mocks.getReleaseId,
72
70
  isChannelSwitched: mocks.isChannelSwitched,
73
71
  notifyAppReady: mocks.notifyAppReady,
74
72
  reload: mocks.reload,
@@ -97,13 +95,21 @@ describe("HotUpdater client initialization", () => {
97
95
  mocks.wrap.mockImplementation((Component: unknown) => Component);
98
96
  });
99
97
 
98
+ it("exposes the console ID through the existing getter only", async () => {
99
+ const HotUpdater = await importHotUpdater();
100
+
101
+ expect(HotUpdater.getBundleId()).toBe("release-id");
102
+ expect(HotUpdater).not.toHaveProperty("getReleaseId");
103
+ expect(HotUpdater).not.toHaveProperty("getUpdateId");
104
+ });
105
+
100
106
  it("initializes a private HTTP client from the required baseURL", async () => {
101
107
  const client = { createSession: vi.fn() };
102
108
  mocks.createHttpClient.mockReturnValue(client as never);
103
109
  const HotUpdater = await importHotUpdater();
104
110
 
105
111
  HotUpdater.init({
106
- analytics: true,
112
+ insights: true,
107
113
  baseURL: "https://updates.example.com",
108
114
  requestHeaders: { Authorization: "Bearer token" },
109
115
  requestTimeout: 1000,
@@ -113,7 +119,7 @@ describe("HotUpdater client initialization", () => {
113
119
  "https://updates.example.com",
114
120
  );
115
121
  expect(mocks.init).toHaveBeenCalledWith({
116
- analytics: true,
122
+ insights: true,
117
123
  client,
118
124
  requestHeaders: { Authorization: "Bearer token" },
119
125
  requestTimeout: 1000,
@@ -130,6 +136,44 @@ describe("HotUpdater client initialization", () => {
130
136
  expect(resolveBaseURL).not.toHaveBeenCalled();
131
137
  });
132
138
 
139
+ it("enables insights by default for init and wrap", async () => {
140
+ const HotUpdater = await importHotUpdater();
141
+
142
+ HotUpdater.init({ baseURL: "https://updates.example.com" });
143
+ HotUpdater.wrap({
144
+ baseURL: "https://updates.example.com",
145
+ updateStrategy: "appVersion",
146
+ });
147
+
148
+ expect(mocks.init).toHaveBeenCalledWith(
149
+ expect.objectContaining({ insights: true }),
150
+ );
151
+ expect(mocks.wrap).toHaveBeenCalledWith(
152
+ expect.objectContaining({ insights: true }),
153
+ );
154
+ });
155
+
156
+ it("preserves an explicit insights opt-out", async () => {
157
+ const HotUpdater = await importHotUpdater();
158
+
159
+ HotUpdater.init({
160
+ baseURL: "https://updates.example.com",
161
+ insights: false,
162
+ });
163
+ HotUpdater.wrap({
164
+ baseURL: "https://updates.example.com",
165
+ insights: false,
166
+ updateStrategy: "appVersion",
167
+ });
168
+
169
+ expect(mocks.init).toHaveBeenCalledWith(
170
+ expect.objectContaining({ insights: false }),
171
+ );
172
+ expect(mocks.wrap).toHaveBeenCalledWith(
173
+ expect.objectContaining({ insights: false }),
174
+ );
175
+ });
176
+
133
177
  it("requires baseURL and rejects the removed resolver shape", async () => {
134
178
  const HotUpdater = await importHotUpdater();
135
179
 
@@ -171,13 +215,13 @@ describe("HotUpdater client initialization", () => {
171
215
  expect(wrapOptions.updateStrategy).toBe("appVersion");
172
216
  });
173
217
 
174
- it("merges settings and carries the analytics gate to checks", async () => {
218
+ it("merges settings and carries the insights gate to checks", async () => {
175
219
  const client = { createSession: vi.fn() };
176
220
  mocks.createHttpClient.mockReturnValue(client as never);
177
221
  const checkOnError = vi.fn();
178
222
  const HotUpdater = await importHotUpdater();
179
223
  HotUpdater.init({
180
- analytics: true,
224
+ insights: true,
181
225
  baseURL: "https://updates.example.com",
182
226
  requestHeaders: { Authorization: "Bearer token" },
183
227
  requestTimeout: 1000,
@@ -190,7 +234,7 @@ describe("HotUpdater client initialization", () => {
190
234
  });
191
235
 
192
236
  expect(mocks.checkForUpdate).toHaveBeenCalledWith({
193
- analytics: true,
237
+ insights: true,
194
238
  client,
195
239
  onError: checkOnError,
196
240
  requestHeaders: {
@@ -208,13 +252,13 @@ describe("HotUpdater client initialization", () => {
208
252
  const HotUpdater = await importHotUpdater();
209
253
 
210
254
  HotUpdater.wrap({
211
- analytics: true,
255
+ insights: true,
212
256
  baseURL: "https://updates.example.com",
213
257
  updateStrategy: "appVersion",
214
258
  });
215
259
 
216
260
  expect(mocks.wrap).toHaveBeenCalledWith({
217
- analytics: true,
261
+ insights: true,
218
262
  client,
219
263
  updateStrategy: "appVersion",
220
264
  });
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  clearCrashHistory,
11
11
  getAppVersion,
12
12
  getBaseURL,
13
- getBundleId,
13
+ getUpdateId,
14
14
  getChannel,
15
15
  getCohort,
16
16
  getCrashHistory,
@@ -19,7 +19,6 @@ import {
19
19
  getInstallId,
20
20
  getManifest,
21
21
  getMinBundleId,
22
- getReleaseId,
23
22
  isChannelSwitched,
24
23
  notifyAppReady,
25
24
  reload,
@@ -101,12 +100,13 @@ function createHotUpdaterClient() {
101
100
 
102
101
  // Global configuration stored from wrap
103
102
  const globalConfig: {
104
- analytics?: boolean;
103
+ insights: boolean;
105
104
  client: HotUpdaterHttpClient | null;
106
105
  requestHeaders?: Record<string, string>;
107
106
  requestTimeout?: number;
108
107
  onError?: (error: unknown) => void;
109
108
  } = {
109
+ insights: true,
110
110
  client: null,
111
111
  };
112
112
 
@@ -158,6 +158,7 @@ function createHotUpdaterClient() {
158
158
  const { baseURL, ...rest } = autoOptions;
159
159
  return {
160
160
  ...rest,
161
+ insights: rest.insights ?? true,
161
162
  client: createHttpClient(baseURL),
162
163
  };
163
164
  }
@@ -177,6 +178,7 @@ function createHotUpdaterClient() {
177
178
  const { baseURL, ...baseURLRest } = rest;
178
179
  return {
179
180
  ...baseURLRest,
181
+ insights: baseURLRest.insights ?? true,
180
182
  client: createHttpClient(baseURL),
181
183
  };
182
184
  }
@@ -188,7 +190,7 @@ function createHotUpdaterClient() {
188
190
  normalizedOptions: InternalInitOptions | InternalWrapOptions,
189
191
  options: HotUpdaterOptions | HotUpdaterInitOptions,
190
192
  ) => {
191
- globalConfig.analytics = normalizedOptions.analytics;
193
+ globalConfig.insights = normalizedOptions.insights ?? true;
192
194
  globalConfig.client = normalizedOptions.client;
193
195
  globalConfig.requestHeaders = options.requestHeaders;
194
196
  globalConfig.requestTimeout = options.requestTimeout;
@@ -333,16 +335,14 @@ function createHotUpdaterClient() {
333
335
  getActiveUpdateState: async () => getPublicActiveUpdateState(),
334
336
 
335
337
  /**
336
- * Fetches the current bundle ID of the app.
338
+ * Returns the selected update ID, matching the ID shown in the console.
339
+ * A staged update can change this ID before the app reloads.
337
340
  */
338
- getBundleId,
341
+ getBundleId: getUpdateId,
339
342
 
340
343
  /** Returns the minimum bundle ID based on the native app build time. */
341
344
  getMinBundleId,
342
345
 
343
- /** Returns the active Release identity, or null for BUILTIN/legacy state. */
344
- getReleaseId,
345
-
346
346
  /**
347
347
  * Fetches the current manifest for the active bundle.
348
348
  */
@@ -448,7 +448,7 @@ function createHotUpdaterClient() {
448
448
 
449
449
  const mergedConfig: InternalCheckForUpdateOptions = {
450
450
  ...config,
451
- analytics: globalConfig.analytics,
451
+ insights: globalConfig.insights,
452
452
  client,
453
453
  requestHeaders: {
454
454
  ...globalConfig.requestHeaders,
@@ -52,6 +52,13 @@ vi.mock("./specs/NativeHotUpdater", () => ({
52
52
  describe("notifyAppReady", () => {
53
53
  beforeEach(() => {
54
54
  vi.resetModules();
55
+ nativeModuleMock.getActiveUpdateState.mockReset();
56
+ nativeModuleMock.getActiveUpdateState.mockReturnValue({
57
+ activeSelection: null,
58
+ stableSelection: null,
59
+ verificationPending: false,
60
+ highestSeenCatalogs: {},
61
+ });
55
62
  nativeModuleMock.notifyAppReady.mockReset();
56
63
  nativeModuleMock.getBaseURL.mockReset();
57
64
  nativeModuleMock.getBundleId.mockReset();
@@ -122,6 +129,89 @@ describe("notifyAppReady", () => {
122
129
  });
123
130
  });
124
131
 
132
+ it("tracks same-file promotions without changing artifact or crash identities", async () => {
133
+ const state = {
134
+ activeSelection: {
135
+ kind: "BUNDLE",
136
+ bundleId: "bundle-123",
137
+ releaseId: "release-1",
138
+ },
139
+ };
140
+ nativeModuleMock.getActiveUpdateState.mockImplementation(() => state);
141
+ nativeModuleMock.getBundleId.mockReturnValue("bundle-123");
142
+ nativeModuleMock.getManifest.mockReturnValue({ assets: {} });
143
+ nativeModuleMock.getCrashHistory.mockReturnValue(["bundle-failed"]);
144
+ const { getUpdateId, getBundleId, getManifest, getCrashHistory } =
145
+ await import("./native");
146
+
147
+ expect(getUpdateId()).toBe("release-1");
148
+ state.activeSelection.releaseId = "release-2";
149
+ expect(getUpdateId()).toBe("release-2");
150
+ expect(getBundleId()).toBe("bundle-123");
151
+ expect(getManifest().bundleId).toBe("bundle-123");
152
+ expect(getCrashHistory()).toEqual(["bundle-failed"]);
153
+ });
154
+
155
+ it.each([
156
+ [null, "min-bundle-id"],
157
+ ["legacy-bundle", "legacy-bundle"],
158
+ ])(
159
+ "preserves the fallback ID for native bundle %s",
160
+ async (bundleId, expected) => {
161
+ nativeModuleMock.getBundleId.mockReturnValue(bundleId);
162
+ const { getUpdateId } = await import("./native");
163
+
164
+ expect(getUpdateId()).toBe(expected);
165
+ },
166
+ );
167
+
168
+ it("reports a staged update while the manifest still identifies the running file", async () => {
169
+ const runningSelection = {
170
+ kind: "BUNDLE",
171
+ bundleId: "running-file",
172
+ releaseId: "running-update",
173
+ };
174
+ const state = {
175
+ activeSelection: runningSelection,
176
+ stableSelection: runningSelection,
177
+ };
178
+ nativeModuleMock.getActiveUpdateState.mockImplementation(() => state);
179
+ nativeModuleMock.getBundleId.mockReturnValue("running-file");
180
+ nativeModuleMock.getManifest.mockReturnValue({
181
+ assets: {},
182
+ bundleId: "running-file",
183
+ });
184
+ const { getUpdateId, getBundleId, getManifest } = await import("./native");
185
+
186
+ expect(getUpdateId()).toBe("running-update");
187
+ state.activeSelection = {
188
+ kind: "BUNDLE",
189
+ bundleId: "staged-file",
190
+ releaseId: "staged-update",
191
+ };
192
+
193
+ expect(getUpdateId()).toBe("staged-update");
194
+ expect(getBundleId()).toBe("running-file");
195
+ expect(getManifest().bundleId).toBe("running-file");
196
+ });
197
+
198
+ it("reports an explicit built-in rollback by its update ID", async () => {
199
+ nativeModuleMock.getActiveUpdateState.mockReturnValue(
200
+ JSON.stringify({
201
+ activeSelection: {
202
+ kind: "EMBEDDED",
203
+ releaseId: "rollback-1",
204
+ bundleId: "min-bundle-id",
205
+ },
206
+ }),
207
+ );
208
+ nativeModuleMock.getBundleId.mockReturnValue(null);
209
+ const { getUpdateId, getBundleId } = await import("./native");
210
+
211
+ expect(getUpdateId()).toBe("rollback-1");
212
+ expect(getBundleId()).toBe("min-bundle-id");
213
+ });
214
+
125
215
  it("normalizes legacy PROMOTED launch reports to UPDATE_APPLIED", async () => {
126
216
  nativeModuleMock.notifyAppReady.mockReturnValue(
127
217
  JSON.stringify({
@@ -157,7 +247,7 @@ describe("notifyAppReady", () => {
157
247
  toBundleId: "bundle-122",
158
248
  });
159
249
  expect(readNotifyAppReady()).toEqual({
160
- analyticsEvent: {
250
+ insightsEvent: {
161
251
  fromBundleId: "bundle-123",
162
252
  toBundleId: "bundle-122",
163
253
  type: "RECOVERED",
@@ -184,13 +274,13 @@ describe("notifyAppReady", () => {
184
274
 
185
275
  expect(notifyAppReady()).toEqual({ status: "UNCHANGED" });
186
276
  expect(readNotifyAppReady()).toEqual({
187
- analyticsEvent: null,
277
+ insightsEvent: null,
188
278
  pending: false,
189
279
  result: { status: "UNCHANGED" },
190
280
  });
191
281
  });
192
282
 
193
- it("returns UNCHANGED when automatic analytics metadata is incomplete", async () => {
283
+ it("returns UNCHANGED when automatic insights metadata is incomplete", async () => {
194
284
  nativeModuleMock.notifyAppReady.mockReturnValue({
195
285
  status: "UPDATE_APPLIED",
196
286
  });
@@ -199,7 +289,7 @@ describe("notifyAppReady", () => {
199
289
 
200
290
  expect(notifyAppReady()).toEqual({ status: "UNCHANGED" });
201
291
  expect(readNotifyAppReady()).toEqual({
202
- analyticsEvent: null,
292
+ insightsEvent: null,
203
293
  pending: false,
204
294
  result: { status: "UNCHANGED" },
205
295
  });
@@ -212,7 +302,7 @@ describe("notifyAppReady", () => {
212
302
 
213
303
  expect(notifyAppReady()).toEqual({ status: "UNCHANGED" });
214
304
  expect(readNotifyAppReady()).toEqual({
215
- analyticsEvent: null,
305
+ insightsEvent: null,
216
306
  pending: false,
217
307
  result: { status: "UNCHANGED" },
218
308
  });
@@ -225,7 +315,7 @@ describe("notifyAppReady", () => {
225
315
 
226
316
  expect(notifyAppReady()).toEqual({ status: "UNCHANGED" });
227
317
  expect(readNotifyAppReady()).toEqual({
228
- analyticsEvent: null,
318
+ insightsEvent: null,
229
319
  pending: true,
230
320
  result: { status: "UNCHANGED" },
231
321
  });
@@ -728,6 +818,20 @@ describe("notifyAppReady", () => {
728
818
  expect(getInstallId()).toBe("install-123");
729
819
  });
730
820
 
821
+ it("normalizes an omitted native fingerprint constant to null", async () => {
822
+ nativeModuleMock.getConstants.mockReturnValue({
823
+ APP_VERSION: null,
824
+ CHANNEL: "production",
825
+ DEFAULT_CHANNEL: "production",
826
+ FINGERPRINT_HASH: undefined as unknown as null,
827
+ MIN_BUNDLE_ID: "min-bundle-id",
828
+ });
829
+
830
+ const { getFingerprintHash } = await import("./native");
831
+
832
+ expect(getFingerprintHash()).toBeNull();
833
+ });
834
+
731
835
  it("throws when native SDK does not expose getInstallId", async () => {
732
836
  const nativeModule = nativeModuleMock as typeof nativeModuleMock & {
733
837
  getInstallId?: typeof nativeModuleMock.getInstallId;
package/src/native.ts CHANGED
@@ -599,8 +599,9 @@ export const getMinBundleId = (): string => {
599
599
  return constants.MIN_BUNDLE_ID;
600
600
  };
601
601
 
602
- export const getReleaseId = async (): Promise<string | null> =>
603
- getActiveUpdateState().activeSelection?.releaseId ?? null;
602
+ /** Returns the selected update ID shown in the console, including staged updates. */
603
+ export const getUpdateId = (): string =>
604
+ getActiveUpdateState().activeSelection?.releaseId ?? getBundleId();
604
605
 
605
606
  /**
606
607
  * Fetches the current bundle version id.
@@ -687,7 +688,7 @@ export const isChannelSwitched = (): boolean => {
687
688
  */
688
689
  export const getFingerprintHash = (): string | null => {
689
690
  const constants = HotUpdaterNative.getConstants();
690
- return constants.FINGERPRINT_HASH;
691
+ return constants.FINGERPRINT_HASH ?? null;
691
692
  };
692
693
 
693
694
  export const getInstallId = (): string => {
@@ -783,7 +784,7 @@ export type NotifyAppReadyResult =
783
784
  toReleaseId?: string;
784
785
  };
785
786
 
786
- export type NotifyAppReadyAnalyticsEvent = {
787
+ export type NotifyAppReadyInsightsEvent = {
787
788
  type: "UPDATE_APPLIED" | "RECOVERED";
788
789
  fromBundleId: string;
789
790
  toBundleId: string;
@@ -891,9 +892,9 @@ const getNotifyAppReadyTransition = (
891
892
  return null;
892
893
  };
893
894
 
894
- const getNotifyAppReadyAnalyticsEvent = (
895
+ const getNotifyAppReadyInsightsEvent = (
895
896
  result: RawNotifyAppReadyResult,
896
- ): NotifyAppReadyAnalyticsEvent | null => {
897
+ ): NotifyAppReadyInsightsEvent | null => {
897
898
  const transition = getNotifyAppReadyTransition(result);
898
899
 
899
900
  if (!transition || !isPersistedUpdateStrategy(result.updateStrategy)) {
@@ -938,14 +939,14 @@ const normalizeNotifyAppReadyResult = (
938
939
 
939
940
  export const readNotifyAppReady = (): {
940
941
  result: NotifyAppReadyResult;
941
- analyticsEvent: NotifyAppReadyAnalyticsEvent | null;
942
+ insightsEvent: NotifyAppReadyInsightsEvent | null;
942
943
  pending: boolean;
943
944
  } => {
944
945
  const rawResult = readRawNotifyAppReadyResult();
945
946
 
946
947
  return {
947
948
  result: normalizeNotifyAppReadyResult(rawResult),
948
- analyticsEvent: getNotifyAppReadyAnalyticsEvent(rawResult),
949
+ insightsEvent: getNotifyAppReadyInsightsEvent(rawResult),
949
950
  pending: rawResult.status === "PENDING",
950
951
  };
951
952
  };
@@ -1,13 +1,13 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
3
  import type {
4
- NotifyAppReadyAnalyticsEvent,
4
+ NotifyAppReadyInsightsEvent,
5
5
  NotifyAppReadyResult,
6
6
  } from "./native";
7
7
  import {
8
8
  createNotifyReadResult,
9
9
  stubNotifyFrame,
10
- } from "./notifyAppReadyAnalytics.test-utils";
10
+ } from "./notifyAppReadyInsights.test-utils";
11
11
 
12
12
  const platformState = vi.hoisted((): { OS: "ios" | "android" } => ({
13
13
  OS: "ios",
@@ -48,7 +48,7 @@ const mocks = vi.hoisted(() => ({
48
48
  getPersistedUserIdentity: vi.fn(() => ({})),
49
49
  readNotifyAppReady: vi.fn<
50
50
  () => {
51
- analyticsEvent: NotifyAppReadyAnalyticsEvent | null;
51
+ insightsEvent: NotifyAppReadyInsightsEvent | null;
52
52
  pending: boolean;
53
53
  result: NotifyAppReadyResult;
54
54
  }
@@ -78,20 +78,20 @@ vi.mock("./specs/NativeHotUpdater", () => ({
78
78
  }));
79
79
 
80
80
  const createClient = () => {
81
- const sendAnalyticsEvent = vi.fn().mockResolvedValue(undefined);
81
+ const sendInsightsEvent = vi.fn().mockResolvedValue(undefined);
82
82
  return {
83
83
  client: {
84
84
  createSession: vi.fn(async () => ({
85
85
  fetchReleaseCatalog: vi.fn(),
86
86
  resolveArtifact: vi.fn(),
87
- sendAnalyticsEvent,
87
+ sendInsightsEvent,
88
88
  })),
89
89
  },
90
- sendAnalyticsEvent,
90
+ sendInsightsEvent,
91
91
  };
92
92
  };
93
93
 
94
- describe("automatic notifyAppReady analytics boundaries", () => {
94
+ describe("automatic notifyAppReady insights boundaries", () => {
95
95
  beforeEach(() => {
96
96
  vi.resetModules();
97
97
  vi.unstubAllGlobals();
@@ -124,7 +124,7 @@ describe("automatic notifyAppReady analytics boundaries", () => {
124
124
  const { init } = await import("./wrap");
125
125
 
126
126
  init({
127
- analytics: true,
127
+ insights: true,
128
128
  client: {
129
129
  createSession: vi.fn(async () => {
130
130
  throw new Error("baseURL unavailable");
@@ -138,7 +138,7 @@ describe("automatic notifyAppReady analytics boundaries", () => {
138
138
  expect(onError).not.toHaveBeenCalled();
139
139
  expect(onNotifyAppReady).toHaveBeenCalledWith({ status: "UNCHANGED" });
140
140
  expect(warn).toHaveBeenCalledWith(
141
- "[HotUpdater] Automatic notifyAppReady analytics failed:",
141
+ "[HotUpdater] Automatic notifyAppReady insights failed:",
142
142
  expect.objectContaining({
143
143
  message: "baseURL unavailable",
144
144
  }),
@@ -163,17 +163,17 @@ describe("automatic notifyAppReady analytics boundaries", () => {
163
163
  } else {
164
164
  mocks.getInstallId.mockReturnValue(value);
165
165
  }
166
- const { client, sendAnalyticsEvent } = createClient();
166
+ const { client, sendInsightsEvent } = createClient();
167
167
  const { init } = await import("./wrap");
168
168
 
169
- init({ analytics: true, client, onError, onNotifyAppReady });
169
+ init({ insights: true, client, onError, onNotifyAppReady });
170
170
  await vi.runOnlyPendingTimersAsync();
171
171
 
172
- expect(sendAnalyticsEvent).not.toHaveBeenCalled();
172
+ expect(sendInsightsEvent).not.toHaveBeenCalled();
173
173
  expect(onError).not.toHaveBeenCalled();
174
174
  expect(onNotifyAppReady).toHaveBeenCalledWith({ status: "UNCHANGED" });
175
175
  expect(warn).toHaveBeenCalledWith(
176
- "[HotUpdater] Automatic notifyAppReady analytics failed:",
176
+ "[HotUpdater] Automatic notifyAppReady insights failed:",
177
177
  expect.any(Error),
178
178
  );
179
179
  warn.mockRestore();
@@ -197,17 +197,17 @@ describe("automatic notifyAppReady analytics boundaries", () => {
197
197
  } else {
198
198
  mocks.getBundleId.mockReturnValue(value);
199
199
  }
200
- const { client, sendAnalyticsEvent } = createClient();
200
+ const { client, sendInsightsEvent } = createClient();
201
201
  const { init } = await import("./wrap");
202
202
 
203
- init({ analytics: true, client, onError, onNotifyAppReady });
203
+ init({ insights: true, client, onError, onNotifyAppReady });
204
204
  await vi.runOnlyPendingTimersAsync();
205
205
 
206
- expect(sendAnalyticsEvent).not.toHaveBeenCalled();
206
+ expect(sendInsightsEvent).not.toHaveBeenCalled();
207
207
  expect(onError).not.toHaveBeenCalled();
208
208
  expect(onNotifyAppReady).toHaveBeenCalledWith({ status: "UNCHANGED" });
209
209
  expect(warn).toHaveBeenCalledWith(
210
- "[HotUpdater] Automatic notifyAppReady analytics failed:",
210
+ "[HotUpdater] Automatic notifyAppReady insights failed:",
211
211
  expect.any(Error),
212
212
  );
213
213
  warn.mockRestore();