@hot-updater/react-native 1.0.0-rc.3 → 1.0.0-rc.31
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 +954 -837
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +15 -11
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +43 -31
- 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/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 +127 -50
- 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/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 +109 -84
- package/android/src/oldarch/HotUpdater.kt +7 -9
- package/android/src/oldarch/HotUpdaterModule.kt +98 -74
- package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
- package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +950 -1112
- package/ios/HotUpdater/Internal/BundleMetadata.swift +8 -53
- package/ios/HotUpdater/Internal/FileManagerService.swift +48 -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 +13 -45
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +190 -73
- package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
- package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
- package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
- package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
- 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/appReady.js +111 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +124 -69
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +13 -0
- package/lib/commonjs/clientPlugin.js.map +1 -0
- package/lib/commonjs/error.js +4 -13
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/fetchJSON.js +16 -27
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/fetchUpdateResponse.js +54 -0
- package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
- package/lib/commonjs/httpClient.js +63 -94
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +71 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +78 -60
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/pluginHost.js +44 -0
- package/lib/commonjs/pluginHost.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +36 -87
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- 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/updateError.js +132 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/wrap.js +43 -26
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/appReady.js +103 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/checkForUpdate.js +117 -64
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/clientPlugin.js +4 -0
- package/lib/module/clientPlugin.js.map +1 -0
- package/lib/module/error.js +4 -13
- package/lib/module/error.js.map +1 -1
- package/lib/module/fetchJSON.js +14 -26
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/fetchUpdateResponse.js +49 -0
- package/lib/module/fetchUpdateResponse.js.map +1 -0
- package/lib/module/httpClient.js +62 -93
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +58 -20
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +71 -53
- package/lib/module/native.js.map +1 -1
- package/lib/module/pluginHost.js +35 -0
- package/lib/module/pluginHost.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +32 -82
- package/lib/module/releaseCatalogCache.js.map +1 -1
- 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/updateError.js +126 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/wrap.js +43 -26
- package/lib/module/wrap.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/checkForUpdate.d.ts +16 -4
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
- 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 +5 -5
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
- package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
- package/lib/typescript/commonjs/httpClient.d.ts +4 -33
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -8
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +31 -22
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- 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 +42 -47
- 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/types.d.ts +1 -1
- package/lib/typescript/commonjs/types.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 -7
- 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/checkForUpdate.d.ts +16 -4
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +2 -0
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
- 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 +5 -5
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
- package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
- package/lib/typescript/module/httpClient.d.ts +4 -33
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -8
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +31 -22
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +4 -0
- package/lib/typescript/module/pluginHost.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- 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 +42 -47
- 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/types.d.ts +1 -1
- package/lib/typescript/module/types.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 -7
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/appReady.ts +149 -0
- package/src/checkForUpdate.ts +151 -88
- package/src/clientPlugin.ts +18 -0
- package/src/error.ts +4 -14
- package/src/fetchJSON.ts +14 -30
- package/src/fetchUpdateResponse.ts +54 -0
- package/src/httpClient.ts +88 -159
- package/src/index.ts +90 -24
- package/src/native.ts +106 -102
- package/src/pluginHost.ts +45 -0
- package/src/releaseCatalogCache.ts +43 -172
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +41 -48
- package/src/store.ts +2 -23
- package/src/types.ts +1 -1
- package/src/updateError.ts +153 -0
- package/src/wrap.tsx +57 -26
- package/src/wrap.types.ts +6 -7
- 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/BsdiffPatchTest.kt +0 -46
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
- package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
- package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
- package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
- 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/catalogCacheNative.spec.js +0 -31
- package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -615
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/commonjs/httpClient.spec.js +0 -102
- package/lib/commonjs/httpClient.spec.js.map +0 -1
- package/lib/commonjs/index.spec.js +0 -302
- package/lib/commonjs/index.spec.js.map +0 -1
- package/lib/commonjs/iosInterop.spec.js +0 -18
- package/lib/commonjs/iosInterop.spec.js.map +0 -1
- package/lib/commonjs/native.spec.js +0 -883
- package/lib/commonjs/native.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.js +0 -133
- package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
- package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/commonjs/store.notification.spec.js +0 -76
- package/lib/commonjs/store.notification.spec.js.map +0 -1
- package/lib/commonjs/store.spec.js +0 -316
- package/lib/commonjs/store.spec.js.map +0 -1
- package/lib/commonjs/wrap.spec.js +0 -329
- package/lib/commonjs/wrap.spec.js.map +0 -1
- package/lib/module/catalogCacheNative.spec.js +0 -31
- package/lib/module/catalogCacheNative.spec.js.map +0 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -614
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/module/httpClient.spec.js +0 -102
- package/lib/module/httpClient.spec.js.map +0 -1
- package/lib/module/index.spec.js +0 -301
- package/lib/module/index.spec.js.map +0 -1
- package/lib/module/iosInterop.spec.js +0 -18
- package/lib/module/iosInterop.spec.js.map +0 -1
- package/lib/module/native.spec.js +0 -882
- package/lib/module/native.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.js +0 -128
- package/lib/module/notifyAppReadyInsights.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.spec.js +0 -245
- package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/module/releaseCatalogCache.spec.js +0 -195
- package/lib/module/releaseCatalogCache.spec.js.map +0 -1
- package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/module/store.notification.spec.js +0 -77
- package/lib/module/store.notification.spec.js.map +0 -1
- package/lib/module/store.spec.js +0 -315
- package/lib/module/store.spec.js.map +0 -1
- package/lib/module/wrap.spec.js +0 -330
- package/lib/module/wrap.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
- 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/module/notifyAppReadyInsights.d.ts +0 -13
- 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/src/catalogCacheNative.spec.ts +0 -63
- package/src/checkForUpdate.releaseCatalog.spec.ts +0 -611
- package/src/httpClient.spec.ts +0 -132
- package/src/index.spec.ts +0 -337
- package/src/iosInterop.spec.ts +0 -30
- package/src/native.spec.ts +0 -945
- package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
- package/src/notifyAppReadyInsights.spec.ts +0 -243
- package/src/notifyAppReadyInsights.test-utils.ts +0 -31
- package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
- package/src/notifyAppReadyInsights.ts +0 -194
- package/src/releaseCatalogCache.spec.ts +0 -282
- package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
- package/src/store.notification.spec.ts +0 -92
- package/src/store.spec.ts +0 -372
- package/src/wrap.spec.ts +0 -406
package/src/native.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type
|
|
2
|
+
type ArtifactAsset,
|
|
3
3
|
type CatalogHighWater,
|
|
4
4
|
INVALID_COHORT_ERROR_MESSAGE,
|
|
5
5
|
isValidCohort,
|
|
6
6
|
normalizeCohortValue,
|
|
7
7
|
type UpdateStatus,
|
|
8
8
|
type PersistedSelectionReceipt,
|
|
9
|
-
} from "@hot-updater/
|
|
9
|
+
} from "@hot-updater/protocol";
|
|
10
10
|
import { NativeEventEmitter, Platform } from "react-native";
|
|
11
11
|
|
|
12
12
|
import { HotUpdaterErrorCode, isHotUpdaterError } from "./error";
|
|
@@ -74,7 +74,10 @@ class HotUpdaterSessionState {
|
|
|
74
74
|
private readonly defaultChannel: string;
|
|
75
75
|
private currentChannel: string;
|
|
76
76
|
private cachedCohort: string | undefined;
|
|
77
|
-
private readonly inflightUpdates = new Map<
|
|
77
|
+
private readonly inflightUpdates = new Map<
|
|
78
|
+
string,
|
|
79
|
+
Promise<BundleDelivery | null>
|
|
80
|
+
>();
|
|
78
81
|
private lastInstalledBundleId: string | null = null;
|
|
79
82
|
private readonly activeBundleSnapshotCache = new Map<
|
|
80
83
|
ActiveBundleSnapshotCacheKey,
|
|
@@ -103,11 +106,16 @@ class HotUpdaterSessionState {
|
|
|
103
106
|
return this.lastInstalledBundleId === bundleId;
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
getInflightUpdate(
|
|
109
|
+
getInflightUpdate(
|
|
110
|
+
bundleId: string,
|
|
111
|
+
): Promise<BundleDelivery | null> | undefined {
|
|
107
112
|
return this.inflightUpdates.get(bundleId);
|
|
108
113
|
}
|
|
109
114
|
|
|
110
|
-
trackInflightUpdate(
|
|
115
|
+
trackInflightUpdate(
|
|
116
|
+
bundleId: string,
|
|
117
|
+
promise: Promise<BundleDelivery | null>,
|
|
118
|
+
) {
|
|
111
119
|
this.inflightUpdates.set(bundleId, promise);
|
|
112
120
|
}
|
|
113
121
|
|
|
@@ -234,7 +242,7 @@ const getReloadProcess = (): (() => Promise<void>) | null => {
|
|
|
234
242
|
: null;
|
|
235
243
|
};
|
|
236
244
|
|
|
237
|
-
export type HotUpdaterProgressArtifactType = "
|
|
245
|
+
export type HotUpdaterProgressArtifactType = "diff";
|
|
238
246
|
|
|
239
247
|
export type HotUpdaterDiffFileStatus =
|
|
240
248
|
| "pending"
|
|
@@ -286,18 +294,11 @@ export interface HotUpdaterDiffProgressDetails {
|
|
|
286
294
|
files: HotUpdaterDiffFileSnapshot[];
|
|
287
295
|
}
|
|
288
296
|
|
|
289
|
-
export type HotUpdaterProgressEvent =
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
totalBytes?: number;
|
|
295
|
-
}
|
|
296
|
-
| {
|
|
297
|
-
progress: number;
|
|
298
|
-
artifactType: "diff";
|
|
299
|
-
details: HotUpdaterDiffProgressDetails;
|
|
300
|
-
};
|
|
297
|
+
export type HotUpdaterProgressEvent = {
|
|
298
|
+
progress: number;
|
|
299
|
+
artifactType: "diff";
|
|
300
|
+
details: HotUpdaterDiffProgressDetails;
|
|
301
|
+
};
|
|
301
302
|
|
|
302
303
|
export type HotUpdaterEvent = {
|
|
303
304
|
onProgress: HotUpdaterProgressEvent;
|
|
@@ -424,18 +425,42 @@ export const commitReleaseSelection = async (input: {
|
|
|
424
425
|
return ok;
|
|
425
426
|
};
|
|
426
427
|
|
|
428
|
+
/** How native delivered a staged bundle. */
|
|
429
|
+
export type BundleDelivery = {
|
|
430
|
+
/**
|
|
431
|
+
* "patch" when a bsdiff patch produced a file, "manifest" when only the
|
|
432
|
+
* changed files were downloaded, "archive" for the full archive.
|
|
433
|
+
*/
|
|
434
|
+
readonly delivery: "patch" | "manifest" | "archive";
|
|
435
|
+
/** A patch was tried, but the file or the archive was downloaded instead. */
|
|
436
|
+
readonly patchFallback: boolean;
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const readBundleDelivery = (value: unknown): BundleDelivery | null => {
|
|
440
|
+
if (typeof value !== "object" || value === null) return null;
|
|
441
|
+
const { delivery, patchFallback } = value as Record<string, unknown>;
|
|
442
|
+
if (
|
|
443
|
+
delivery !== "patch" &&
|
|
444
|
+
delivery !== "manifest" &&
|
|
445
|
+
delivery !== "archive"
|
|
446
|
+
) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
return { delivery, patchFallback: patchFallback === true };
|
|
450
|
+
};
|
|
451
|
+
|
|
427
452
|
/**
|
|
428
|
-
*
|
|
453
|
+
* Stages a bundle for the next launch. Resolves with how native delivered
|
|
454
|
+
* it, or with null when this runtime had already staged it and nothing was
|
|
455
|
+
* downloaded; rejects when the download or install fails.
|
|
429
456
|
*
|
|
430
|
-
* @param {UpdateParams} params - Parameters object required for bundle update
|
|
431
|
-
* @returns {Promise<boolean>} Resolves with true if download was successful
|
|
432
457
|
* @throws {Error} Rejects with error.code from HotUpdaterErrorCode enum and error.message
|
|
433
458
|
*/
|
|
434
|
-
export async function
|
|
459
|
+
export async function stageBundle(
|
|
460
|
+
params: UpdateParams,
|
|
461
|
+
): Promise<BundleDelivery | null> {
|
|
435
462
|
const updateBundleId = params.bundleId;
|
|
436
463
|
const status = params.status;
|
|
437
|
-
const targetFileUrl = params.fileUrl;
|
|
438
|
-
|
|
439
464
|
const currentBundleId = status === "UPDATE" ? getFreshBundleId() : undefined;
|
|
440
465
|
|
|
441
466
|
// If native is still on the same bundle we installed in this session,
|
|
@@ -446,7 +471,7 @@ export async function updateBundle(params: UpdateParams): Promise<boolean> {
|
|
|
446
471
|
sessionState.hasInstalledBundle(updateBundleId) &&
|
|
447
472
|
currentBundleId === updateBundleId
|
|
448
473
|
) {
|
|
449
|
-
return
|
|
474
|
+
return null;
|
|
450
475
|
}
|
|
451
476
|
|
|
452
477
|
const shouldSkipCurrentBundleIdCheck =
|
|
@@ -467,30 +492,25 @@ export async function updateBundle(params: UpdateParams): Promise<boolean> {
|
|
|
467
492
|
const existing = sessionState.getInflightUpdate(updateBundleId);
|
|
468
493
|
if (existing) return existing;
|
|
469
494
|
|
|
470
|
-
const targetFileHash = params.fileHash;
|
|
471
495
|
const targetChannel = params.channel;
|
|
472
496
|
const targetManifestUrl = params.manifestUrl;
|
|
473
497
|
const targetManifestFileHash = params.manifestFileHash;
|
|
474
|
-
const
|
|
498
|
+
const targetAssets = params.assets;
|
|
475
499
|
|
|
476
500
|
const promise = (async () => {
|
|
477
501
|
try {
|
|
478
502
|
const selection = params.selection;
|
|
479
|
-
const
|
|
503
|
+
const staged = await HotUpdaterNative.updateBundle({
|
|
480
504
|
bundleId: updateBundleId,
|
|
481
505
|
channel: targetChannel,
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
manifestFileHash: targetManifestFileHash ?? null,
|
|
487
|
-
manifestUrl: targetManifestUrl ?? null,
|
|
506
|
+
assets: targetAssets as Record<string, ArtifactAsset>,
|
|
507
|
+
manifestFileHash: targetManifestFileHash,
|
|
508
|
+
manifestUrl: targetManifestUrl,
|
|
509
|
+
...(params.archiveUrl ? { archiveUrl: params.archiveUrl } : {}),
|
|
488
510
|
...(selection === undefined ? {} : { selection }),
|
|
489
511
|
});
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
return ok;
|
|
512
|
+
sessionState.markBundleInstalled(updateBundleId, targetChannel);
|
|
513
|
+
return readBundleDelivery(staged);
|
|
494
514
|
} finally {
|
|
495
515
|
sessionState.clearInflightUpdate(updateBundleId);
|
|
496
516
|
}
|
|
@@ -500,6 +520,18 @@ export async function updateBundle(params: UpdateParams): Promise<boolean> {
|
|
|
500
520
|
return promise;
|
|
501
521
|
}
|
|
502
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Downloads files and applies them to the app.
|
|
525
|
+
*
|
|
526
|
+
* @param {UpdateParams} params - Parameters object required for bundle update
|
|
527
|
+
* @returns {Promise<boolean>} Resolves with true if download was successful
|
|
528
|
+
* @throws {Error} Rejects with error.code from HotUpdaterErrorCode enum and error.message
|
|
529
|
+
*/
|
|
530
|
+
export async function updateBundle(params: UpdateParams): Promise<boolean> {
|
|
531
|
+
await stageBundle(params);
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
|
|
503
535
|
/**
|
|
504
536
|
* Fetches the current app version.
|
|
505
537
|
*/
|
|
@@ -705,64 +737,31 @@ export const getInstallId = (): string => {
|
|
|
705
737
|
return nativeModule.getInstallId();
|
|
706
738
|
};
|
|
707
739
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
export const getPersistedUserIdentity = (): PersistedUserIdentity => {
|
|
714
|
-
const nativeModule = HotUpdaterNative as typeof HotUpdaterNative & {
|
|
715
|
-
getUserId?: () => string | null;
|
|
716
|
-
getUsername?: () => string | null;
|
|
717
|
-
};
|
|
718
|
-
|
|
719
|
-
if (
|
|
720
|
-
typeof nativeModule.getUserId !== "function" ||
|
|
721
|
-
typeof nativeModule.getUsername !== "function"
|
|
722
|
-
) {
|
|
740
|
+
const requireStorageNativeMethod = <T extends (...args: any[]) => any>(
|
|
741
|
+
name: "getStorageItem" | "setStorageItem",
|
|
742
|
+
): T => {
|
|
743
|
+
const method = (HotUpdaterNative as unknown as Record<string, unknown>)[name];
|
|
744
|
+
if (typeof method !== "function") {
|
|
723
745
|
throw new Error(
|
|
724
|
-
|
|
746
|
+
`[HotUpdater] Native module is missing '${name}()'. This JS bundle requires a newer native @hot-updater/react-native SDK. Rebuild and release a new app version before delivering this OTA update.`,
|
|
725
747
|
);
|
|
726
748
|
}
|
|
727
|
-
|
|
728
|
-
const userId = nativeModule.getUserId();
|
|
729
|
-
const username = nativeModule.getUsername();
|
|
730
|
-
|
|
731
|
-
return {
|
|
732
|
-
...(userId != null ? { userId } : {}),
|
|
733
|
-
...(username != null ? { username } : {}),
|
|
734
|
-
};
|
|
749
|
+
return method.bind(HotUpdaterNative) as T;
|
|
735
750
|
};
|
|
736
751
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
752
|
+
/** Reads a value from the SDK's native key-value store. */
|
|
753
|
+
export const getStorageItem = (key: string): string | null => {
|
|
754
|
+
const value =
|
|
755
|
+
requireStorageNativeMethod<(key: string) => unknown>("getStorageItem")(key);
|
|
756
|
+
return typeof value === "string" ? value : null;
|
|
740
757
|
};
|
|
741
758
|
|
|
742
|
-
|
|
743
|
-
export
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
if (typeof nativeModule.setUser !== "function") {
|
|
750
|
-
throw new Error(
|
|
751
|
-
"[HotUpdater] Native module is missing 'setUser()'. This JS bundle requires a newer native @hot-updater/react-native SDK. Rebuild and release a new app version before delivering this OTA update.",
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
if (params === null) {
|
|
756
|
-
nativeModule.setUser(null, null);
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
const normalizedUserId =
|
|
761
|
-
params.userId === null || params.userId === undefined
|
|
762
|
-
? null
|
|
763
|
-
: String(params.userId);
|
|
764
|
-
nativeModule.setUser(normalizedUserId, params.username ?? null);
|
|
765
|
-
}
|
|
759
|
+
/** Writes a value to the SDK's native key-value store; null removes it. */
|
|
760
|
+
export const setStorageItem = (key: string, value: string | null): void => {
|
|
761
|
+
requireStorageNativeMethod<(key: string, value: string | null) => void>(
|
|
762
|
+
"setStorageItem",
|
|
763
|
+
)(key, value);
|
|
764
|
+
};
|
|
766
765
|
|
|
767
766
|
/**
|
|
768
767
|
* Result returned by notifyAppReady()
|
|
@@ -784,18 +783,20 @@ export type NotifyAppReadyResult =
|
|
|
784
783
|
toReleaseId?: string;
|
|
785
784
|
};
|
|
786
785
|
|
|
787
|
-
|
|
786
|
+
/** An update applied or recovered at this launch, as native persisted it. */
|
|
787
|
+
export type LaunchTransition = {
|
|
788
788
|
type: "UPDATE_APPLIED" | "RECOVERED";
|
|
789
789
|
fromBundleId: string;
|
|
790
790
|
toBundleId: string;
|
|
791
|
-
fromReleaseId
|
|
792
|
-
toReleaseId
|
|
791
|
+
fromReleaseId: string | null;
|
|
792
|
+
toReleaseId: string | null;
|
|
793
793
|
updateStrategy: PersistedUpdateStrategy;
|
|
794
794
|
};
|
|
795
795
|
|
|
796
796
|
type RawNotifyAppReadyResult = {
|
|
797
797
|
status?: string;
|
|
798
798
|
crashedBundleId?: string;
|
|
799
|
+
previousProcessExit?: string;
|
|
799
800
|
fromReleaseId?: string;
|
|
800
801
|
fromBundleId?: string;
|
|
801
802
|
toReleaseId?: string;
|
|
@@ -892,9 +893,9 @@ const getNotifyAppReadyTransition = (
|
|
|
892
893
|
return null;
|
|
893
894
|
};
|
|
894
895
|
|
|
895
|
-
const
|
|
896
|
+
const getLaunchTransition = (
|
|
896
897
|
result: RawNotifyAppReadyResult,
|
|
897
|
-
):
|
|
898
|
+
): LaunchTransition | null => {
|
|
898
899
|
const transition = getNotifyAppReadyTransition(result);
|
|
899
900
|
|
|
900
901
|
if (!transition || !isPersistedUpdateStrategy(result.updateStrategy)) {
|
|
@@ -905,12 +906,8 @@ const getNotifyAppReadyInsightsEvent = (
|
|
|
905
906
|
type: transition.status,
|
|
906
907
|
fromBundleId: transition.fromBundleId,
|
|
907
908
|
toBundleId: transition.toBundleId,
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
: { fromReleaseId: transition.fromReleaseId }),
|
|
911
|
-
...(transition.toReleaseId === null
|
|
912
|
-
? {}
|
|
913
|
-
: { toReleaseId: transition.toReleaseId }),
|
|
909
|
+
fromReleaseId: transition.fromReleaseId,
|
|
910
|
+
toReleaseId: transition.toReleaseId,
|
|
914
911
|
updateStrategy: result.updateStrategy,
|
|
915
912
|
};
|
|
916
913
|
};
|
|
@@ -939,14 +936,21 @@ const normalizeNotifyAppReadyResult = (
|
|
|
939
936
|
|
|
940
937
|
export const readNotifyAppReady = (): {
|
|
941
938
|
result: NotifyAppReadyResult;
|
|
942
|
-
|
|
939
|
+
transition: LaunchTransition | null;
|
|
940
|
+
/** Android 11+: why the previous main process exited, when native knows. */
|
|
941
|
+
previousProcessExit: string | null;
|
|
943
942
|
pending: boolean;
|
|
944
943
|
} => {
|
|
945
944
|
const rawResult = readRawNotifyAppReadyResult();
|
|
945
|
+
const { previousProcessExit } = rawResult;
|
|
946
946
|
|
|
947
947
|
return {
|
|
948
948
|
result: normalizeNotifyAppReadyResult(rawResult),
|
|
949
|
-
|
|
949
|
+
transition: getLaunchTransition(rawResult),
|
|
950
|
+
previousProcessExit:
|
|
951
|
+
typeof previousProcessExit === "string" && previousProcessExit !== ""
|
|
952
|
+
? previousProcessExit
|
|
953
|
+
: null,
|
|
950
954
|
pending: rawResult.status === "PENDING",
|
|
951
955
|
};
|
|
952
956
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createPluginHost } from "@hot-updater/protocol";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getAppVersion,
|
|
6
|
+
getBundleId,
|
|
7
|
+
getChannel,
|
|
8
|
+
getCohort,
|
|
9
|
+
getFingerprintHash,
|
|
10
|
+
getInstallId,
|
|
11
|
+
getStorageItem,
|
|
12
|
+
setStorageItem,
|
|
13
|
+
} from "./native";
|
|
14
|
+
import { HOT_UPDATER_SDK_VERSION } from "./sdkVersion";
|
|
15
|
+
|
|
16
|
+
export type { PluginHostConfig } from "@hot-updater/protocol";
|
|
17
|
+
|
|
18
|
+
/** The app's plugin host: plugins read the native module and fetch the configured server. */
|
|
19
|
+
export const {
|
|
20
|
+
configurePlugins,
|
|
21
|
+
hasPluginHook,
|
|
22
|
+
buildPluginEvent,
|
|
23
|
+
dispatchPluginHook,
|
|
24
|
+
emitPluginHook,
|
|
25
|
+
} = createPluginHost({
|
|
26
|
+
fetch: (url, init) => fetch(url, init),
|
|
27
|
+
get platform() {
|
|
28
|
+
return Platform.OS === "android" ? "android" : "ios";
|
|
29
|
+
},
|
|
30
|
+
get isDebugBuild() {
|
|
31
|
+
return typeof __DEV__ !== "undefined" && __DEV__;
|
|
32
|
+
},
|
|
33
|
+
get sdkVersion() {
|
|
34
|
+
return HOT_UPDATER_SDK_VERSION;
|
|
35
|
+
},
|
|
36
|
+
getInstallId: () => getInstallId(),
|
|
37
|
+
getAppVersion: () => getAppVersion(),
|
|
38
|
+
getBundleId: () => getBundleId(),
|
|
39
|
+
getChannel: () => getChannel(),
|
|
40
|
+
getCohort: () => getCohort(),
|
|
41
|
+
getFingerprintHash: () => getFingerprintHash(),
|
|
42
|
+
getStorageItem: (key) => getStorageItem(key),
|
|
43
|
+
setStorageItem: (key, value) => setStorageItem(key, value),
|
|
44
|
+
now: () => Date.now(),
|
|
45
|
+
});
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
isUUIDv7,
|
|
7
|
-
parseReleaseCatalogScopeKey,
|
|
2
|
+
type ExpectedReleaseCatalogScope,
|
|
3
|
+
getUtf8ByteLength,
|
|
4
|
+
MAX_RELEASE_CATALOG_WIRE_BYTES,
|
|
5
|
+
parseReleaseCatalog,
|
|
8
6
|
type ReleaseCatalog,
|
|
9
|
-
} from "@hot-updater/
|
|
7
|
+
} from "@hot-updater/protocol";
|
|
8
|
+
|
|
9
|
+
import { fetchUpdateResponse, type UpdateRequest } from "./fetchUpdateResponse";
|
|
10
|
+
import { InvalidUpdateResponseError, UpdateHttpError } from "./updateError";
|
|
10
11
|
|
|
11
12
|
const CACHE_FORMAT_VERSION = "1";
|
|
12
13
|
const MAX_ETAG_BYTES = 1024;
|
|
13
|
-
export const MAX_RELEASE_CATALOG_WIRE_BYTES =
|
|
14
|
-
MAX_COMPILED_CATALOG_BYTES * 2 + 4 * 1024;
|
|
15
14
|
export const MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES =
|
|
16
15
|
MAX_RELEASE_CATALOG_WIRE_BYTES + MAX_ETAG_BYTES + 3;
|
|
17
16
|
|
|
@@ -46,137 +45,13 @@ let lastParsedCache:
|
|
|
46
45
|
})
|
|
47
46
|
| null = null;
|
|
48
47
|
|
|
49
|
-
export type ExpectedReleaseCatalogScope = {
|
|
50
|
-
readonly channelKey: string;
|
|
51
|
-
readonly platform: "ios" | "android";
|
|
52
|
-
} & (
|
|
53
|
-
| {
|
|
54
|
-
readonly strategy: "APP_VERSION";
|
|
55
|
-
readonly fingerprintHash?: never;
|
|
56
|
-
}
|
|
57
|
-
| {
|
|
58
|
-
readonly strategy: "FINGERPRINT";
|
|
59
|
-
readonly fingerprintHash: string;
|
|
60
|
-
}
|
|
61
|
-
);
|
|
62
|
-
|
|
63
48
|
type FetchReleaseCatalogInput = {
|
|
64
49
|
readonly baseURL: string;
|
|
65
50
|
readonly expectedScope: ExpectedReleaseCatalogScope;
|
|
66
51
|
readonly requestHeaders?: Record<string, string>;
|
|
67
52
|
readonly requestTimeout?: number;
|
|
68
53
|
readonly url: string;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const getUtf8ByteLength = (value: string): number => {
|
|
72
|
-
let bytes = 0;
|
|
73
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
74
|
-
const code = value.charCodeAt(index);
|
|
75
|
-
if (code <= 0x7f) {
|
|
76
|
-
bytes += 1;
|
|
77
|
-
} else if (code <= 0x7ff) {
|
|
78
|
-
bytes += 2;
|
|
79
|
-
} else if (
|
|
80
|
-
code >= 0xd800 &&
|
|
81
|
-
code <= 0xdbff &&
|
|
82
|
-
index + 1 < value.length &&
|
|
83
|
-
value.charCodeAt(index + 1) >= 0xdc00 &&
|
|
84
|
-
value.charCodeAt(index + 1) <= 0xdfff
|
|
85
|
-
) {
|
|
86
|
-
bytes += 4;
|
|
87
|
-
index += 1;
|
|
88
|
-
} else {
|
|
89
|
-
bytes += 3;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return bytes;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const isStringArray = (value: unknown): value is readonly string[] =>
|
|
96
|
-
Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
97
|
-
|
|
98
|
-
const isValidReleaseDescriptor = (value: unknown): boolean => {
|
|
99
|
-
if (value === null || typeof value !== "object") return false;
|
|
100
|
-
const descriptor = value as Record<string, unknown>;
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
isUUIDv7(descriptor.releaseId) &&
|
|
104
|
-
(descriptor.kind === "BUNDLE" || descriptor.kind === "EMBEDDED") &&
|
|
105
|
-
((descriptor.kind === "BUNDLE" &&
|
|
106
|
-
typeof descriptor.bundleId === "string") ||
|
|
107
|
-
(descriptor.kind === "EMBEDDED" && descriptor.bundleId === null)) &&
|
|
108
|
-
Number.isSafeInteger(descriptor.rolloutCohortCount) &&
|
|
109
|
-
(descriptor.rolloutCohortCount as number) >= 0 &&
|
|
110
|
-
(descriptor.rolloutCohortCount as number) <= NUMERIC_COHORT_SIZE &&
|
|
111
|
-
isStringArray(descriptor.targetCohorts) &&
|
|
112
|
-
descriptor.targetCohorts.length <= MAX_TARGET_COHORTS_PER_RELEASE &&
|
|
113
|
-
typeof descriptor.shouldForceUpdate === "boolean" &&
|
|
114
|
-
(descriptor.message === null || typeof descriptor.message === "string")
|
|
115
|
-
);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export const hasExpectedReleaseCatalogScope = (
|
|
119
|
-
catalog: ReleaseCatalog,
|
|
120
|
-
expected: ExpectedReleaseCatalogScope,
|
|
121
|
-
): boolean => {
|
|
122
|
-
try {
|
|
123
|
-
const parsed = parseReleaseCatalogScopeKey(catalog.scopeKey);
|
|
124
|
-
return (
|
|
125
|
-
parsed.channelKey === expected.channelKey &&
|
|
126
|
-
parsed.platform === expected.platform &&
|
|
127
|
-
parsed.strategy === expected.strategy &&
|
|
128
|
-
(parsed.strategy === "APP_VERSION" ||
|
|
129
|
-
(expected.strategy === "FINGERPRINT" &&
|
|
130
|
-
parsed.fingerprintHash === expected.fingerprintHash))
|
|
131
|
-
);
|
|
132
|
-
} catch {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const parseValidatedCatalog = (
|
|
138
|
-
value: string,
|
|
139
|
-
expectedScope: ExpectedReleaseCatalogScope,
|
|
140
|
-
): ReleaseCatalog | null => {
|
|
141
|
-
if (getUtf8ByteLength(value) > MAX_RELEASE_CATALOG_WIRE_BYTES) return null;
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
const catalog = JSON.parse(value) as Partial<ReleaseCatalog>;
|
|
145
|
-
if (
|
|
146
|
-
catalog.schemaVersion !== 1 ||
|
|
147
|
-
typeof catalog.catalogId !== "string" ||
|
|
148
|
-
catalog.catalogId.length === 0 ||
|
|
149
|
-
typeof catalog.scopeKey !== "string" ||
|
|
150
|
-
!Number.isSafeInteger(catalog.generation) ||
|
|
151
|
-
(catalog.generation ?? 0) < 1 ||
|
|
152
|
-
typeof catalog.catalogHash !== "string" ||
|
|
153
|
-
!/^sha256:[0-9a-f]{64}$/.test(catalog.catalogHash) ||
|
|
154
|
-
catalog.fallbackPolicy !== "BUILTIN_IF_ACTIVE_INELIGIBLE" ||
|
|
155
|
-
!Array.isArray(catalog.releases) ||
|
|
156
|
-
!catalog.releases.every(isValidReleaseDescriptor) ||
|
|
157
|
-
(catalog.rollbackReleases !== undefined &&
|
|
158
|
-
(!Array.isArray(catalog.rollbackReleases) ||
|
|
159
|
-
!catalog.rollbackReleases.every(isValidReleaseDescriptor)))
|
|
160
|
-
) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
if (
|
|
164
|
-
!hasExpectedReleaseCatalogScope(catalog as ReleaseCatalog, expectedScope)
|
|
165
|
-
) {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
const distinctTargetCohorts = new Set(
|
|
169
|
-
[...catalog.releases, ...(catalog.rollbackReleases ?? [])].flatMap(
|
|
170
|
-
(release) => release.targetCohorts,
|
|
171
|
-
),
|
|
172
|
-
);
|
|
173
|
-
if (distinctTargetCohorts.size > MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE) {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
return catalog as ReleaseCatalog;
|
|
177
|
-
} catch {
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
54
|
+
readonly onResponse?: UpdateRequest["onResponse"];
|
|
180
55
|
};
|
|
181
56
|
|
|
182
57
|
const isValidETag = (value: string | null): value is string =>
|
|
@@ -214,7 +89,7 @@ const parseCache = (
|
|
|
214
89
|
const etag = value.slice(versionEnd + 1, etagEnd);
|
|
215
90
|
const body = value.slice(etagEnd + 1);
|
|
216
91
|
if (!isValidETag(etag)) return null;
|
|
217
|
-
const catalog =
|
|
92
|
+
const catalog = parseReleaseCatalog(body, expectedScope);
|
|
218
93
|
if (catalog === null) return null;
|
|
219
94
|
const parsed = { catalog, etag, partition, serialized: value };
|
|
220
95
|
lastParsedCache = parsed;
|
|
@@ -240,36 +115,24 @@ export const createReleaseCatalogCachePartition = (
|
|
|
240
115
|
version: 2,
|
|
241
116
|
});
|
|
242
117
|
|
|
243
|
-
const
|
|
118
|
+
const isEmptyCatalogResponse = (response: Response) =>
|
|
119
|
+
response.status === 404 &&
|
|
120
|
+
response.headers.get("x-hot-updater-catalog")?.trim().toLowerCase() ===
|
|
121
|
+
"none";
|
|
122
|
+
|
|
123
|
+
const fetchCatalogResponse = (
|
|
244
124
|
input: FetchReleaseCatalogInput,
|
|
245
125
|
etag?: string,
|
|
246
|
-
)
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (etag === undefined) {
|
|
257
|
-
headers.delete("If-None-Match");
|
|
258
|
-
} else {
|
|
259
|
-
headers.set("If-None-Match", etag);
|
|
260
|
-
}
|
|
261
|
-
return await fetch(input.url, {
|
|
262
|
-
headers,
|
|
263
|
-
signal: controller.signal,
|
|
264
|
-
});
|
|
265
|
-
} catch (error: unknown) {
|
|
266
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
267
|
-
throw new Error("Request timed out");
|
|
268
|
-
}
|
|
269
|
-
throw error;
|
|
270
|
-
} finally {
|
|
271
|
-
clearTimeout(timeoutId);
|
|
272
|
-
}
|
|
126
|
+
) => {
|
|
127
|
+
const headers = new Headers(input.requestHeaders);
|
|
128
|
+
headers.set("Accept", "application/json");
|
|
129
|
+
if (etag === undefined) headers.delete("If-None-Match");
|
|
130
|
+
else headers.set("If-None-Match", etag);
|
|
131
|
+
return fetchUpdateResponse({
|
|
132
|
+
...input,
|
|
133
|
+
resource: "catalog",
|
|
134
|
+
requestHeaders: Object.fromEntries(headers.entries()),
|
|
135
|
+
});
|
|
273
136
|
};
|
|
274
137
|
|
|
275
138
|
const readValidatedCache = async (
|
|
@@ -287,24 +150,28 @@ const readValidatedCache = async (
|
|
|
287
150
|
};
|
|
288
151
|
|
|
289
152
|
const consumeSuccessfulResponse = async (
|
|
290
|
-
response:
|
|
153
|
+
{ response, body }: Awaited<ReturnType<typeof fetchCatalogResponse>>,
|
|
291
154
|
input: FetchReleaseCatalogInput,
|
|
292
155
|
partition: string,
|
|
293
|
-
): Promise<ReleaseCatalog> => {
|
|
156
|
+
): Promise<ReleaseCatalog | null> => {
|
|
157
|
+
// The server marks the 404 of a scope that has no catalog yet: no update,
|
|
158
|
+
// not a failure. Any other 404 is a wrong baseURL or route.
|
|
159
|
+
if (isEmptyCatalogResponse(response)) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
294
162
|
if (response.status !== 200) {
|
|
295
|
-
throw new
|
|
163
|
+
throw new UpdateHttpError(response.status, response.statusText);
|
|
296
164
|
}
|
|
297
165
|
|
|
298
|
-
const
|
|
299
|
-
const catalog = parseValidatedCatalog(body, input.expectedScope);
|
|
166
|
+
const catalog = parseReleaseCatalog(body!, input.expectedScope);
|
|
300
167
|
if (catalog === null) {
|
|
301
168
|
await removeNativeReleaseCatalogCache(partition);
|
|
302
|
-
throw new
|
|
169
|
+
throw new InvalidUpdateResponseError("Received an invalid Release catalog");
|
|
303
170
|
}
|
|
304
171
|
|
|
305
172
|
const etag = response.headers.get("etag");
|
|
306
173
|
if (isValidETag(etag)) {
|
|
307
|
-
const serialized = serializeCache(etag, body);
|
|
174
|
+
const serialized = serializeCache(etag, body!);
|
|
308
175
|
if (getUtf8ByteLength(serialized) > MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES) {
|
|
309
176
|
await removeNativeReleaseCatalogCache(partition);
|
|
310
177
|
return catalog;
|
|
@@ -323,14 +190,18 @@ const consumeSuccessfulResponse = async (
|
|
|
323
190
|
return catalog;
|
|
324
191
|
};
|
|
325
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Fetches a scope's catalog, or null when the server answers that the scope
|
|
195
|
+
* has none (a 404 with `x-hot-updater-catalog: none`).
|
|
196
|
+
*/
|
|
326
197
|
export const fetchReleaseCatalogWithCache = async (
|
|
327
198
|
input: FetchReleaseCatalogInput,
|
|
328
|
-
): Promise<ReleaseCatalog> => {
|
|
199
|
+
): Promise<ReleaseCatalog | null> => {
|
|
329
200
|
const partition = createReleaseCatalogCachePartition(input);
|
|
330
201
|
const cached = await readValidatedCache(partition, input.expectedScope);
|
|
331
202
|
const response = await fetchCatalogResponse(input, cached?.etag);
|
|
332
203
|
|
|
333
|
-
if (response.status === 304) {
|
|
204
|
+
if (response.response.status === 304) {
|
|
334
205
|
if (cached !== null) return cached.catalog;
|
|
335
206
|
|
|
336
207
|
const repairResponse = await fetchCatalogResponse(input);
|
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.31";
|