@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.
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1 -1
- package/lib/commonjs/checkForUpdate.js +4 -4
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +34 -23
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/commonjs/httpClient.js +2 -2
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +8 -6
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +49 -11
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +8 -6
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +107 -6
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +21 -21
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyAnalytics.js → notifyAppReadyInsights.js} +27 -27
- package/lib/commonjs/notifyAppReadyInsights.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +29 -29
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +7 -7
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +1 -0
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/wrap.js +5 -5
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +58 -25
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/checkForUpdate.js +4 -4
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +34 -23
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/module/httpClient.js +2 -2
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +9 -7
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +49 -11
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +6 -4
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +107 -6
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +16 -16
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +1 -0
- package/lib/module/{notifyAppReadyAnalytics.js → notifyAppReadyInsights.js} +27 -27
- package/lib/module/notifyAppReadyInsights.js.map +1 -0
- package/lib/module/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +21 -21
- package/lib/module/notifyAppReadyInsights.spec.js.map +1 -0
- package/lib/module/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +1 -0
- package/lib/module/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +4 -4
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +1 -0
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/wrap.js +4 -4
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +60 -25
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +5 -5
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -5
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +4 -3
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/{module/notifyAppReadyAnalytics.d.ts → commonjs/notifyAppReadyInsights.d.ts} +2 -2
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +1 -0
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +8 -0
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +5 -6
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +5 -5
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -5
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +4 -3
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/{commonjs/notifyAppReadyAnalytics.d.ts → module/notifyAppReadyInsights.d.ts} +2 -2
- package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +1 -0
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +8 -0
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/wrap.types.d.ts +5 -6
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/checkForUpdate.releaseCatalog.spec.ts +71 -56
- package/src/checkForUpdate.ts +5 -9
- package/src/httpClient.ts +10 -10
- package/src/index.spec.ts +55 -11
- package/src/index.ts +10 -10
- package/src/native.spec.ts +110 -6
- package/src/native.ts +9 -8
- package/src/{notifyAppReadyAnalytics.boundary.spec.ts → notifyAppReadyInsights.boundary.spec.ts} +17 -17
- package/src/{notifyAppReadyAnalytics.spec.ts → notifyAppReadyInsights.spec.ts} +22 -22
- package/src/{notifyAppReadyAnalytics.test-utils.ts → notifyAppReadyInsights.test-utils.ts} +4 -4
- package/src/{notifyAppReadyAnalytics.transport.spec.ts → notifyAppReadyInsights.transport.spec.ts} +5 -5
- package/src/{notifyAppReadyAnalytics.ts → notifyAppReadyInsights.ts} +33 -33
- package/src/sdkVersion.ts +1 -1
- package/src/wrap.spec.ts +70 -31
- package/src/wrap.tsx +4 -4
- package/src/wrap.types.ts +5 -6
- package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyAnalytics.js.map +0 -1
- package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.spec.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
|
|
451
|
+
insights: globalConfig.insights,
|
|
452
452
|
client,
|
|
453
453
|
requestHeaders: {
|
|
454
454
|
...globalConfig.requestHeaders,
|
package/src/native.spec.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
277
|
+
insightsEvent: null,
|
|
188
278
|
pending: false,
|
|
189
279
|
result: { status: "UNCHANGED" },
|
|
190
280
|
});
|
|
191
281
|
});
|
|
192
282
|
|
|
193
|
-
it("returns UNCHANGED when automatic
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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
|
|
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
|
|
895
|
+
const getNotifyAppReadyInsightsEvent = (
|
|
895
896
|
result: RawNotifyAppReadyResult,
|
|
896
|
-
):
|
|
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
|
-
|
|
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
|
-
|
|
949
|
+
insightsEvent: getNotifyAppReadyInsightsEvent(rawResult),
|
|
949
950
|
pending: rawResult.status === "PENDING",
|
|
950
951
|
};
|
|
951
952
|
};
|
package/src/{notifyAppReadyAnalytics.boundary.spec.ts → notifyAppReadyInsights.boundary.spec.ts}
RENAMED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
|
|
4
|
+
NotifyAppReadyInsightsEvent,
|
|
5
5
|
NotifyAppReadyResult,
|
|
6
6
|
} from "./native";
|
|
7
7
|
import {
|
|
8
8
|
createNotifyReadResult,
|
|
9
9
|
stubNotifyFrame,
|
|
10
|
-
} from "./
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
87
|
+
sendInsightsEvent,
|
|
88
88
|
})),
|
|
89
89
|
},
|
|
90
|
-
|
|
90
|
+
sendInsightsEvent,
|
|
91
91
|
};
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
-
describe("automatic notifyAppReady
|
|
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
|
-
|
|
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
|
|
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,
|
|
166
|
+
const { client, sendInsightsEvent } = createClient();
|
|
167
167
|
const { init } = await import("./wrap");
|
|
168
168
|
|
|
169
|
-
init({
|
|
169
|
+
init({ insights: true, client, onError, onNotifyAppReady });
|
|
170
170
|
await vi.runOnlyPendingTimersAsync();
|
|
171
171
|
|
|
172
|
-
expect(
|
|
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
|
|
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,
|
|
200
|
+
const { client, sendInsightsEvent } = createClient();
|
|
201
201
|
const { init } = await import("./wrap");
|
|
202
202
|
|
|
203
|
-
init({
|
|
203
|
+
init({ insights: true, client, onError, onNotifyAppReady });
|
|
204
204
|
await vi.runOnlyPendingTimersAsync();
|
|
205
205
|
|
|
206
|
-
expect(
|
|
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
|
|
210
|
+
"[HotUpdater] Automatic notifyAppReady insights failed:",
|
|
211
211
|
expect.any(Error),
|
|
212
212
|
);
|
|
213
213
|
warn.mockRestore();
|