@hot-updater/react-native 1.0.0-rc.0 → 1.0.0-rc.14
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/proguard-rules.pro +15 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1 -1
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +7 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
- package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +97 -0
- package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +44 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +102 -20
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
- package/lib/commonjs/checkForUpdate.js +26 -14
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +89 -25
- 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 +32 -11
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +59 -14
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +15 -11
- 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/notifyAppReadyInsights.js +185 -0
- package/lib/commonjs/notifyAppReadyInsights.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +102 -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/sdkVersion.js.map +1 -1
- package/lib/commonjs/wrap.js +10 -9
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +90 -25
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/checkForUpdate.js +27 -15
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +89 -25
- 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 +33 -12
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +59 -14
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +12 -8
- 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/notifyAppReadyInsights.js +178 -0
- package/lib/module/notifyAppReadyInsights.js.map +1 -0
- package/lib/module/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +94 -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/sdkVersion.js.map +1 -1
- package/lib/module/wrap.js +10 -9
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +92 -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 +6 -6
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +6 -7
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +9 -6
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +25 -0
- 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/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts.map +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 +6 -6
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +6 -7
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +9 -6
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/notifyAppReadyInsights.d.ts +25 -0
- 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/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts.map +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 +3 -3
- package/src/checkForUpdate.releaseCatalog.spec.ts +128 -58
- package/src/checkForUpdate.ts +32 -20
- package/src/httpClient.ts +11 -11
- package/src/index.spec.ts +62 -14
- package/src/index.ts +44 -16
- package/src/native.spec.ts +110 -6
- package/src/native.ts +15 -12
- package/src/{notifyAppReadyAnalytics.boundary.spec.ts → notifyAppReadyInsights.boundary.spec.ts} +17 -17
- package/src/{notifyAppReadyAnalytics.spec.ts → notifyAppReadyInsights.spec.ts} +81 -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/notifyAppReadyInsights.ts +281 -0
- package/src/sdkVersion.ts +1 -1
- package/src/wrap.spec.ts +101 -31
- package/src/wrap.tsx +11 -9
- package/src/wrap.types.ts +5 -6
- package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyAnalytics.js +0 -133
- 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 +0 -128
- 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 +0 -13
- 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 +0 -13
- 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/notifyAppReadyAnalytics.ts +0 -194
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(() => []),
|
|
@@ -30,8 +30,7 @@ const mocks = vi.hoisted(() => {
|
|
|
30
30
|
getFingerprintHash: vi.fn(() => null),
|
|
31
31
|
getInstallId: vi.fn(() => "install-id"),
|
|
32
32
|
getManifest: vi.fn(() => null),
|
|
33
|
-
|
|
34
|
-
getReleaseId: vi.fn(async () => null),
|
|
33
|
+
getMinBundleId: vi.fn(() => "min-bundle-id"),
|
|
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
|
-
|
|
65
|
+
getUpdateId: mocks.getUpdateId,
|
|
63
66
|
getChannel: mocks.getChannel,
|
|
64
67
|
getCohort: mocks.getCohort,
|
|
65
68
|
getCrashHistory: mocks.getCrashHistory,
|
|
@@ -67,8 +70,7 @@ vi.mock("./native", () => ({
|
|
|
67
70
|
getFingerprintHash: mocks.getFingerprintHash,
|
|
68
71
|
getInstallId: mocks.getInstallId,
|
|
69
72
|
getManifest: mocks.getManifest,
|
|
70
|
-
|
|
71
|
-
getReleaseId: mocks.getReleaseId,
|
|
73
|
+
getMinBundleId: mocks.getMinBundleId,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
265
|
+
insights: true,
|
|
218
266
|
client,
|
|
219
267
|
updateStrategy: "appVersion",
|
|
220
268
|
});
|
|
@@ -283,7 +331,7 @@ describe("HotUpdater client initialization", () => {
|
|
|
283
331
|
const HotUpdater = await importHotUpdater();
|
|
284
332
|
|
|
285
333
|
expect(HotUpdater.getInstallId()).toBe("install-id");
|
|
286
|
-
expect(HotUpdater.
|
|
334
|
+
expect(HotUpdater.getMinBundleId()).toBe("min-bundle-id");
|
|
287
335
|
HotUpdater.setUser({ userId: "user-123", username: "alice" });
|
|
288
336
|
expect(mocks.setUser).toHaveBeenCalledWith({
|
|
289
337
|
userId: "user-123",
|
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
|
-
|
|
15
|
+
getUpdateId,
|
|
14
16
|
getChannel,
|
|
15
17
|
getCohort,
|
|
16
18
|
getCrashHistory,
|
|
@@ -18,8 +20,7 @@ import {
|
|
|
18
20
|
getFingerprintHash,
|
|
19
21
|
getInstallId,
|
|
20
22
|
getManifest,
|
|
21
|
-
|
|
22
|
-
getReleaseId,
|
|
23
|
+
getMinBundleId,
|
|
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
|
-
|
|
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.
|
|
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,15 +338,13 @@ function createHotUpdaterClient() {
|
|
|
333
338
|
getActiveUpdateState: async () => getPublicActiveUpdateState(),
|
|
334
339
|
|
|
335
340
|
/**
|
|
336
|
-
*
|
|
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
|
-
/**
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
/** Returns the active Release identity, or null for BUILTIN/legacy state. */
|
|
344
|
-
getReleaseId,
|
|
346
|
+
/** Returns the minimum bundle ID based on the native app build time. */
|
|
347
|
+
getMinBundleId,
|
|
345
348
|
|
|
346
349
|
/**
|
|
347
350
|
* Fetches the current manifest for the active bundle.
|
|
@@ -448,7 +451,7 @@ function createHotUpdaterClient() {
|
|
|
448
451
|
|
|
449
452
|
const mergedConfig: InternalCheckForUpdateOptions = {
|
|
450
453
|
...config,
|
|
451
|
-
|
|
454
|
+
insights: globalConfig.insights,
|
|
452
455
|
client,
|
|
453
456
|
requestHeaders: {
|
|
454
457
|
...globalConfig.requestHeaders,
|
|
@@ -491,9 +494,34 @@ function createHotUpdaterClient() {
|
|
|
491
494
|
* }
|
|
492
495
|
* ```
|
|
493
496
|
*/
|
|
494
|
-
updateBundle: (params: UpdateParams) => {
|
|
495
|
-
ensureGlobalClient("updateBundle");
|
|
496
|
-
|
|
497
|
+
updateBundle: async (params: UpdateParams) => {
|
|
498
|
+
const client = ensureGlobalClient("updateBundle");
|
|
499
|
+
const fromBundleId = getBundleId();
|
|
500
|
+
const state = getActiveUpdateState();
|
|
501
|
+
const active = state.activeSelection;
|
|
502
|
+
const downloaded = await updateBundle(params);
|
|
503
|
+
if (downloaded && params.fileUrl !== null) {
|
|
504
|
+
const selection = getActiveUpdateState().activeSelection;
|
|
505
|
+
await reportBundleDownloaded(
|
|
506
|
+
{ ...globalConfig, client },
|
|
507
|
+
{
|
|
508
|
+
fromBundleId,
|
|
509
|
+
fromReleaseId:
|
|
510
|
+
active?.bundleId === fromBundleId
|
|
511
|
+
? active.releaseId
|
|
512
|
+
: state.stableSelection?.bundleId === fromBundleId
|
|
513
|
+
? state.stableSelection.releaseId
|
|
514
|
+
: null,
|
|
515
|
+
toBundleId: params.bundleId,
|
|
516
|
+
toReleaseId: selection?.releaseId ?? null,
|
|
517
|
+
channel: params.channel ?? getChannel(),
|
|
518
|
+
updateStrategy: selection?.scopeKey?.startsWith("v1:fingerprint:")
|
|
519
|
+
? "fingerprint"
|
|
520
|
+
: "appVersion",
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
return downloaded;
|
|
497
525
|
},
|
|
498
526
|
|
|
499
527
|
/**
|
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
|
@@ -215,7 +215,7 @@ const getNativeBundleId = (): string | null => {
|
|
|
215
215
|
};
|
|
216
216
|
|
|
217
217
|
const resolveBundleId = (bundleId: string | null): string => {
|
|
218
|
-
return !bundleId || bundleId === NIL_UUID ?
|
|
218
|
+
return !bundleId || bundleId === NIL_UUID ? getMinBundleId() : bundleId;
|
|
219
219
|
};
|
|
220
220
|
|
|
221
221
|
const getFreshBundleId = (): string => {
|
|
@@ -588,17 +588,20 @@ export function setReloadBehavior(
|
|
|
588
588
|
}
|
|
589
589
|
|
|
590
590
|
/**
|
|
591
|
-
* Fetches the
|
|
591
|
+
* Fetches the minimum bundle id created from the native app build time.
|
|
592
592
|
*
|
|
593
|
-
*
|
|
593
|
+
* Release Catalog selection uses this value as its minimum Release id.
|
|
594
|
+
*
|
|
595
|
+
* @returns {string} The minimum bundle id.
|
|
594
596
|
*/
|
|
595
|
-
export const
|
|
597
|
+
export const getMinBundleId = (): string => {
|
|
596
598
|
const constants = HotUpdaterNative.getConstants();
|
|
597
599
|
return constants.MIN_BUNDLE_ID;
|
|
598
600
|
};
|
|
599
601
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
+
/** Returns the selected update ID shown in the console, including staged updates. */
|
|
603
|
+
export const getUpdateId = (): string =>
|
|
604
|
+
getActiveUpdateState().activeSelection?.releaseId ?? getBundleId();
|
|
602
605
|
|
|
603
606
|
/**
|
|
604
607
|
* Fetches the current bundle version id.
|
|
@@ -685,7 +688,7 @@ export const isChannelSwitched = (): boolean => {
|
|
|
685
688
|
*/
|
|
686
689
|
export const getFingerprintHash = (): string | null => {
|
|
687
690
|
const constants = HotUpdaterNative.getConstants();
|
|
688
|
-
return constants.FINGERPRINT_HASH;
|
|
691
|
+
return constants.FINGERPRINT_HASH ?? null;
|
|
689
692
|
};
|
|
690
693
|
|
|
691
694
|
export const getInstallId = (): string => {
|
|
@@ -781,7 +784,7 @@ export type NotifyAppReadyResult =
|
|
|
781
784
|
toReleaseId?: string;
|
|
782
785
|
};
|
|
783
786
|
|
|
784
|
-
export type
|
|
787
|
+
export type NotifyAppReadyInsightsEvent = {
|
|
785
788
|
type: "UPDATE_APPLIED" | "RECOVERED";
|
|
786
789
|
fromBundleId: string;
|
|
787
790
|
toBundleId: string;
|
|
@@ -889,9 +892,9 @@ const getNotifyAppReadyTransition = (
|
|
|
889
892
|
return null;
|
|
890
893
|
};
|
|
891
894
|
|
|
892
|
-
const
|
|
895
|
+
const getNotifyAppReadyInsightsEvent = (
|
|
893
896
|
result: RawNotifyAppReadyResult,
|
|
894
|
-
):
|
|
897
|
+
): NotifyAppReadyInsightsEvent | null => {
|
|
895
898
|
const transition = getNotifyAppReadyTransition(result);
|
|
896
899
|
|
|
897
900
|
if (!transition || !isPersistedUpdateStrategy(result.updateStrategy)) {
|
|
@@ -936,14 +939,14 @@ const normalizeNotifyAppReadyResult = (
|
|
|
936
939
|
|
|
937
940
|
export const readNotifyAppReady = (): {
|
|
938
941
|
result: NotifyAppReadyResult;
|
|
939
|
-
|
|
942
|
+
insightsEvent: NotifyAppReadyInsightsEvent | null;
|
|
940
943
|
pending: boolean;
|
|
941
944
|
} => {
|
|
942
945
|
const rawResult = readRawNotifyAppReadyResult();
|
|
943
946
|
|
|
944
947
|
return {
|
|
945
948
|
result: normalizeNotifyAppReadyResult(rawResult),
|
|
946
|
-
|
|
949
|
+
insightsEvent: getNotifyAppReadyInsightsEvent(rawResult),
|
|
947
950
|
pending: rawResult.status === "PENDING",
|
|
948
951
|
};
|
|
949
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();
|