@hot-updater/react-native 1.0.0-rc.4 → 1.0.0-rc.41
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/build.gradle +13 -2
- package/android/proguard-rules.pro +8 -2
- package/android/src/bridge/BridgeReactInstance.kt +38 -0
- package/android/src/bridgeless/BridgeReactInstance.kt +24 -0
- package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +94 -0
- package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1064 -844
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +50 -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 +46 -30
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +75 -192
- 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/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/newarch/JavaScriptExceptionHooks.kt +48 -0
- package/android/src/newarch/ReactIntegrationManager.kt +6 -32
- 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/android/src/oldarch/JavaScriptExceptionHooks.kt +71 -0
- package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1032 -1116
- package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -54
- 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 +32 -67
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +229 -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 +96 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +121 -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 +5 -14
- 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 +60 -0
- package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
- package/lib/commonjs/httpClient.js +64 -95
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +355 -324
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/init.types.js +6 -0
- package/lib/commonjs/init.types.js.map +1 -0
- package/lib/commonjs/native.js +77 -63
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/pluginHost.js +41 -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 +145 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/wrap.js +46 -38
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/appReady.js +90 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/checkForUpdate.js +114 -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 +5 -14
- 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 +55 -0
- package/lib/module/fetchUpdateResponse.js.map +1 -0
- package/lib/module/httpClient.js +63 -94
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +335 -327
- package/lib/module/index.js.map +1 -1
- package/lib/module/init.types.js +4 -0
- package/lib/module/init.types.js.map +1 -0
- package/lib/module/native.js +70 -56
- package/lib/module/native.js.map +1 -1
- package/lib/module/pluginHost.js +36 -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 +139 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/wrap.js +47 -38
- package/lib/module/wrap.js.map +1 -1
- package/lib/typescript/commonjs/appReady.d.ts +30 -0
- package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +20 -6
- 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 +5 -14
- 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 +5 -34
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +124 -116
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/init.types.d.ts +21 -0
- package/lib/typescript/commonjs/init.types.d.ts.map +1 -0
- package/lib/typescript/commonjs/native.d.ts +32 -25
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +8 -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 +35 -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 +2 -3
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +13 -41
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/appReady.d.ts +30 -0
- package/lib/typescript/module/appReady.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +20 -6
- 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 +5 -14
- 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 +5 -34
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +124 -116
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/init.types.d.ts +21 -0
- package/lib/typescript/module/init.types.d.ts.map +1 -0
- package/lib/typescript/module/native.d.ts +32 -25
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +8 -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 +35 -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 +2 -3
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +13 -41
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/appReady.ts +147 -0
- package/src/checkForUpdate.ts +156 -89
- package/src/clientPlugin.ts +21 -0
- package/src/error.ts +5 -15
- package/src/fetchJSON.ts +14 -30
- package/src/fetchUpdateResponse.ts +60 -0
- package/src/httpClient.ts +89 -160
- package/src/index.ts +413 -425
- package/src/init.types.ts +24 -0
- package/src/native.ts +101 -105
- package/src/pluginHost.ts +52 -0
- package/src/releaseCatalogCache.ts +43 -172
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +34 -48
- package/src/store.ts +2 -23
- package/src/types.ts +1 -1
- package/src/updateError.ts +167 -0
- package/src/wrap.tsx +60 -43
- package/src/wrap.types.ts +18 -46
- 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 -616
- 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 -615
- 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 -612
- 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
|
@@ -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.41";
|
|
@@ -2,48 +2,21 @@ import type { TurboModule } from "react-native";
|
|
|
2
2
|
import { TurboModuleRegistry } from "react-native";
|
|
3
3
|
import type { UnsafeObject } from "react-native/Libraries/Types/CodegenTypes";
|
|
4
4
|
|
|
5
|
-
export interface ChangedAsset {
|
|
6
|
-
file?: {
|
|
7
|
-
compression?: "br" | null;
|
|
8
|
-
url: string;
|
|
9
|
-
} | null;
|
|
10
|
-
fileHash: string;
|
|
11
|
-
patch?: {
|
|
12
|
-
algorithm: "bsdiff";
|
|
13
|
-
baseBundleId: string;
|
|
14
|
-
baseFileHash: string;
|
|
15
|
-
patchFileHash: string;
|
|
16
|
-
patchUrl: string;
|
|
17
|
-
} | null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
5
|
export interface UpdateBundleParams {
|
|
21
6
|
bundleId: string;
|
|
22
7
|
channel?: string;
|
|
23
|
-
fileUrl: string | null;
|
|
24
8
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* Format depends on signing configuration:
|
|
28
|
-
* - Signed: `sig:<base64_signature>` - Native will verify signature (and implicitly hash)
|
|
29
|
-
* - Unsigned: `<hex_hash>` - Native will verify SHA256 hash only
|
|
30
|
-
*
|
|
31
|
-
* Native determines verification mode by checking for "sig:" prefix.
|
|
9
|
+
* Signed manifest URL for installation.
|
|
32
10
|
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Optional signed manifest URL for manifest-driven installation.
|
|
36
|
-
*/
|
|
37
|
-
manifestUrl?: string | null;
|
|
11
|
+
manifestUrl: string;
|
|
38
12
|
/**
|
|
39
13
|
* File hash/signature for the manifest file itself.
|
|
40
14
|
*/
|
|
41
|
-
manifestFileHash
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
changedAssets?: UnsafeObject | null;
|
|
15
|
+
manifestFileHash: string;
|
|
16
|
+
/** Optional tar.br URL; integrity and sizes come from the verified manifest. */
|
|
17
|
+
archiveUrl?: string | null;
|
|
18
|
+
/** Full protocol v1 target file descriptor map. */
|
|
19
|
+
assets: UnsafeObject;
|
|
47
20
|
/** Full Release Catalog selection receipt committed with the staged Bundle. */
|
|
48
21
|
selection?: UnsafeObject | null;
|
|
49
22
|
}
|
|
@@ -61,18 +34,21 @@ export interface Spec extends TurboModule {
|
|
|
61
34
|
* Downloads and applies a bundle update.
|
|
62
35
|
*
|
|
63
36
|
* @param params - Update bundle parameters
|
|
64
|
-
* @returns Promise that resolves to
|
|
37
|
+
* @returns Promise that resolves, once the bundle is staged, to how it
|
|
38
|
+
* arrived: `{ delivery, patchFallback }`. `delivery` is "patch" when a
|
|
39
|
+
* bsdiff patch produced a file, "manifest" when only changed files were
|
|
40
|
+
* downloaded, or "archive" for the full archive; `patchFallback` is true
|
|
41
|
+
* when a patch was tried but the file or archive was downloaded instead.
|
|
65
42
|
* @throws {HotUpdaterErrorCode} Rejects with one of the following error codes:
|
|
66
43
|
*
|
|
67
44
|
* Parameter validation:
|
|
68
45
|
* - MISSING_BUNDLE_ID: Missing or empty bundleId
|
|
69
|
-
* - INVALID_FILE_URL: Invalid
|
|
46
|
+
* - INVALID_FILE_URL: Invalid manifest or asset URL provided
|
|
70
47
|
*
|
|
71
48
|
* Bundle storage:
|
|
72
49
|
* - DIRECTORY_CREATION_FAILED: Failed to create bundle directory
|
|
73
50
|
* - DOWNLOAD_FAILED: Failed to download bundle
|
|
74
51
|
* - INCOMPLETE_DOWNLOAD: Download incomplete (size mismatch)
|
|
75
|
-
* - EXTRACTION_FORMAT_ERROR: Invalid or corrupted archive format
|
|
76
52
|
* - INVALID_BUNDLE: Bundle missing required platform files
|
|
77
53
|
* - INSUFFICIENT_DISK_SPACE: Insufficient disk space
|
|
78
54
|
* - MOVE_OPERATION_FAILED: Failed to move bundle files
|
|
@@ -87,8 +63,20 @@ export interface Spec extends TurboModule {
|
|
|
87
63
|
*
|
|
88
64
|
* Note: iOS normalizes rare signature/storage errors to SIGNATURE_VERIFICATION_FAILED
|
|
89
65
|
* or UNKNOWN_ERROR to keep the JS error surface small.
|
|
66
|
+
*
|
|
67
|
+
* A rejection from the download or install also carries `userInfo`
|
|
68
|
+
* `{ stage, reason, resource?, httpStatus?, transport?, originCode? }`:
|
|
69
|
+
* `stage` is "download" (transfer and verification) or "install" (patch,
|
|
70
|
+
* extract, move into place); `reason` is one of "network", "http",
|
|
71
|
+
* "invalid_response", "hash_mismatch", "signature", "patch", "extract",
|
|
72
|
+
* "storage", or "unknown"; `resource` is what was being fetched or
|
|
73
|
+
* applied: "manifest", "file", "patch", or "archive". `httpStatus` is
|
|
74
|
+
* present when `reason` is "http", with `originCode`, the storage
|
|
75
|
+
* origin's XML error `<Code>` when the body names one. `transport` is
|
|
76
|
+
* present when no response arrived: "timeout", "dns", "tls",
|
|
77
|
+
* "connection", "offline", or "cancelled".
|
|
90
78
|
*/
|
|
91
|
-
updateBundle(params: UpdateBundleParams): Promise<
|
|
79
|
+
updateBundle(params: UpdateBundleParams): Promise<UnsafeObject>;
|
|
92
80
|
|
|
93
81
|
/** Accepts and durably advances the catalog high-water for a scope. */
|
|
94
82
|
acceptReleaseCatalog(params: UnsafeObject): boolean;
|
|
@@ -192,24 +180,22 @@ export interface Spec extends TurboModule {
|
|
|
192
180
|
getCohort: () => string;
|
|
193
181
|
|
|
194
182
|
/**
|
|
195
|
-
* Gets the
|
|
183
|
+
* Gets the install id: a random id native creates once per app
|
|
184
|
+
* installation and keeps out of device backups.
|
|
196
185
|
*/
|
|
197
186
|
getInstallId: () => string;
|
|
198
187
|
|
|
199
188
|
/**
|
|
200
|
-
*
|
|
201
|
-
|
|
202
|
-
getUserId: () => string | null;
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Gets the persisted nullable username associated with this installation.
|
|
189
|
+
* Reads a value from the SDK's persistent key-value store, which native
|
|
190
|
+
* keeps out of device backups. Returns null when the key has no value.
|
|
206
191
|
*/
|
|
207
|
-
|
|
192
|
+
getStorageItem: (key: string) => string | null;
|
|
208
193
|
|
|
209
194
|
/**
|
|
210
|
-
*
|
|
195
|
+
* Writes a value to the SDK's persistent key-value store; null removes the
|
|
196
|
+
* key.
|
|
211
197
|
*/
|
|
212
|
-
|
|
198
|
+
setStorageItem: (key: string, value: string | null) => void;
|
|
213
199
|
|
|
214
200
|
// EventEmitter
|
|
215
201
|
addListener(eventName: string): void;
|
package/src/store.ts
CHANGED
|
@@ -9,10 +9,8 @@ import { addListener } from "./native";
|
|
|
9
9
|
|
|
10
10
|
export type HotUpdaterState = {
|
|
11
11
|
progress: number;
|
|
12
|
-
downloadedBytes: number | undefined;
|
|
13
|
-
totalBytes: number | undefined;
|
|
14
12
|
isUpdateDownloaded: boolean;
|
|
15
|
-
artifactType: "
|
|
13
|
+
artifactType: "diff" | null;
|
|
16
14
|
details: HotUpdaterDiffProgressDetails | null;
|
|
17
15
|
};
|
|
18
16
|
|
|
@@ -56,8 +54,6 @@ const areDiffDetailsEqual = (
|
|
|
56
54
|
const areStatesEqual = (left: HotUpdaterState, right: HotUpdaterState) => {
|
|
57
55
|
return (
|
|
58
56
|
left.progress === right.progress &&
|
|
59
|
-
left.downloadedBytes === right.downloadedBytes &&
|
|
60
|
-
left.totalBytes === right.totalBytes &&
|
|
61
57
|
left.isUpdateDownloaded === right.isUpdateDownloaded &&
|
|
62
58
|
left.artifactType === right.artifactType &&
|
|
63
59
|
areDiffDetailsEqual(left.details, right.details)
|
|
@@ -78,8 +74,6 @@ const normalizeDownloadPath = (value: string | undefined) => {
|
|
|
78
74
|
const createHotUpdaterStore = () => {
|
|
79
75
|
let state: HotUpdaterState = {
|
|
80
76
|
progress: 0,
|
|
81
|
-
downloadedBytes: undefined,
|
|
82
|
-
totalBytes: undefined,
|
|
83
77
|
isUpdateDownloaded: false,
|
|
84
78
|
artifactType: null,
|
|
85
79
|
details: null,
|
|
@@ -165,10 +159,6 @@ const createHotUpdaterStore = () => {
|
|
|
165
159
|
nextState.details = state.details;
|
|
166
160
|
}
|
|
167
161
|
|
|
168
|
-
if (nextState.artifactType !== "diff") {
|
|
169
|
-
nextState.details = null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
162
|
if (areStatesEqual(state, nextState)) {
|
|
173
163
|
return;
|
|
174
164
|
}
|
|
@@ -180,19 +170,8 @@ const createHotUpdaterStore = () => {
|
|
|
180
170
|
const applyProgressEvent = (event: HotUpdaterProgressEvent) => {
|
|
181
171
|
setState({
|
|
182
172
|
artifactType: event.artifactType,
|
|
183
|
-
details:
|
|
184
|
-
event.artifactType === "diff"
|
|
185
|
-
? normalizeDiffDetails(event.details)
|
|
186
|
-
: null,
|
|
187
|
-
downloadedBytes:
|
|
188
|
-
event.artifactType === "archive"
|
|
189
|
-
? normalizeByteCount(event.downloadedBytes)
|
|
190
|
-
: undefined,
|
|
173
|
+
details: normalizeDiffDetails(event.details),
|
|
191
174
|
progress: event.progress,
|
|
192
|
-
totalBytes:
|
|
193
|
-
event.artifactType === "archive"
|
|
194
|
-
? normalizeByteCount(event.totalBytes)
|
|
195
|
-
: undefined,
|
|
196
175
|
});
|
|
197
176
|
};
|
|
198
177
|
|
package/src/types.ts
CHANGED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
UpdateError,
|
|
3
|
+
UpdateErrorReason,
|
|
4
|
+
UpdateErrorResource,
|
|
5
|
+
UpdateErrorStage,
|
|
6
|
+
UpdateErrorTransport,
|
|
7
|
+
} from "./clientPlugin";
|
|
8
|
+
import { HotUpdaterError } from "./error";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A response the SDK cannot use: a malformed or stale catalog, an artifact
|
|
12
|
+
* in another protocol, or an invalid URL.
|
|
13
|
+
*/
|
|
14
|
+
export class InvalidUpdateResponseError extends HotUpdaterError {
|
|
15
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "InvalidUpdateResponseError";
|
|
18
|
+
if (options?.cause !== undefined) this.cause = options.cause;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A request the server answered with a status the SDK does not accept. */
|
|
23
|
+
export class UpdateHttpError extends HotUpdaterError {
|
|
24
|
+
constructor(
|
|
25
|
+
readonly status: number,
|
|
26
|
+
statusText: string,
|
|
27
|
+
) {
|
|
28
|
+
super(
|
|
29
|
+
`Request failed with HTTP ${status}${statusText ? ` ${statusText}` : ""}`,
|
|
30
|
+
);
|
|
31
|
+
this.name = "UpdateHttpError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const STAGES: readonly UpdateErrorStage[] = ["check", "download", "install"];
|
|
36
|
+
const REASONS: readonly UpdateErrorReason[] = [
|
|
37
|
+
"network",
|
|
38
|
+
"http",
|
|
39
|
+
"invalid_response",
|
|
40
|
+
"hash_mismatch",
|
|
41
|
+
"signature",
|
|
42
|
+
"patch",
|
|
43
|
+
"extract",
|
|
44
|
+
"storage",
|
|
45
|
+
"unknown",
|
|
46
|
+
];
|
|
47
|
+
const RESOURCES: readonly UpdateErrorResource[] = [
|
|
48
|
+
"catalog",
|
|
49
|
+
"artifact",
|
|
50
|
+
"manifest",
|
|
51
|
+
"file",
|
|
52
|
+
"patch",
|
|
53
|
+
"archive",
|
|
54
|
+
];
|
|
55
|
+
const TRANSPORTS: readonly UpdateErrorTransport[] = [
|
|
56
|
+
"timeout",
|
|
57
|
+
"dns",
|
|
58
|
+
"tls",
|
|
59
|
+
"connection",
|
|
60
|
+
"offline",
|
|
61
|
+
"cancelled",
|
|
62
|
+
];
|
|
63
|
+
/** An S3, R2, or GCS error code: letters, digits, and punctuation only. */
|
|
64
|
+
const ORIGIN_CODE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
65
|
+
/** Expo's iOS and Android names for a fetch canceled before its response. */
|
|
66
|
+
const EXPO_FETCH_CANCELED =
|
|
67
|
+
/FetchRequestCanceledException|Fetch request has been canceled|The operation was aborted/;
|
|
68
|
+
|
|
69
|
+
type Classification = Pick<
|
|
70
|
+
UpdateError,
|
|
71
|
+
"stage" | "reason" | "resource" | "httpStatus" | "originCode" | "transport"
|
|
72
|
+
>;
|
|
73
|
+
|
|
74
|
+
const oneOf = <T extends string>(
|
|
75
|
+
values: readonly T[],
|
|
76
|
+
value: unknown,
|
|
77
|
+
): value is T => values.includes(value as T);
|
|
78
|
+
|
|
79
|
+
const readNativeClassification = (
|
|
80
|
+
error: object,
|
|
81
|
+
): Classification | null | undefined => {
|
|
82
|
+
if (!("code" in error) || typeof error.code !== "string") return undefined;
|
|
83
|
+
const userInfo =
|
|
84
|
+
"userInfo" in error && typeof error.userInfo === "object"
|
|
85
|
+
? (error.userInfo as Record<string, unknown> | null)
|
|
86
|
+
: null;
|
|
87
|
+
const { stage, reason, resource, transport, originCode } = userInfo ?? {};
|
|
88
|
+
// Native classifies every failure of its download and install; a rejection
|
|
89
|
+
// without it, such as a stale selection, is not an update failure.
|
|
90
|
+
if (!oneOf(STAGES, stage) || !oneOf(REASONS, reason)) return null;
|
|
91
|
+
const httpStatus = Number(userInfo?.httpStatus);
|
|
92
|
+
return {
|
|
93
|
+
stage,
|
|
94
|
+
reason,
|
|
95
|
+
...(oneOf(RESOURCES, resource) ? { resource } : {}),
|
|
96
|
+
...(reason === "http" && Number.isSafeInteger(httpStatus)
|
|
97
|
+
? { httpStatus }
|
|
98
|
+
: {}),
|
|
99
|
+
...(reason === "http" &&
|
|
100
|
+
typeof originCode === "string" &&
|
|
101
|
+
ORIGIN_CODE.test(originCode)
|
|
102
|
+
? { originCode }
|
|
103
|
+
: {}),
|
|
104
|
+
...(reason === "network" && oneOf(TRANSPORTS, transport)
|
|
105
|
+
? { transport }
|
|
106
|
+
: {}),
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Where and why an update failed, or null when the error is not an update
|
|
112
|
+
* failure. Native rejections carry their own classification; an error the
|
|
113
|
+
* SDK's JavaScript raised happened in `stage`, fetching `resource` when one
|
|
114
|
+
* is given.
|
|
115
|
+
*/
|
|
116
|
+
export const classifyUpdateError = (
|
|
117
|
+
error: unknown,
|
|
118
|
+
stage: UpdateErrorStage,
|
|
119
|
+
resource?: UpdateErrorResource,
|
|
120
|
+
): Classification | null => {
|
|
121
|
+
if (typeof error === "object" && error !== null) {
|
|
122
|
+
const native = readNativeClassification(error);
|
|
123
|
+
if (native !== undefined) return native;
|
|
124
|
+
}
|
|
125
|
+
const scope = {
|
|
126
|
+
stage,
|
|
127
|
+
...(resource === undefined ? {} : { resource }),
|
|
128
|
+
};
|
|
129
|
+
// Names, not classes, so an error from another copy of a module still
|
|
130
|
+
// classifies, as it does across test module resets.
|
|
131
|
+
const name = error instanceof Error ? error.name : null;
|
|
132
|
+
// A selection that became stale is a race between checks, not a failure.
|
|
133
|
+
if (name === "StaleReleaseCatalogError") return null;
|
|
134
|
+
if (name === "UpdateHttpError") {
|
|
135
|
+
return {
|
|
136
|
+
...scope,
|
|
137
|
+
reason: "http",
|
|
138
|
+
httpStatus: (error as UpdateHttpError).status,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (name === "InvalidUpdateResponseError" || name === "SyntaxError") {
|
|
142
|
+
return { ...scope, reason: "invalid_response" };
|
|
143
|
+
}
|
|
144
|
+
if (error instanceof Error && error.message === "Request timed out") {
|
|
145
|
+
return { ...scope, reason: "network", transport: "timeout" };
|
|
146
|
+
}
|
|
147
|
+
if (name === "AbortError") {
|
|
148
|
+
return { ...scope, reason: "network", transport: "cancelled" };
|
|
149
|
+
}
|
|
150
|
+
// Expo's fetch rejects with "fetch failed: <native exception>" when no
|
|
151
|
+
// response arrives, and names a canceled request.
|
|
152
|
+
if (error instanceof Error && error.message.startsWith("fetch failed:")) {
|
|
153
|
+
return {
|
|
154
|
+
...scope,
|
|
155
|
+
reason: "network",
|
|
156
|
+
...(EXPO_FETCH_CANCELED.test(error.message)
|
|
157
|
+
? { transport: "cancelled" }
|
|
158
|
+
: {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (name === "TypeError") {
|
|
162
|
+
// fetch rejects with a TypeError when no response arrives, without
|
|
163
|
+
// saying why.
|
|
164
|
+
return { ...scope, reason: "network" };
|
|
165
|
+
}
|
|
166
|
+
return { ...scope, reason: "unknown" };
|
|
167
|
+
};
|