@hot-updater/react-native 0.36.6 → 1.0.0-rc.0
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 +357 -6
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +268 -34
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +75 -9
- package/android/src/main/java/com/hotupdater/NativeConfigUtils.kt +2 -16
- package/android/src/main/java/com/hotupdater/ReleaseCatalogCacheService.kt +166 -0
- package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +1 -2
- package/android/src/newarch/HotUpdater.kt +1 -1
- package/android/src/newarch/HotUpdaterModule.kt +116 -0
- package/android/src/oldarch/HotUpdater.kt +1 -1
- package/android/src/oldarch/HotUpdaterModule.kt +128 -0
- package/android/src/oldarch/HotUpdaterSpec.kt +38 -0
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +280 -2
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +57 -2
- package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +118 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +491 -30
- package/ios/HotUpdater/Internal/BundleMetadata.swift +229 -6
- package/ios/HotUpdater/Internal/HotUpdater.mm +122 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +176 -7
- package/ios/HotUpdater/Internal/ReleaseCatalogCacheService.swift +213 -0
- package/lib/commonjs/catalogCacheNative.js +44 -0
- package/lib/commonjs/catalogCacheNative.js.map +1 -0
- package/lib/commonjs/catalogCacheNative.spec.js +31 -0
- package/lib/commonjs/catalogCacheNative.spec.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +231 -60
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +604 -0
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -0
- package/lib/commonjs/error.js +8 -1
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/{fetchUpdateInfo.js → fetchJSON.js} +11 -14
- package/lib/commonjs/fetchJSON.js.map +1 -0
- package/lib/commonjs/httpClient.js +153 -0
- package/lib/commonjs/httpClient.js.map +1 -0
- package/lib/commonjs/httpClient.spec.js +102 -0
- package/lib/commonjs/httpClient.spec.js.map +1 -0
- package/lib/commonjs/index.js +48 -71
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +117 -161
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/iosInterop.spec.js +18 -0
- package/lib/commonjs/iosInterop.spec.js.map +1 -0
- package/lib/commonjs/native.js +222 -55
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +226 -12
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js +218 -0
- package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +1 -0
- package/lib/commonjs/notifyAppReadyAnalytics.js +133 -0
- package/lib/commonjs/notifyAppReadyAnalytics.js.map +1 -0
- package/lib/commonjs/notifyAppReadyAnalytics.spec.js +246 -0
- package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +1 -0
- package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js +24 -0
- package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +1 -0
- package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js +117 -0
- package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +179 -0
- package/lib/commonjs/releaseCatalogCache.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.spec.js +195 -0
- package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -0
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +64 -0
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +1 -0
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/sdkVersion.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/types.js +0 -12
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/wrap.js +7 -82
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +203 -56
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/commonjs/wrap.types.js +6 -0
- package/lib/commonjs/wrap.types.js.map +1 -0
- package/lib/module/catalogCacheNative.js +36 -0
- package/lib/module/catalogCacheNative.js.map +1 -0
- package/lib/module/catalogCacheNative.spec.js +31 -0
- package/lib/module/catalogCacheNative.spec.js.map +1 -0
- package/lib/module/checkForUpdate.js +233 -62
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +603 -0
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -0
- package/lib/module/error.js +6 -0
- package/lib/module/error.js.map +1 -1
- package/lib/module/{fetchUpdateInfo.js → fetchJSON.js} +9 -12
- package/lib/module/fetchJSON.js.map +1 -0
- package/lib/module/httpClient.js +148 -0
- package/lib/module/httpClient.js.map +1 -0
- package/lib/module/httpClient.spec.js +102 -0
- package/lib/module/httpClient.spec.js.map +1 -0
- package/lib/module/index.js +49 -66
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +117 -161
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/iosInterop.spec.js +18 -0
- package/lib/module/iosInterop.spec.js.map +1 -0
- package/lib/module/native.js +206 -51
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +226 -12
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/notifyAppReadyAnalytics.boundary.spec.js +217 -0
- package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +1 -0
- package/lib/module/notifyAppReadyAnalytics.js +128 -0
- package/lib/module/notifyAppReadyAnalytics.js.map +1 -0
- package/lib/module/notifyAppReadyAnalytics.spec.js +245 -0
- package/lib/module/notifyAppReadyAnalytics.spec.js.map +1 -0
- package/lib/module/notifyAppReadyAnalytics.test-utils.js +18 -0
- package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +1 -0
- package/lib/module/notifyAppReadyAnalytics.transport.spec.js +116 -0
- package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +171 -0
- package/lib/module/releaseCatalogCache.js.map +1 -0
- package/lib/module/releaseCatalogCache.spec.js +195 -0
- package/lib/module/releaseCatalogCache.spec.js.map +1 -0
- package/lib/module/releaseCatalogCacheNativeContract.spec.js +64 -0
- package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +1 -0
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/sdkVersion.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/types.js +0 -12
- package/lib/module/types.js.map +1 -1
- package/lib/module/wrap.js +6 -81
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +203 -56
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/module/wrap.types.js +4 -0
- package/lib/module/wrap.types.js.map +1 -0
- package/lib/typescript/commonjs/catalogCacheNative.d.ts +4 -0
- package/lib/typescript/commonjs/catalogCacheNative.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +14 -4
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/error.d.ts +3 -0
- package/lib/typescript/commonjs/error.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchJSON.d.ts +6 -0
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -0
- package/lib/typescript/commonjs/httpClient.d.ts +56 -0
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +32 -41
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +83 -18
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts +13 -0
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +1 -0
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +8 -0
- package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +25 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +40 -5
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +0 -118
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +2 -191
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +65 -0
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -0
- package/lib/typescript/module/catalogCacheNative.d.ts +4 -0
- package/lib/typescript/module/catalogCacheNative.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +14 -4
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/error.d.ts +3 -0
- package/lib/typescript/module/error.d.ts.map +1 -1
- package/lib/typescript/module/fetchJSON.d.ts +6 -0
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -0
- package/lib/typescript/module/httpClient.d.ts +56 -0
- package/lib/typescript/module/httpClient.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +32 -41
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +83 -18
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/notifyAppReadyAnalytics.d.ts +13 -0
- package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +1 -0
- package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +8 -0
- package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +25 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -0
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +40 -5
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +0 -118
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +2 -191
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +65 -0
- package/lib/typescript/module/wrap.types.d.ts.map +1 -0
- package/package.json +6 -24
- package/src/catalogCacheNative.spec.ts +63 -0
- package/src/catalogCacheNative.ts +74 -0
- package/src/checkForUpdate.releaseCatalog.spec.ts +596 -0
- package/src/checkForUpdate.ts +368 -97
- package/src/error.ts +7 -0
- package/src/{fetchUpdateInfo.ts → fetchJSON.ts} +8 -15
- package/src/httpClient.spec.ts +132 -0
- package/src/httpClient.ts +261 -0
- package/src/index.spec.ts +110 -203
- package/src/index.ts +70 -93
- package/src/iosInterop.spec.ts +30 -0
- package/src/native.spec.ts +226 -10
- package/src/native.ts +385 -83
- package/src/notifyAppReadyAnalytics.boundary.spec.ts +234 -0
- package/src/notifyAppReadyAnalytics.spec.ts +243 -0
- package/src/notifyAppReadyAnalytics.test-utils.ts +31 -0
- package/src/notifyAppReadyAnalytics.transport.spec.ts +130 -0
- package/src/notifyAppReadyAnalytics.ts +194 -0
- package/src/releaseCatalogCache.spec.ts +282 -0
- package/src/releaseCatalogCache.ts +341 -0
- package/src/releaseCatalogCacheNativeContract.spec.ts +80 -0
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +51 -5
- package/src/types.ts +0 -140
- package/src/wrap.spec.ts +251 -56
- package/src/wrap.tsx +23 -318
- package/src/wrap.types.ts +78 -0
- package/android/src/main/java/com/hotupdater/StringResourceUtils.kt +0 -45
- package/app.plugin.js +0 -1
- package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.h +0 -7
- package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.mm +0 -4
- package/lib/commonjs/DefaultResolver.js +0 -50
- package/lib/commonjs/DefaultResolver.js.map +0 -1
- package/lib/commonjs/DefaultResolver.spec.js +0 -144
- package/lib/commonjs/DefaultResolver.spec.js.map +0 -1
- package/lib/commonjs/fetchUpdateInfo.js.map +0 -1
- package/lib/module/DefaultResolver.js +0 -46
- package/lib/module/DefaultResolver.js.map +0 -1
- package/lib/module/DefaultResolver.spec.js +0 -144
- package/lib/module/DefaultResolver.spec.js.map +0 -1
- package/lib/module/fetchUpdateInfo.js.map +0 -1
- package/lib/typescript/commonjs/DefaultResolver.d.ts +0 -11
- package/lib/typescript/commonjs/DefaultResolver.d.ts.map +0 -1
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +0 -7
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +0 -1
- package/lib/typescript/module/DefaultResolver.d.ts +0 -11
- package/lib/typescript/module/DefaultResolver.d.ts.map +0 -1
- package/lib/typescript/module/fetchUpdateInfo.d.ts +0 -7
- package/lib/typescript/module/fetchUpdateInfo.d.ts.map +0 -1
- package/plugin/build/transformers.js +0 -314
- package/plugin/build/withHotUpdater.js +0 -228
- package/src/DefaultResolver.spec.ts +0 -193
- package/src/DefaultResolver.ts +0 -63
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_TARGET_COHORTS_PER_RELEASE,
|
|
3
|
+
MAX_COMPILED_CATALOG_BYTES,
|
|
4
|
+
MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE,
|
|
5
|
+
NUMERIC_COHORT_SIZE,
|
|
6
|
+
isUUIDv7,
|
|
7
|
+
parseReleaseCatalogScopeKey,
|
|
8
|
+
type ReleaseCatalog,
|
|
9
|
+
} from "@hot-updater/core";
|
|
10
|
+
|
|
11
|
+
const CACHE_FORMAT_VERSION = "1";
|
|
12
|
+
const MAX_ETAG_BYTES = 1024;
|
|
13
|
+
export const MAX_RELEASE_CATALOG_WIRE_BYTES =
|
|
14
|
+
MAX_COMPILED_CATALOG_BYTES * 2 + 4 * 1024;
|
|
15
|
+
export const MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES =
|
|
16
|
+
MAX_RELEASE_CATALOG_WIRE_BYTES + MAX_ETAG_BYTES + 3;
|
|
17
|
+
|
|
18
|
+
const readNativeReleaseCatalogCache = async (partition: string) =>
|
|
19
|
+
(await import("./catalogCacheNative")).readNativeReleaseCatalogCache(
|
|
20
|
+
partition,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const writeNativeReleaseCatalogCache = async (
|
|
24
|
+
partition: string,
|
|
25
|
+
payload: string,
|
|
26
|
+
) =>
|
|
27
|
+
(await import("./catalogCacheNative")).writeNativeReleaseCatalogCache(
|
|
28
|
+
partition,
|
|
29
|
+
payload,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const removeNativeReleaseCatalogCache = async (partition: string) =>
|
|
33
|
+
(await import("./catalogCacheNative")).removeNativeReleaseCatalogCache(
|
|
34
|
+
partition,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
type CachedCatalog = {
|
|
38
|
+
readonly catalog: ReleaseCatalog;
|
|
39
|
+
readonly etag: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let lastParsedCache:
|
|
43
|
+
| (CachedCatalog & {
|
|
44
|
+
readonly partition: string;
|
|
45
|
+
readonly serialized: string;
|
|
46
|
+
})
|
|
47
|
+
| null = null;
|
|
48
|
+
|
|
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
|
+
type FetchReleaseCatalogInput = {
|
|
64
|
+
readonly baseURL: string;
|
|
65
|
+
readonly expectedScope: ExpectedReleaseCatalogScope;
|
|
66
|
+
readonly requestHeaders?: Record<string, string>;
|
|
67
|
+
readonly requestTimeout?: number;
|
|
68
|
+
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
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const isValidETag = (value: string | null): value is string =>
|
|
183
|
+
value !== null &&
|
|
184
|
+
value.length > 0 &&
|
|
185
|
+
!value.includes("\r") &&
|
|
186
|
+
!value.includes("\n") &&
|
|
187
|
+
getUtf8ByteLength(value) <= MAX_ETAG_BYTES;
|
|
188
|
+
|
|
189
|
+
const serializeCache = (etag: string, body: string): string =>
|
|
190
|
+
`${CACHE_FORMAT_VERSION}\n${etag}\n${body}`;
|
|
191
|
+
|
|
192
|
+
const parseCache = (
|
|
193
|
+
value: string,
|
|
194
|
+
expectedScope: ExpectedReleaseCatalogScope,
|
|
195
|
+
partition: string,
|
|
196
|
+
): CachedCatalog | null => {
|
|
197
|
+
if (
|
|
198
|
+
lastParsedCache !== null &&
|
|
199
|
+
lastParsedCache.serialized === value &&
|
|
200
|
+
lastParsedCache.partition === partition
|
|
201
|
+
) {
|
|
202
|
+
return lastParsedCache;
|
|
203
|
+
}
|
|
204
|
+
const versionEnd = value.indexOf("\n");
|
|
205
|
+
const etagEnd = value.indexOf("\n", versionEnd + 1);
|
|
206
|
+
if (
|
|
207
|
+
versionEnd === -1 ||
|
|
208
|
+
etagEnd === -1 ||
|
|
209
|
+
value.slice(0, versionEnd) !== CACHE_FORMAT_VERSION
|
|
210
|
+
) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const etag = value.slice(versionEnd + 1, etagEnd);
|
|
215
|
+
const body = value.slice(etagEnd + 1);
|
|
216
|
+
if (!isValidETag(etag)) return null;
|
|
217
|
+
const catalog = parseValidatedCatalog(body, expectedScope);
|
|
218
|
+
if (catalog === null) return null;
|
|
219
|
+
const parsed = { catalog, etag, partition, serialized: value };
|
|
220
|
+
lastParsedCache = parsed;
|
|
221
|
+
return parsed;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const normalizedHeaders = (
|
|
225
|
+
requestHeaders: Record<string, string> | undefined,
|
|
226
|
+
): readonly (readonly [string, string])[] => {
|
|
227
|
+
const headers = new Headers(requestHeaders);
|
|
228
|
+
return [...headers.entries()]
|
|
229
|
+
.map(([name, value]) => [name.toLowerCase(), value] as const)
|
|
230
|
+
.sort(([firstName], [secondName]) => firstName.localeCompare(secondName));
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const createReleaseCatalogCachePartition = (
|
|
234
|
+
input: Pick<FetchReleaseCatalogInput, "baseURL" | "requestHeaders" | "url">,
|
|
235
|
+
): string =>
|
|
236
|
+
JSON.stringify({
|
|
237
|
+
baseURL: input.baseURL,
|
|
238
|
+
headers: normalizedHeaders(input.requestHeaders),
|
|
239
|
+
url: input.url,
|
|
240
|
+
version: 2,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const fetchCatalogResponse = async (
|
|
244
|
+
input: FetchReleaseCatalogInput,
|
|
245
|
+
etag?: string,
|
|
246
|
+
): Promise<Response> => {
|
|
247
|
+
const controller = new AbortController();
|
|
248
|
+
const timeoutId = setTimeout(
|
|
249
|
+
() => controller.abort(),
|
|
250
|
+
input.requestTimeout ?? 5000,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const headers = new Headers(input.requestHeaders);
|
|
255
|
+
headers.set("Accept", "application/json");
|
|
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
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const readValidatedCache = async (
|
|
276
|
+
partition: string,
|
|
277
|
+
expectedScope: ExpectedReleaseCatalogScope,
|
|
278
|
+
): Promise<CachedCatalog | null> => {
|
|
279
|
+
const value = await readNativeReleaseCatalogCache(partition);
|
|
280
|
+
if (value === null) return null;
|
|
281
|
+
|
|
282
|
+
const cached = parseCache(value, expectedScope, partition);
|
|
283
|
+
if (cached === null) {
|
|
284
|
+
await removeNativeReleaseCatalogCache(partition);
|
|
285
|
+
}
|
|
286
|
+
return cached;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const consumeSuccessfulResponse = async (
|
|
290
|
+
response: Response,
|
|
291
|
+
input: FetchReleaseCatalogInput,
|
|
292
|
+
partition: string,
|
|
293
|
+
): Promise<ReleaseCatalog> => {
|
|
294
|
+
if (response.status !== 200) {
|
|
295
|
+
throw new Error(response.statusText);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const body = await response.text();
|
|
299
|
+
const catalog = parseValidatedCatalog(body, input.expectedScope);
|
|
300
|
+
if (catalog === null) {
|
|
301
|
+
await removeNativeReleaseCatalogCache(partition);
|
|
302
|
+
throw new Error("Received an invalid Release catalog");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const etag = response.headers.get("etag");
|
|
306
|
+
if (isValidETag(etag)) {
|
|
307
|
+
const serialized = serializeCache(etag, body);
|
|
308
|
+
if (getUtf8ByteLength(serialized) > MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES) {
|
|
309
|
+
await removeNativeReleaseCatalogCache(partition);
|
|
310
|
+
return catalog;
|
|
311
|
+
}
|
|
312
|
+
if (await writeNativeReleaseCatalogCache(partition, serialized)) {
|
|
313
|
+
lastParsedCache = {
|
|
314
|
+
catalog,
|
|
315
|
+
etag,
|
|
316
|
+
partition,
|
|
317
|
+
serialized,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
await removeNativeReleaseCatalogCache(partition);
|
|
322
|
+
}
|
|
323
|
+
return catalog;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export const fetchReleaseCatalogWithCache = async (
|
|
327
|
+
input: FetchReleaseCatalogInput,
|
|
328
|
+
): Promise<ReleaseCatalog> => {
|
|
329
|
+
const partition = createReleaseCatalogCachePartition(input);
|
|
330
|
+
const cached = await readValidatedCache(partition, input.expectedScope);
|
|
331
|
+
const response = await fetchCatalogResponse(input, cached?.etag);
|
|
332
|
+
|
|
333
|
+
if (response.status === 304) {
|
|
334
|
+
if (cached !== null) return cached.catalog;
|
|
335
|
+
|
|
336
|
+
const repairResponse = await fetchCatalogResponse(input);
|
|
337
|
+
return consumeSuccessfulResponse(repairResponse, input, partition);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return consumeSuccessfulResponse(response, input, partition);
|
|
341
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createReleaseCatalogScopeKey,
|
|
3
|
+
encodeChannelKey,
|
|
4
|
+
type ReleaseCatalog,
|
|
5
|
+
} from "@hot-updater/core";
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
|
|
8
|
+
const nativeModule = vi.hoisted(
|
|
9
|
+
() => ({}) as Record<string, ReturnType<typeof vi.fn>>,
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
vi.mock("./specs/NativeHotUpdater", () => ({ default: nativeModule }));
|
|
13
|
+
|
|
14
|
+
import { fetchReleaseCatalogWithCache } from "./releaseCatalogCache";
|
|
15
|
+
|
|
16
|
+
const catalogId = "project-a";
|
|
17
|
+
const scopeKey = createReleaseCatalogScopeKey({
|
|
18
|
+
channelKey: encodeChannelKey("production"),
|
|
19
|
+
platform: "ios",
|
|
20
|
+
strategy: "APP_VERSION",
|
|
21
|
+
});
|
|
22
|
+
const input = {
|
|
23
|
+
baseURL: "https://updates.example.com",
|
|
24
|
+
expectedScope: {
|
|
25
|
+
channelKey: encodeChannelKey("production"),
|
|
26
|
+
platform: "ios",
|
|
27
|
+
strategy: "APP_VERSION",
|
|
28
|
+
} as const,
|
|
29
|
+
requestHeaders: { "x-api-key": "key" },
|
|
30
|
+
url: "https://updates.example.com/catalog",
|
|
31
|
+
};
|
|
32
|
+
const catalog: ReleaseCatalog = {
|
|
33
|
+
catalogId,
|
|
34
|
+
catalogHash: `sha256:${"a".repeat(64)}`,
|
|
35
|
+
fallbackPolicy: "BUILTIN_IF_ACTIVE_INELIGIBLE",
|
|
36
|
+
generation: 1,
|
|
37
|
+
releases: [],
|
|
38
|
+
schemaVersion: 1,
|
|
39
|
+
scopeKey,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe("Release Catalog native cache contract", () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
for (const key of Object.keys(nativeModule)) delete nativeModule[key];
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("fails before fetching when the v1 native cache API is missing", async () => {
|
|
48
|
+
const fetchMock = vi.fn();
|
|
49
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
50
|
+
|
|
51
|
+
await expect(fetchReleaseCatalogWithCache(input)).rejects.toThrow(
|
|
52
|
+
"Native module is missing 'getReleaseCatalogCache()'. Rebuild the native app before using Release catalogs.",
|
|
53
|
+
);
|
|
54
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("uses the network when native cache I/O fails", async () => {
|
|
58
|
+
nativeModule.getReleaseCatalogCache = vi
|
|
59
|
+
.fn()
|
|
60
|
+
.mockRejectedValue(new Error("read"));
|
|
61
|
+
nativeModule.setReleaseCatalogCache = vi
|
|
62
|
+
.fn()
|
|
63
|
+
.mockRejectedValue(new Error("write"));
|
|
64
|
+
nativeModule.removeReleaseCatalogCache = vi
|
|
65
|
+
.fn()
|
|
66
|
+
.mockRejectedValue(new Error("remove"));
|
|
67
|
+
const fetchMock = vi.fn().mockResolvedValue(
|
|
68
|
+
new Response(JSON.stringify(catalog), {
|
|
69
|
+
headers: { ETag: '"v1"' },
|
|
70
|
+
status: 200,
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
74
|
+
|
|
75
|
+
await expect(fetchReleaseCatalogWithCache(input)).resolves.toEqual(catalog);
|
|
76
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
77
|
+
expect(nativeModule.getReleaseCatalogCache).toHaveBeenCalledOnce();
|
|
78
|
+
expect(nativeModule.setReleaseCatalogCache).toHaveBeenCalledOnce();
|
|
79
|
+
});
|
|
80
|
+
});
|
package/src/sdkVersion.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const HOT_UPDATER_SDK_VERSION = "0.
|
|
1
|
+
export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.0";
|
|
@@ -44,6 +44,8 @@ export interface UpdateBundleParams {
|
|
|
44
44
|
* the currently active bundle.
|
|
45
45
|
*/
|
|
46
46
|
changedAssets?: UnsafeObject | null;
|
|
47
|
+
/** Full Release Catalog selection receipt committed with the staged Bundle. */
|
|
48
|
+
selection?: UnsafeObject | null;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export interface Spec extends TurboModule {
|
|
@@ -88,17 +90,41 @@ export interface Spec extends TurboModule {
|
|
|
88
90
|
*/
|
|
89
91
|
updateBundle(params: UpdateBundleParams): Promise<boolean>;
|
|
90
92
|
|
|
93
|
+
/** Accepts and durably advances the catalog high-water for a scope. */
|
|
94
|
+
acceptReleaseCatalog(params: UnsafeObject): boolean;
|
|
95
|
+
|
|
96
|
+
/** Reads active/stable selection receipts and catalog high-water state. */
|
|
97
|
+
getActiveUpdateState(): UnsafeObject;
|
|
98
|
+
|
|
99
|
+
/** Reads a checksum-verified Release Catalog cache entry. */
|
|
100
|
+
getReleaseCatalogCache(partition: string): Promise<string | null>;
|
|
101
|
+
|
|
102
|
+
/** Atomically stores a bounded Release Catalog cache entry. */
|
|
103
|
+
setReleaseCatalogCache(partition: string, payload: string): Promise<boolean>;
|
|
104
|
+
|
|
105
|
+
/** Removes an incompatible Release Catalog cache entry. */
|
|
106
|
+
removeReleaseCatalogCache(partition: string): Promise<boolean>;
|
|
107
|
+
|
|
108
|
+
/** Rechecks generation/context immediately before a catalog side effect. */
|
|
109
|
+
isReleaseSelectionCurrent(params: UnsafeObject): boolean;
|
|
110
|
+
|
|
111
|
+
/** Atomically commits a metadata-only, EMBEDDED, or BUILTIN selection. */
|
|
112
|
+
commitReleaseSelection(params: UnsafeObject): Promise<boolean>;
|
|
113
|
+
|
|
91
114
|
/**
|
|
92
115
|
* Reads the launch report for the current process.
|
|
93
116
|
* This is a read-only API; native launch state has already been finalized.
|
|
94
117
|
*
|
|
95
|
-
* @returns Object
|
|
96
|
-
*
|
|
97
|
-
* - `status: "STABLE"` - No changes, already stable
|
|
98
|
-
* - `crashedBundleId` - Present only when status is "RECOVERED"
|
|
118
|
+
* @returns Object describing whether launch state changed and, for qualifying
|
|
119
|
+
* transitions, the relevant bundle ids plus persisted transition metadata.
|
|
99
120
|
*/
|
|
100
121
|
notifyAppReady(): {
|
|
101
|
-
status: "
|
|
122
|
+
status: "PENDING" | "UNCHANGED" | "UPDATE_APPLIED" | "RECOVERED";
|
|
123
|
+
fromReleaseId?: string;
|
|
124
|
+
fromBundleId?: string;
|
|
125
|
+
toReleaseId?: string;
|
|
126
|
+
toBundleId?: string;
|
|
127
|
+
updateStrategy?: "fingerprint" | "appVersion";
|
|
102
128
|
crashedBundleId?: string;
|
|
103
129
|
};
|
|
104
130
|
|
|
@@ -165,6 +191,26 @@ export interface Spec extends TurboModule {
|
|
|
165
191
|
*/
|
|
166
192
|
getCohort: () => string;
|
|
167
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Gets the persisted install id for this app installation.
|
|
196
|
+
*/
|
|
197
|
+
getInstallId: () => string;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Gets the persisted nullable user id associated with this installation.
|
|
201
|
+
*/
|
|
202
|
+
getUserId: () => string | null;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Gets the persisted nullable username associated with this installation.
|
|
206
|
+
*/
|
|
207
|
+
getUsername: () => string | null;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Persists nullable user identity fields associated with this installation.
|
|
211
|
+
*/
|
|
212
|
+
setUser: (userId: string | null, username: string | null) => void;
|
|
213
|
+
|
|
168
214
|
// EventEmitter
|
|
169
215
|
addListener(eventName: string): void;
|
|
170
216
|
removeListeners(count: number): void;
|
package/src/types.ts
CHANGED
|
@@ -1,145 +1,5 @@
|
|
|
1
|
-
import type { AppUpdateInfo } from "@hot-updater/core";
|
|
2
|
-
|
|
3
|
-
import type { NotifyAppReadyResult } from "./native";
|
|
4
|
-
|
|
5
1
|
export type HotUpdaterBaseURL = string | (() => string | Promise<string>);
|
|
6
2
|
|
|
7
|
-
/**
|
|
8
|
-
* Parameters passed to resolver.checkUpdate method
|
|
9
|
-
*/
|
|
10
|
-
export interface ResolverCheckUpdateParams {
|
|
11
|
-
/**
|
|
12
|
-
* The platform the app is running on
|
|
13
|
-
*/
|
|
14
|
-
platform: "ios" | "android";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* The current app version
|
|
18
|
-
*/
|
|
19
|
-
appVersion: string;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The current bundle ID
|
|
23
|
-
*/
|
|
24
|
-
bundleId: string;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Minimum bundle ID from build time
|
|
28
|
-
*/
|
|
29
|
-
minBundleId: string;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The channel name (e.g., "production", "staging")
|
|
33
|
-
*/
|
|
34
|
-
channel: string;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Cohort identifier used for server-side rollout decisions.
|
|
38
|
-
*/
|
|
39
|
-
cohort: string;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Update strategy being used
|
|
43
|
-
*/
|
|
44
|
-
updateStrategy: "fingerprint" | "appVersion";
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* The fingerprint hash (only present when using fingerprint strategy)
|
|
48
|
-
*/
|
|
49
|
-
fingerprintHash: string | null;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Request headers from global config (for optional use)
|
|
53
|
-
*/
|
|
54
|
-
requestHeaders?: Record<string, string>;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Request timeout from global config (for optional use)
|
|
58
|
-
*/
|
|
59
|
-
requestTimeout?: number;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Parameters passed to resolver.notifyAppReady method
|
|
64
|
-
*/
|
|
65
|
-
export interface ResolverNotifyAppReadyParams {
|
|
66
|
-
/**
|
|
67
|
-
* The bundle state from native notifyAppReady
|
|
68
|
-
* - "RECOVERED": App recovered from crash, rollback occurred
|
|
69
|
-
* - "STABLE": No changes, bundle is stable
|
|
70
|
-
*/
|
|
71
|
-
status: "RECOVERED" | "STABLE";
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Present only when status is "RECOVERED"
|
|
75
|
-
*/
|
|
76
|
-
crashedBundleId?: string;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Request headers from global config (for optional use)
|
|
80
|
-
*/
|
|
81
|
-
requestHeaders?: Record<string, string>;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Request timeout from global config (for optional use)
|
|
85
|
-
*/
|
|
86
|
-
requestTimeout?: number;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Resolver interface for custom network operations
|
|
91
|
-
*/
|
|
92
|
-
export interface HotUpdaterResolver {
|
|
93
|
-
/**
|
|
94
|
-
* Custom implementation for checking updates.
|
|
95
|
-
* When provided, this completely replaces the default fetchUpdateInfo flow.
|
|
96
|
-
*
|
|
97
|
-
* @param params - All parameters needed to check for updates
|
|
98
|
-
* @returns Update information or null if up to date
|
|
99
|
-
*
|
|
100
|
-
* @example
|
|
101
|
-
* ```typescript
|
|
102
|
-
* checkUpdate: async (params) => {
|
|
103
|
-
* const response = await fetch(`https://api.custom.com/check`, {
|
|
104
|
-
* method: 'POST',
|
|
105
|
-
* body: JSON.stringify(params),
|
|
106
|
-
* headers: params.requestHeaders,
|
|
107
|
-
* });
|
|
108
|
-
*
|
|
109
|
-
* if (!response.ok) return null;
|
|
110
|
-
* return response.json();
|
|
111
|
-
* }
|
|
112
|
-
* ```
|
|
113
|
-
*/
|
|
114
|
-
checkUpdate?: (
|
|
115
|
-
params: ResolverCheckUpdateParams,
|
|
116
|
-
) => Promise<AppUpdateInfo | null>;
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Custom implementation for notifying app ready.
|
|
120
|
-
* When provided, this completely replaces the default notifyAppReady network flow.
|
|
121
|
-
* Note: Native rollback/promotion semantics are already finalized before this callback runs.
|
|
122
|
-
*
|
|
123
|
-
* @param params - All parameters about the current app state
|
|
124
|
-
* @returns Notification result
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* ```typescript
|
|
128
|
-
* notifyAppReady: async (params) => {
|
|
129
|
-
* await fetch(`https://api.custom.com/notify`, {
|
|
130
|
-
* method: 'POST',
|
|
131
|
-
* body: JSON.stringify(params),
|
|
132
|
-
* });
|
|
133
|
-
*
|
|
134
|
-
* return { status: "STABLE" };
|
|
135
|
-
* }
|
|
136
|
-
* ```
|
|
137
|
-
*/
|
|
138
|
-
notifyAppReady?: (
|
|
139
|
-
params: ResolverNotifyAppReadyParams,
|
|
140
|
-
) => Promise<NotifyAppReadyResult | undefined>;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
3
|
/**
|
|
144
4
|
* Information about a signature verification failure.
|
|
145
5
|
* This is a security-critical event that indicates the bundle
|