@hot-updater/react-native 1.0.0-rc.1 → 1.0.0-rc.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/proguard-rules.pro +17 -2
- package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +651 -637
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +10 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +0 -13
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +10 -14
- 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/main/java/com/hotupdater/OkHttpDownloadService.kt +19 -0
- package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
- package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
- package/android/src/newarch/HotUpdater.kt +7 -9
- package/android/src/newarch/HotUpdaterModule.kt +40 -20
- package/android/src/oldarch/HotUpdater.kt +7 -9
- package/android/src/oldarch/HotUpdaterModule.kt +40 -20
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +1126 -74
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -5
- package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +97 -0
- package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +44 -0
- package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +206 -0
- package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +773 -1016
- package/ios/HotUpdater/Internal/BundleMetadata.swift +5 -0
- package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
- package/ios/HotUpdater/Internal/HotUpdater.mm +5 -27
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +149 -51
- package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
- package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +13 -72
- 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 +32 -23
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +97 -28
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/commonjs/error.js +4 -13
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/fetchJSON.js +9 -2
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/httpClient.js +46 -29
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/httpClient.spec.js +57 -5
- package/lib/commonjs/httpClient.spec.js.map +1 -1
- package/lib/commonjs/index.js +31 -14
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +56 -11
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +15 -14
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +128 -22
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +28 -21
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +1 -0
- package/lib/commonjs/notifyAppReadyInsights.js +188 -0
- package/lib/commonjs/notifyAppReadyInsights.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +161 -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} +14 -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/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/store.js +3 -10
- package/lib/commonjs/store.js.map +1 -1
- package/lib/commonjs/store.notification.spec.js +12 -4
- package/lib/commonjs/store.notification.spec.js.map +1 -1
- package/lib/commonjs/store.spec.js +2 -136
- package/lib/commonjs/store.spec.js.map +1 -1
- package/lib/commonjs/wrap.js +11 -12
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +97 -25
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/checkForUpdate.js +32 -23
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +97 -28
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/module/error.js +4 -13
- package/lib/module/error.js.map +1 -1
- package/lib/module/fetchJSON.js +7 -1
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/httpClient.js +48 -31
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/httpClient.spec.js +57 -5
- package/lib/module/httpClient.spec.js.map +1 -1
- package/lib/module/index.js +32 -15
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +56 -11
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +13 -12
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +128 -22
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +23 -16
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +1 -0
- package/lib/module/notifyAppReadyInsights.js +181 -0
- package/lib/module/notifyAppReadyInsights.js.map +1 -0
- package/lib/module/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +153 -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} +11 -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/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/store.js +3 -10
- package/lib/module/store.js.map +1 -1
- package/lib/module/store.notification.spec.js +12 -4
- package/lib/module/store.notification.spec.js.map +1 -1
- package/lib/module/store.spec.js +2 -136
- package/lib/module/store.spec.js.map +1 -1
- package/lib/module/wrap.js +11 -12
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +99 -25
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -3
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/error.d.ts +4 -13
- package/lib/typescript/commonjs/error.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchJSON.d.ts +4 -0
- package/lib/typescript/commonjs/fetchJSON.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 +4 -7
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +5 -9
- 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/specs/NativeHotUpdater.d.ts +8 -35
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/store.d.ts +1 -3
- package/lib/typescript/commonjs/store.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +5 -8
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts +2 -3
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/error.d.ts +4 -13
- package/lib/typescript/module/error.d.ts.map +1 -1
- package/lib/typescript/module/fetchJSON.d.ts +4 -0
- package/lib/typescript/module/fetchJSON.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 +4 -7
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +5 -9
- 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/specs/NativeHotUpdater.d.ts +8 -35
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/store.d.ts +1 -3
- package/lib/typescript/module/store.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +5 -8
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/checkForUpdate.releaseCatalog.spec.ts +137 -61
- package/src/checkForUpdate.ts +35 -29
- package/src/error.ts +4 -14
- package/src/fetchJSON.ts +12 -1
- package/src/httpClient.spec.ts +70 -5
- package/src/httpClient.ts +78 -64
- package/src/index.spec.ts +59 -11
- package/src/index.ts +41 -17
- package/src/native.spec.ts +133 -22
- package/src/native.ts +21 -32
- package/src/{notifyAppReadyAnalytics.boundary.spec.ts → notifyAppReadyInsights.boundary.spec.ts} +25 -17
- package/src/{notifyAppReadyAnalytics.spec.ts → notifyAppReadyInsights.spec.ts} +141 -22
- package/src/{notifyAppReadyAnalytics.test-utils.ts → notifyAppReadyInsights.test-utils.ts} +4 -4
- package/src/{notifyAppReadyAnalytics.transport.spec.ts → notifyAppReadyInsights.transport.spec.ts} +13 -5
- package/src/notifyAppReadyInsights.ts +293 -0
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +8 -36
- package/src/store.notification.spec.ts +13 -3
- package/src/store.spec.ts +0 -157
- package/src/store.ts +2 -23
- package/src/wrap.spec.ts +109 -31
- package/src/wrap.tsx +11 -11
- package/src/wrap.types.ts +5 -8
- package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
- package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
- package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
- package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
- package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
- package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
- package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
- package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
- package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
- package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
- package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
- package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
- 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
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
|
|
4
|
+
ActiveUpdateState,
|
|
5
|
+
NotifyAppReadyInsightsEvent,
|
|
5
6
|
NotifyAppReadyResult,
|
|
6
7
|
} from "./native";
|
|
7
8
|
import {
|
|
8
9
|
createNotifyReadResult,
|
|
9
10
|
stubNotifyFrame,
|
|
10
|
-
} from "./
|
|
11
|
+
} from "./notifyAppReadyInsights.test-utils";
|
|
11
12
|
|
|
12
13
|
vi.mock("react-native", () => ({
|
|
13
14
|
Platform: { OS: "ios" },
|
|
@@ -19,13 +20,14 @@ const mocks = vi.hoisted(() => ({
|
|
|
19
20
|
getAppVersion: vi.fn(() => "1.0.0"),
|
|
20
21
|
getBundleId: vi.fn(() => "bundle-id"),
|
|
21
22
|
getChannel: vi.fn(() => "production"),
|
|
23
|
+
getActiveUpdateState: vi.fn<() => ActiveUpdateState>(),
|
|
22
24
|
getCohort: vi.fn(() => "123"),
|
|
23
25
|
getFingerprintHash: vi.fn(() => "fingerprint-hash"),
|
|
24
26
|
getInstallId: vi.fn(() => "install-id"),
|
|
25
27
|
getPersistedUserIdentity: vi.fn(() => ({})),
|
|
26
28
|
readNotifyAppReady: vi.fn<
|
|
27
29
|
() => {
|
|
28
|
-
|
|
30
|
+
insightsEvent: NotifyAppReadyInsightsEvent | null;
|
|
29
31
|
pending: boolean;
|
|
30
32
|
result: NotifyAppReadyResult;
|
|
31
33
|
}
|
|
@@ -42,6 +44,7 @@ vi.mock("./native", () => ({
|
|
|
42
44
|
getAppVersion: mocks.getAppVersion,
|
|
43
45
|
getBundleId: mocks.getBundleId,
|
|
44
46
|
getChannel: mocks.getChannel,
|
|
47
|
+
getActiveUpdateState: mocks.getActiveUpdateState,
|
|
45
48
|
getCohort: mocks.getCohort,
|
|
46
49
|
getFingerprintHash: mocks.getFingerprintHash,
|
|
47
50
|
getInstallId: mocks.getInstallId,
|
|
@@ -51,20 +54,20 @@ vi.mock("./native", () => ({
|
|
|
51
54
|
}));
|
|
52
55
|
|
|
53
56
|
const createClient = () => {
|
|
54
|
-
const
|
|
57
|
+
const sendInsightsEvent = vi.fn().mockResolvedValue(undefined);
|
|
55
58
|
return {
|
|
56
59
|
client: {
|
|
57
60
|
createSession: vi.fn(async () => ({
|
|
58
61
|
fetchReleaseCatalog: vi.fn(),
|
|
59
62
|
resolveArtifact: vi.fn(),
|
|
60
|
-
|
|
63
|
+
sendInsightsEvent,
|
|
61
64
|
})),
|
|
62
65
|
},
|
|
63
|
-
|
|
66
|
+
sendInsightsEvent,
|
|
64
67
|
};
|
|
65
68
|
};
|
|
66
69
|
|
|
67
|
-
describe("automatic notifyAppReady
|
|
70
|
+
describe("automatic notifyAppReady insights", () => {
|
|
68
71
|
beforeEach(() => {
|
|
69
72
|
vi.resetModules();
|
|
70
73
|
vi.unstubAllGlobals();
|
|
@@ -78,6 +81,11 @@ describe("automatic notifyAppReady analytics", () => {
|
|
|
78
81
|
mocks.addListener.mockReturnValue(() => {});
|
|
79
82
|
mocks.getBundleId.mockReturnValue("bundle-id");
|
|
80
83
|
mocks.getChannel.mockReturnValue("production");
|
|
84
|
+
mocks.getActiveUpdateState.mockReturnValue({
|
|
85
|
+
activeSelection: null,
|
|
86
|
+
stableSelection: null,
|
|
87
|
+
verificationPending: false,
|
|
88
|
+
});
|
|
81
89
|
mocks.getCohort.mockReturnValue("123");
|
|
82
90
|
mocks.getFingerprintHash.mockReturnValue("fingerprint-hash");
|
|
83
91
|
mocks.getInstallId.mockReturnValue("install-id");
|
|
@@ -85,6 +93,117 @@ describe("automatic notifyAppReady analytics", () => {
|
|
|
85
93
|
mocks.readNotifyAppReady.mockReturnValue(createNotifyReadResult());
|
|
86
94
|
});
|
|
87
95
|
|
|
96
|
+
it.each([
|
|
97
|
+
{
|
|
98
|
+
label: "running selection",
|
|
99
|
+
pending: false,
|
|
100
|
+
channel: "production",
|
|
101
|
+
expected: "release-running",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: "staged selection",
|
|
105
|
+
pending: true,
|
|
106
|
+
channel: "production",
|
|
107
|
+
expected: "release-running",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
label: "another channel",
|
|
111
|
+
pending: false,
|
|
112
|
+
channel: "preview",
|
|
113
|
+
expected: null,
|
|
114
|
+
},
|
|
115
|
+
])(
|
|
116
|
+
"attributes an unchanged launch with $label",
|
|
117
|
+
async ({ pending, channel, expected }) => {
|
|
118
|
+
const running = {
|
|
119
|
+
kind: "BUNDLE" as const,
|
|
120
|
+
releaseId: "release-running",
|
|
121
|
+
bundleId: "bundle-id",
|
|
122
|
+
channel,
|
|
123
|
+
};
|
|
124
|
+
mocks.getActiveUpdateState.mockReturnValue({
|
|
125
|
+
activeSelection: pending
|
|
126
|
+
? {
|
|
127
|
+
...running,
|
|
128
|
+
releaseId: "release-pending",
|
|
129
|
+
bundleId: "bundle-pending",
|
|
130
|
+
}
|
|
131
|
+
: running,
|
|
132
|
+
stableSelection: pending ? running : null,
|
|
133
|
+
verificationPending: pending,
|
|
134
|
+
});
|
|
135
|
+
const { reportNoChange } = await import("./notifyAppReadyInsights");
|
|
136
|
+
const { client, sendInsightsEvent } = createClient();
|
|
137
|
+
await reportNoChange({ client, insights: true });
|
|
138
|
+
expect(sendInsightsEvent).toHaveBeenCalledWith(
|
|
139
|
+
expect.objectContaining({
|
|
140
|
+
type: "UNCHANGED",
|
|
141
|
+
toBundleId: "bundle-id",
|
|
142
|
+
toReleaseId: expected,
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
it("orders download after in-flight startup reporting and suppresses a later no-change report", async () => {
|
|
149
|
+
const { handleNotifyAppReady, reportBundleDownloaded, reportNoChange } =
|
|
150
|
+
await import("./notifyAppReadyInsights");
|
|
151
|
+
const { client, sendInsightsEvent } = createClient();
|
|
152
|
+
let finishStartup!: () => void;
|
|
153
|
+
sendInsightsEvent.mockImplementationOnce(
|
|
154
|
+
() =>
|
|
155
|
+
new Promise<void>((resolve) => {
|
|
156
|
+
finishStartup = resolve;
|
|
157
|
+
}),
|
|
158
|
+
);
|
|
159
|
+
const options = { client, insights: true };
|
|
160
|
+
const startup = handleNotifyAppReady(options);
|
|
161
|
+
await vi.waitFor(() => expect(sendInsightsEvent).toHaveBeenCalledOnce());
|
|
162
|
+
const download = reportBundleDownloaded(options, {
|
|
163
|
+
fromBundleId: "bundle-id",
|
|
164
|
+
fromReleaseId: null,
|
|
165
|
+
toBundleId: "next-bundle",
|
|
166
|
+
toReleaseId: "next-release",
|
|
167
|
+
channel: "production",
|
|
168
|
+
updateStrategy: "appVersion",
|
|
169
|
+
});
|
|
170
|
+
await Promise.resolve();
|
|
171
|
+
expect(sendInsightsEvent).toHaveBeenCalledOnce();
|
|
172
|
+
finishStartup();
|
|
173
|
+
await Promise.all([startup, download]);
|
|
174
|
+
await reportNoChange(options);
|
|
175
|
+
expect(sendInsightsEvent.mock.calls.map(([params]) => params.type)).toEqual(
|
|
176
|
+
["UNCHANGED", "UPDATE_DOWNLOADED"],
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("reports selecting a previous download again after a different pending bundle", async () => {
|
|
181
|
+
const { reportBundleDownloaded } = await import("./notifyAppReadyInsights");
|
|
182
|
+
const { client, sendInsightsEvent } = createClient();
|
|
183
|
+
const transition = {
|
|
184
|
+
fromBundleId: "bundle-id",
|
|
185
|
+
fromReleaseId: null,
|
|
186
|
+
toBundleId: "next-bundle",
|
|
187
|
+
toReleaseId: "next-release",
|
|
188
|
+
channel: "production",
|
|
189
|
+
updateStrategy: "appVersion" as const,
|
|
190
|
+
};
|
|
191
|
+
await reportBundleDownloaded({ client, insights: true }, transition);
|
|
192
|
+
await reportBundleDownloaded({ client, insights: true }, transition);
|
|
193
|
+
await reportBundleDownloaded(
|
|
194
|
+
{ client, insights: true },
|
|
195
|
+
{
|
|
196
|
+
...transition,
|
|
197
|
+
toBundleId: "other-bundle",
|
|
198
|
+
toReleaseId: "other-release",
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
await reportBundleDownloaded({ client, insights: true }, transition);
|
|
202
|
+
expect(
|
|
203
|
+
sendInsightsEvent.mock.calls.map(([params]) => params.toBundleId),
|
|
204
|
+
).toEqual(["next-bundle", "other-bundle", "next-bundle"]);
|
|
205
|
+
});
|
|
206
|
+
|
|
88
207
|
it("sends UPDATE_APPLIED with transition metadata and invokes readiness", async () => {
|
|
89
208
|
stubNotifyFrame();
|
|
90
209
|
mocks.getPersistedUserIdentity.mockReturnValue({
|
|
@@ -107,12 +226,12 @@ describe("automatic notifyAppReady analytics", () => {
|
|
|
107
226
|
),
|
|
108
227
|
);
|
|
109
228
|
|
|
110
|
-
const { client,
|
|
229
|
+
const { client, sendInsightsEvent } = createClient();
|
|
111
230
|
const onNotifyAppReady = vi.fn();
|
|
112
231
|
const { init } = await import("./wrap");
|
|
113
232
|
|
|
114
233
|
init({
|
|
115
|
-
|
|
234
|
+
insights: true,
|
|
116
235
|
onNotifyAppReady,
|
|
117
236
|
requestHeaders: { Authorization: "Bearer token" },
|
|
118
237
|
requestTimeout: 1000,
|
|
@@ -120,7 +239,7 @@ describe("automatic notifyAppReady analytics", () => {
|
|
|
120
239
|
});
|
|
121
240
|
await vi.runOnlyPendingTimersAsync();
|
|
122
241
|
|
|
123
|
-
expect(
|
|
242
|
+
expect(sendInsightsEvent).toHaveBeenCalledWith({
|
|
124
243
|
appVersion: "1.0.0",
|
|
125
244
|
channel: "production",
|
|
126
245
|
cohort: "123",
|
|
@@ -162,14 +281,14 @@ describe("automatic notifyAppReady analytics", () => {
|
|
|
162
281
|
},
|
|
163
282
|
),
|
|
164
283
|
);
|
|
165
|
-
const { client,
|
|
284
|
+
const { client, sendInsightsEvent } = createClient();
|
|
166
285
|
const onNotifyAppReady = vi.fn();
|
|
167
286
|
const { init } = await import("./wrap");
|
|
168
287
|
|
|
169
|
-
init({
|
|
288
|
+
init({ insights: true, client, onNotifyAppReady });
|
|
170
289
|
await vi.runOnlyPendingTimersAsync();
|
|
171
290
|
|
|
172
|
-
expect(
|
|
291
|
+
expect(sendInsightsEvent).toHaveBeenCalledWith({
|
|
173
292
|
appVersion: "1.0.0",
|
|
174
293
|
channel: "production",
|
|
175
294
|
cohort: "123",
|
|
@@ -194,16 +313,16 @@ describe("automatic notifyAppReady analytics", () => {
|
|
|
194
313
|
|
|
195
314
|
it("sends one UNCHANGED event for repeated init calls", async () => {
|
|
196
315
|
stubNotifyFrame();
|
|
197
|
-
const { client,
|
|
316
|
+
const { client, sendInsightsEvent } = createClient();
|
|
198
317
|
const onNotifyAppReady = vi.fn();
|
|
199
318
|
const { init } = await import("./wrap");
|
|
200
319
|
|
|
201
|
-
init({
|
|
202
|
-
init({
|
|
320
|
+
init({ insights: true, client, onNotifyAppReady });
|
|
321
|
+
init({ insights: true, client, onNotifyAppReady });
|
|
203
322
|
await vi.runOnlyPendingTimersAsync();
|
|
204
323
|
|
|
205
|
-
expect(
|
|
206
|
-
expect(
|
|
324
|
+
expect(sendInsightsEvent).toHaveBeenCalledTimes(1);
|
|
325
|
+
expect(sendInsightsEvent).toHaveBeenCalledWith({
|
|
207
326
|
appVersion: "1.0.0",
|
|
208
327
|
channel: "production",
|
|
209
328
|
cohort: "123",
|
|
@@ -228,16 +347,16 @@ describe("automatic notifyAppReady analytics", () => {
|
|
|
228
347
|
});
|
|
229
348
|
});
|
|
230
349
|
|
|
231
|
-
it("skips automatic
|
|
350
|
+
it("skips automatic insights when disabled while preserving readiness", async () => {
|
|
232
351
|
stubNotifyFrame();
|
|
233
|
-
const { client,
|
|
352
|
+
const { client, sendInsightsEvent } = createClient();
|
|
234
353
|
const onNotifyAppReady = vi.fn();
|
|
235
354
|
const { init } = await import("./wrap");
|
|
236
355
|
|
|
237
|
-
init({
|
|
356
|
+
init({ insights: false, client, onNotifyAppReady });
|
|
238
357
|
await vi.runOnlyPendingTimersAsync();
|
|
239
358
|
|
|
240
|
-
expect(
|
|
359
|
+
expect(sendInsightsEvent).not.toHaveBeenCalled();
|
|
241
360
|
expect(onNotifyAppReady).toHaveBeenCalledWith({ status: "UNCHANGED" });
|
|
242
361
|
});
|
|
243
362
|
});
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
|
|
4
|
+
NotifyAppReadyInsightsEvent,
|
|
5
5
|
NotifyAppReadyResult,
|
|
6
6
|
} from "./native";
|
|
7
7
|
|
|
8
8
|
export const createNotifyReadResult = (
|
|
9
9
|
result: NotifyAppReadyResult = { status: "UNCHANGED" },
|
|
10
|
-
|
|
10
|
+
insightsEvent: NotifyAppReadyInsightsEvent | null = null,
|
|
11
11
|
pending = false,
|
|
12
12
|
): {
|
|
13
|
-
|
|
13
|
+
insightsEvent: NotifyAppReadyInsightsEvent | null;
|
|
14
14
|
pending: boolean;
|
|
15
15
|
result: NotifyAppReadyResult;
|
|
16
16
|
} => ({
|
|
17
|
-
|
|
17
|
+
insightsEvent,
|
|
18
18
|
pending,
|
|
19
19
|
result,
|
|
20
20
|
});
|
package/src/{notifyAppReadyAnalytics.transport.spec.ts → notifyAppReadyInsights.transport.spec.ts}
RENAMED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
|
|
4
|
+
ActiveUpdateState,
|
|
5
|
+
NotifyAppReadyInsightsEvent,
|
|
5
6
|
NotifyAppReadyResult,
|
|
6
7
|
} from "./native";
|
|
7
8
|
import {
|
|
8
9
|
createNotifyReadResult,
|
|
9
10
|
stubNotifyFrame,
|
|
10
|
-
} from "./
|
|
11
|
+
} from "./notifyAppReadyInsights.test-utils";
|
|
11
12
|
|
|
12
13
|
vi.mock("react-native", () => ({
|
|
13
14
|
Platform: { OS: "ios" },
|
|
@@ -30,13 +31,14 @@ const mocks = vi.hoisted(() => ({
|
|
|
30
31
|
getAppVersion: vi.fn(() => "1.0.0"),
|
|
31
32
|
getBundleId: vi.fn(() => "bundle-id"),
|
|
32
33
|
getChannel: vi.fn(() => "production"),
|
|
34
|
+
getActiveUpdateState: vi.fn<() => ActiveUpdateState>(),
|
|
33
35
|
getCohort: vi.fn(() => "123"),
|
|
34
36
|
getFingerprintHash: vi.fn(() => "fingerprint-hash"),
|
|
35
37
|
getInstallId: vi.fn(() => "install-id"),
|
|
36
38
|
getPersistedUserIdentity: vi.fn(() => ({})),
|
|
37
39
|
readNotifyAppReady: vi.fn<
|
|
38
40
|
() => {
|
|
39
|
-
|
|
41
|
+
insightsEvent: NotifyAppReadyInsightsEvent | null;
|
|
40
42
|
pending: boolean;
|
|
41
43
|
result: NotifyAppReadyResult;
|
|
42
44
|
}
|
|
@@ -53,6 +55,7 @@ vi.mock("./native", () => ({
|
|
|
53
55
|
getAppVersion: mocks.getAppVersion,
|
|
54
56
|
getBundleId: mocks.getBundleId,
|
|
55
57
|
getChannel: mocks.getChannel,
|
|
58
|
+
getActiveUpdateState: mocks.getActiveUpdateState,
|
|
56
59
|
getCohort: mocks.getCohort,
|
|
57
60
|
getFingerprintHash: mocks.getFingerprintHash,
|
|
58
61
|
getInstallId: mocks.getInstallId,
|
|
@@ -75,6 +78,11 @@ describe("automatic notifyAppReady transport failures", () => {
|
|
|
75
78
|
mocks.getAppVersion.mockReturnValue("1.0.0");
|
|
76
79
|
mocks.getBundleId.mockReturnValue("bundle-id");
|
|
77
80
|
mocks.getChannel.mockReturnValue("production");
|
|
81
|
+
mocks.getActiveUpdateState.mockReturnValue({
|
|
82
|
+
activeSelection: null,
|
|
83
|
+
stableSelection: null,
|
|
84
|
+
verificationPending: false,
|
|
85
|
+
});
|
|
78
86
|
mocks.getCohort.mockReturnValue("123");
|
|
79
87
|
mocks.getFingerprintHash.mockReturnValue("fingerprint-hash");
|
|
80
88
|
mocks.getInstallId.mockReturnValue("install-id");
|
|
@@ -110,7 +118,7 @@ describe("automatic notifyAppReady transport failures", () => {
|
|
|
110
118
|
"https://updates.example.test/hot-updater",
|
|
111
119
|
);
|
|
112
120
|
|
|
113
|
-
init({
|
|
121
|
+
init({ insights: true, client, onError, onNotifyAppReady });
|
|
114
122
|
await vi.runOnlyPendingTimersAsync();
|
|
115
123
|
|
|
116
124
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
@@ -119,7 +127,7 @@ describe("automatic notifyAppReady transport failures", () => {
|
|
|
119
127
|
status: "UNCHANGED",
|
|
120
128
|
});
|
|
121
129
|
expect(warn).toHaveBeenCalledWith(
|
|
122
|
-
"[HotUpdater] Automatic notifyAppReady
|
|
130
|
+
"[HotUpdater] Automatic notifyAppReady insights failed:",
|
|
123
131
|
expect.any(Error),
|
|
124
132
|
);
|
|
125
133
|
} finally {
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
|
|
3
|
+
import type { HotUpdaterError } from "./error";
|
|
4
|
+
import type { InsightsEventParams, HotUpdaterHttpClient } from "./httpClient";
|
|
5
|
+
import {
|
|
6
|
+
getActiveUpdateState,
|
|
7
|
+
getAppVersion,
|
|
8
|
+
getBundleId,
|
|
9
|
+
getChannel,
|
|
10
|
+
getCohort,
|
|
11
|
+
getFingerprintHash,
|
|
12
|
+
getInstallId,
|
|
13
|
+
getPersistedUserIdentity,
|
|
14
|
+
type NotifyAppReadyInsightsEvent,
|
|
15
|
+
type NotifyAppReadyResult,
|
|
16
|
+
readNotifyAppReady,
|
|
17
|
+
} from "./native";
|
|
18
|
+
|
|
19
|
+
export type NotifyAppReadyOptions = {
|
|
20
|
+
insights?: boolean;
|
|
21
|
+
client: HotUpdaterHttpClient;
|
|
22
|
+
requestHeaders?: Record<string, string>;
|
|
23
|
+
requestTimeout?: number;
|
|
24
|
+
reportUnchanged?: boolean;
|
|
25
|
+
onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
|
|
26
|
+
onError?: (error: HotUpdaterError | Error | unknown) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type RequestAnimationFrame = (callback: (timestamp: number) => void) => number;
|
|
30
|
+
|
|
31
|
+
let didAttemptAutomaticInsights = false;
|
|
32
|
+
let didDownload = false;
|
|
33
|
+
let readinessPromise: Promise<NotifyAppReadyResult | undefined> | undefined;
|
|
34
|
+
|
|
35
|
+
const waitForNextFrame = () =>
|
|
36
|
+
new Promise<void>((resolve) => {
|
|
37
|
+
const requestAnimationFrame = (
|
|
38
|
+
globalThis as typeof globalThis & {
|
|
39
|
+
requestAnimationFrame?: RequestAnimationFrame;
|
|
40
|
+
}
|
|
41
|
+
)?.requestAnimationFrame;
|
|
42
|
+
|
|
43
|
+
if (requestAnimationFrame) {
|
|
44
|
+
requestAnimationFrame(() => resolve());
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void Promise.resolve().then(resolve);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const assertNever = (value: never): never => {
|
|
52
|
+
throw new Error(`[HotUpdater] Unexpected notifyAppReady status: ${value}`);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const buildNotifyAppReadyInsightsParams = (
|
|
56
|
+
nativeResult: NotifyAppReadyResult,
|
|
57
|
+
insightsEvent: NotifyAppReadyInsightsEvent | null,
|
|
58
|
+
options: Pick<NotifyAppReadyOptions, "requestHeaders" | "requestTimeout">,
|
|
59
|
+
): InsightsEventParams => {
|
|
60
|
+
const appVersion = getAppVersion();
|
|
61
|
+
|
|
62
|
+
if (!appVersion) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"[HotUpdater] Automatic insights requires a non-null native app version.",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { userId, username } = getPersistedUserIdentity();
|
|
69
|
+
const installId = getInstallId();
|
|
70
|
+
|
|
71
|
+
if (!installId) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
"[HotUpdater] Automatic insights requires a non-null native install id.",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const platform: "ios" | "android" =
|
|
78
|
+
Platform.OS === "android" ? "android" : "ios";
|
|
79
|
+
|
|
80
|
+
const commonParams = {
|
|
81
|
+
appVersion,
|
|
82
|
+
channel: getChannel(),
|
|
83
|
+
cohort: getCohort(),
|
|
84
|
+
fingerprintHash: getFingerprintHash(),
|
|
85
|
+
fromReleaseId: insightsEvent?.fromReleaseId ?? null,
|
|
86
|
+
installId,
|
|
87
|
+
platform,
|
|
88
|
+
requestHeaders: options.requestHeaders,
|
|
89
|
+
requestTimeout: options.requestTimeout,
|
|
90
|
+
toReleaseId: insightsEvent?.toReleaseId ?? null,
|
|
91
|
+
...(userId != null ? { userId } : {}),
|
|
92
|
+
...(username != null ? { username } : {}),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
switch (nativeResult.status) {
|
|
96
|
+
case "UNCHANGED": {
|
|
97
|
+
const bundleId = getBundleId();
|
|
98
|
+
|
|
99
|
+
if (!bundleId) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"[HotUpdater] Automatic insights requires a non-null current bundle id.",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const state = getActiveUpdateState();
|
|
106
|
+
const runningSelection = [
|
|
107
|
+
state.activeSelection,
|
|
108
|
+
state.stableSelection,
|
|
109
|
+
].find(
|
|
110
|
+
(selection) =>
|
|
111
|
+
selection?.bundleId === bundleId &&
|
|
112
|
+
selection.channel === commonParams.channel,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
...commonParams,
|
|
117
|
+
fromBundleId: null,
|
|
118
|
+
toBundleId: bundleId,
|
|
119
|
+
toReleaseId: runningSelection?.releaseId ?? null,
|
|
120
|
+
type: "UNCHANGED",
|
|
121
|
+
updateStrategy: null,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
case "UPDATE_APPLIED":
|
|
125
|
+
case "RECOVERED":
|
|
126
|
+
if (!insightsEvent) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
"[HotUpdater] Native launch report is missing persisted metadata required for automatic insights.",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
switch (insightsEvent.type) {
|
|
133
|
+
case "UPDATE_APPLIED":
|
|
134
|
+
return {
|
|
135
|
+
...commonParams,
|
|
136
|
+
fromBundleId: insightsEvent.fromBundleId,
|
|
137
|
+
toBundleId: insightsEvent.toBundleId,
|
|
138
|
+
type: "UPDATE_APPLIED",
|
|
139
|
+
updateStrategy: insightsEvent.updateStrategy,
|
|
140
|
+
};
|
|
141
|
+
case "RECOVERED":
|
|
142
|
+
return {
|
|
143
|
+
...commonParams,
|
|
144
|
+
fromBundleId: insightsEvent.fromBundleId,
|
|
145
|
+
toBundleId: insightsEvent.toBundleId,
|
|
146
|
+
type: "RECOVERED",
|
|
147
|
+
updateStrategy: insightsEvent.updateStrategy,
|
|
148
|
+
};
|
|
149
|
+
default:
|
|
150
|
+
return assertNever(insightsEvent.type);
|
|
151
|
+
}
|
|
152
|
+
default:
|
|
153
|
+
return assertNever(nativeResult);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
let lastInsightsReport = Promise.resolve();
|
|
158
|
+
const sendInsights = (
|
|
159
|
+
options: Pick<NotifyAppReadyOptions, "client">,
|
|
160
|
+
params: InsightsEventParams,
|
|
161
|
+
): Promise<void> => {
|
|
162
|
+
lastInsightsReport = lastInsightsReport
|
|
163
|
+
.catch(() => {})
|
|
164
|
+
.then(async () => {
|
|
165
|
+
const session = await options.client.createSession();
|
|
166
|
+
await session.sendInsightsEvent(params);
|
|
167
|
+
});
|
|
168
|
+
return lastInsightsReport;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const maybeSendAutomaticInsights = async (
|
|
172
|
+
options: NotifyAppReadyOptions,
|
|
173
|
+
nativeResult: NotifyAppReadyResult,
|
|
174
|
+
insightsEvent: NotifyAppReadyInsightsEvent | null,
|
|
175
|
+
): Promise<void> => {
|
|
176
|
+
if (
|
|
177
|
+
!options.insights ||
|
|
178
|
+
didAttemptAutomaticInsights ||
|
|
179
|
+
(nativeResult.status === "UNCHANGED" &&
|
|
180
|
+
(options.reportUnchanged === false || didDownload))
|
|
181
|
+
) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
didAttemptAutomaticInsights = true;
|
|
186
|
+
|
|
187
|
+
if (nativeResult.status !== "UNCHANGED" && !insightsEvent) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"[HotUpdater] Native launch report is missing persisted metadata required for automatic insights.",
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
await sendInsights(
|
|
194
|
+
options,
|
|
195
|
+
buildNotifyAppReadyInsightsParams(nativeResult, insightsEvent, options),
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const notifyAppReady = async (
|
|
200
|
+
options: NotifyAppReadyOptions,
|
|
201
|
+
): Promise<NotifyAppReadyResult | undefined> => {
|
|
202
|
+
try {
|
|
203
|
+
let nativeReadResult: ReturnType<typeof readNotifyAppReady>;
|
|
204
|
+
do {
|
|
205
|
+
await waitForNextFrame();
|
|
206
|
+
nativeReadResult = readNotifyAppReady();
|
|
207
|
+
} while (nativeReadResult.pending);
|
|
208
|
+
|
|
209
|
+
const { insightsEvent, result: nativeResult } = nativeReadResult;
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
await maybeSendAutomaticInsights(options, nativeResult, insightsEvent);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const warning = error instanceof Error ? error : new Error(String(error));
|
|
215
|
+
console.warn(
|
|
216
|
+
"[HotUpdater] Automatic notifyAppReady insights failed:",
|
|
217
|
+
warning,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
options.onNotifyAppReady?.(nativeResult);
|
|
222
|
+
return nativeResult;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
const normalizedError =
|
|
225
|
+
error instanceof Error ? error : new Error(String(error));
|
|
226
|
+
options.onError?.(error);
|
|
227
|
+
console.warn("[HotUpdater] Failed to notify app ready:", normalizedError);
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const handleNotifyAppReady = (
|
|
233
|
+
options: NotifyAppReadyOptions,
|
|
234
|
+
): Promise<NotifyAppReadyResult | undefined> => {
|
|
235
|
+
readinessPromise = notifyAppReady(options);
|
|
236
|
+
return readinessPromise;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/** Called after an automatic update check finds no download or transition. */
|
|
240
|
+
export const reportNoChange = async (
|
|
241
|
+
options: NotifyAppReadyOptions,
|
|
242
|
+
): Promise<void> => {
|
|
243
|
+
try {
|
|
244
|
+
await maybeSendAutomaticInsights(options, { status: "UNCHANGED" }, null);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.warn("[HotUpdater] Automatic no-change insights failed:", error);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
let lastDownloadedSelection: string | undefined;
|
|
251
|
+
|
|
252
|
+
/** Report only after the manifest update has been verified and staged successfully. */
|
|
253
|
+
export const reportBundleDownloaded = async (
|
|
254
|
+
options: Pick<
|
|
255
|
+
NotifyAppReadyOptions,
|
|
256
|
+
"insights" | "client" | "requestHeaders" | "requestTimeout"
|
|
257
|
+
>,
|
|
258
|
+
transition: {
|
|
259
|
+
readonly fromBundleId: string;
|
|
260
|
+
readonly fromReleaseId: string | null;
|
|
261
|
+
readonly toBundleId: string;
|
|
262
|
+
readonly toReleaseId: string | null;
|
|
263
|
+
readonly channel: string;
|
|
264
|
+
readonly updateStrategy: "fingerprint" | "appVersion";
|
|
265
|
+
},
|
|
266
|
+
): Promise<void> => {
|
|
267
|
+
if (!options.insights || transition.fromBundleId === transition.toBundleId)
|
|
268
|
+
return;
|
|
269
|
+
const key = JSON.stringify([
|
|
270
|
+
transition.channel,
|
|
271
|
+
transition.toReleaseId,
|
|
272
|
+
transition.toBundleId,
|
|
273
|
+
]);
|
|
274
|
+
if (lastDownloadedSelection === key) return;
|
|
275
|
+
lastDownloadedSelection = key;
|
|
276
|
+
// A later init/mount in this JS runtime must not overwrite pending apply with UNCHANGED.
|
|
277
|
+
didDownload = true;
|
|
278
|
+
try {
|
|
279
|
+
await readinessPromise;
|
|
280
|
+
const common = buildNotifyAppReadyInsightsParams(
|
|
281
|
+
{ status: "UNCHANGED" },
|
|
282
|
+
null,
|
|
283
|
+
options,
|
|
284
|
+
);
|
|
285
|
+
await sendInsights(options, {
|
|
286
|
+
...common,
|
|
287
|
+
...transition,
|
|
288
|
+
type: "UPDATE_DOWNLOADED",
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.warn("[HotUpdater] Download insights failed:", error);
|
|
292
|
+
}
|
|
293
|
+
};
|
package/src/sdkVersion.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.
|
|
1
|
+
export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.15";
|