react-native-update 10.48.2 → 10.49.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/BUNDLEHASH_MIGRATION.md +335 -0
- package/NATIVE_CHECKUPDATE_DESIGN.md +192 -0
- package/README-CN.md +40 -4
- package/README.md +40 -4
- package/android/jni/Android.mk +1 -0
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +89 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +13 -4
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +11 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +5 -0
- package/biome.json +59 -0
- package/cpp/patch_core/digest.cpp +177 -0
- package/cpp/patch_core/digest.h +43 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +70 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +1 -0
- package/harmony/pushy/src/main/cpp/pushy.cpp +47 -0
- package/harmony/pushy/src/main/ets/DownloadTask.ts +2 -2
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +2 -0
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +54 -0
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +55 -0
- package/package.json +5 -6
- package/react-native-update.podspec +1 -0
- package/src/NativePushy.ts +7 -1
- package/src/client.ts +83 -57
- package/src/context.ts +7 -7
- package/src/core.ts +78 -51
- package/src/endpoint.ts +7 -7
- package/src/error.ts +3 -3
- package/src/i18n.ts +2 -2
- package/src/index.ts +4 -4
- package/src/isInRollout.ts +10 -1
- package/src/locales/en.ts +2 -0
- package/src/locales/zh.ts +2 -0
- package/src/provider.tsx +46 -56
- package/src/resolveCheckResult.ts +1 -1
- package/src/telemetry.ts +1 -1
- package/src/type.ts +31 -5
- package/src/utils.ts +30 -30
- package/.eslintrc.js +0 -4
- package/.prettierrc.js +0 -6
package/src/endpoint.ts
CHANGED
|
@@ -28,7 +28,7 @@ const normalizeError = (error: unknown) => {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export const dedupeEndpoints = (
|
|
31
|
-
endpoints: Array<string | null | undefined
|
|
31
|
+
endpoints: Array<string | null | undefined>
|
|
32
32
|
): string[] => {
|
|
33
33
|
const result: string[] = [];
|
|
34
34
|
const visited = new Set<string>();
|
|
@@ -46,7 +46,7 @@ export const dedupeEndpoints = (
|
|
|
46
46
|
|
|
47
47
|
export const pickRandomEndpoint = (
|
|
48
48
|
endpoints: string[],
|
|
49
|
-
random: () => number = Math.random
|
|
49
|
+
random: () => number = Math.random
|
|
50
50
|
) => {
|
|
51
51
|
if (!endpoints.length) {
|
|
52
52
|
throw new UpdateError('No endpoints configured', 'NO_ENDPOINTS');
|
|
@@ -57,13 +57,13 @@ export const pickRandomEndpoint = (
|
|
|
57
57
|
export async function selectFastestSuccessfulEndpoint<T>(
|
|
58
58
|
endpoints: string[],
|
|
59
59
|
tryEndpoint: (endpoint: string) => Promise<T>,
|
|
60
|
-
now: () => number = Date.now
|
|
60
|
+
now: () => number = Date.now
|
|
61
61
|
): Promise<{
|
|
62
62
|
successes: EndpointAttemptSuccess<T>[];
|
|
63
63
|
failures: EndpointAttemptFailure[];
|
|
64
64
|
}> {
|
|
65
65
|
const attempts = await Promise.all(
|
|
66
|
-
endpoints.map(async endpoint => {
|
|
66
|
+
endpoints.map(async (endpoint) => {
|
|
67
67
|
const start = now();
|
|
68
68
|
try {
|
|
69
69
|
const value = await tryEndpoint(endpoint);
|
|
@@ -80,7 +80,7 @@ export async function selectFastestSuccessfulEndpoint<T>(
|
|
|
80
80
|
error: normalizeError(error),
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
-
})
|
|
83
|
+
})
|
|
84
84
|
);
|
|
85
85
|
|
|
86
86
|
const successes: EndpointAttemptSuccess<T>[] = [];
|
|
@@ -147,7 +147,7 @@ export async function executeEndpointFallback<T>({
|
|
|
147
147
|
? await getRemoteEndpoints().catch(() => [])
|
|
148
148
|
: [];
|
|
149
149
|
candidates = dedupeEndpoints([...candidates, ...remoteEndpoints]).filter(
|
|
150
|
-
endpoint => !excludedEndpoints.has(endpoint)
|
|
150
|
+
(endpoint) => !excludedEndpoints.has(endpoint)
|
|
151
151
|
);
|
|
152
152
|
|
|
153
153
|
if (!candidates.length) {
|
|
@@ -157,7 +157,7 @@ export async function executeEndpointFallback<T>({
|
|
|
157
157
|
const { successes, failures } = await selectFastestSuccessfulEndpoint(
|
|
158
158
|
candidates,
|
|
159
159
|
tryEndpoint,
|
|
160
|
-
now
|
|
160
|
+
now
|
|
161
161
|
);
|
|
162
162
|
|
|
163
163
|
if (successes.length) {
|
package/src/error.ts
CHANGED
|
@@ -62,7 +62,7 @@ const KNOWN_CODES = new Set<string>([
|
|
|
62
62
|
* else is treated as absent so it never leaks into telemetry aggregation.
|
|
63
63
|
*/
|
|
64
64
|
export const asUpdateErrorCode = (
|
|
65
|
-
code: unknown
|
|
65
|
+
code: unknown
|
|
66
66
|
): UpdateErrorCode | undefined =>
|
|
67
67
|
typeof code === 'string' && KNOWN_CODES.has(code)
|
|
68
68
|
? (code as UpdateErrorCode)
|
|
@@ -76,7 +76,7 @@ export class UpdateError extends Error {
|
|
|
76
76
|
constructor(
|
|
77
77
|
message: string,
|
|
78
78
|
code: UpdateErrorCode,
|
|
79
|
-
options?: { cause?: unknown; extra?: Record<string, string | number> }
|
|
79
|
+
options?: { cause?: unknown; extra?: Record<string, string | number> }
|
|
80
80
|
) {
|
|
81
81
|
super(message);
|
|
82
82
|
this.name = 'UpdateError';
|
|
@@ -96,7 +96,7 @@ export class UpdateError extends Error {
|
|
|
96
96
|
*/
|
|
97
97
|
export const toUpdateError = (
|
|
98
98
|
e: unknown,
|
|
99
|
-
code: UpdateErrorCode
|
|
99
|
+
code: UpdateErrorCode
|
|
100
100
|
): UpdateError => {
|
|
101
101
|
if (e instanceof Error) {
|
|
102
102
|
const err = e as UpdateError;
|
package/src/i18n.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import zhTranslations from './locales/zh';
|
|
2
1
|
import enTranslations from './locales/en';
|
|
2
|
+
import zhTranslations from './locales/zh';
|
|
3
3
|
|
|
4
4
|
type TranslationKey = keyof typeof zhTranslations | keyof typeof enTranslations;
|
|
5
5
|
type TranslationValues = Record<string, string | number>;
|
|
@@ -92,7 +92,7 @@ class I18n {
|
|
|
92
92
|
const i18n = new I18n();
|
|
93
93
|
|
|
94
94
|
// Export both the instance and the class for flexibility
|
|
95
|
-
export {
|
|
95
|
+
export { I18n, i18n };
|
|
96
96
|
export default i18n;
|
|
97
97
|
|
|
98
98
|
/**
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
export { Pushy, Cresc } from './client';
|
|
2
1
|
export type { UpdateErrorListener } from './client';
|
|
3
|
-
export {
|
|
4
|
-
export type { UpdateErrorCode } from './error';
|
|
2
|
+
export { Cresc, Pushy } from './client';
|
|
5
3
|
export {
|
|
6
4
|
ProgressContext,
|
|
7
5
|
UpdateContext,
|
|
@@ -9,5 +7,7 @@ export {
|
|
|
9
7
|
useUpdate,
|
|
10
8
|
useUpdateProgress,
|
|
11
9
|
} from './context';
|
|
12
|
-
export { PushyProvider, UpdateProvider } from './provider';
|
|
13
10
|
export { PushyModule, UpdateModule } from './core';
|
|
11
|
+
export type { UpdateErrorCode } from './error';
|
|
12
|
+
export { UpdateError } from './error';
|
|
13
|
+
export { PushyProvider, UpdateProvider } from './provider';
|
package/src/isInRollout.ts
CHANGED
|
@@ -4,7 +4,14 @@ import { cInfo } from './core';
|
|
|
4
4
|
|
|
5
5
|
/* eslint-disable no-bitwise */
|
|
6
6
|
export function murmurhash3_32_gc(key: string, seed = 0) {
|
|
7
|
-
let remainder
|
|
7
|
+
let remainder: number,
|
|
8
|
+
bytes: number,
|
|
9
|
+
h1: number,
|
|
10
|
+
h1b: number,
|
|
11
|
+
c1: number,
|
|
12
|
+
c2: number,
|
|
13
|
+
k1: number,
|
|
14
|
+
i: number;
|
|
8
15
|
|
|
9
16
|
remainder = key.length & 3; // key.length % 4
|
|
10
17
|
bytes = key.length - remainder;
|
|
@@ -36,8 +43,10 @@ export function murmurhash3_32_gc(key: string, seed = 0) {
|
|
|
36
43
|
k1 = 0;
|
|
37
44
|
|
|
38
45
|
switch (remainder) {
|
|
46
|
+
// biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough
|
|
39
47
|
case 3:
|
|
40
48
|
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
|
49
|
+
// biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough
|
|
41
50
|
case 2:
|
|
42
51
|
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
|
43
52
|
case 1:
|
package/src/locales/en.ts
CHANGED
|
@@ -44,6 +44,8 @@ export default {
|
|
|
44
44
|
// Error messages
|
|
45
45
|
error_appkey_required: 'appKey is required',
|
|
46
46
|
error_parse_version_info: 'Failed to parse currentVersionInfo: {{info}}',
|
|
47
|
+
warn_unknown_bundle:
|
|
48
|
+
'The bundle embedded in this binary is not registered on the update platform; incremental updates are degraded to full downloads. Upload the package with pushy uploadIpa / uploadApk / uploadApp.',
|
|
47
49
|
error_update_check_failed: 'Update check failed',
|
|
48
50
|
error_cannot_connect_server:
|
|
49
51
|
'Can not connect to update server. Please check your network.',
|
package/src/locales/zh.ts
CHANGED
|
@@ -42,6 +42,8 @@ export default {
|
|
|
42
42
|
// Error messages
|
|
43
43
|
error_appkey_required: '需要提供 appKey',
|
|
44
44
|
error_parse_version_info: '解析 currentVersionInfo 失败: {{info}}',
|
|
45
|
+
warn_unknown_bundle:
|
|
46
|
+
'当前二进制内嵌 bundle 未在更新平台注册,增量更新已降级为全量。请使用 pushy uploadIpa / uploadApk / uploadApp 上传对应安装包。',
|
|
45
47
|
error_update_check_failed: '更新检查失败',
|
|
46
48
|
error_cannot_connect_server: '无法连接到更新服务器。请检查网络连接。',
|
|
47
49
|
error_cannot_connect_backup:
|
package/src/provider.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
// biome-ignore lint/correctness/noUnusedImports: preserve React import for compatibility with older React versions
|
|
1
2
|
import React, {
|
|
2
|
-
ReactNode,
|
|
3
|
+
type ReactNode,
|
|
3
4
|
useCallback,
|
|
4
5
|
useEffect,
|
|
5
6
|
useMemo,
|
|
@@ -8,26 +9,22 @@ import React, {
|
|
|
8
9
|
} from 'react';
|
|
9
10
|
import {
|
|
10
11
|
Alert,
|
|
11
|
-
NativeEventSubscription,
|
|
12
12
|
AppState,
|
|
13
|
-
Platform,
|
|
14
13
|
Linking,
|
|
14
|
+
type NativeEventSubscription,
|
|
15
|
+
Platform,
|
|
15
16
|
} from 'react-native';
|
|
16
|
-
import {
|
|
17
|
+
import { URL } from 'react-native-url-polyfill';
|
|
18
|
+
import { type Cresc, type Pushy, sharedState } from './client';
|
|
19
|
+
import { ProgressContext, UpdateContext } from './context';
|
|
17
20
|
import {
|
|
18
21
|
currentVersion,
|
|
19
|
-
packageVersion,
|
|
20
|
-
getCurrentVersionInfo,
|
|
21
22
|
currentVersionInfo,
|
|
23
|
+
getCurrentVersionInfo,
|
|
24
|
+
packageVersion,
|
|
22
25
|
} from './core';
|
|
23
|
-
import {
|
|
24
|
-
CheckResult,
|
|
25
|
-
ProgressData,
|
|
26
|
-
UpdateTestPayload,
|
|
27
|
-
} from './type';
|
|
28
|
-
import { ProgressContext, UpdateContext } from './context';
|
|
29
|
-
import { URL } from 'react-native-url-polyfill';
|
|
30
26
|
import { resolveCheckResult } from './resolveCheckResult';
|
|
27
|
+
import type { CheckResult, ProgressData, UpdateTestPayload } from './type';
|
|
31
28
|
import { assertWeb, log, noop } from './utils';
|
|
32
29
|
|
|
33
30
|
export const UpdateProvider = ({
|
|
@@ -59,7 +56,7 @@ export const UpdateProvider = ({
|
|
|
59
56
|
Alert.alert(...args);
|
|
60
57
|
}
|
|
61
58
|
},
|
|
62
|
-
[options.updateStrategy]
|
|
59
|
+
[options.updateStrategy]
|
|
63
60
|
);
|
|
64
61
|
|
|
65
62
|
const alertError = useCallback(
|
|
@@ -68,7 +65,7 @@ export const UpdateProvider = ({
|
|
|
68
65
|
Alert.alert(...args);
|
|
69
66
|
}
|
|
70
67
|
},
|
|
71
|
-
[options.updateStrategy]
|
|
68
|
+
[options.updateStrategy]
|
|
72
69
|
);
|
|
73
70
|
|
|
74
71
|
// All client errors flow through this single subscription (regardless of
|
|
@@ -82,35 +79,35 @@ export const UpdateProvider = ({
|
|
|
82
79
|
client.t(
|
|
83
80
|
eventType === 'errorChecking'
|
|
84
81
|
? 'error_update_check_failed'
|
|
85
|
-
: 'update_failed'
|
|
82
|
+
: 'update_failed'
|
|
86
83
|
),
|
|
87
|
-
e.message
|
|
84
|
+
e.message
|
|
88
85
|
);
|
|
89
86
|
}),
|
|
90
|
-
[client, alertError]
|
|
87
|
+
[client, alertError]
|
|
91
88
|
);
|
|
92
89
|
|
|
93
90
|
const switchVersion = useCallback(
|
|
94
91
|
async (info: CheckResult | undefined = updateInfoRef.current) => {
|
|
95
|
-
if (info
|
|
92
|
+
if (info?.hash) {
|
|
96
93
|
return client.switchVersion(info.hash);
|
|
97
94
|
}
|
|
98
95
|
},
|
|
99
|
-
[client]
|
|
96
|
+
[client]
|
|
100
97
|
);
|
|
101
98
|
|
|
102
99
|
const switchVersionLater = useCallback(
|
|
103
100
|
async (info: CheckResult | undefined = updateInfoRef.current) => {
|
|
104
|
-
if (info
|
|
101
|
+
if (info?.hash) {
|
|
105
102
|
return client.switchVersionLater(info.hash);
|
|
106
103
|
}
|
|
107
104
|
},
|
|
108
|
-
[client]
|
|
105
|
+
[client]
|
|
109
106
|
);
|
|
110
107
|
|
|
111
108
|
const downloadUpdate = useCallback(
|
|
112
109
|
async (info: CheckResult | undefined = updateInfoRef.current) => {
|
|
113
|
-
if (!info
|
|
110
|
+
if (!info?.update) {
|
|
114
111
|
return false;
|
|
115
112
|
}
|
|
116
113
|
try {
|
|
@@ -167,7 +164,7 @@ export const UpdateProvider = ({
|
|
|
167
164
|
return false;
|
|
168
165
|
}
|
|
169
166
|
},
|
|
170
|
-
[client, options, alertUpdate, alertError]
|
|
167
|
+
[client, options, alertUpdate, alertError]
|
|
171
168
|
);
|
|
172
169
|
|
|
173
170
|
const downloadAndInstallApk = useCallback(
|
|
@@ -176,7 +173,7 @@ export const UpdateProvider = ({
|
|
|
176
173
|
await client.downloadAndInstallApk(downloadUrl, setProgress);
|
|
177
174
|
}
|
|
178
175
|
},
|
|
179
|
-
[client]
|
|
176
|
+
[client]
|
|
180
177
|
);
|
|
181
178
|
|
|
182
179
|
const checkUpdate = useCallback(
|
|
@@ -229,25 +226,18 @@ export const UpdateProvider = ({
|
|
|
229
226
|
}
|
|
230
227
|
return info;
|
|
231
228
|
}
|
|
232
|
-
alertUpdate(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
downloadUrl.endsWith('.apk')
|
|
242
|
-
) {
|
|
243
|
-
downloadAndInstallApk(downloadUrl).catch(noop);
|
|
244
|
-
} else {
|
|
245
|
-
Linking.openURL(downloadUrl);
|
|
246
|
-
}
|
|
247
|
-
},
|
|
229
|
+
alertUpdate(client.t('alert_title'), client.t('alert_app_updated'), [
|
|
230
|
+
{
|
|
231
|
+
text: client.t('alert_update_button'),
|
|
232
|
+
onPress: () => {
|
|
233
|
+
if (Platform.OS === 'android' && downloadUrl.endsWith('.apk')) {
|
|
234
|
+
downloadAndInstallApk(downloadUrl).catch(noop);
|
|
235
|
+
} else {
|
|
236
|
+
Linking.openURL(downloadUrl);
|
|
237
|
+
}
|
|
248
238
|
},
|
|
249
|
-
|
|
250
|
-
);
|
|
239
|
+
},
|
|
240
|
+
]);
|
|
251
241
|
}
|
|
252
242
|
} else if (info.update) {
|
|
253
243
|
if (
|
|
@@ -272,7 +262,7 @@ export const UpdateProvider = ({
|
|
|
272
262
|
downloadUpdate().catch(noop);
|
|
273
263
|
},
|
|
274
264
|
},
|
|
275
|
-
]
|
|
265
|
+
]
|
|
276
266
|
);
|
|
277
267
|
}
|
|
278
268
|
return info;
|
|
@@ -284,7 +274,7 @@ export const UpdateProvider = ({
|
|
|
284
274
|
alertError,
|
|
285
275
|
downloadAndInstallApk,
|
|
286
276
|
downloadUpdate,
|
|
287
|
-
]
|
|
277
|
+
]
|
|
288
278
|
);
|
|
289
279
|
|
|
290
280
|
const markSuccess = client.markSuccess;
|
|
@@ -307,11 +297,11 @@ export const UpdateProvider = ({
|
|
|
307
297
|
if (checkStrategy === 'both' || checkStrategy === 'onAppResume') {
|
|
308
298
|
stateListener.current = AppState.addEventListener(
|
|
309
299
|
'change',
|
|
310
|
-
nextAppState => {
|
|
300
|
+
(nextAppState) => {
|
|
311
301
|
if (nextAppState === 'active') {
|
|
312
302
|
checkUpdate().catch(noop);
|
|
313
303
|
}
|
|
314
|
-
}
|
|
304
|
+
}
|
|
315
305
|
);
|
|
316
306
|
}
|
|
317
307
|
if (checkStrategy === 'both' || checkStrategy === 'onAppStart') {
|
|
@@ -321,9 +311,9 @@ export const UpdateProvider = ({
|
|
|
321
311
|
if (markSuccessTimer) {
|
|
322
312
|
clearTimeout(markSuccessTimer);
|
|
323
313
|
}
|
|
324
|
-
stateListener.current
|
|
314
|
+
stateListener.current?.remove();
|
|
325
315
|
};
|
|
326
|
-
}, [checkUpdate, options,
|
|
316
|
+
}, [checkUpdate, options, markSuccess, client]);
|
|
327
317
|
|
|
328
318
|
useEffect(() => {
|
|
329
319
|
const { dismissErrorAfter } = options;
|
|
@@ -343,7 +333,7 @@ export const UpdateProvider = ({
|
|
|
343
333
|
|
|
344
334
|
const parseTestPayload = useCallback(
|
|
345
335
|
(payload: UpdateTestPayload) => {
|
|
346
|
-
if (payload
|
|
336
|
+
if (payload?.type?.startsWith('__rnPushy')) {
|
|
347
337
|
const logger = options.logger || (() => {});
|
|
348
338
|
options.logger = ({ type, data }) => {
|
|
349
339
|
logger({ type, data });
|
|
@@ -354,10 +344,10 @@ export const UpdateProvider = ({
|
|
|
354
344
|
sharedState.toHash = toHash;
|
|
355
345
|
checkUpdate({ extra: { toHash } })
|
|
356
346
|
.then(() => {
|
|
357
|
-
if (updateInfoRef.current
|
|
347
|
+
if (updateInfoRef.current?.upToDate) {
|
|
358
348
|
Alert.alert(
|
|
359
349
|
client.t('alert_info'),
|
|
360
|
-
client.t('alert_no_update_wait')
|
|
350
|
+
client.t('alert_no_update_wait')
|
|
361
351
|
);
|
|
362
352
|
}
|
|
363
353
|
})
|
|
@@ -370,7 +360,7 @@ export const UpdateProvider = ({
|
|
|
370
360
|
}
|
|
371
361
|
return false;
|
|
372
362
|
},
|
|
373
|
-
[checkUpdate, options, client]
|
|
363
|
+
[checkUpdate, options, client]
|
|
374
364
|
);
|
|
375
365
|
|
|
376
366
|
const parseTestQrCode = useCallback(
|
|
@@ -383,7 +373,7 @@ export const UpdateProvider = ({
|
|
|
383
373
|
return false;
|
|
384
374
|
}
|
|
385
375
|
},
|
|
386
|
-
[parseTestPayload]
|
|
376
|
+
[parseTestPayload]
|
|
387
377
|
);
|
|
388
378
|
|
|
389
379
|
const restartApp = useCallback(async () => {
|
|
@@ -394,7 +384,7 @@ export const UpdateProvider = ({
|
|
|
394
384
|
async (resetOptions?: { restart?: boolean }) => {
|
|
395
385
|
return client.resetToPackagedBundle(resetOptions);
|
|
396
386
|
},
|
|
397
|
-
[client]
|
|
387
|
+
[client]
|
|
398
388
|
);
|
|
399
389
|
|
|
400
390
|
useEffect(() => {
|
|
@@ -469,7 +459,7 @@ export const UpdateProvider = ({
|
|
|
469
459
|
parseTestQrCode,
|
|
470
460
|
restartApp,
|
|
471
461
|
resetToPackagedBundle,
|
|
472
|
-
]
|
|
462
|
+
]
|
|
473
463
|
);
|
|
474
464
|
|
|
475
465
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { currentVersion, packageVersion } from './core';
|
|
2
2
|
import { isInRollout } from './isInRollout';
|
|
3
|
-
import { CheckResult } from './type';
|
|
3
|
+
import type { CheckResult } from './type';
|
|
4
4
|
import { log } from './utils';
|
|
5
5
|
|
|
6
6
|
export function resolveCheckResult(rootInfo: CheckResult): CheckResult {
|
package/src/telemetry.ts
CHANGED
package/src/type.ts
CHANGED
|
@@ -17,15 +17,37 @@ export interface VersionInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
interface RootResult {
|
|
20
|
-
upToDate?:
|
|
21
|
-
expired?:
|
|
20
|
+
upToDate?: boolean;
|
|
21
|
+
expired?: boolean;
|
|
22
22
|
downloadUrl?: string;
|
|
23
|
-
update?:
|
|
23
|
+
update?: boolean;
|
|
24
24
|
paused?: 'app' | 'package';
|
|
25
25
|
message?: string;
|
|
26
26
|
paths?: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Server verdict on the reported bundleHash: 'matched' — the installed
|
|
29
|
+
* binary is registered; 'rebuiltSameJs' — repackaged binary with identical
|
|
30
|
+
* JS (no upload needed); 'unknownBundle' — unregistered binary, incremental
|
|
31
|
+
* updates degraded to full downloads until the package is uploaded.
|
|
32
|
+
*/
|
|
33
|
+
bundleStatus?: 'matched' | 'rebuiltSameJs' | 'unknownBundle';
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
export type UpToDateCheckResult = RootResult & {
|
|
37
|
+
upToDate: true;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type UpdateAvailableCheckResult = RootResult &
|
|
41
|
+
Partial<VersionInfo> & {
|
|
42
|
+
update: true;
|
|
43
|
+
expVersion?: VersionInfo;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type ExpiredCheckResult = RootResult & {
|
|
47
|
+
expired: true;
|
|
48
|
+
downloadUrl: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
29
51
|
export type CheckResult = RootResult &
|
|
30
52
|
Partial<VersionInfo> & {
|
|
31
53
|
expVersion?: VersionInfo;
|
|
@@ -57,6 +79,10 @@ export type EventType =
|
|
|
57
79
|
| 'errorMarkSuccess'
|
|
58
80
|
| 'reset'
|
|
59
81
|
| 'errorReset'
|
|
82
|
+
// Server reported bundleStatus: unknownBundle — the installed binary is not
|
|
83
|
+
// registered on the update platform (developer-facing only, local logger;
|
|
84
|
+
// not a server telemetry event).
|
|
85
|
+
| 'bundleMismatch'
|
|
60
86
|
| 'errorRestart'
|
|
61
87
|
| 'errorSwitchVersion'
|
|
62
88
|
| 'downloadingApk'
|
|
@@ -126,8 +152,8 @@ export interface ClientOptions {
|
|
|
126
152
|
beforeDownloadUpdate?: (info: CheckResult) => Promise<boolean> | boolean;
|
|
127
153
|
afterDownloadUpdate?: (info: CheckResult) => Promise<boolean> | boolean;
|
|
128
154
|
beforeReload?: (
|
|
129
|
-
context: BeforeReloadContext
|
|
130
|
-
) => Promise<boolean |
|
|
155
|
+
context: BeforeReloadContext
|
|
156
|
+
) => Promise<boolean | undefined> | boolean | undefined;
|
|
131
157
|
onPackageExpired?: (info: CheckResult) => Promise<boolean> | boolean;
|
|
132
158
|
overridePackageVersion?: string;
|
|
133
159
|
/** Maximum number of retry attempts for failed downloads (default: 3) */
|
package/src/utils.ts
CHANGED
|
@@ -28,7 +28,7 @@ export function promiseAny<T>(promises: Promise<T>[]) {
|
|
|
28
28
|
}
|
|
29
29
|
let count = 0;
|
|
30
30
|
|
|
31
|
-
promises.forEach(promise => {
|
|
31
|
+
promises.forEach((promise) => {
|
|
32
32
|
Promise.resolve(promise)
|
|
33
33
|
.then(resolve)
|
|
34
34
|
.catch(() => {
|
|
@@ -44,25 +44,26 @@ export function promiseAny<T>(promises: Promise<T>[]) {
|
|
|
44
44
|
export const emptyObj = {};
|
|
45
45
|
export const noop = () => {};
|
|
46
46
|
const emptyModuleTarget: Record<string, typeof noop> = {};
|
|
47
|
-
export const emptyModule = new Proxy(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
get(_target, _prop) {
|
|
51
|
-
return noop;
|
|
52
|
-
},
|
|
47
|
+
export const emptyModule = new Proxy(emptyModuleTarget, {
|
|
48
|
+
get(_target, _prop) {
|
|
49
|
+
return noop;
|
|
53
50
|
},
|
|
54
|
-
);
|
|
51
|
+
});
|
|
55
52
|
|
|
56
53
|
const ping = isWeb
|
|
57
54
|
? Promise.resolve
|
|
58
55
|
: async (url: string) => {
|
|
59
56
|
try {
|
|
60
|
-
const {
|
|
57
|
+
const {
|
|
58
|
+
status,
|
|
59
|
+
statusText,
|
|
60
|
+
url: finalUrl,
|
|
61
|
+
} = await fetchWithTimeout(
|
|
61
62
|
url,
|
|
62
63
|
{
|
|
63
64
|
method: 'HEAD',
|
|
64
65
|
},
|
|
65
|
-
DEFAULT_FETCH_TIMEOUT_MS
|
|
66
|
+
DEFAULT_FETCH_TIMEOUT_MS
|
|
66
67
|
);
|
|
67
68
|
if (status === 200) {
|
|
68
69
|
return finalUrl;
|
|
@@ -77,7 +78,7 @@ const ping = isWeb
|
|
|
77
78
|
|
|
78
79
|
export function joinUrls(paths: string[], fileName?: string) {
|
|
79
80
|
if (fileName) {
|
|
80
|
-
return paths.map(path => {
|
|
81
|
+
return paths.map((path) => {
|
|
81
82
|
const normalizedPath = path.replace(/\/+$/, '');
|
|
82
83
|
// Keep explicit http(s) URLs for local/self-hosted update sources.
|
|
83
84
|
const baseUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(normalizedPath)
|
|
@@ -120,7 +121,7 @@ export const computeProgress = (received: number, total: number): number =>
|
|
|
120
121
|
export const fetchWithTimeout = (
|
|
121
122
|
url: string,
|
|
122
123
|
params: Parameters<typeof fetch>[1],
|
|
123
|
-
timeoutMs = DEFAULT_FETCH_TIMEOUT_MS
|
|
124
|
+
timeoutMs = DEFAULT_FETCH_TIMEOUT_MS
|
|
124
125
|
): Promise<Response> => {
|
|
125
126
|
// AbortController landed in the RN fetch polyfill around 0.60; we support
|
|
126
127
|
// older peers, so fall back to a plain timer race when it is unavailable
|
|
@@ -151,7 +152,7 @@ export const fetchWithTimeout = (
|
|
|
151
152
|
controller.abort();
|
|
152
153
|
}, timeoutMs);
|
|
153
154
|
|
|
154
|
-
return enhancedFetch(url, { ...params, signal: controller.signal })
|
|
155
|
+
return enhancedFetch(url, { ...params, signal: controller.signal as any })
|
|
155
156
|
.catch((e: any) => {
|
|
156
157
|
if (controller.signal.aborted) {
|
|
157
158
|
throw Error(i18n.t('error_ping_timeout'));
|
|
@@ -171,22 +172,21 @@ const isIdempotentRequest = (params: Parameters<typeof fetch>[1]) => {
|
|
|
171
172
|
export const enhancedFetch = async (
|
|
172
173
|
url: string,
|
|
173
174
|
params: Parameters<typeof fetch>[1],
|
|
174
|
-
isRetry = false
|
|
175
|
+
isRetry = false
|
|
175
176
|
): Promise<Response> => {
|
|
176
|
-
return fetch(url, params)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
});
|
|
177
|
+
return fetch(url, params).catch((e) => {
|
|
178
|
+
log('fetch error', url, e);
|
|
179
|
+
if (
|
|
180
|
+
isRetry ||
|
|
181
|
+
(params as any)?.signal?.aborted ||
|
|
182
|
+
!url.startsWith('https:') ||
|
|
183
|
+
// Never replay non-idempotent requests (e.g. the checkUpdate POST)
|
|
184
|
+
// over plaintext http: the server may have processed the original.
|
|
185
|
+
!isIdempotentRequest(params)
|
|
186
|
+
) {
|
|
187
|
+
throw e;
|
|
188
|
+
}
|
|
189
|
+
log('trying fallback to http');
|
|
190
|
+
return enhancedFetch(url.replace(/^https:/, 'http:'), params, true);
|
|
191
|
+
});
|
|
192
192
|
};
|
package/.eslintrc.js
DELETED