@hot-updater/react-native 1.0.0-rc.17 → 1.0.0-rc.21
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/lib/commonjs/checkForUpdate.js +7 -9
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +7 -43
- package/lib/commonjs/clientPlugin.js.map +1 -1
- package/lib/commonjs/httpClient.js +7 -9
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +10 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +4 -4
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/pluginHost.js +26 -190
- package/lib/commonjs/pluginHost.js.map +1 -1
- package/lib/commonjs/releaseCatalogCache.js +7 -59
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/module/checkForUpdate.js +1 -3
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/clientPlugin.js +1 -41
- package/lib/module/clientPlugin.js.map +1 -1
- package/lib/module/httpClient.js +1 -3
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +1 -1
- package/lib/module/native.js.map +1 -1
- package/lib/module/pluginHost.js +22 -187
- package/lib/module/pluginHost.js.map +1 -1
- package/lib/module/releaseCatalogCache.js +3 -54
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +1 -184
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +1 -2
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +1 -1
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +3 -28
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +1 -13
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +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/wrap.types.d.ts +2 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +1 -184
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +1 -2
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +1 -1
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +3 -28
- package/lib/typescript/module/pluginHost.d.ts.map +1 -1
- package/lib/typescript/module/releaseCatalogCache.d.ts +1 -13
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +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/wrap.types.d.ts +2 -2
- package/package.json +7 -16
- package/src/checkForUpdate.ts +4 -6
- package/src/clientPlugin.ts +17 -231
- package/src/httpClient.ts +4 -4
- package/src/index.ts +7 -0
- package/src/native.ts +1 -1
- package/src/pluginHost.ts +24 -241
- package/src/releaseCatalogCache.ts +7 -136
- package/src/sdkVersion.ts +1 -1
- package/src/types.ts +1 -1
- package/src/wrap.types.ts +2 -2
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -2236
- package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +0 -220
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -264
- package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +0 -97
- package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +0 -71
- package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +0 -106
- package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
- package/android/src/test/java/com/hotupdater/NoBackupContext.kt +0 -11
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -397
- package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +0 -44
- package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +0 -57
- 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/TarBrArchiveExtractorTest.kt +0 -206
- package/lib/commonjs/appReady.spec.js +0 -360
- package/lib/commonjs/appReady.spec.js.map +0 -1
- package/lib/commonjs/appReady.test-utils.js +0 -25
- package/lib/commonjs/appReady.test-utils.js.map +0 -1
- package/lib/commonjs/baseURL.js +0 -16
- package/lib/commonjs/baseURL.js.map +0 -1
- 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 -936
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/commonjs/entryImportGraph.spec.js +0 -50
- package/lib/commonjs/entryImportGraph.spec.js.map +0 -1
- package/lib/commonjs/httpClient.spec.js +0 -171
- package/lib/commonjs/httpClient.spec.js.map +0 -1
- package/lib/commonjs/index.spec.js +0 -395
- 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 -910
- package/lib/commonjs/native.spec.js.map +0 -1
- package/lib/commonjs/pluginHost.spec.js +0 -292
- package/lib/commonjs/pluginHost.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/eventId.js +0 -129
- package/lib/commonjs/plugins/insights/eventId.js.map +0 -1
- package/lib/commonjs/plugins/insights/eventId.spec.js +0 -47
- package/lib/commonjs/plugins/insights/eventId.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/index.js +0 -272
- package/lib/commonjs/plugins/insights/index.js.map +0 -1
- package/lib/commonjs/plugins/insights/insights.spec.js +0 -640
- package/lib/commonjs/plugins/insights/insights.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/sender.js +0 -128
- package/lib/commonjs/plugins/insights/sender.js.map +0 -1
- package/lib/commonjs/plugins/insights/sender.spec.js +0 -265
- package/lib/commonjs/plugins/insights/sender.spec.js.map +0 -1
- package/lib/commonjs/plugins/insights/state.js +0 -145
- package/lib/commonjs/plugins/insights/state.js.map +0 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +0 -213
- 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 -84
- package/lib/commonjs/store.notification.spec.js.map +0 -1
- package/lib/commonjs/store.spec.js +0 -182
- package/lib/commonjs/store.spec.js.map +0 -1
- package/lib/commonjs/updateError.spec.js +0 -149
- package/lib/commonjs/updateError.spec.js.map +0 -1
- package/lib/commonjs/wrap.progress.spec.js +0 -195
- package/lib/commonjs/wrap.progress.spec.js.map +0 -1
- package/lib/commonjs/wrap.spec.js +0 -232
- package/lib/commonjs/wrap.spec.js.map +0 -1
- package/lib/module/appReady.spec.js +0 -359
- package/lib/module/appReady.spec.js.map +0 -1
- package/lib/module/appReady.test-utils.js +0 -19
- package/lib/module/appReady.test-utils.js.map +0 -1
- package/lib/module/baseURL.js +0 -11
- package/lib/module/baseURL.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 -935
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/module/entryImportGraph.spec.js +0 -50
- package/lib/module/entryImportGraph.spec.js.map +0 -1
- package/lib/module/httpClient.spec.js +0 -171
- package/lib/module/httpClient.spec.js.map +0 -1
- package/lib/module/index.spec.js +0 -394
- 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 -909
- package/lib/module/native.spec.js.map +0 -1
- package/lib/module/pluginHost.spec.js +0 -291
- package/lib/module/pluginHost.spec.js.map +0 -1
- package/lib/module/plugins/insights/eventId.js +0 -122
- package/lib/module/plugins/insights/eventId.js.map +0 -1
- package/lib/module/plugins/insights/eventId.spec.js +0 -47
- package/lib/module/plugins/insights/eventId.spec.js.map +0 -1
- package/lib/module/plugins/insights/index.js +0 -268
- package/lib/module/plugins/insights/index.js.map +0 -1
- package/lib/module/plugins/insights/insights.spec.js +0 -639
- package/lib/module/plugins/insights/insights.spec.js.map +0 -1
- package/lib/module/plugins/insights/sender.js +0 -123
- package/lib/module/plugins/insights/sender.js.map +0 -1
- package/lib/module/plugins/insights/sender.spec.js +0 -265
- package/lib/module/plugins/insights/sender.spec.js.map +0 -1
- package/lib/module/plugins/insights/state.js +0 -139
- package/lib/module/plugins/insights/state.js.map +0 -1
- package/lib/module/releaseCatalogCache.spec.js +0 -213
- 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 -85
- package/lib/module/store.notification.spec.js.map +0 -1
- package/lib/module/store.spec.js +0 -181
- package/lib/module/store.spec.js.map +0 -1
- package/lib/module/updateError.spec.js +0 -149
- package/lib/module/updateError.spec.js.map +0 -1
- package/lib/module/wrap.progress.spec.js +0 -196
- package/lib/module/wrap.progress.spec.js.map +0 -1
- package/lib/module/wrap.spec.js +0 -233
- package/lib/module/wrap.spec.js.map +0 -1
- package/lib/typescript/commonjs/appReady.test-utils.d.ts +0 -9
- package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +0 -1
- package/lib/typescript/commonjs/baseURL.d.ts +0 -4
- package/lib/typescript/commonjs/baseURL.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +0 -18
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/index.d.ts +0 -40
- package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts +0 -74
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +0 -1
- package/lib/typescript/commonjs/plugins/insights/state.d.ts +0 -46
- package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +0 -1
- package/lib/typescript/module/appReady.test-utils.d.ts +0 -9
- package/lib/typescript/module/appReady.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/baseURL.d.ts +0 -4
- package/lib/typescript/module/baseURL.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/eventId.d.ts +0 -18
- package/lib/typescript/module/plugins/insights/eventId.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/index.d.ts +0 -40
- package/lib/typescript/module/plugins/insights/index.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/sender.d.ts +0 -74
- package/lib/typescript/module/plugins/insights/sender.d.ts.map +0 -1
- package/lib/typescript/module/plugins/insights/state.d.ts +0 -46
- package/lib/typescript/module/plugins/insights/state.d.ts.map +0 -1
- package/plugins/insights/package.json +0 -8
- package/src/appReady.spec.ts +0 -393
- package/src/appReady.test-utils.ts +0 -31
- package/src/baseURL.ts +0 -15
- package/src/catalogCacheNative.spec.ts +0 -63
- package/src/checkForUpdate.releaseCatalog.spec.ts +0 -966
- package/src/entryImportGraph.spec.ts +0 -65
- package/src/httpClient.spec.ts +0 -216
- package/src/index.spec.ts +0 -434
- package/src/iosInterop.spec.ts +0 -30
- package/src/native.spec.ts +0 -978
- package/src/pluginHost.spec.ts +0 -297
- package/src/plugins/insights/eventId.spec.ts +0 -60
- package/src/plugins/insights/eventId.ts +0 -171
- package/src/plugins/insights/index.ts +0 -367
- package/src/plugins/insights/insights.spec.ts +0 -732
- package/src/plugins/insights/sender.spec.ts +0 -287
- package/src/plugins/insights/sender.ts +0 -221
- package/src/plugins/insights/state.ts +0 -184
- package/src/releaseCatalogCache.spec.ts +0 -304
- package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
- package/src/store.notification.spec.ts +0 -102
- package/src/store.spec.ts +0 -215
- package/src/updateError.spec.ts +0 -144
- package/src/wrap.progress.spec.ts +0 -232
- package/src/wrap.spec.ts +0 -262
package/src/pluginHost.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
+
import { createPluginHost } from "@hot-updater/protocol";
|
|
1
2
|
import { Platform } from "react-native";
|
|
2
3
|
|
|
3
|
-
import { resolveBaseURL } from "./baseURL";
|
|
4
|
-
import type {
|
|
5
|
-
HotUpdaterClientContext,
|
|
6
|
-
HotUpdaterClientHooks,
|
|
7
|
-
HotUpdaterClientPlugin,
|
|
8
|
-
HotUpdaterClientStorage,
|
|
9
|
-
} from "./clientPlugin";
|
|
10
4
|
import {
|
|
11
5
|
getAppVersion,
|
|
12
6
|
getBundleId,
|
|
@@ -18,245 +12,34 @@ import {
|
|
|
18
12
|
setStorageItem,
|
|
19
13
|
} from "./native";
|
|
20
14
|
import { HOT_UPDATER_SDK_VERSION } from "./sdkVersion";
|
|
21
|
-
import type { HotUpdaterBaseURL } from "./types";
|
|
22
15
|
|
|
23
|
-
type
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
/** Storage values stay small, since native reads them on the JS thread. */
|
|
37
|
-
const MAX_STORAGE_VALUE_LENGTH = 64 * 1024;
|
|
38
|
-
|
|
39
|
-
let config: PluginHostConfig | null = null;
|
|
40
|
-
/** Hooks by plugin, so a plugin configured again is not set up twice. */
|
|
41
|
-
const setUpPlugins = new WeakMap<
|
|
42
|
-
HotUpdaterClientPlugin,
|
|
43
|
-
HotUpdaterClientHooks
|
|
44
|
-
>();
|
|
45
|
-
let activePlugins: readonly {
|
|
46
|
-
readonly plugin: HotUpdaterClientPlugin;
|
|
47
|
-
readonly hooks: HotUpdaterClientHooks;
|
|
48
|
-
}[] = [];
|
|
49
|
-
/** What plugins read and wrote, so a read follows a write before native does. */
|
|
50
|
-
const storageValues = new Map<string, string | null>();
|
|
51
|
-
|
|
52
|
-
const reportHookError = (message: string, error: unknown) => {
|
|
53
|
-
const report = new Error(`[HotUpdater] ${message}`, { cause: error });
|
|
54
|
-
try {
|
|
55
|
-
if (config?.onError) {
|
|
56
|
-
config.onError(report);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
// A throwing onError must not break the update path either.
|
|
61
|
-
}
|
|
62
|
-
console.warn(report.message, error);
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const fetchFromServer = async (
|
|
66
|
-
path: string,
|
|
67
|
-
init: RequestInit = {},
|
|
68
|
-
): Promise<Response> => {
|
|
69
|
-
if (config === null) {
|
|
70
|
-
throw new Error("[HotUpdater] Plugins can fetch only after init or wrap.");
|
|
71
|
-
}
|
|
72
|
-
if (/^[a-z][a-z\d+.-]*:/i.test(path) || path.startsWith("//")) {
|
|
73
|
-
throw new TypeError(
|
|
74
|
-
"[HotUpdater] Plugin fetch takes a path relative to baseURL.",
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
const { requestHeaders, requestTimeout } = config;
|
|
78
|
-
const url = `${await resolveBaseURL(config.baseURL)}/${path.replace(/^\/+/, "")}`;
|
|
79
|
-
const headers = new Headers(requestHeaders);
|
|
80
|
-
new Headers(init.headers).forEach((value, name) => {
|
|
81
|
-
headers.set(name, value);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const controller = new AbortController();
|
|
85
|
-
const callerSignal = init.signal;
|
|
86
|
-
const abortFromCaller = () => controller.abort();
|
|
87
|
-
if (callerSignal?.aborted) controller.abort();
|
|
88
|
-
callerSignal?.addEventListener("abort", abortFromCaller);
|
|
89
|
-
const timeoutId = setTimeout(
|
|
90
|
-
() => controller.abort(),
|
|
91
|
-
requestTimeout ?? 5000,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
return await fetch(url, { ...init, headers, signal: controller.signal });
|
|
96
|
-
} catch (error: unknown) {
|
|
97
|
-
if (
|
|
98
|
-
error instanceof Error &&
|
|
99
|
-
error.name === "AbortError" &&
|
|
100
|
-
!callerSignal?.aborted
|
|
101
|
-
) {
|
|
102
|
-
throw new Error("Request timed out");
|
|
103
|
-
}
|
|
104
|
-
throw error;
|
|
105
|
-
} finally {
|
|
106
|
-
clearTimeout(timeoutId);
|
|
107
|
-
callerSignal?.removeEventListener("abort", abortFromCaller);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const createStorage = (pluginId: string): HotUpdaterClientStorage => {
|
|
112
|
-
const scopedKey = (key: string) => {
|
|
113
|
-
if (typeof key !== "string" || key.length === 0) {
|
|
114
|
-
throw new TypeError(
|
|
115
|
-
"[HotUpdater] Plugin storage keys are non-empty strings.",
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
// Encoded, so no id and key pair can name another plugin's key.
|
|
119
|
-
return `plugins/${encodeURIComponent(pluginId)}/${key}`;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
get: (key) => {
|
|
124
|
-
const storageKey = scopedKey(key);
|
|
125
|
-
const cached = storageValues.get(storageKey);
|
|
126
|
-
if (cached !== undefined) return cached;
|
|
127
|
-
const value = getStorageItem(storageKey);
|
|
128
|
-
storageValues.set(storageKey, value);
|
|
129
|
-
return value;
|
|
130
|
-
},
|
|
131
|
-
set: (key, value) => {
|
|
132
|
-
const storageKey = scopedKey(key);
|
|
133
|
-
if (value !== null && typeof value !== "string") {
|
|
134
|
-
throw new TypeError(
|
|
135
|
-
"[HotUpdater] Plugin storage values are strings or null.",
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
if (value !== null && value.length > MAX_STORAGE_VALUE_LENGTH) {
|
|
139
|
-
throw new RangeError(
|
|
140
|
-
"[HotUpdater] Plugin storage values are at most 64 KB.",
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
setStorageItem(storageKey, value);
|
|
144
|
-
storageValues.set(storageKey, value);
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const createContext = (pluginId: string): HotUpdaterClientContext => ({
|
|
150
|
-
fetch: fetchFromServer,
|
|
151
|
-
get installId() {
|
|
152
|
-
return getInstallId();
|
|
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";
|
|
153
29
|
},
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return getAppVersion();
|
|
30
|
+
get isDebugBuild() {
|
|
31
|
+
return typeof __DEV__ !== "undefined" && __DEV__;
|
|
157
32
|
},
|
|
158
33
|
get sdkVersion() {
|
|
159
34
|
return HOT_UPDATER_SDK_VERSION;
|
|
160
35
|
},
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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),
|
|
167
44
|
now: () => Date.now(),
|
|
168
45
|
});
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Sets up the plugins of an init or wrap call. A plugin passed to an earlier
|
|
172
|
-
* call keeps its hooks, and a plugin left out stops receiving events.
|
|
173
|
-
*/
|
|
174
|
-
export const configurePlugins = (
|
|
175
|
-
plugins: readonly HotUpdaterClientPlugin[] | undefined,
|
|
176
|
-
nextConfig: PluginHostConfig,
|
|
177
|
-
): void => {
|
|
178
|
-
const ids = new Set<string>();
|
|
179
|
-
for (const plugin of plugins ?? []) {
|
|
180
|
-
if (typeof plugin.id !== "string" || plugin.id.length === 0) {
|
|
181
|
-
throw new Error("[HotUpdater] A plugin id must be a non-empty string.");
|
|
182
|
-
}
|
|
183
|
-
if (ids.has(plugin.id)) {
|
|
184
|
-
throw new Error(
|
|
185
|
-
`[HotUpdater] Two plugins use the id "${plugin.id}". Plugin ids must be unique.`,
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
ids.add(plugin.id);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
config = nextConfig;
|
|
192
|
-
activePlugins = (plugins ?? []).map((plugin) => {
|
|
193
|
-
let hooks = setUpPlugins.get(plugin);
|
|
194
|
-
if (hooks === undefined) {
|
|
195
|
-
try {
|
|
196
|
-
hooks = plugin.setup(createContext(plugin.id)) ?? {};
|
|
197
|
-
} catch (error) {
|
|
198
|
-
reportHookError(`Plugin "${plugin.id}" failed in setup`, error);
|
|
199
|
-
hooks = {};
|
|
200
|
-
}
|
|
201
|
-
setUpPlugins.set(plugin, hooks);
|
|
202
|
-
}
|
|
203
|
-
return { plugin, hooks };
|
|
204
|
-
});
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
/** Whether any plugin listens to this hook, so callers build events only when one does. */
|
|
208
|
-
export const hasPluginHook = (name: HookName): boolean =>
|
|
209
|
-
activePlugins.some(({ hooks }) => typeof hooks[name] === "function");
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Builds a hook's event when a plugin listens. A failure to build it is
|
|
213
|
-
* reported, not thrown, since reporting never changes an update.
|
|
214
|
-
*/
|
|
215
|
-
export const buildPluginEvent = <K extends HookName>(
|
|
216
|
-
name: K,
|
|
217
|
-
createPayload: () => HookPayload<K> | null,
|
|
218
|
-
): HookPayload<K> | null => {
|
|
219
|
-
if (!hasPluginHook(name)) return null;
|
|
220
|
-
try {
|
|
221
|
-
return createPayload();
|
|
222
|
-
} catch (error) {
|
|
223
|
-
reportHookError(`Failed to build the ${name} event for plugins`, error);
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
/** Calls a hook on every plugin that listens, without waiting for any. */
|
|
229
|
-
export const dispatchPluginHook = <K extends HookName>(
|
|
230
|
-
name: K,
|
|
231
|
-
payload: HookPayload<K>,
|
|
232
|
-
): void => {
|
|
233
|
-
for (const { plugin, hooks } of activePlugins) {
|
|
234
|
-
const hook = hooks[name] as
|
|
235
|
-
| ((payload: HookPayload<K>) => unknown)
|
|
236
|
-
| undefined;
|
|
237
|
-
if (typeof hook !== "function") continue;
|
|
238
|
-
try {
|
|
239
|
-
const result = hook.call(hooks, payload);
|
|
240
|
-
if (
|
|
241
|
-
result !== null &&
|
|
242
|
-
typeof result === "object" &&
|
|
243
|
-
typeof (result as PromiseLike<unknown>).then === "function"
|
|
244
|
-
) {
|
|
245
|
-
void Promise.resolve(result).catch((error: unknown) => {
|
|
246
|
-
reportHookError(`Plugin "${plugin.id}" failed in ${name}`, error);
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
} catch (error) {
|
|
250
|
-
reportHookError(`Plugin "${plugin.id}" failed in ${name}`, error);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
/** Builds a hook's event when a plugin listens and calls the hook right away. */
|
|
256
|
-
export const emitPluginHook = <K extends HookName>(
|
|
257
|
-
name: K,
|
|
258
|
-
createPayload: () => HookPayload<K> | null,
|
|
259
|
-
): void => {
|
|
260
|
-
const payload = buildPluginEvent(name, createPayload);
|
|
261
|
-
if (payload !== null) dispatchPluginHook(name, payload);
|
|
262
|
-
};
|
|
@@ -1,19 +1,15 @@
|
|
|
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";
|
|
10
8
|
|
|
11
9
|
import { InvalidUpdateResponseError, UpdateHttpError } from "./updateError";
|
|
12
10
|
|
|
13
11
|
const CACHE_FORMAT_VERSION = "1";
|
|
14
12
|
const MAX_ETAG_BYTES = 1024;
|
|
15
|
-
export const MAX_RELEASE_CATALOG_WIRE_BYTES =
|
|
16
|
-
MAX_COMPILED_CATALOG_BYTES * 2 + 4 * 1024;
|
|
17
13
|
export const MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES =
|
|
18
14
|
MAX_RELEASE_CATALOG_WIRE_BYTES + MAX_ETAG_BYTES + 3;
|
|
19
15
|
|
|
@@ -48,20 +44,6 @@ let lastParsedCache:
|
|
|
48
44
|
})
|
|
49
45
|
| null = null;
|
|
50
46
|
|
|
51
|
-
export type ExpectedReleaseCatalogScope = {
|
|
52
|
-
readonly channelKey: string;
|
|
53
|
-
readonly platform: "ios" | "android";
|
|
54
|
-
} & (
|
|
55
|
-
| {
|
|
56
|
-
readonly strategy: "APP_VERSION";
|
|
57
|
-
readonly fingerprintHash?: never;
|
|
58
|
-
}
|
|
59
|
-
| {
|
|
60
|
-
readonly strategy: "FINGERPRINT";
|
|
61
|
-
readonly fingerprintHash: string;
|
|
62
|
-
}
|
|
63
|
-
);
|
|
64
|
-
|
|
65
47
|
type FetchReleaseCatalogInput = {
|
|
66
48
|
readonly baseURL: string;
|
|
67
49
|
readonly expectedScope: ExpectedReleaseCatalogScope;
|
|
@@ -70,117 +52,6 @@ type FetchReleaseCatalogInput = {
|
|
|
70
52
|
readonly url: string;
|
|
71
53
|
};
|
|
72
54
|
|
|
73
|
-
const getUtf8ByteLength = (value: string): number => {
|
|
74
|
-
let bytes = 0;
|
|
75
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
76
|
-
const code = value.charCodeAt(index);
|
|
77
|
-
if (code <= 0x7f) {
|
|
78
|
-
bytes += 1;
|
|
79
|
-
} else if (code <= 0x7ff) {
|
|
80
|
-
bytes += 2;
|
|
81
|
-
} else if (
|
|
82
|
-
code >= 0xd800 &&
|
|
83
|
-
code <= 0xdbff &&
|
|
84
|
-
index + 1 < value.length &&
|
|
85
|
-
value.charCodeAt(index + 1) >= 0xdc00 &&
|
|
86
|
-
value.charCodeAt(index + 1) <= 0xdfff
|
|
87
|
-
) {
|
|
88
|
-
bytes += 4;
|
|
89
|
-
index += 1;
|
|
90
|
-
} else {
|
|
91
|
-
bytes += 3;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return bytes;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const isStringArray = (value: unknown): value is readonly string[] =>
|
|
98
|
-
Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
99
|
-
|
|
100
|
-
const isValidReleaseDescriptor = (value: unknown): boolean => {
|
|
101
|
-
if (value === null || typeof value !== "object") return false;
|
|
102
|
-
const descriptor = value as Record<string, unknown>;
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
isUUIDv7(descriptor.releaseId) &&
|
|
106
|
-
(descriptor.kind === "BUNDLE" || descriptor.kind === "EMBEDDED") &&
|
|
107
|
-
((descriptor.kind === "BUNDLE" &&
|
|
108
|
-
typeof descriptor.bundleId === "string") ||
|
|
109
|
-
(descriptor.kind === "EMBEDDED" && descriptor.bundleId === null)) &&
|
|
110
|
-
Number.isSafeInteger(descriptor.rolloutCohortCount) &&
|
|
111
|
-
(descriptor.rolloutCohortCount as number) >= 0 &&
|
|
112
|
-
(descriptor.rolloutCohortCount as number) <= NUMERIC_COHORT_SIZE &&
|
|
113
|
-
isStringArray(descriptor.targetCohorts) &&
|
|
114
|
-
descriptor.targetCohorts.length <= MAX_TARGET_COHORTS_PER_RELEASE &&
|
|
115
|
-
typeof descriptor.shouldForceUpdate === "boolean" &&
|
|
116
|
-
(descriptor.message === null || typeof descriptor.message === "string")
|
|
117
|
-
);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
export const hasExpectedReleaseCatalogScope = (
|
|
121
|
-
catalog: ReleaseCatalog,
|
|
122
|
-
expected: ExpectedReleaseCatalogScope,
|
|
123
|
-
): boolean => {
|
|
124
|
-
try {
|
|
125
|
-
const parsed = parseReleaseCatalogScopeKey(catalog.scopeKey);
|
|
126
|
-
return (
|
|
127
|
-
parsed.channelKey === expected.channelKey &&
|
|
128
|
-
parsed.platform === expected.platform &&
|
|
129
|
-
parsed.strategy === expected.strategy &&
|
|
130
|
-
(parsed.strategy === "APP_VERSION" ||
|
|
131
|
-
(expected.strategy === "FINGERPRINT" &&
|
|
132
|
-
parsed.fingerprintHash === expected.fingerprintHash))
|
|
133
|
-
);
|
|
134
|
-
} catch {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const parseValidatedCatalog = (
|
|
140
|
-
value: string,
|
|
141
|
-
expectedScope: ExpectedReleaseCatalogScope,
|
|
142
|
-
): ReleaseCatalog | null => {
|
|
143
|
-
if (getUtf8ByteLength(value) > MAX_RELEASE_CATALOG_WIRE_BYTES) return null;
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const catalog = JSON.parse(value) as Partial<ReleaseCatalog>;
|
|
147
|
-
if (
|
|
148
|
-
catalog.schemaVersion !== 1 ||
|
|
149
|
-
typeof catalog.catalogId !== "string" ||
|
|
150
|
-
catalog.catalogId.length === 0 ||
|
|
151
|
-
typeof catalog.scopeKey !== "string" ||
|
|
152
|
-
!Number.isSafeInteger(catalog.generation) ||
|
|
153
|
-
(catalog.generation ?? 0) < 1 ||
|
|
154
|
-
typeof catalog.catalogHash !== "string" ||
|
|
155
|
-
!/^sha256:[0-9a-f]{64}$/.test(catalog.catalogHash) ||
|
|
156
|
-
catalog.fallbackPolicy !== "BUILTIN_IF_ACTIVE_INELIGIBLE" ||
|
|
157
|
-
!Array.isArray(catalog.releases) ||
|
|
158
|
-
!catalog.releases.every(isValidReleaseDescriptor) ||
|
|
159
|
-
(catalog.rollbackReleases !== undefined &&
|
|
160
|
-
(!Array.isArray(catalog.rollbackReleases) ||
|
|
161
|
-
!catalog.rollbackReleases.every(isValidReleaseDescriptor)))
|
|
162
|
-
) {
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
if (
|
|
166
|
-
!hasExpectedReleaseCatalogScope(catalog as ReleaseCatalog, expectedScope)
|
|
167
|
-
) {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
const distinctTargetCohorts = new Set(
|
|
171
|
-
[...catalog.releases, ...(catalog.rollbackReleases ?? [])].flatMap(
|
|
172
|
-
(release) => release.targetCohorts,
|
|
173
|
-
),
|
|
174
|
-
);
|
|
175
|
-
if (distinctTargetCohorts.size > MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE) {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
return catalog as ReleaseCatalog;
|
|
179
|
-
} catch {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
55
|
const isValidETag = (value: string | null): value is string =>
|
|
185
56
|
value !== null &&
|
|
186
57
|
value.length > 0 &&
|
|
@@ -216,7 +87,7 @@ const parseCache = (
|
|
|
216
87
|
const etag = value.slice(versionEnd + 1, etagEnd);
|
|
217
88
|
const body = value.slice(etagEnd + 1);
|
|
218
89
|
if (!isValidETag(etag)) return null;
|
|
219
|
-
const catalog =
|
|
90
|
+
const catalog = parseReleaseCatalog(body, expectedScope);
|
|
220
91
|
if (catalog === null) return null;
|
|
221
92
|
const parsed = { catalog, etag, partition, serialized: value };
|
|
222
93
|
lastParsedCache = parsed;
|
|
@@ -307,7 +178,7 @@ const consumeSuccessfulResponse = async (
|
|
|
307
178
|
}
|
|
308
179
|
|
|
309
180
|
const body = await response.text();
|
|
310
|
-
const catalog =
|
|
181
|
+
const catalog = parseReleaseCatalog(body, input.expectedScope);
|
|
311
182
|
if (catalog === null) {
|
|
312
183
|
await removeNativeReleaseCatalogCache(partition);
|
|
313
184
|
throw new InvalidUpdateResponseError("Received an invalid Release catalog");
|
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.21";
|
package/src/types.ts
CHANGED
package/src/wrap.types.ts
CHANGED
|
@@ -29,8 +29,8 @@ export type HotUpdaterFallbackComponentProps = {
|
|
|
29
29
|
|
|
30
30
|
interface CommonHotUpdaterOptions {
|
|
31
31
|
/**
|
|
32
|
-
* Client plugins, such as `insights()` from
|
|
33
|
-
*
|
|
32
|
+
* Client plugins, such as `insights()` from `@hot-updater/react-native`.
|
|
33
|
+
* Each plugin's `setup` runs
|
|
34
34
|
* once, and its hooks observe launches, update checks, downloads, and
|
|
35
35
|
* update failures. Plugin ids must be unique.
|
|
36
36
|
*/
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
package com.hotupdater
|
|
2
|
-
|
|
3
|
-
import org.junit.Assert.assertArrayEquals
|
|
4
|
-
import org.junit.Assert.assertFalse
|
|
5
|
-
import org.junit.Assert.assertThrows
|
|
6
|
-
import org.junit.Assert.assertTrue
|
|
7
|
-
import org.junit.Rule
|
|
8
|
-
import org.junit.Test
|
|
9
|
-
import org.junit.rules.TemporaryFolder
|
|
10
|
-
import java.util.Base64
|
|
11
|
-
|
|
12
|
-
class BsdiffPatchTest {
|
|
13
|
-
@get:Rule
|
|
14
|
-
val temporaryFolder = TemporaryFolder()
|
|
15
|
-
|
|
16
|
-
@Test
|
|
17
|
-
fun `applies bsdiff patch and verifies patched output hash`() {
|
|
18
|
-
val base = "console.log(\"base bundle\");\n".toByteArray()
|
|
19
|
-
val expected = "console.log(\"patched bundle\");\n".toByteArray()
|
|
20
|
-
val patch = Base64.getDecoder().decode(BSDIFF_PATCH_FIXTURE_BASE64)
|
|
21
|
-
val baseFile = temporaryFolder.newFile("base.bundle").apply { writeBytes(base) }
|
|
22
|
-
val patchFile = temporaryFolder.newFile("patch.bsdiff").apply { writeBytes(patch) }
|
|
23
|
-
val outputFile = temporaryFolder.newFile("output.bundle")
|
|
24
|
-
|
|
25
|
-
BsdiffPatch.apply(baseFile, patchFile, outputFile)
|
|
26
|
-
|
|
27
|
-
assertArrayEquals(expected, outputFile.readBytes())
|
|
28
|
-
assertTrue(HashUtils.verifyHash(outputFile, HashUtils.calculateSHA256(outputFile)))
|
|
29
|
-
assertFalse(HashUtils.verifyHash(outputFile, HashUtils.calculateSHA256(baseFile)))
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@Test
|
|
33
|
-
fun `rejects invalid bsdiff patch`() {
|
|
34
|
-
val base = "console.log(\"base bundle\");\n".toByteArray()
|
|
35
|
-
val invalidPatch = "not-a-bsdiff-patch-with-header".toByteArray()
|
|
36
|
-
|
|
37
|
-
assertThrows(IllegalArgumentException::class.java) {
|
|
38
|
-
BsdiffPatch.apply(base, invalidPatch)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
companion object {
|
|
43
|
-
const val BSDIFF_PATCH_FIXTURE_BASE64 =
|
|
44
|
-
"RU5EU0xFWS9CU0RJRkY0Mx8AAAAAAAAAQlpoOTFBWSZTWb12MIEAAAB5gEQYAADQYQAIPsXOACAAIo0A0NAaNCgAGgZMgHAtYscVxxRtTt4nmaj70g4gQSF5+T4u5IpwoSF67GEC"
|
|
45
|
-
}
|
|
46
|
-
}
|