react-native-update 10.59.1 → 10.60.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/README-CN.md +7 -7
- package/README.md +7 -6
- package/android/build.gradle +25 -33
- package/android/jni/Android.mk +16 -24
- package/android/lib/arm64-v8a/librnpushy.so +0 -0
- package/android/lib/armeabi-v7a/librnpushy.so +0 -0
- package/android/lib/x86/librnpushy.so +0 -0
- package/android/lib/x86_64/librnpushy.so +0 -0
- package/android/proguard.pro +9 -7
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +11 -12
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +23 -6
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +50 -51
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +12 -13
- package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/CrashHold.java +22 -30
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +181 -332
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -1
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +27 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/FlowBridge.java +32 -8
- package/android/src/main/java/cn/reactnative/modules/update/HttpClients.java +63 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +0 -11
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +66 -116
- package/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +9 -1
- package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +32 -60
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +8 -23
- package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +42 -80
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +22 -39
- package/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +176 -299
- package/android/src/main/java/cn/reactnative/modules/update/Threads.java +35 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +242 -174
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +75 -17
- package/android/src/main/java/cn/reactnative/modules/update/{UpdateModuleImpl.java → UpdateModule.java} +113 -95
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +16 -19
- package/android/src/oldarch/cn/reactnative/modules/update/NativePushySpec.java +69 -0
- package/cpp/android_jni/archive_limits_jni.cpp +83 -0
- package/cpp/android_jni/install_record_jni.cpp +89 -0
- package/cpp/android_jni/jni_log.h +31 -0
- package/cpp/android_jni/jni_natives.h +40 -0
- package/cpp/android_jni/jni_registration.cpp +133 -0
- package/cpp/android_jni/jni_util.h +160 -0
- package/cpp/android_jni/update_core_android.cpp +450 -0
- package/cpp/android_jni/update_flow_jni.cpp +133 -0
- package/{android/jni → cpp/hpatch}/hpatch.c +5 -5
- package/{android/jni → cpp/hpatch}/hpatch.h +4 -1
- package/cpp/patch_core/archive_limits.cpp +194 -0
- package/cpp/patch_core/archive_limits.h +99 -10
- package/cpp/patch_core/archive_patch_core.cpp +96 -12
- package/cpp/patch_core/archive_patch_core.h +49 -0
- package/cpp/patch_core/digest.h +5 -4
- package/cpp/patch_core/error_codes.h +5 -3
- package/cpp/patch_core/install_record.cpp +430 -0
- package/cpp/patch_core/install_record.h +163 -9
- package/cpp/patch_core/patch_core.cpp +62 -16
- package/cpp/patch_core/patch_core.h +27 -7
- package/cpp/patch_core/patch_manifest.cpp +201 -0
- package/cpp/patch_core/patch_manifest.h +74 -0
- package/cpp/patch_core/state_ops.h +3 -3
- package/cpp/update_flow_core/flow_json.cpp +82 -8
- package/cpp/update_flow_core/flow_json.h +21 -0
- package/cpp/update_flow_core/native_config.cpp +456 -0
- package/cpp/update_flow_core/native_config.h +84 -0
- package/cpp/update_flow_core/native_round.cpp +326 -0
- package/cpp/update_flow_core/native_round.h +90 -0
- package/cpp/update_flow_core/update_flow_core.h +2 -2
- package/harmony/hvigor-plugin.ts +11 -19
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +19 -26
- package/harmony/pushy/src/main/cpp/PushyPackage.h +2 -2
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +55 -94
- package/harmony/pushy/src/main/cpp/pushy.cpp +694 -517
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +48 -72
- package/harmony/pushy/src/main/ets/DownloadTask.ts +164 -404
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +1 -3
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +1 -14
- package/harmony/pushy/src/main/ets/EventHub.ts +1 -1
- package/harmony/pushy/src/main/ets/InstallRecord.ts +52 -151
- package/harmony/pushy/src/main/ets/Logger.ts +20 -34
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +116 -42
- package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -85
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -25
- package/harmony/pushy/src/main/ets/SaveFile.ts +99 -18
- package/harmony/pushy/src/main/ets/SyncCoordinator.ts +136 -335
- package/harmony/pushy/src/main/ets/Texts.ts +0 -4
- package/harmony/pushy/src/main/ets/UpdateContext.ts +106 -119
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy+Launch.mm +185 -0
- package/ios/RCTPushy/RCTPushy.h +12 -0
- package/ios/RCTPushy/RCTPushy.mm +87 -2628
- package/ios/RCTPushy/RCTPushyCommon.h +34 -0
- package/ios/RCTPushy/RCTPushyConfiguration.h +0 -8
- package/ios/RCTPushy/RCTPushyConfiguration.mm +40 -121
- package/ios/RCTPushy/RCTPushyDownloader.mm +40 -37
- package/ios/RCTPushy/RCTPushyInstallRecord.mm +61 -0
- package/ios/RCTPushy/RCTPushyInstaller.mm +689 -0
- package/ios/RCTPushy/RCTPushyInternal.h +144 -0
- package/ios/RCTPushy/RCTPushyOrchestrator.mm +1001 -0
- package/ios/RCTPushy/RCTPushyState.mm +314 -0
- package/package.json +25 -23
- package/react-native-update.podspec +74 -83
- package/src/client.ts +361 -451
- package/src/context.ts +1 -1
- package/src/core.ts +35 -8
- package/src/endpoint.ts +0 -2
- package/src/error.ts +13 -32
- package/src/i18n.ts +18 -83
- package/src/locales/en.ts +0 -4
- package/src/locales/zh.ts +0 -4
- package/src/provider.tsx +60 -53
- package/src/telemetry.ts +0 -13
- package/src/type.ts +0 -15
- package/src/updateFlowCore.ts +116 -10
- package/src/utils.ts +49 -42
- package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +0 -6
- package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +0 -104
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +0 -37
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +0 -121
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +0 -125
- package/cpp/patch_core/jni_natives.h +0 -27
- package/cpp/patch_core/jni_registration.cpp +0 -103
- package/cpp/patch_core/jni_util.h +0 -60
- package/cpp/patch_core/patch_core_android.cpp +0 -97
- package/cpp/patch_core/update_core_android.cpp +0 -485
- package/cpp/update_flow_core/update_flow_jni.cpp +0 -150
package/src/context.ts
CHANGED
|
@@ -46,7 +46,7 @@ export interface UpdateContextValue {
|
|
|
46
46
|
lastError?: Error;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
const defaultContext: UpdateContextValue = {
|
|
50
50
|
checkUpdate: asyncNoop,
|
|
51
51
|
switchVersion: asyncNoop,
|
|
52
52
|
switchVersionLater: asyncNoop,
|
package/src/core.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
2
2
|
import { UpdateError } from './error';
|
|
3
3
|
import i18n from './i18n';
|
|
4
|
-
import { emptyModule, error, log } from './utils';
|
|
4
|
+
import { createNonSecureId, emptyModule, error, log } from './utils';
|
|
5
5
|
|
|
6
6
|
// RN's JS global object. Declared locally because RN 0.87's own typings no
|
|
7
7
|
// longer define `global` (only @types/node did), and this source is
|
|
@@ -35,6 +35,34 @@ if (!PushyModule) {
|
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* `module[name]` as an always-async function (a synchronous bridge throw
|
|
40
|
+
* becomes a rejection), or undefined when the module lacks it: the JS layer
|
|
41
|
+
* can arrive via hot update onto an older binary whose native module predates
|
|
42
|
+
* the method. Always undefined on web, where the module is a noop Proxy that
|
|
43
|
+
* would pass any typeof check.
|
|
44
|
+
*/
|
|
45
|
+
export const bindNativeMethod = (
|
|
46
|
+
module: Record<string, any>,
|
|
47
|
+
name: string
|
|
48
|
+
): ((...args: any[]) => Promise<any>) | undefined => {
|
|
49
|
+
const method = module === emptyModule ? undefined : module[name];
|
|
50
|
+
if (typeof method !== 'function') {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return (...args) => {
|
|
54
|
+
try {
|
|
55
|
+
return Promise.resolve(method.apply(module, args));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return Promise.reject(e);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** An optional PushyModule method, see bindNativeMethod. */
|
|
63
|
+
export const nativeMethod = (name: string) =>
|
|
64
|
+
bindNativeMethod(PushyModule, name);
|
|
65
|
+
|
|
38
66
|
// On web PushyModule is a Proxy whose every property is a noop function, so
|
|
39
67
|
// reading the constants off it would make each of them a function:
|
|
40
68
|
// `isRolledBack` true (a fake rollback report on every page load), a uuid of
|
|
@@ -99,7 +127,6 @@ export const currentBundleSha256: string =
|
|
|
99
127
|
: '';
|
|
100
128
|
|
|
101
129
|
export const isFirstTime: boolean = PushyConstants.isFirstTime;
|
|
102
|
-
export const isFirstTimeDebug: boolean = isFirstTime && isDebugChannel;
|
|
103
130
|
export const rolledBackVersion: string = PushyConstants.rolledBackVersion;
|
|
104
131
|
export const isRolledBack: boolean = !!rolledBackVersion;
|
|
105
132
|
|
|
@@ -121,9 +148,10 @@ let uuid = PushyConstants.uuid;
|
|
|
121
148
|
// between its dedup window and the fetch, and a hash is never worth delaying
|
|
122
149
|
// a check for.
|
|
123
150
|
let bundleHash = '';
|
|
124
|
-
//
|
|
125
|
-
// module
|
|
126
|
-
//
|
|
151
|
+
// Feature-detected like nativeMethod (older natives lack it, web has no native
|
|
152
|
+
// module), but called directly rather than through it: a synchronous throw
|
|
153
|
+
// means a broken native integration and must propagate at module load, not be
|
|
154
|
+
// turned into a logged rejection. Only an async failure is tolerated.
|
|
127
155
|
if (!isWebPlatform && typeof PushyModule.getBundleHash === 'function') {
|
|
128
156
|
Promise.resolve(PushyModule.getBundleHash())
|
|
129
157
|
.then((hash: unknown) => {
|
|
@@ -156,7 +184,7 @@ export async function getCurrentVersionInfo(): Promise<{
|
|
|
156
184
|
export const pushyNativeEventEmitter = new NativeEventEmitter(PushyModule);
|
|
157
185
|
|
|
158
186
|
if (!uuid) {
|
|
159
|
-
uuid =
|
|
187
|
+
uuid = createNonSecureId();
|
|
160
188
|
// If persisting fails the uuid drifts on every launch, which skews gray
|
|
161
189
|
// release bucketing and inflates stats — log it instead of failing silently.
|
|
162
190
|
// Web has nowhere to persist it (nothing runs there anyway).
|
|
@@ -171,7 +199,7 @@ export const cInfo = {
|
|
|
171
199
|
rnu: require('../package.json').version,
|
|
172
200
|
rn: RNVersion,
|
|
173
201
|
// tvOS reports Platform.OS 'ios'; label it apart so the server can bucket
|
|
174
|
-
// tvOS devices separately. Must match PushyOsName in
|
|
202
|
+
// tvOS devices separately. Must match PushyOsName in RCTPushyOrchestrator.mm.
|
|
175
203
|
os: `${Platform.OS === 'ios' && Platform.isTV ? 'tvos' : Platform.OS} ${Platform.Version}`,
|
|
176
204
|
uuid,
|
|
177
205
|
};
|
|
@@ -181,7 +209,6 @@ log('bootup status', {
|
|
|
181
209
|
currentVersion,
|
|
182
210
|
currentVersionInfo,
|
|
183
211
|
isFirstTime,
|
|
184
|
-
isFirstTimeDebug,
|
|
185
212
|
isDebugChannel,
|
|
186
213
|
cInfo,
|
|
187
214
|
});
|
package/src/endpoint.ts
CHANGED
package/src/error.ts
CHANGED
|
@@ -7,42 +7,13 @@
|
|
|
7
7
|
* Android/iOS/Harmony modules — and flow through promise rejections onto the
|
|
8
8
|
* `code` property, which toUpdateError() preserves.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
const UPDATE_ERROR_CODES = [
|
|
11
11
|
// JS-layer codes
|
|
12
|
-
| 'MODULE_NOT_LOADED'
|
|
13
|
-
| 'APPKEY_REQUIRED'
|
|
14
|
-
| 'NO_ENDPOINTS'
|
|
15
|
-
| 'HTTP_STATUS'
|
|
16
|
-
// A 2xx check response that is not a check verdict (schema gate).
|
|
17
|
-
| 'INVALID_RESPONSE'
|
|
18
|
-
| 'CHECK_FAILED'
|
|
19
|
-
| 'DOWNLOAD_FAILED'
|
|
20
|
-
| 'SWITCH_VERSION_FAILED'
|
|
21
|
-
| 'MARK_SUCCESS_FAILED'
|
|
22
|
-
| 'APK_INSTALL_PENDING'
|
|
23
|
-
| 'APK_DOWNLOAD_FAILED'
|
|
24
|
-
// A throw from a user-provided hook (e.g. beforeReload) — not an update
|
|
25
|
-
// pipeline failure, and excluded from server-side patch-health telemetry.
|
|
26
|
-
| 'USER_HOOK_ERROR'
|
|
27
|
-
// A second client/provider in the same process — a hard integration error;
|
|
28
|
-
// the SDK is a process-level singleton.
|
|
29
|
-
| 'SINGLETON_VIOLATION'
|
|
30
|
-
// Native codes (see cpp/patch_core/error_codes.h)
|
|
31
|
-
| 'INVALID_OPTIONS'
|
|
32
|
-
| 'PATCH_FAILED'
|
|
33
|
-
| 'FILE_OPERATION_FAILED'
|
|
34
|
-
| 'RESTART_FAILED'
|
|
35
|
-
| 'RESET_FAILED'
|
|
36
|
-
| 'INVALID_HASH_INFO'
|
|
37
|
-
| 'UNSUPPORTED_PLATFORM'
|
|
38
|
-
| 'APK_INSTALL_PERMISSION_REQUIRED'
|
|
39
|
-
| 'APK_INSTALL_FAILED';
|
|
40
|
-
|
|
41
|
-
const KNOWN_CODES = new Set<string>([
|
|
42
12
|
'MODULE_NOT_LOADED',
|
|
43
13
|
'APPKEY_REQUIRED',
|
|
44
14
|
'NO_ENDPOINTS',
|
|
45
15
|
'HTTP_STATUS',
|
|
16
|
+
// A 2xx check response that is not a check verdict (schema gate).
|
|
46
17
|
'INVALID_RESPONSE',
|
|
47
18
|
'CHECK_FAILED',
|
|
48
19
|
'DOWNLOAD_FAILED',
|
|
@@ -50,8 +21,13 @@ const KNOWN_CODES = new Set<string>([
|
|
|
50
21
|
'MARK_SUCCESS_FAILED',
|
|
51
22
|
'APK_INSTALL_PENDING',
|
|
52
23
|
'APK_DOWNLOAD_FAILED',
|
|
24
|
+
// A throw from a user-provided hook (e.g. beforeReload) — not an update
|
|
25
|
+
// pipeline failure, and excluded from server-side patch-health telemetry.
|
|
53
26
|
'USER_HOOK_ERROR',
|
|
27
|
+
// A second client/provider in the same process — a hard integration error;
|
|
28
|
+
// the SDK is a process-level singleton.
|
|
54
29
|
'SINGLETON_VIOLATION',
|
|
30
|
+
// Native codes (see cpp/patch_core/error_codes.h)
|
|
55
31
|
'INVALID_OPTIONS',
|
|
56
32
|
'PATCH_FAILED',
|
|
57
33
|
'FILE_OPERATION_FAILED',
|
|
@@ -61,7 +37,12 @@ const KNOWN_CODES = new Set<string>([
|
|
|
61
37
|
'UNSUPPORTED_PLATFORM',
|
|
62
38
|
'APK_INSTALL_PERMISSION_REQUIRED',
|
|
63
39
|
'APK_INSTALL_FAILED',
|
|
64
|
-
]
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
/** A stable error code (see UPDATE_ERROR_CODES). */
|
|
43
|
+
export type UpdateErrorCode = (typeof UPDATE_ERROR_CODES)[number];
|
|
44
|
+
|
|
45
|
+
const KNOWN_CODES = new Set<string>(UPDATE_ERROR_CODES);
|
|
65
46
|
|
|
66
47
|
/**
|
|
67
48
|
* Narrow an arbitrary `code` property (axios' ERR_NETWORK, Node's
|
package/src/i18n.ts
CHANGED
|
@@ -1,108 +1,43 @@
|
|
|
1
1
|
import enTranslations from './locales/en';
|
|
2
2
|
import zhTranslations from './locales/zh';
|
|
3
3
|
|
|
4
|
+
type Locale = 'zh' | 'en';
|
|
4
5
|
type TranslationKey = keyof typeof zhTranslations | keyof typeof enTranslations;
|
|
5
6
|
type TranslationValues = Record<string, string | number>;
|
|
6
7
|
|
|
8
|
+
const translations: Record<Locale, Record<string, string>> = {
|
|
9
|
+
zh: zhTranslations,
|
|
10
|
+
en: enTranslations,
|
|
11
|
+
};
|
|
12
|
+
|
|
7
13
|
class I18n {
|
|
8
|
-
private currentLocale:
|
|
9
|
-
private translations = {
|
|
10
|
-
zh: zhTranslations,
|
|
11
|
-
en: enTranslations,
|
|
12
|
-
};
|
|
14
|
+
private currentLocale: Locale = 'en';
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
* Set locale directly
|
|
16
|
-
* @param locale - 'zh' or 'en'
|
|
17
|
-
*/
|
|
18
|
-
setLocale(locale: 'zh' | 'en') {
|
|
16
|
+
setLocale(locale: Locale) {
|
|
19
17
|
this.currentLocale = locale;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return this.currentLocale;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Translate a key with optional interpolation
|
|
31
|
-
* @param key - Translation key
|
|
32
|
-
* @param values - Values for interpolation (optional)
|
|
33
|
-
* @returns Translated string with interpolated values
|
|
21
|
+
* Translate a key, filling `{{name}}` placeholders from `values` (unknown
|
|
22
|
+
* placeholders are kept). A key missing from the current locale falls back
|
|
23
|
+
* to the other one, then to the key itself.
|
|
34
24
|
*/
|
|
35
25
|
t(key: TranslationKey, values?: TranslationValues): string {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
if (!translation) {
|
|
42
|
-
// Fallback to the other locale if key not found
|
|
43
|
-
const fallbackLocale = this.currentLocale === 'zh' ? 'en' : 'zh';
|
|
44
|
-
const fallbackTranslation =
|
|
45
|
-
this.translations[fallbackLocale][
|
|
46
|
-
key as keyof (typeof this.translations)[typeof fallbackLocale]
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
if (!fallbackTranslation) {
|
|
50
|
-
// If still not found, return the key itself
|
|
51
|
-
return String(key);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return this.interpolate(fallbackTranslation, values);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return this.interpolate(translation, values);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Interpolate values into a string template
|
|
62
|
-
* Supports {{key}} syntax
|
|
63
|
-
* @param template - String template with {{key}} placeholders
|
|
64
|
-
* @param values - Values to interpolate
|
|
65
|
-
* @returns Interpolated string
|
|
66
|
-
*/
|
|
67
|
-
private interpolate(template: string, values?: TranslationValues): string {
|
|
26
|
+
const fallbackLocale = this.currentLocale === 'zh' ? 'en' : 'zh';
|
|
27
|
+
const template =
|
|
28
|
+
translations[this.currentLocale][key] ||
|
|
29
|
+
translations[fallbackLocale][key] ||
|
|
30
|
+
String(key);
|
|
68
31
|
if (!values) {
|
|
69
32
|
return template;
|
|
70
33
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const value = values[key];
|
|
34
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, name) => {
|
|
35
|
+
const value = values[name];
|
|
74
36
|
return value !== undefined ? String(value) : match;
|
|
75
37
|
});
|
|
76
38
|
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Add or update translations for a specific locale
|
|
80
|
-
* @param locale - Target locale
|
|
81
|
-
* @param translations - Translation object to merge
|
|
82
|
-
*/
|
|
83
|
-
addTranslations(locale: 'zh' | 'en', translations: Record<string, string>) {
|
|
84
|
-
this.translations[locale] = {
|
|
85
|
-
...this.translations[locale],
|
|
86
|
-
...translations,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
39
|
}
|
|
90
40
|
|
|
91
|
-
// Create singleton instance
|
|
92
41
|
const i18n = new I18n();
|
|
93
42
|
|
|
94
|
-
// Export both the instance and the class for flexibility
|
|
95
|
-
export { I18n, i18n };
|
|
96
43
|
export default i18n;
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Usage examples:
|
|
100
|
-
*
|
|
101
|
-
* // Direct locale setting (new preferred method)
|
|
102
|
-
* i18n.setLocale('zh'); // Chinese
|
|
103
|
-
* i18n.setLocale('en'); // English
|
|
104
|
-
*
|
|
105
|
-
* // Get translations
|
|
106
|
-
* i18n.t('checking_update'); // Based on current locale
|
|
107
|
-
* i18n.t('download_progress', { progress: 50 }); // With interpolation
|
|
108
|
-
*/
|
package/src/locales/en.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
// Common messages
|
|
3
|
-
checking_update: 'Checking for updates...',
|
|
4
3
|
update_failed: 'Update failed',
|
|
5
4
|
|
|
6
|
-
// Progress messages with interpolation
|
|
7
|
-
download_progress: 'Download progress: {{progress}}%',
|
|
8
|
-
|
|
9
5
|
// Alert messages
|
|
10
6
|
alert_title: 'Notice',
|
|
11
7
|
alert_update_ready: 'Download completed. Update now?',
|
package/src/locales/zh.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
// Common messages
|
|
3
|
-
checking_update: '正在检查更新...',
|
|
4
3
|
update_failed: '更新失败',
|
|
5
4
|
|
|
6
|
-
// Progress messages with interpolation
|
|
7
|
-
download_progress: '下载进度: {{progress}}%',
|
|
8
|
-
|
|
9
5
|
// Alert messages
|
|
10
6
|
alert_title: '提示',
|
|
11
7
|
alert_update_ready: '下载完毕,是否立即更新?',
|
package/src/provider.tsx
CHANGED
|
@@ -22,6 +22,16 @@ import type { CheckResult, ProgressData, UpdateTestPayload } from './type';
|
|
|
22
22
|
import { decideDownload, resolveCheckResult } from './updateFlowCore';
|
|
23
23
|
import { assertWeb, isWeb, log, noop, parseQueryParams } from './utils';
|
|
24
24
|
|
|
25
|
+
// A context action applied to the hash of the given (default: the last
|
|
26
|
+
// checked) update info; a no-op without one.
|
|
27
|
+
const forUpdateHash =
|
|
28
|
+
<T,>(
|
|
29
|
+
updateInfoRef: { current?: CheckResult },
|
|
30
|
+
apply: (hash: string) => Promise<T>
|
|
31
|
+
) =>
|
|
32
|
+
async (info: CheckResult | undefined = updateInfoRef.current) =>
|
|
33
|
+
info?.hash ? apply(info.hash) : undefined;
|
|
34
|
+
|
|
25
35
|
export const UpdateProvider = ({
|
|
26
36
|
client,
|
|
27
37
|
children,
|
|
@@ -100,22 +110,32 @@ export const UpdateProvider = ({
|
|
|
100
110
|
[client, alertError]
|
|
101
111
|
);
|
|
102
112
|
|
|
103
|
-
const switchVersion =
|
|
104
|
-
|
|
105
|
-
if (info?.hash) {
|
|
106
|
-
return client.switchVersion(info.hash);
|
|
107
|
-
}
|
|
108
|
-
},
|
|
113
|
+
const switchVersion = useMemo(
|
|
114
|
+
() => forUpdateHash(updateInfoRef, client.switchVersion),
|
|
109
115
|
[client]
|
|
110
116
|
);
|
|
111
117
|
|
|
112
|
-
const switchVersionLater =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
const switchVersionLater = useMemo(
|
|
119
|
+
() => forUpdateHash(updateInfoRef, client.switchVersionLater),
|
|
120
|
+
[client]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Client pipeline errors were already surfaced via the onError
|
|
124
|
+
// subscription; errors thrown by user hooks (beforeCheckUpdate,
|
|
125
|
+
// afterDownloadUpdate) bypass the pipeline and are surfaced here. Asking
|
|
126
|
+
// the client instead of checking `e.code` matters: axios/system errors
|
|
127
|
+
// carry their own code without ever entering the pipeline.
|
|
128
|
+
const surfaceCaughtError = useCallback(
|
|
129
|
+
(e: any, title: string) => {
|
|
130
|
+
if (!client.wasEmitted(e)) {
|
|
131
|
+
setLastError(e);
|
|
132
|
+
alertError(title, e.message);
|
|
133
|
+
}
|
|
134
|
+
if (options.throwError) {
|
|
135
|
+
throw e;
|
|
116
136
|
}
|
|
117
137
|
},
|
|
118
|
-
[client]
|
|
138
|
+
[client, options, alertError]
|
|
119
139
|
);
|
|
120
140
|
|
|
121
141
|
const downloadUpdate = useCallback(
|
|
@@ -162,27 +182,17 @@ export const UpdateProvider = ({
|
|
|
162
182
|
]);
|
|
163
183
|
return true;
|
|
164
184
|
} catch (e: any) {
|
|
165
|
-
|
|
166
|
-
// subscription; errors thrown by user hooks (afterDownloadUpdate)
|
|
167
|
-
// bypass the pipeline and are surfaced here. Asking the client
|
|
168
|
-
// instead of checking `e.code` matters: axios/system errors carry
|
|
169
|
-
// their own code without ever entering the pipeline.
|
|
170
|
-
if (!client.wasEmitted(e)) {
|
|
171
|
-
setLastError(e);
|
|
172
|
-
alertError(client.t('update_failed'), e.message);
|
|
173
|
-
}
|
|
174
|
-
if (options.throwError) {
|
|
175
|
-
throw e;
|
|
176
|
-
}
|
|
185
|
+
surfaceCaughtError(e, client.t('update_failed'));
|
|
177
186
|
return false;
|
|
178
187
|
}
|
|
179
188
|
},
|
|
180
|
-
[client, options, alertUpdate,
|
|
189
|
+
[client, options, alertUpdate, surfaceCaughtError]
|
|
181
190
|
);
|
|
182
191
|
|
|
183
192
|
const downloadAndInstallApk = useCallback(
|
|
184
193
|
async (downloadUrl: string) => {
|
|
185
|
-
|
|
194
|
+
// The client itself is a no-op off Android.
|
|
195
|
+
if (downloadUrl) {
|
|
186
196
|
await client.downloadAndInstallApk(downloadUrl, setProgress);
|
|
187
197
|
}
|
|
188
198
|
},
|
|
@@ -219,16 +229,7 @@ export const UpdateProvider = ({
|
|
|
219
229
|
try {
|
|
220
230
|
rootInfo = await client.checkUpdate(extra);
|
|
221
231
|
} catch (e: any) {
|
|
222
|
-
|
|
223
|
-
// subscription; errors thrown by user hooks (beforeCheckUpdate)
|
|
224
|
-
// bypass the pipeline and are surfaced here (see wasEmitted).
|
|
225
|
-
if (!client.wasEmitted(e)) {
|
|
226
|
-
setLastError(e);
|
|
227
|
-
alertError(client.t('error_update_check_failed'), e.message);
|
|
228
|
-
}
|
|
229
|
-
if (options.throwError) {
|
|
230
|
-
throw e;
|
|
231
|
-
}
|
|
232
|
+
surfaceCaughtError(e, client.t('error_update_check_failed'));
|
|
232
233
|
return;
|
|
233
234
|
}
|
|
234
235
|
if (!rootInfo) {
|
|
@@ -354,13 +355,14 @@ export const UpdateProvider = ({
|
|
|
354
355
|
client,
|
|
355
356
|
options,
|
|
356
357
|
alertUpdate,
|
|
357
|
-
|
|
358
|
+
surfaceCaughtError,
|
|
358
359
|
openExpiredDownload,
|
|
359
360
|
downloadUpdate,
|
|
360
361
|
]
|
|
361
362
|
);
|
|
362
363
|
|
|
363
|
-
|
|
364
|
+
// Stable arrow-function properties of the (pinned) client.
|
|
365
|
+
const { markSuccess, restartApp, resetToPackagedBundle } = client;
|
|
364
366
|
|
|
365
367
|
// The lifecycle effects below key on the primitive option values they read
|
|
366
368
|
// (re-read on every render, i.e. after every setOptions), so a setOptions
|
|
@@ -528,16 +530,15 @@ export const UpdateProvider = ({
|
|
|
528
530
|
[parseTestPayload]
|
|
529
531
|
);
|
|
530
532
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
);
|
|
533
|
+
// parseTestPayload's identity follows checkUpdate (and through it the
|
|
534
|
+
// update strategy callbacks); the Linking subscription reads it through a
|
|
535
|
+
// ref so it is set up once instead of re-running — and replaying the
|
|
536
|
+
// initial URL — on every such change.
|
|
537
|
+
const latestParseTestPayload = useRef(parseTestPayload);
|
|
538
|
+
useEffect(() => {
|
|
539
|
+
latestParseTestPayload.current = parseTestPayload;
|
|
540
|
+
}, [parseTestPayload]);
|
|
541
|
+
const initialUrlHandled = useRef(false);
|
|
541
542
|
|
|
542
543
|
useEffect(() => {
|
|
543
544
|
if (!assertWeb()) {
|
|
@@ -552,25 +553,31 @@ export const UpdateProvider = ({
|
|
|
552
553
|
// malformed deep link must not become an unhandled rejection / a throw
|
|
553
554
|
// inside the 'url' event handler.
|
|
554
555
|
const params = parseQueryParams(url);
|
|
555
|
-
|
|
556
|
+
latestParseTestPayload.current({
|
|
556
557
|
type: params.type ?? null,
|
|
557
558
|
data: params.data ?? null,
|
|
558
559
|
});
|
|
559
560
|
};
|
|
560
561
|
|
|
561
|
-
|
|
562
|
+
// The launch URL is one event: a remount (StrictMode included) must not
|
|
563
|
+
// replay it.
|
|
564
|
+
if (!initialUrlHandled.current) {
|
|
565
|
+
initialUrlHandled.current = true;
|
|
566
|
+
Linking.getInitialURL().then(parseLinking).catch(noop);
|
|
567
|
+
}
|
|
562
568
|
const linkingHandler = ({ url }: { url: string }) => {
|
|
563
569
|
parseLinking(url);
|
|
564
570
|
};
|
|
565
571
|
const linkingListener = Linking.addEventListener('url', linkingHandler);
|
|
566
572
|
return () => {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
} else {
|
|
573
|
+
// RN < 0.65 returns no subscription, only removeEventListener.
|
|
574
|
+
if (typeof linkingListener?.remove === 'function') {
|
|
570
575
|
linkingListener.remove();
|
|
576
|
+
} else {
|
|
577
|
+
(Linking as any).removeEventListener('url', linkingHandler);
|
|
571
578
|
}
|
|
572
579
|
};
|
|
573
|
-
}, [
|
|
580
|
+
}, []);
|
|
574
581
|
|
|
575
582
|
// progress lives in its own context (see context.ts), so this value only
|
|
576
583
|
// changes when the update state itself changes, not on every progress tick.
|
package/src/telemetry.ts
CHANGED
|
@@ -14,19 +14,6 @@ export type ServerEventType =
|
|
|
14
14
|
// Failure detail is stored server-side in a VARCHAR(512); stay under it.
|
|
15
15
|
export const MAX_DETAIL_LENGTH = 500;
|
|
16
16
|
|
|
17
|
-
export interface ServerReportPayload {
|
|
18
|
-
type: ServerEventType;
|
|
19
|
-
hash: string;
|
|
20
|
-
packageVersion?: string;
|
|
21
|
-
cInfo?: {
|
|
22
|
-
rnu?: string;
|
|
23
|
-
rn?: string;
|
|
24
|
-
os?: string;
|
|
25
|
-
uuid?: string;
|
|
26
|
-
};
|
|
27
|
-
detail?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
17
|
/**
|
|
31
18
|
* Maps a local report event to the server-side aggregate type, or undefined
|
|
32
19
|
* for events the server does not collect. A DOWNLOAD_FAILED whose underlying
|
package/src/type.ts
CHANGED
|
@@ -41,21 +41,6 @@ interface RootResult {
|
|
|
41
41
|
bundleStatus?: 'matched' | 'rebuiltSameJs' | 'unknownBundle';
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export type UpToDateCheckResult = RootResult & {
|
|
45
|
-
upToDate: true;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type UpdateAvailableCheckResult = RootResult &
|
|
49
|
-
Partial<VersionInfo> & {
|
|
50
|
-
update: true;
|
|
51
|
-
expVersion?: VersionInfo;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export type ExpiredCheckResult = RootResult & {
|
|
55
|
-
expired: true;
|
|
56
|
-
downloadUrl: string;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
44
|
export type CheckResult = RootResult &
|
|
60
45
|
Partial<VersionInfo> & {
|
|
61
46
|
expVersion?: VersionInfo;
|