@hot-updater/react-native 1.0.0-rc.16 → 1.0.0-rc.17
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 +491 -388
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +5 -11
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +290 -12
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +35 -19
- package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
- package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
- package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
- package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +110 -52
- package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
- package/android/src/newarch/HotUpdaterModule.kt +74 -69
- package/android/src/oldarch/HotUpdaterModule.kt +63 -59
- package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +1 -1
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +446 -61
- package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +220 -0
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +19 -27
- package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +71 -0
- package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +106 -0
- package/android/src/test/java/com/hotupdater/NoBackupContext.kt +11 -0
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +309 -23
- package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +57 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +238 -157
- package/ios/HotUpdater/Internal/BundleMetadata.swift +3 -53
- package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +8 -18
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +48 -29
- package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +108 -1
- package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
- package/lib/commonjs/appReady.js +111 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/appReady.spec.js +360 -0
- package/lib/commonjs/appReady.spec.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
- package/lib/commonjs/appReady.test-utils.js.map +1 -0
- package/lib/commonjs/baseURL.js +16 -0
- package/lib/commonjs/baseURL.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +104 -56
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +337 -74
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +49 -0
- package/lib/commonjs/clientPlugin.js.map +1 -0
- package/lib/commonjs/entryImportGraph.spec.js +50 -0
- package/lib/commonjs/entryImportGraph.spec.js.map +1 -0
- package/lib/commonjs/fetchJSON.js +2 -6
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/httpClient.js +18 -165
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/httpClient.spec.js +26 -199
- package/lib/commonjs/httpClient.spec.js.map +1 -1
- package/lib/commonjs/index.js +49 -36
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +108 -123
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +67 -48
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +80 -58
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/pluginHost.js +208 -0
- package/lib/commonjs/pluginHost.js.map +1 -0
- package/lib/commonjs/pluginHost.spec.js +292 -0
- package/lib/commonjs/pluginHost.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/eventId.js +129 -0
- package/lib/commonjs/plugins/insights/eventId.js.map +1 -0
- package/lib/commonjs/plugins/insights/eventId.spec.js +47 -0
- package/lib/commonjs/plugins/insights/eventId.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/index.js +272 -0
- package/lib/commonjs/plugins/insights/index.js.map +1 -0
- package/lib/commonjs/plugins/insights/insights.spec.js +640 -0
- package/lib/commonjs/plugins/insights/insights.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/sender.js +128 -0
- package/lib/commonjs/plugins/insights/sender.js.map +1 -0
- package/lib/commonjs/plugins/insights/sender.spec.js +265 -0
- package/lib/commonjs/plugins/insights/sender.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/state.js +145 -0
- package/lib/commonjs/plugins/insights/state.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +13 -2
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +18 -0
- package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/updateError.js +132 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/updateError.spec.js +149 -0
- package/lib/commonjs/updateError.spec.js.map +1 -0
- package/lib/commonjs/wrap.js +43 -25
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.progress.spec.js +195 -0
- package/lib/commonjs/wrap.progress.spec.js.map +1 -0
- package/lib/commonjs/wrap.spec.js +94 -230
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/appReady.js +103 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/appReady.spec.js +359 -0
- package/lib/module/appReady.spec.js.map +1 -0
- package/lib/module/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
- package/lib/module/appReady.test-utils.js.map +1 -0
- package/lib/module/baseURL.js +11 -0
- package/lib/module/baseURL.js.map +1 -0
- package/lib/module/checkForUpdate.js +103 -57
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +337 -74
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/module/clientPlugin.js +44 -0
- package/lib/module/clientPlugin.js.map +1 -0
- package/lib/module/entryImportGraph.spec.js +50 -0
- package/lib/module/entryImportGraph.spec.js.map +1 -0
- package/lib/module/fetchJSON.js +2 -6
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/httpClient.js +18 -165
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/httpClient.spec.js +27 -200
- package/lib/module/httpClient.spec.js.map +1 -1
- package/lib/module/index.js +44 -38
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +108 -123
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +63 -44
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +80 -58
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/pluginHost.js +200 -0
- package/lib/module/pluginHost.js.map +1 -0
- package/lib/module/pluginHost.spec.js +291 -0
- package/lib/module/pluginHost.spec.js.map +1 -0
- package/lib/module/plugins/insights/eventId.js +122 -0
- package/lib/module/plugins/insights/eventId.js.map +1 -0
- package/lib/module/plugins/insights/eventId.spec.js +47 -0
- package/lib/module/plugins/insights/eventId.spec.js.map +1 -0
- package/lib/module/plugins/insights/index.js +268 -0
- package/lib/module/plugins/insights/index.js.map +1 -0
- package/lib/module/plugins/insights/insights.spec.js +639 -0
- package/lib/module/plugins/insights/insights.spec.js.map +1 -0
- package/lib/module/plugins/insights/sender.js +123 -0
- package/lib/module/plugins/insights/sender.js.map +1 -0
- package/lib/module/plugins/insights/sender.spec.js +265 -0
- package/lib/module/plugins/insights/sender.spec.js.map +1 -0
- package/lib/module/plugins/insights/state.js +139 -0
- package/lib/module/plugins/insights/state.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +13 -2
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/releaseCatalogCache.spec.js +18 -0
- package/lib/module/releaseCatalogCache.spec.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/updateError.js +126 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/updateError.spec.js +149 -0
- package/lib/module/updateError.spec.js.map +1 -0
- package/lib/module/wrap.js +43 -25
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.progress.spec.js +196 -0
- package/lib/module/wrap.progress.spec.js.map +1 -0
- package/lib/module/wrap.spec.js +93 -229
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/appReady.d.ts +23 -0
- package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
- package/lib/typescript/commonjs/appReady.test-utils.d.ts +9 -0
- package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/baseURL.d.ts +4 -0
- package/lib/typescript/commonjs/baseURL.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +15 -2
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +185 -0
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/commonjs/fetchJSON.d.ts +2 -3
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +2 -36
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -6
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +29 -15
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +29 -0
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +18 -0
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/index.d.ts +40 -0
- package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts +74 -0
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/state.d.ts +46 -0
- package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +5 -1
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +34 -12
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/updateError.d.ts +26 -0
- package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +6 -10
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/appReady.d.ts +23 -0
- package/lib/typescript/module/appReady.d.ts.map +1 -0
- package/lib/typescript/module/appReady.test-utils.d.ts +9 -0
- package/lib/typescript/module/appReady.test-utils.d.ts.map +1 -0
- package/lib/typescript/module/baseURL.d.ts +4 -0
- package/lib/typescript/module/baseURL.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +15 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +185 -0
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/module/fetchJSON.d.ts +2 -3
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +2 -36
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -6
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +29 -15
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +29 -0
- package/lib/typescript/module/pluginHost.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/eventId.d.ts +18 -0
- package/lib/typescript/module/plugins/insights/eventId.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/index.d.ts +40 -0
- package/lib/typescript/module/plugins/insights/index.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/sender.d.ts +74 -0
- package/lib/typescript/module/plugins/insights/sender.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/state.d.ts +46 -0
- package/lib/typescript/module/plugins/insights/state.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +5 -1
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +34 -12
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/updateError.d.ts +26 -0
- package/lib/typescript/module/updateError.d.ts.map +1 -0
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +6 -10
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +14 -2
- package/plugins/insights/package.json +8 -0
- package/src/appReady.spec.ts +393 -0
- package/src/{notifyAppReadyInsights.test-utils.ts → appReady.test-utils.ts} +7 -7
- package/src/appReady.ts +149 -0
- package/src/baseURL.ts +15 -0
- package/src/checkForUpdate.releaseCatalog.spec.ts +370 -76
- package/src/checkForUpdate.ts +135 -80
- package/src/clientPlugin.ts +231 -0
- package/src/entryImportGraph.spec.ts +65 -0
- package/src/fetchJSON.ts +3 -10
- package/src/httpClient.spec.ts +30 -227
- package/src/httpClient.ts +20 -256
- package/src/index.spec.ts +117 -93
- package/src/index.ts +67 -52
- package/src/native.spec.ts +98 -72
- package/src/native.ts +93 -77
- package/src/pluginHost.spec.ts +297 -0
- package/src/pluginHost.ts +262 -0
- package/src/plugins/insights/eventId.spec.ts +60 -0
- package/src/plugins/insights/eventId.ts +171 -0
- package/src/plugins/insights/index.ts +367 -0
- package/src/plugins/insights/insights.spec.ts +732 -0
- package/src/plugins/insights/sender.spec.ts +287 -0
- package/src/plugins/insights/sender.ts +221 -0
- package/src/plugins/insights/state.ts +184 -0
- package/src/releaseCatalogCache.spec.ts +22 -0
- package/src/releaseCatalogCache.ts +19 -4
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +34 -13
- package/src/updateError.spec.ts +144 -0
- package/src/updateError.ts +153 -0
- package/src/wrap.progress.spec.ts +232 -0
- package/src/wrap.spec.ts +100 -283
- package/src/wrap.tsx +57 -26
- package/src/wrap.types.ts +6 -12
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -225
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.js +0 -188
- package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -378
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -171
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/commonjs/uuidv7.js +0 -41
- package/lib/commonjs/uuidv7.js.map +0 -1
- package/lib/commonjs/uuidv7.spec.js +0 -30
- package/lib/commonjs/uuidv7.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -224
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.js +0 -181
- package/lib/module/notifyAppReadyInsights.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.spec.js +0 -377
- package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -170
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/module/uuidv7.js +0 -36
- package/lib/module/uuidv7.js.map +0 -1
- package/lib/module/uuidv7.spec.js +0 -30
- package/lib/module/uuidv7.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -25
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/lib/typescript/commonjs/uuidv7.d.ts +0 -10
- package/lib/typescript/commonjs/uuidv7.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -25
- package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/uuidv7.d.ts +0 -10
- package/lib/typescript/module/uuidv7.d.ts.map +0 -1
- package/src/notifyAppReadyInsights.boundary.spec.ts +0 -242
- package/src/notifyAppReadyInsights.spec.ts +0 -362
- package/src/notifyAppReadyInsights.transport.spec.ts +0 -196
- package/src/notifyAppReadyInsights.ts +0 -293
- package/src/uuidv7.spec.ts +0 -25
- package/src/uuidv7.ts +0 -38
package/src/checkForUpdate.ts
CHANGED
|
@@ -9,8 +9,18 @@ import {
|
|
|
9
9
|
import { canonicalizeAppVersion } from "@hot-updater/plugin-core";
|
|
10
10
|
import { Platform } from "react-native";
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
emitAfterAppReady,
|
|
14
|
+
getPreviousProcessExit,
|
|
15
|
+
getRunningReleaseId,
|
|
16
|
+
} from "./appReady";
|
|
17
|
+
import type {
|
|
18
|
+
ReleaseTransitionKind,
|
|
19
|
+
UpdateErrorResource,
|
|
20
|
+
UpdateErrorStage,
|
|
21
|
+
} from "./clientPlugin";
|
|
12
22
|
import { HotUpdaterError, StaleReleaseCatalogError } from "./error";
|
|
13
|
-
import type { HotUpdaterHttpClient
|
|
23
|
+
import type { HotUpdaterHttpClient } from "./httpClient";
|
|
14
24
|
import {
|
|
15
25
|
acceptReleaseCatalog,
|
|
16
26
|
commitReleaseSelection,
|
|
@@ -21,20 +31,20 @@ import {
|
|
|
21
31
|
getCohort,
|
|
22
32
|
getDefaultChannel,
|
|
23
33
|
getFingerprintHash,
|
|
24
|
-
getInstallId,
|
|
25
34
|
getMinBundleId,
|
|
26
35
|
getCrashHistory,
|
|
27
|
-
getPersistedUserIdentity,
|
|
28
36
|
isReleaseSelectionCurrent,
|
|
29
37
|
isChannelSwitched,
|
|
30
|
-
|
|
38
|
+
stageBundle,
|
|
31
39
|
} from "./native";
|
|
32
|
-
import { reportBundleDownloaded } from "./notifyAppReadyInsights";
|
|
33
40
|
import {
|
|
34
41
|
hasExpectedReleaseCatalogScope,
|
|
35
42
|
type ExpectedReleaseCatalogScope,
|
|
36
43
|
} from "./releaseCatalogCache";
|
|
37
44
|
import { hotUpdaterStore } from "./store";
|
|
45
|
+
import { classifyUpdateError, InvalidUpdateResponseError } from "./updateError";
|
|
46
|
+
|
|
47
|
+
export type { ReleaseTransitionKind };
|
|
38
48
|
|
|
39
49
|
export interface CheckForUpdateOptions {
|
|
40
50
|
/**
|
|
@@ -76,14 +86,7 @@ export type CheckForUpdateResult = {
|
|
|
76
86
|
updateBundle: () => Promise<boolean>;
|
|
77
87
|
};
|
|
78
88
|
|
|
79
|
-
export type ReleaseTransitionKind =
|
|
80
|
-
| "INSTALL"
|
|
81
|
-
| "ADOPT_RELEASE"
|
|
82
|
-
| "USE_EMBEDDED"
|
|
83
|
-
| "USE_BUILTIN";
|
|
84
|
-
|
|
85
89
|
export interface InternalCheckForUpdateOptions extends CheckForUpdateOptions {
|
|
86
|
-
insights?: boolean;
|
|
87
90
|
client: HotUpdaterHttpClient;
|
|
88
91
|
}
|
|
89
92
|
|
|
@@ -113,7 +116,7 @@ const validateCatalog = (
|
|
|
113
116
|
!/^sha256:[0-9a-f]{64}$/.test(catalog.catalogHash) ||
|
|
114
117
|
!hasExpectedReleaseCatalogScope(catalog, expectedScope)
|
|
115
118
|
) {
|
|
116
|
-
throw new
|
|
119
|
+
throw new InvalidUpdateResponseError("Received an invalid Release catalog");
|
|
117
120
|
}
|
|
118
121
|
};
|
|
119
122
|
|
|
@@ -126,40 +129,45 @@ const resetProgress = () => {
|
|
|
126
129
|
});
|
|
127
130
|
};
|
|
128
131
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
readonly
|
|
132
|
-
readonly
|
|
133
|
-
readonly
|
|
134
|
-
readonly
|
|
135
|
-
readonly
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
132
|
+
/** The running bundle, the channel checked, and the update's target. */
|
|
133
|
+
type UpdateErrorScope = {
|
|
134
|
+
readonly channel: string;
|
|
135
|
+
readonly bundleId: string;
|
|
136
|
+
readonly updateStrategy: "fingerprint" | "appVersion";
|
|
137
|
+
readonly targetBundleId?: string;
|
|
138
|
+
readonly targetReleaseId?: string | null;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Tells plugins why an update failed, when it failed as an update. An error
|
|
143
|
+
* the SDK's JavaScript raised happened in `stage`, fetching `resource`.
|
|
144
|
+
*/
|
|
145
|
+
export const reportUpdateError = (
|
|
146
|
+
error: unknown,
|
|
147
|
+
stage: UpdateErrorStage,
|
|
148
|
+
resource: UpdateErrorResource | undefined,
|
|
149
|
+
scope: UpdateErrorScope,
|
|
150
|
+
): void => {
|
|
151
|
+
emitAfterAppReady("onUpdateError", () => {
|
|
152
|
+
const classification = classifyUpdateError(error, stage, resource);
|
|
153
|
+
if (classification === null) return null;
|
|
154
|
+
const previousProcessExit = getPreviousProcessExit();
|
|
155
|
+
return {
|
|
156
|
+
...classification,
|
|
157
|
+
...(previousProcessExit === null ? {} : { previousProcessExit }),
|
|
158
|
+
channel: scope.channel,
|
|
159
|
+
bundleId: scope.bundleId,
|
|
160
|
+
releaseId: getRunningReleaseId(scope.bundleId, getChannel()),
|
|
161
|
+
updateStrategy: scope.updateStrategy,
|
|
162
|
+
...(scope.targetBundleId === undefined
|
|
163
|
+
? {}
|
|
164
|
+
: { targetBundleId: scope.targetBundleId }),
|
|
165
|
+
...(scope.targetReleaseId == null
|
|
166
|
+
? {}
|
|
167
|
+
: { targetReleaseId: scope.targetReleaseId }),
|
|
168
|
+
cause: error,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
163
171
|
};
|
|
164
172
|
|
|
165
173
|
async function checkForReleaseCatalogUpdate(input: {
|
|
@@ -210,6 +218,8 @@ async function checkForReleaseCatalogUpdate(input: {
|
|
|
210
218
|
requestTimeout: options.requestTimeout,
|
|
211
219
|
updateStrategy: options.updateStrategy,
|
|
212
220
|
});
|
|
221
|
+
// The server says this scope has no catalog yet: no update, not a failure.
|
|
222
|
+
if (catalog === null) return null;
|
|
213
223
|
validateCatalog(catalog, expectedScope);
|
|
214
224
|
const catalogId = catalog.catalogId;
|
|
215
225
|
const scopeKey = catalog.scopeKey;
|
|
@@ -263,7 +273,7 @@ async function checkForReleaseCatalogUpdate(input: {
|
|
|
263
273
|
scopeKey,
|
|
264
274
|
})
|
|
265
275
|
) {
|
|
266
|
-
throw new
|
|
276
|
+
throw new InvalidUpdateResponseError(
|
|
267
277
|
"Rejected a stale or inconsistent Release catalog",
|
|
268
278
|
);
|
|
269
279
|
}
|
|
@@ -327,7 +337,20 @@ async function checkForReleaseCatalogUpdate(input: {
|
|
|
327
337
|
scopeKey,
|
|
328
338
|
selectionContextHash,
|
|
329
339
|
};
|
|
330
|
-
const
|
|
340
|
+
const fromReleaseId =
|
|
341
|
+
active?.bundleId === input.currentBundleId
|
|
342
|
+
? active.releaseId
|
|
343
|
+
: activeState.stableSelection?.bundleId === input.currentBundleId
|
|
344
|
+
? activeState.stableSelection.releaseId
|
|
345
|
+
: null;
|
|
346
|
+
const errorScope = {
|
|
347
|
+
bundleId: input.currentBundleId,
|
|
348
|
+
channel: input.targetChannel,
|
|
349
|
+
targetBundleId: desired.bundleId,
|
|
350
|
+
targetReleaseId: desired.releaseId,
|
|
351
|
+
updateStrategy: options.updateStrategy,
|
|
352
|
+
};
|
|
353
|
+
const installSelection = async (): Promise<boolean> => {
|
|
331
354
|
resetProgress();
|
|
332
355
|
if (!isReleaseSelectionCurrent(guard)) {
|
|
333
356
|
throw new StaleReleaseCatalogError();
|
|
@@ -338,18 +361,14 @@ async function checkForReleaseCatalogUpdate(input: {
|
|
|
338
361
|
guard,
|
|
339
362
|
selection: receipt,
|
|
340
363
|
});
|
|
341
|
-
if (committed && transitionKind === "ADOPT_RELEASE"
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
requestHeaders: options.requestHeaders,
|
|
350
|
-
requestTimeout: options.requestTimeout,
|
|
351
|
-
session,
|
|
352
|
-
});
|
|
364
|
+
if (committed && transitionKind === "ADOPT_RELEASE") {
|
|
365
|
+
emitAfterAppReady("onUpdateCheck", () => ({
|
|
366
|
+
status: "UNCHANGED",
|
|
367
|
+
channel: receipt.channel,
|
|
368
|
+
bundleId: receipt.bundleId,
|
|
369
|
+
releaseId: receipt.releaseId,
|
|
370
|
+
previousReleaseId: active?.releaseId ?? null,
|
|
371
|
+
}));
|
|
353
372
|
}
|
|
354
373
|
return committed;
|
|
355
374
|
}
|
|
@@ -363,7 +382,7 @@ async function checkForReleaseCatalogUpdate(input: {
|
|
|
363
382
|
if (!isReleaseSelectionCurrent(guard)) {
|
|
364
383
|
throw new StaleReleaseCatalogError();
|
|
365
384
|
}
|
|
366
|
-
const
|
|
385
|
+
const delivery = await stageBundle({
|
|
367
386
|
assets: artifact.assets,
|
|
368
387
|
bundleId: desired.bundleId,
|
|
369
388
|
channel: input.targetChannel,
|
|
@@ -374,35 +393,53 @@ async function checkForReleaseCatalogUpdate(input: {
|
|
|
374
393
|
shouldSkipCurrentBundleIdCheck: true,
|
|
375
394
|
status: desired.status,
|
|
376
395
|
});
|
|
377
|
-
if (
|
|
378
|
-
|
|
396
|
+
if (delivery !== null && desired.bundleId !== input.currentBundleId) {
|
|
397
|
+
emitAfterAppReady("onBundleDownloaded", () => ({
|
|
398
|
+
channel: input.targetChannel,
|
|
379
399
|
fromBundleId: input.currentBundleId,
|
|
380
|
-
fromReleaseId
|
|
381
|
-
active?.bundleId === input.currentBundleId
|
|
382
|
-
? active.releaseId
|
|
383
|
-
: activeState.stableSelection?.bundleId === input.currentBundleId
|
|
384
|
-
? activeState.stableSelection.releaseId
|
|
385
|
-
: null,
|
|
400
|
+
fromReleaseId,
|
|
386
401
|
toBundleId: desired.bundleId,
|
|
387
402
|
toReleaseId: desired.releaseId,
|
|
388
|
-
channel: input.targetChannel,
|
|
389
403
|
updateStrategy: options.updateStrategy,
|
|
390
|
-
|
|
404
|
+
...delivery,
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
407
|
+
return true;
|
|
408
|
+
};
|
|
409
|
+
const updateBundleForSelection = async (): Promise<boolean> => {
|
|
410
|
+
try {
|
|
411
|
+
return await installSelection();
|
|
412
|
+
} catch (error) {
|
|
413
|
+
reportUpdateError(error, "download", "artifact", errorScope);
|
|
414
|
+
throw error;
|
|
391
415
|
}
|
|
392
|
-
return downloaded;
|
|
393
416
|
};
|
|
394
417
|
|
|
418
|
+
const shouldForceUpdate =
|
|
419
|
+
transitionKind === "ADOPT_RELEASE"
|
|
420
|
+
? false
|
|
421
|
+
: desired.status === "ROLLBACK"
|
|
422
|
+
? true
|
|
423
|
+
: (release?.shouldForceUpdate ?? false);
|
|
424
|
+
emitAfterAppReady("onUpdateCheck", () => ({
|
|
425
|
+
status: "UPDATE_AVAILABLE",
|
|
426
|
+
channel: input.targetChannel,
|
|
427
|
+
fromBundleId: input.currentBundleId,
|
|
428
|
+
fromReleaseId,
|
|
429
|
+
toBundleId: desired.bundleId,
|
|
430
|
+
toReleaseId: desired.releaseId,
|
|
431
|
+
transitionKind,
|
|
432
|
+
updateStatus: desired.status,
|
|
433
|
+
shouldForceUpdate,
|
|
434
|
+
updateStrategy: options.updateStrategy,
|
|
435
|
+
}));
|
|
436
|
+
|
|
395
437
|
return {
|
|
396
438
|
id: desired.releaseId ?? desired.bundleId,
|
|
397
439
|
message: release?.message ?? null,
|
|
398
440
|
releaseId: desired.releaseId,
|
|
399
441
|
rolloutCohortCount: release?.rolloutCohortCount ?? 1000,
|
|
400
|
-
shouldForceUpdate
|
|
401
|
-
transitionKind === "ADOPT_RELEASE"
|
|
402
|
-
? false
|
|
403
|
-
: desired.status === "ROLLBACK"
|
|
404
|
-
? true
|
|
405
|
-
: (release?.shouldForceUpdate ?? false),
|
|
442
|
+
shouldForceUpdate,
|
|
406
443
|
status: desired.status,
|
|
407
444
|
targetCohorts: release?.targetCohorts ? [...release.targetCohorts] : [],
|
|
408
445
|
transitionKind,
|
|
@@ -451,7 +488,7 @@ export async function checkForUpdate(
|
|
|
451
488
|
const fingerprintHash = getFingerprintHash();
|
|
452
489
|
|
|
453
490
|
try {
|
|
454
|
-
|
|
491
|
+
const result = await checkForReleaseCatalogUpdate({
|
|
455
492
|
cohort,
|
|
456
493
|
currentAppVersion,
|
|
457
494
|
currentBundleId,
|
|
@@ -465,7 +502,25 @@ export async function checkForUpdate(
|
|
|
465
502
|
platform,
|
|
466
503
|
targetChannel,
|
|
467
504
|
});
|
|
505
|
+
if (result === null) {
|
|
506
|
+
emitAfterAppReady("onUpdateCheck", () => {
|
|
507
|
+
const releaseId = getRunningReleaseId(currentBundleId, currentChannel);
|
|
508
|
+
return {
|
|
509
|
+
status: "UNCHANGED",
|
|
510
|
+
channel: currentChannel,
|
|
511
|
+
bundleId: currentBundleId,
|
|
512
|
+
releaseId,
|
|
513
|
+
previousReleaseId: releaseId,
|
|
514
|
+
};
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
return result;
|
|
468
518
|
} catch (error) {
|
|
519
|
+
reportUpdateError(error, "check", "catalog", {
|
|
520
|
+
bundleId: currentBundleId,
|
|
521
|
+
channel: targetChannel,
|
|
522
|
+
updateStrategy: options.updateStrategy,
|
|
523
|
+
});
|
|
469
524
|
options.onError?.(error as Error);
|
|
470
525
|
return null;
|
|
471
526
|
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client plugin contract. Built-in plugins, such as
|
|
3
|
+
* `@hot-updater/react-native/plugins/insights`, and third-party plugins use
|
|
4
|
+
* the same one, as server plugins do.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type UpdateStrategy = "fingerprint" | "appVersion";
|
|
8
|
+
|
|
9
|
+
/** How an update moves the app's selection. */
|
|
10
|
+
export type ReleaseTransitionKind =
|
|
11
|
+
| "INSTALL"
|
|
12
|
+
| "ADOPT_RELEASE"
|
|
13
|
+
| "USE_EMBEDDED"
|
|
14
|
+
| "USE_BUILTIN";
|
|
15
|
+
|
|
16
|
+
/** The launch outcome native reports once per JavaScript runtime. */
|
|
17
|
+
export type AppReadyResult = (
|
|
18
|
+
| {
|
|
19
|
+
/** No update was applied or recovered at this launch. */
|
|
20
|
+
readonly status: "UNCHANGED";
|
|
21
|
+
readonly channel: string;
|
|
22
|
+
/** The running bundle. */
|
|
23
|
+
readonly bundleId: string;
|
|
24
|
+
/** The Release the running bundle was selected as, when known. */
|
|
25
|
+
readonly releaseId: string | null;
|
|
26
|
+
}
|
|
27
|
+
| {
|
|
28
|
+
/**
|
|
29
|
+
* `UPDATE_APPLIED`: a staged update became active at this launch.
|
|
30
|
+
* `RECOVERED`: a failed update was rolled back to `toBundleId`.
|
|
31
|
+
*/
|
|
32
|
+
readonly status: "UPDATE_APPLIED" | "RECOVERED";
|
|
33
|
+
readonly channel: string;
|
|
34
|
+
readonly fromBundleId: string;
|
|
35
|
+
readonly fromReleaseId: string | null;
|
|
36
|
+
readonly toBundleId: string;
|
|
37
|
+
readonly toReleaseId: string | null;
|
|
38
|
+
readonly updateStrategy: UpdateStrategy;
|
|
39
|
+
}
|
|
40
|
+
) & {
|
|
41
|
+
/**
|
|
42
|
+
* Android 11+: why the app's previous main process exited, the
|
|
43
|
+
* `ApplicationExitInfo` reason without its `REASON_` prefix, such as
|
|
44
|
+
* `CRASH`, `ANR`, `LOW_MEMORY`, or `USER_REQUESTED`. Null on iOS and on
|
|
45
|
+
* older Android. It tells a crash from the system or the user closing the
|
|
46
|
+
* app.
|
|
47
|
+
*/
|
|
48
|
+
readonly previousProcessExit: string | null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** What an update check found. */
|
|
52
|
+
export type UpdateCheckResult =
|
|
53
|
+
| {
|
|
54
|
+
/**
|
|
55
|
+
* The app keeps running its bundle: the check found nothing to
|
|
56
|
+
* install, or the app adopted a newer Release of the running bundle.
|
|
57
|
+
*/
|
|
58
|
+
readonly status: "UNCHANGED";
|
|
59
|
+
readonly channel: string;
|
|
60
|
+
readonly bundleId: string;
|
|
61
|
+
/** The Release the running bundle is selected as after the check. */
|
|
62
|
+
readonly releaseId: string | null;
|
|
63
|
+
/** The Release before the check; it differs after an adoption. */
|
|
64
|
+
readonly previousReleaseId: string | null;
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
/** The check found a Release to install, adopt, or roll back to. */
|
|
68
|
+
readonly status: "UPDATE_AVAILABLE";
|
|
69
|
+
readonly channel: string;
|
|
70
|
+
readonly fromBundleId: string;
|
|
71
|
+
readonly fromReleaseId: string | null;
|
|
72
|
+
readonly toBundleId: string;
|
|
73
|
+
readonly toReleaseId: string | null;
|
|
74
|
+
readonly transitionKind: ReleaseTransitionKind;
|
|
75
|
+
readonly updateStatus: "UPDATE" | "ROLLBACK";
|
|
76
|
+
readonly shouldForceUpdate: boolean;
|
|
77
|
+
readonly updateStrategy: UpdateStrategy;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** A bundle the SDK downloaded, verified, and staged for the next launch. */
|
|
81
|
+
export interface BundleDownloadedInfo {
|
|
82
|
+
readonly channel: string;
|
|
83
|
+
readonly fromBundleId: string;
|
|
84
|
+
readonly fromReleaseId: string | null;
|
|
85
|
+
readonly toBundleId: string;
|
|
86
|
+
readonly toReleaseId: string | null;
|
|
87
|
+
readonly updateStrategy: UpdateStrategy;
|
|
88
|
+
/**
|
|
89
|
+
* How the bundle arrived: `patch` when a bsdiff patch produced a file,
|
|
90
|
+
* `manifest` when only the changed files were downloaded, `archive` for
|
|
91
|
+
* the full archive.
|
|
92
|
+
*/
|
|
93
|
+
readonly delivery: "patch" | "manifest" | "archive";
|
|
94
|
+
/** A patch was tried, but the file or the archive was downloaded instead. */
|
|
95
|
+
readonly patchFallback: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Where an update failed: `check` covers the Release catalog request,
|
|
100
|
+
* `download` the transfer and verification of the update's files, and
|
|
101
|
+
* `install` patching, extracting, and moving them into place.
|
|
102
|
+
*/
|
|
103
|
+
export type UpdateErrorStage = "check" | "download" | "install";
|
|
104
|
+
|
|
105
|
+
/** What the SDK was fetching or applying when an update failed. */
|
|
106
|
+
export type UpdateErrorResource =
|
|
107
|
+
| "catalog"
|
|
108
|
+
| "artifact"
|
|
109
|
+
| "manifest"
|
|
110
|
+
| "file"
|
|
111
|
+
| "patch"
|
|
112
|
+
| "archive";
|
|
113
|
+
|
|
114
|
+
/** Why no response arrived, for a `network` failure. */
|
|
115
|
+
export type UpdateErrorTransport =
|
|
116
|
+
| "timeout"
|
|
117
|
+
| "dns"
|
|
118
|
+
| "tls"
|
|
119
|
+
| "connection"
|
|
120
|
+
| "offline"
|
|
121
|
+
| "cancelled";
|
|
122
|
+
|
|
123
|
+
export type UpdateErrorReason =
|
|
124
|
+
| "network"
|
|
125
|
+
| "http"
|
|
126
|
+
| "invalid_response"
|
|
127
|
+
| "hash_mismatch"
|
|
128
|
+
| "signature"
|
|
129
|
+
| "patch"
|
|
130
|
+
| "extract"
|
|
131
|
+
| "storage"
|
|
132
|
+
| "unknown";
|
|
133
|
+
|
|
134
|
+
/** An update check, download, or install that failed. */
|
|
135
|
+
export interface UpdateError {
|
|
136
|
+
readonly stage: UpdateErrorStage;
|
|
137
|
+
readonly reason: UpdateErrorReason;
|
|
138
|
+
/** What was being fetched or applied, when the SDK knows. */
|
|
139
|
+
readonly resource?: UpdateErrorResource;
|
|
140
|
+
/** The response status when `reason` is `"http"`. */
|
|
141
|
+
readonly httpStatus?: number;
|
|
142
|
+
/**
|
|
143
|
+
* The storage origin's XML error `<Code>`, such as `AccessDenied`,
|
|
144
|
+
* `NoSuchKey`, or `ExpiredToken`, when an `http` failure's body names one.
|
|
145
|
+
* It tells an expired signed URL from a missing file.
|
|
146
|
+
*/
|
|
147
|
+
readonly originCode?: string;
|
|
148
|
+
/** Why no response arrived, for a `network` failure, when the SDK knows. */
|
|
149
|
+
readonly transport?: UpdateErrorTransport;
|
|
150
|
+
/** Android 11+: why the previous main process exited (see `AppReadyResult`). */
|
|
151
|
+
readonly previousProcessExit?: string;
|
|
152
|
+
/** The bundle the update targeted; absent for the check stage. */
|
|
153
|
+
readonly targetBundleId?: string;
|
|
154
|
+
/** The Release the update targeted, when it names one. */
|
|
155
|
+
readonly targetReleaseId?: string;
|
|
156
|
+
readonly channel: string;
|
|
157
|
+
/** The running bundle and the Release it was selected as. */
|
|
158
|
+
readonly bundleId: string;
|
|
159
|
+
readonly releaseId: string | null;
|
|
160
|
+
readonly updateStrategy: UpdateStrategy;
|
|
161
|
+
/** The error the SDK caught. */
|
|
162
|
+
readonly cause: unknown;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Lifecycle hooks a plugin returns from `setup`. Hooks observe: the SDK
|
|
167
|
+
* never waits for one and ignores what it returns, and a hook that throws or
|
|
168
|
+
* rejects is reported through `onError`, or a console warning without one,
|
|
169
|
+
* so a plugin can never delay or change an update.
|
|
170
|
+
*/
|
|
171
|
+
export interface HotUpdaterClientHooks {
|
|
172
|
+
/** Called once per JavaScript runtime, after native reports the launch. */
|
|
173
|
+
onAppReady?(result: AppReadyResult): void | Promise<void>;
|
|
174
|
+
/** Called after each update check, and after a Release adoption commits. */
|
|
175
|
+
onUpdateCheck?(result: UpdateCheckResult): void | Promise<void>;
|
|
176
|
+
/** Called after the SDK stages a downloaded bundle. */
|
|
177
|
+
onBundleDownloaded?(info: BundleDownloadedInfo): void | Promise<void>;
|
|
178
|
+
/** Called when an update check, download, or install fails. */
|
|
179
|
+
onUpdateError?(error: UpdateError): void | Promise<void>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A plugin's persistent key-value store. Native keeps it on the device,
|
|
184
|
+
* outside device backups, and the SDK scopes its keys to the plugin's id.
|
|
185
|
+
* It is meant for small values, up to 64 KB each.
|
|
186
|
+
*/
|
|
187
|
+
export interface HotUpdaterClientStorage {
|
|
188
|
+
get(key: string): string | null;
|
|
189
|
+
/** Writes the value; `null` removes the key. */
|
|
190
|
+
set(key: string, value: string | null): void;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface HotUpdaterClientContext {
|
|
194
|
+
/**
|
|
195
|
+
* Requests a path relative to the configured `baseURL`, with the SDK's
|
|
196
|
+
* `requestHeaders` and `requestTimeout` applied. Rejects with
|
|
197
|
+
* `Request timed out` when the timeout passes before a response arrives.
|
|
198
|
+
*/
|
|
199
|
+
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
200
|
+
/** A random id native creates once per installation. */
|
|
201
|
+
readonly installId: string;
|
|
202
|
+
readonly platform: "ios" | "android";
|
|
203
|
+
/** The native app version, or null when the native build has none. */
|
|
204
|
+
readonly appVersion: string | null;
|
|
205
|
+
readonly sdkVersion: string;
|
|
206
|
+
/** Whether this is a debug build (`__DEV__`). */
|
|
207
|
+
readonly isDebugBuild: boolean;
|
|
208
|
+
/** The bundle native reports as current. */
|
|
209
|
+
getBundleId(): string;
|
|
210
|
+
getChannel(): string;
|
|
211
|
+
getCohort(): string;
|
|
212
|
+
getFingerprintHash(): string | null;
|
|
213
|
+
readonly storage: HotUpdaterClientStorage;
|
|
214
|
+
/** The current time in milliseconds since the epoch. */
|
|
215
|
+
now(): number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface HotUpdaterClientPlugin {
|
|
219
|
+
/** Unique among an app's plugins; two plugins with one id throw at init. */
|
|
220
|
+
readonly id: string;
|
|
221
|
+
/**
|
|
222
|
+
* Called once, when `HotUpdater.init` or `HotUpdater.wrap` first
|
|
223
|
+
* configures the runtime with this plugin.
|
|
224
|
+
*/
|
|
225
|
+
setup(context: HotUpdaterClientContext): HotUpdaterClientHooks | void;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Declares a client plugin; built-in and third-party plugins use the same contract. */
|
|
229
|
+
export const defineClientPlugin = <const P extends HotUpdaterClientPlugin>(
|
|
230
|
+
plugin: P,
|
|
231
|
+
): P => plugin;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
|
|
7
|
+
const sourceRoot = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
/** Every relative module a file imports, statically or dynamically. */
|
|
10
|
+
const readImports = (file: string): string[] => {
|
|
11
|
+
const source = readFileSync(file, "utf8");
|
|
12
|
+
const specifiers = [
|
|
13
|
+
...source.matchAll(/\b(?:import|export)\s[^;]*?\bfrom\s*["']([^"']+)["']/g),
|
|
14
|
+
...source.matchAll(/\bimport\s*["']([^"']+)["']/g),
|
|
15
|
+
...source.matchAll(/\bimport\(\s*["']([^"']+)["']\s*\)/g),
|
|
16
|
+
].map((match) => match[1]!);
|
|
17
|
+
return specifiers.filter((specifier) => specifier.startsWith("."));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const resolveModule = (from: string, specifier: string): string => {
|
|
21
|
+
const base = resolve(dirname(from), specifier);
|
|
22
|
+
const file = [
|
|
23
|
+
`${base}.ts`,
|
|
24
|
+
`${base}.tsx`,
|
|
25
|
+
join(base, "index.ts"),
|
|
26
|
+
join(base, "index.tsx"),
|
|
27
|
+
].find((path) => existsSync(path));
|
|
28
|
+
if (file === undefined) {
|
|
29
|
+
throw new Error(`Cannot resolve ${specifier} from ${from}`);
|
|
30
|
+
}
|
|
31
|
+
return file;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const collectGraph = (entry: string): Set<string> => {
|
|
35
|
+
const seen = new Set<string>();
|
|
36
|
+
const pending = [entry];
|
|
37
|
+
while (pending.length > 0) {
|
|
38
|
+
const file = pending.pop()!;
|
|
39
|
+
if (seen.has(file)) continue;
|
|
40
|
+
seen.add(file);
|
|
41
|
+
for (const specifier of readImports(file)) {
|
|
42
|
+
pending.push(resolveModule(file, specifier));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return seen;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
describe("@hot-updater/react-native root entry", () => {
|
|
49
|
+
const graph = [...collectGraph(join(sourceRoot, "index.ts"))].map((file) =>
|
|
50
|
+
relative(sourceRoot, file),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
it("bundles no plugin, since Metro does not tree-shake", () => {
|
|
54
|
+
expect(graph).toContain("pluginHost.ts");
|
|
55
|
+
expect(graph.filter((file) => file.startsWith("plugins/"))).toEqual([]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("sends no Insights request", () => {
|
|
59
|
+
const insightsCode = graph.filter((file) => {
|
|
60
|
+
const source = readFileSync(join(sourceRoot, file), "utf8");
|
|
61
|
+
return /["'`/]events["'`]/.test(source);
|
|
62
|
+
});
|
|
63
|
+
expect(insightsCode).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
});
|
package/src/fetchJSON.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
statusText: string,
|
|
5
|
-
) {
|
|
6
|
-
super(
|
|
7
|
-
`Request failed with HTTP ${status}${statusText ? ` ${statusText}` : ""}`,
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
1
|
+
import { UpdateHttpError } from "./updateError";
|
|
2
|
+
|
|
3
|
+
export class FetchJSONResponseError extends UpdateHttpError {}
|
|
11
4
|
|
|
12
5
|
export const fetchJSON = async <T>({
|
|
13
6
|
url,
|