react-native-update 10.30.3 → 10.31.0-beta.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/.cursor/mcp.json +11 -0
- package/android/.project +28 -0
- package/android/bin/.project +34 -0
- package/android/bin/.settings/org.eclipse.buildship.core.prefs +13 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +10 -0
- package/ios/RCTPushy/RCTPushy.mm +8 -0
- package/package.json +3 -2
- package/src/client.ts +46 -21
- package/src/i18n.ts +108 -0
- package/src/locales/en.ts +70 -0
- package/src/locales/zh.ts +67 -0
- package/src/provider.tsx +94 -71
- package/src/type.ts +24 -12
- package/src/utils.ts +12 -8
package/.cursor/mcp.json
ADDED
package/android/.project
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<projectDescription>
|
|
3
|
+
<name>react-native-update</name>
|
|
4
|
+
<comment>Project react-native-update created by Buildship.</comment>
|
|
5
|
+
<projects>
|
|
6
|
+
</projects>
|
|
7
|
+
<buildSpec>
|
|
8
|
+
<buildCommand>
|
|
9
|
+
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
|
10
|
+
<arguments>
|
|
11
|
+
</arguments>
|
|
12
|
+
</buildCommand>
|
|
13
|
+
</buildSpec>
|
|
14
|
+
<natures>
|
|
15
|
+
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
|
16
|
+
</natures>
|
|
17
|
+
<filteredResources>
|
|
18
|
+
<filter>
|
|
19
|
+
<id>1727963310481</id>
|
|
20
|
+
<name></name>
|
|
21
|
+
<type>30</type>
|
|
22
|
+
<matcher>
|
|
23
|
+
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
|
24
|
+
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
|
25
|
+
</matcher>
|
|
26
|
+
</filter>
|
|
27
|
+
</filteredResources>
|
|
28
|
+
</projectDescription>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<projectDescription>
|
|
3
|
+
<name>react-native-update</name>
|
|
4
|
+
<comment>Project react-native-update created by Buildship.</comment>
|
|
5
|
+
<projects>
|
|
6
|
+
</projects>
|
|
7
|
+
<buildSpec>
|
|
8
|
+
<buildCommand>
|
|
9
|
+
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
10
|
+
<arguments>
|
|
11
|
+
</arguments>
|
|
12
|
+
</buildCommand>
|
|
13
|
+
<buildCommand>
|
|
14
|
+
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
|
15
|
+
<arguments>
|
|
16
|
+
</arguments>
|
|
17
|
+
</buildCommand>
|
|
18
|
+
</buildSpec>
|
|
19
|
+
<natures>
|
|
20
|
+
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
21
|
+
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
|
22
|
+
</natures>
|
|
23
|
+
<filteredResources>
|
|
24
|
+
<filter>
|
|
25
|
+
<id>1727963310481</id>
|
|
26
|
+
<name></name>
|
|
27
|
+
<type>30</type>
|
|
28
|
+
<matcher>
|
|
29
|
+
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
|
30
|
+
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
|
31
|
+
</matcher>
|
|
32
|
+
</filter>
|
|
33
|
+
</filteredResources>
|
|
34
|
+
</projectDescription>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
arguments=--init-script /var/folders/l6/0fn3x28s5s585ld3p04gsy1h0000gn/T/db3b08fc4a9ef609cb16b96b200fa13e563f396e9bb1ed0905fdab7bc3bc513b.gradle --init-script /var/folders/l6/0fn3x28s5s585ld3p04gsy1h0000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle
|
|
2
|
+
auto.sync=false
|
|
3
|
+
build.scans.enabled=false
|
|
4
|
+
connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(8.9))
|
|
5
|
+
connection.project.dir=
|
|
6
|
+
eclipse.preferences.version=1
|
|
7
|
+
gradle.user.home=
|
|
8
|
+
java.home=/Users/sunny/.sdkman/candidates/java/17.0.9-zulu/zulu-17.jdk/Contents/Home
|
|
9
|
+
jvm.arguments=
|
|
10
|
+
offline.mode=false
|
|
11
|
+
override.workspace.settings=true
|
|
12
|
+
show.console.view=true
|
|
13
|
+
show.executions.view=true
|
|
@@ -40,6 +40,16 @@ public class UpdateContext {
|
|
|
40
40
|
String storedPackageVersion = this.sp.getString("packageVersion", null);
|
|
41
41
|
String storedBuildTime = this.sp.getString("buildTime", null);
|
|
42
42
|
|
|
43
|
+
// If stored versions don't exist, write current versions first
|
|
44
|
+
if (storedPackageVersion == null || storedBuildTime == null) {
|
|
45
|
+
SharedPreferences.Editor editor = sp.edit();
|
|
46
|
+
editor.putString("packageVersion", packageVersion);
|
|
47
|
+
editor.putString("buildTime", buildTime);
|
|
48
|
+
editor.apply();
|
|
49
|
+
storedPackageVersion = packageVersion;
|
|
50
|
+
storedBuildTime = buildTime;
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
boolean packageVersionChanged = !packageVersion.equals(storedPackageVersion);
|
|
44
54
|
boolean buildTimeChanged = !buildTime.equals(storedBuildTime);
|
|
45
55
|
|
package/ios/RCTPushy/RCTPushy.mm
CHANGED
|
@@ -77,6 +77,14 @@ RCT_EXPORT_MODULE(RCTPushy);
|
|
|
77
77
|
NSString *storedPackageVersion = [defaults stringForKey:paramPackageVersion];
|
|
78
78
|
NSString *storedBuildTime = [defaults stringForKey:paramBuildTime];
|
|
79
79
|
|
|
80
|
+
// If stored versions don't exist, write current versions first
|
|
81
|
+
if (!storedPackageVersion || !storedBuildTime) {
|
|
82
|
+
[defaults setObject:curPackageVersion forKey:paramPackageVersion];
|
|
83
|
+
[defaults setObject:curBuildTime forKey:paramBuildTime];
|
|
84
|
+
storedPackageVersion = curPackageVersion;
|
|
85
|
+
storedBuildTime = curBuildTime;
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
BOOL packageVersionChanged = ![curPackageVersion isEqualToString:storedPackageVersion];
|
|
81
89
|
BOOL buildTimeChanged = ![curBuildTime isEqualToString:storedBuildTime];
|
|
82
90
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-update",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.31.0-beta.0",
|
|
4
4
|
"description": "react-native hot update",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"scripts": {
|
|
@@ -72,5 +72,6 @@
|
|
|
72
72
|
"react-native": "0.73",
|
|
73
73
|
"ts-jest": "^29.3.2",
|
|
74
74
|
"typescript": "^5.6.3"
|
|
75
|
-
}
|
|
75
|
+
},
|
|
76
|
+
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
|
|
76
77
|
}
|
package/src/client.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
promiseAny,
|
|
29
29
|
testUrls,
|
|
30
30
|
} from './utils';
|
|
31
|
+
import i18n from './i18n';
|
|
31
32
|
|
|
32
33
|
const SERVER_PRESETS = {
|
|
33
34
|
// cn
|
|
@@ -91,7 +92,8 @@ export class Pushy {
|
|
|
91
92
|
options = defaultClientOptions;
|
|
92
93
|
clientType: 'Pushy' | 'Cresc' = 'Pushy';
|
|
93
94
|
lastChecking?: number;
|
|
94
|
-
lastRespJson?: Promise<
|
|
95
|
+
lastRespJson?: Promise<CheckResult>;
|
|
96
|
+
lastRespText?: Promise<string>;
|
|
95
97
|
|
|
96
98
|
version = cInfo.rnu;
|
|
97
99
|
loggerPromise = (() => {
|
|
@@ -106,13 +108,18 @@ export class Pushy {
|
|
|
106
108
|
})();
|
|
107
109
|
|
|
108
110
|
constructor(options: ClientOptions, clientType?: 'Pushy' | 'Cresc') {
|
|
111
|
+
this.clientType = clientType || 'Pushy';
|
|
112
|
+
this.options.server = SERVER_PRESETS[this.clientType];
|
|
113
|
+
|
|
114
|
+
// Initialize i18n based on clientType
|
|
115
|
+
i18n.setLocale(this.clientType === 'Pushy' ? 'zh' : 'en');
|
|
116
|
+
|
|
109
117
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
110
118
|
if (!options.appKey) {
|
|
111
|
-
throw new Error('
|
|
119
|
+
throw new Error(i18n.t('error_appkey_required'));
|
|
112
120
|
}
|
|
113
121
|
}
|
|
114
|
-
|
|
115
|
-
this.options.server = SERVER_PRESETS[this.clientType];
|
|
122
|
+
|
|
116
123
|
this.setOptions(options);
|
|
117
124
|
if (isRolledBack) {
|
|
118
125
|
this.report({
|
|
@@ -135,6 +142,16 @@ export class Pushy {
|
|
|
135
142
|
}
|
|
136
143
|
};
|
|
137
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Get translated text based on current clientType
|
|
147
|
+
* @param key - Translation key
|
|
148
|
+
* @param values - Values for interpolation (optional)
|
|
149
|
+
* @returns Translated string
|
|
150
|
+
*/
|
|
151
|
+
t = (key: string, values?: Record<string, string | number>) => {
|
|
152
|
+
return i18n.t(key as any, values);
|
|
153
|
+
};
|
|
154
|
+
|
|
138
155
|
report = async ({
|
|
139
156
|
type,
|
|
140
157
|
message = '',
|
|
@@ -174,11 +191,7 @@ export class Pushy {
|
|
|
174
191
|
};
|
|
175
192
|
assertDebug = (matter: string) => {
|
|
176
193
|
if (__DEV__ && !this.options.debug) {
|
|
177
|
-
console.info(
|
|
178
|
-
`You are currently in the development environment and have not enabled debug mode.
|
|
179
|
-
${matter} will not be performed.
|
|
180
|
-
If you need to debug ${matter} in the development environment, please set debug to true in the client.`,
|
|
181
|
-
);
|
|
194
|
+
console.info(this.t('dev_debug_disabled', { matter }));
|
|
182
195
|
return false;
|
|
183
196
|
}
|
|
184
197
|
return true;
|
|
@@ -269,7 +282,7 @@ export class Pushy {
|
|
|
269
282
|
} catch (e: any) {
|
|
270
283
|
this.report({
|
|
271
284
|
type: 'errorChecking',
|
|
272
|
-
message:
|
|
285
|
+
message: this.t('error_cannot_connect_backup', { message: e.message }),
|
|
273
286
|
});
|
|
274
287
|
const backupEndpoints = await this.getBackupEndpoints();
|
|
275
288
|
if (backupEndpoints) {
|
|
@@ -289,24 +302,30 @@ export class Pushy {
|
|
|
289
302
|
if (!resp) {
|
|
290
303
|
this.report({
|
|
291
304
|
type: 'errorChecking',
|
|
292
|
-
message:
|
|
305
|
+
message: this.t('error_cannot_connect_server'),
|
|
293
306
|
});
|
|
294
307
|
this.throwIfEnabled(new Error('errorChecking'));
|
|
295
308
|
return this.lastRespJson ? await this.lastRespJson : emptyObj;
|
|
296
309
|
}
|
|
297
|
-
this.lastRespJson = resp.json();
|
|
298
|
-
|
|
299
|
-
const result: CheckResult = await this.lastRespJson;
|
|
300
|
-
|
|
301
|
-
log('checking result:', result);
|
|
302
310
|
|
|
303
311
|
if (resp.status !== 200) {
|
|
312
|
+
const errorMessage = this.t('error_http_status', {
|
|
313
|
+
status: resp.status,
|
|
314
|
+
statusText: resp.statusText,
|
|
315
|
+
});
|
|
304
316
|
this.report({
|
|
305
317
|
type: 'errorChecking',
|
|
306
|
-
message:
|
|
318
|
+
message: errorMessage,
|
|
307
319
|
});
|
|
308
|
-
this.throwIfEnabled(new Error(
|
|
320
|
+
this.throwIfEnabled(new Error(errorMessage));
|
|
321
|
+
log('error checking response:', resp.status, await resp.text());
|
|
322
|
+
return this.lastRespJson ? await this.lastRespJson : emptyObj;
|
|
309
323
|
}
|
|
324
|
+
this.lastRespJson = resp.json();
|
|
325
|
+
|
|
326
|
+
const result: CheckResult = await this.lastRespJson;
|
|
327
|
+
|
|
328
|
+
log('checking result:', result);
|
|
310
329
|
|
|
311
330
|
return result;
|
|
312
331
|
};
|
|
@@ -412,7 +431,9 @@ export class Pushy {
|
|
|
412
431
|
});
|
|
413
432
|
succeeded = 'diff';
|
|
414
433
|
} catch (e: any) {
|
|
415
|
-
const errorMessage =
|
|
434
|
+
const errorMessage = this.t('error_diff_failed', {
|
|
435
|
+
message: e.message,
|
|
436
|
+
});
|
|
416
437
|
errorMessages.push(errorMessage);
|
|
417
438
|
lastError = new Error(errorMessage);
|
|
418
439
|
log(errorMessage);
|
|
@@ -429,7 +450,9 @@ export class Pushy {
|
|
|
429
450
|
});
|
|
430
451
|
succeeded = 'pdiff';
|
|
431
452
|
} catch (e: any) {
|
|
432
|
-
const errorMessage =
|
|
453
|
+
const errorMessage = this.t('error_pdiff_failed', {
|
|
454
|
+
message: e.message,
|
|
455
|
+
});
|
|
433
456
|
errorMessages.push(errorMessage);
|
|
434
457
|
lastError = new Error(errorMessage);
|
|
435
458
|
log(errorMessage);
|
|
@@ -447,7 +470,9 @@ export class Pushy {
|
|
|
447
470
|
});
|
|
448
471
|
succeeded = 'full';
|
|
449
472
|
} catch (e: any) {
|
|
450
|
-
const errorMessage =
|
|
473
|
+
const errorMessage = this.t('error_full_patch_failed', {
|
|
474
|
+
message: e.message,
|
|
475
|
+
});
|
|
451
476
|
errorMessages.push(errorMessage);
|
|
452
477
|
lastError = new Error(errorMessage);
|
|
453
478
|
log(errorMessage);
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import zhTranslations from './locales/zh';
|
|
2
|
+
import enTranslations from './locales/en';
|
|
3
|
+
|
|
4
|
+
type TranslationKey = keyof typeof zhTranslations | keyof typeof enTranslations;
|
|
5
|
+
type TranslationValues = Record<string, string | number>;
|
|
6
|
+
|
|
7
|
+
class I18n {
|
|
8
|
+
private currentLocale: 'zh' | 'en' = 'en';
|
|
9
|
+
private translations = {
|
|
10
|
+
zh: zhTranslations,
|
|
11
|
+
en: enTranslations,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Set locale directly
|
|
16
|
+
* @param locale - 'zh' or 'en'
|
|
17
|
+
*/
|
|
18
|
+
setLocale(locale: 'zh' | 'en') {
|
|
19
|
+
this.currentLocale = locale;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get current locale
|
|
24
|
+
*/
|
|
25
|
+
getLocale(): 'zh' | 'en' {
|
|
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
|
|
34
|
+
*/
|
|
35
|
+
t(key: TranslationKey, values?: TranslationValues): string {
|
|
36
|
+
const translation =
|
|
37
|
+
this.translations[this.currentLocale][
|
|
38
|
+
key as keyof (typeof this.translations)[typeof this.currentLocale]
|
|
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 {
|
|
68
|
+
if (!values) {
|
|
69
|
+
return template;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
73
|
+
const value = values[key];
|
|
74
|
+
return value !== undefined ? String(value) : match;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
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
|
+
}
|
|
90
|
+
|
|
91
|
+
// Create singleton instance
|
|
92
|
+
const i18n = new I18n();
|
|
93
|
+
|
|
94
|
+
// Export both the instance and the class for flexibility
|
|
95
|
+
export { i18n, I18n };
|
|
96
|
+
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
|
+
*/
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
// Common messages
|
|
3
|
+
checking_update: 'Checking for updates...',
|
|
4
|
+
downloading_update: 'Downloading update package...',
|
|
5
|
+
installing_update: 'Installing update...',
|
|
6
|
+
update_available: 'Update available',
|
|
7
|
+
update_downloaded: 'Update downloaded successfully',
|
|
8
|
+
update_installed: 'Update installed successfully',
|
|
9
|
+
no_update_available: 'You are up to date',
|
|
10
|
+
update_failed: 'Update failed',
|
|
11
|
+
network_error: 'Network connection error',
|
|
12
|
+
download_failed: 'Download failed',
|
|
13
|
+
install_failed: 'Installation failed',
|
|
14
|
+
|
|
15
|
+
// Progress messages with interpolation
|
|
16
|
+
download_progress: 'Download progress: {{progress}}%',
|
|
17
|
+
download_speed: 'Download speed: {{speed}}/s',
|
|
18
|
+
file_size: 'File size: {{size}}',
|
|
19
|
+
time_remaining: 'Time remaining: {{time}}',
|
|
20
|
+
|
|
21
|
+
// Error messages
|
|
22
|
+
error_code: 'Error code: {{code}}',
|
|
23
|
+
error_message: 'Error message: {{message}}',
|
|
24
|
+
retry_count: 'Retry attempt: {{count}}/{{max}}',
|
|
25
|
+
|
|
26
|
+
// Update info
|
|
27
|
+
version_info: 'Version {{version}} ({{build}})',
|
|
28
|
+
release_notes: 'Release notes: {{notes}}',
|
|
29
|
+
update_size: 'Update size: {{size}}MB',
|
|
30
|
+
|
|
31
|
+
// Alert messages
|
|
32
|
+
alert_title: 'Notice',
|
|
33
|
+
alert_update_ready: 'Download completed. Update now?',
|
|
34
|
+
alert_next_time: 'Later',
|
|
35
|
+
alert_update_now: 'Update Now',
|
|
36
|
+
alert_app_updated:
|
|
37
|
+
'Your app version has been updated. Click update to download and install the new version',
|
|
38
|
+
alert_update_button: 'Update',
|
|
39
|
+
alert_cancel: 'Cancel',
|
|
40
|
+
alert_confirm: 'OK',
|
|
41
|
+
alert_info: 'Info',
|
|
42
|
+
alert_no_update_wait:
|
|
43
|
+
'No update found, please wait 10s for the server to generate the patch package',
|
|
44
|
+
|
|
45
|
+
// Error messages
|
|
46
|
+
error_appkey_required: 'appKey is required',
|
|
47
|
+
error_update_check_failed: 'Update check failed',
|
|
48
|
+
error_cannot_connect_server:
|
|
49
|
+
'Can not connect to update server. Please check your network.',
|
|
50
|
+
error_cannot_connect_backup:
|
|
51
|
+
'Can not connect to update server: {{message}}. Trying backup endpoints.',
|
|
52
|
+
error_diff_failed: 'diff error: {{message}}',
|
|
53
|
+
error_pdiff_failed: 'pdiff error: {{message}}',
|
|
54
|
+
error_full_patch_failed: 'full patch error: {{message}}',
|
|
55
|
+
error_all_promises_rejected: 'All promises were rejected',
|
|
56
|
+
error_ping_failed: 'Ping failed',
|
|
57
|
+
error_ping_timeout: 'Ping timeout',
|
|
58
|
+
error_http_status: '{{status}} {{statusText}}',
|
|
59
|
+
|
|
60
|
+
// Development messages
|
|
61
|
+
dev_debug_disabled:
|
|
62
|
+
'You are currently in the development environment and have not enabled debug mode. {{matter}} will not be performed. If you need to debug {{matter}} in the development environment, please set debug to true in the client.',
|
|
63
|
+
dev_log_prefix: 'react-native-update: ',
|
|
64
|
+
dev_web_not_supported:
|
|
65
|
+
'react-native-update does not support the Web platform and will not perform any operations',
|
|
66
|
+
|
|
67
|
+
// More alert messages
|
|
68
|
+
alert_new_version_found:
|
|
69
|
+
'New version {{name}} found. Download now?\n{{description}}',
|
|
70
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
// Common messages
|
|
3
|
+
checking_update: '正在检查更新...',
|
|
4
|
+
downloading_update: '正在下载更新包...',
|
|
5
|
+
installing_update: '正在安装更新...',
|
|
6
|
+
update_available: '发现新版本',
|
|
7
|
+
update_downloaded: '更新包下载完成',
|
|
8
|
+
update_installed: '更新安装完成',
|
|
9
|
+
no_update_available: '已是最新版本',
|
|
10
|
+
update_failed: '更新失败',
|
|
11
|
+
network_error: '网络连接错误',
|
|
12
|
+
download_failed: '下载失败',
|
|
13
|
+
install_failed: '安装失败',
|
|
14
|
+
|
|
15
|
+
// Progress messages with interpolation
|
|
16
|
+
download_progress: '下载进度: {{progress}}%',
|
|
17
|
+
download_speed: '下载速度: {{speed}}/s',
|
|
18
|
+
file_size: '文件大小: {{size}}',
|
|
19
|
+
time_remaining: '剩余时间: {{time}}',
|
|
20
|
+
|
|
21
|
+
// Error messages
|
|
22
|
+
error_code: '错误代码: {{code}}',
|
|
23
|
+
error_message: '错误信息: {{message}}',
|
|
24
|
+
retry_count: '重试次数: {{count}}/{{max}}',
|
|
25
|
+
|
|
26
|
+
// Update info
|
|
27
|
+
version_info: '版本 {{version}} ({{build}})',
|
|
28
|
+
release_notes: '更新说明: {{notes}}',
|
|
29
|
+
update_size: '更新包大小: {{size}}MB',
|
|
30
|
+
|
|
31
|
+
// Alert messages
|
|
32
|
+
alert_title: '提示',
|
|
33
|
+
alert_update_ready: '下载完毕,是否立即更新?',
|
|
34
|
+
alert_next_time: '下次再说',
|
|
35
|
+
alert_update_now: '立即更新',
|
|
36
|
+
alert_app_updated: '您的应用版本已更新,点击更新下载安装新版本',
|
|
37
|
+
alert_update_button: '更新',
|
|
38
|
+
alert_cancel: '取消',
|
|
39
|
+
alert_confirm: '确定',
|
|
40
|
+
alert_info: '信息',
|
|
41
|
+
alert_no_update_wait: '未发现更新,请等待10秒让服务器生成补丁包',
|
|
42
|
+
|
|
43
|
+
// Error messages
|
|
44
|
+
error_appkey_required: '需要提供 appKey',
|
|
45
|
+
error_update_check_failed: '更新检查失败',
|
|
46
|
+
error_cannot_connect_server: '无法连接到更新服务器。请检查网络连接。',
|
|
47
|
+
error_cannot_connect_backup:
|
|
48
|
+
'无法连接到更新服务器: {{message}}。正在尝试备用端点。',
|
|
49
|
+
error_diff_failed: 'diff 错误: {{message}}',
|
|
50
|
+
error_pdiff_failed: 'pdiff 错误: {{message}}',
|
|
51
|
+
error_full_patch_failed: '完整补丁错误: {{message}}',
|
|
52
|
+
error_all_promises_rejected: '所有请求都被拒绝',
|
|
53
|
+
error_ping_failed: 'Ping 失败',
|
|
54
|
+
error_ping_timeout: 'Ping 超时',
|
|
55
|
+
error_http_status: '{{status}} {{statusText}}',
|
|
56
|
+
|
|
57
|
+
// Development messages
|
|
58
|
+
dev_debug_disabled:
|
|
59
|
+
'您当前处于开发环境且未启用调试模式。{{matter}} 将不会执行。如需在开发环境中调试 {{matter}},请在客户端中将 debug 设为 true。',
|
|
60
|
+
dev_log_prefix: 'react-native-update: ',
|
|
61
|
+
dev_web_not_supported:
|
|
62
|
+
'react-native-update 不支持 Web 平台,不会执行任何操作',
|
|
63
|
+
|
|
64
|
+
// More alert messages
|
|
65
|
+
alert_new_version_found:
|
|
66
|
+
'检查到新的版本{{name}},是否下载?\n{{description}}',
|
|
67
|
+
};
|
package/src/provider.tsx
CHANGED
|
@@ -14,7 +14,12 @@ import {
|
|
|
14
14
|
} from 'react-native';
|
|
15
15
|
import { Pushy, Cresc, sharedState } from './client';
|
|
16
16
|
import { currentVersion, packageVersion, getCurrentVersionInfo } from './core';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
CheckResult,
|
|
19
|
+
MixedCheckResult,
|
|
20
|
+
ProgressData,
|
|
21
|
+
UpdateTestPayload,
|
|
22
|
+
} from './type';
|
|
18
23
|
import { UpdateContext } from './context';
|
|
19
24
|
import { URL } from 'react-native-url-polyfill';
|
|
20
25
|
import { isInRollout } from './isInRollout';
|
|
@@ -115,16 +120,16 @@ export const UpdateProvider = ({
|
|
|
115
120
|
client.switchVersionLater(hash);
|
|
116
121
|
return true;
|
|
117
122
|
}
|
|
118
|
-
alertUpdate('
|
|
123
|
+
alertUpdate(client.t('alert_title'), client.t('alert_update_ready'), [
|
|
119
124
|
{
|
|
120
|
-
text: '
|
|
125
|
+
text: client.t('alert_next_time'),
|
|
121
126
|
style: 'cancel',
|
|
122
127
|
onPress: () => {
|
|
123
128
|
client.switchVersionLater(hash);
|
|
124
129
|
},
|
|
125
130
|
},
|
|
126
131
|
{
|
|
127
|
-
text: '
|
|
132
|
+
text: client.t('alert_update_now'),
|
|
128
133
|
style: 'default',
|
|
129
134
|
onPress: () => {
|
|
130
135
|
client.switchVersion(hash);
|
|
@@ -134,7 +139,7 @@ export const UpdateProvider = ({
|
|
|
134
139
|
return true;
|
|
135
140
|
} catch (e: any) {
|
|
136
141
|
setLastError(e);
|
|
137
|
-
alertError('
|
|
142
|
+
alertError(client.t('update_failed'), e.message);
|
|
138
143
|
throwErrorIfEnabled(e);
|
|
139
144
|
return false;
|
|
140
145
|
}
|
|
@@ -158,84 +163,102 @@ export const UpdateProvider = ({
|
|
|
158
163
|
return;
|
|
159
164
|
}
|
|
160
165
|
lastChecking.current = now;
|
|
161
|
-
let
|
|
166
|
+
let rootInfo: MixedCheckResult | undefined;
|
|
162
167
|
try {
|
|
163
|
-
|
|
168
|
+
rootInfo = await client.checkUpdate(extra);
|
|
164
169
|
} catch (e: any) {
|
|
165
170
|
setLastError(e);
|
|
166
|
-
alertError('
|
|
171
|
+
alertError(client.t('error_update_check_failed'), e.message);
|
|
167
172
|
throwErrorIfEnabled(e);
|
|
168
173
|
return;
|
|
169
174
|
}
|
|
170
|
-
if (!
|
|
175
|
+
if (!rootInfo) {
|
|
171
176
|
return;
|
|
172
177
|
}
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
(await options.onPackageExpired(info)) === false
|
|
188
|
-
) {
|
|
189
|
-
log('onPackageExpired returned false, skipping');
|
|
190
|
-
return;
|
|
178
|
+
const versions = rootInfo.versions || [rootInfo as CheckResult];
|
|
179
|
+
delete rootInfo.versions;
|
|
180
|
+
for (const versionInfo of versions) {
|
|
181
|
+
const info: CheckResult = {
|
|
182
|
+
...versionInfo,
|
|
183
|
+
...rootInfo,
|
|
184
|
+
};
|
|
185
|
+
const rollout = info.config?.rollout?.[packageVersion];
|
|
186
|
+
if (info.update && rollout) {
|
|
187
|
+
if (!isInRollout(rollout)) {
|
|
188
|
+
log(`${info.name} not in ${rollout}% rollout, ignored`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
log(`${info.name} in ${rollout}% rollout, continue`);
|
|
191
192
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
info.description = info.description ?? '';
|
|
194
|
+
updateInfoRef.current = info;
|
|
195
|
+
setUpdateInfo(info);
|
|
196
|
+
if (info.expired) {
|
|
197
|
+
if (
|
|
198
|
+
options.onPackageExpired &&
|
|
199
|
+
(await options.onPackageExpired(info)) === false
|
|
200
|
+
) {
|
|
201
|
+
log('onPackageExpired returned false, skipping');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const { downloadUrl } = info;
|
|
205
|
+
if (downloadUrl && sharedState.apkStatus === null) {
|
|
206
|
+
if (options.updateStrategy === 'silentAndNow') {
|
|
207
|
+
if (Platform.OS === 'android' && downloadUrl.endsWith('.apk')) {
|
|
208
|
+
downloadAndInstallApk(downloadUrl);
|
|
209
|
+
} else {
|
|
210
|
+
Linking.openURL(downloadUrl);
|
|
211
|
+
}
|
|
212
|
+
return info;
|
|
199
213
|
}
|
|
214
|
+
alertUpdate(
|
|
215
|
+
client.t('alert_title'),
|
|
216
|
+
client.t('alert_app_updated'),
|
|
217
|
+
[
|
|
218
|
+
{
|
|
219
|
+
text: client.t('alert_update_button'),
|
|
220
|
+
onPress: () => {
|
|
221
|
+
if (
|
|
222
|
+
Platform.OS === 'android' &&
|
|
223
|
+
downloadUrl.endsWith('.apk')
|
|
224
|
+
) {
|
|
225
|
+
downloadAndInstallApk(downloadUrl);
|
|
226
|
+
} else {
|
|
227
|
+
Linking.openURL(downloadUrl);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
} else if (info.update) {
|
|
235
|
+
if (
|
|
236
|
+
options.updateStrategy === 'silentAndNow' ||
|
|
237
|
+
options.updateStrategy === 'silentAndLater'
|
|
238
|
+
) {
|
|
239
|
+
downloadUpdate(info);
|
|
200
240
|
return info;
|
|
201
241
|
}
|
|
202
|
-
alertUpdate(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
242
|
+
alertUpdate(
|
|
243
|
+
client.t('alert_title'),
|
|
244
|
+
client.t('alert_new_version_found', {
|
|
245
|
+
name: info.name,
|
|
246
|
+
description: info.description,
|
|
247
|
+
}),
|
|
248
|
+
[
|
|
249
|
+
{ text: client.t('alert_cancel'), style: 'cancel' },
|
|
250
|
+
{
|
|
251
|
+
text: client.t('alert_confirm'),
|
|
252
|
+
style: 'default',
|
|
253
|
+
onPress: () => {
|
|
254
|
+
downloadUpdate();
|
|
255
|
+
},
|
|
211
256
|
},
|
|
212
|
-
|
|
213
|
-
|
|
257
|
+
],
|
|
258
|
+
);
|
|
214
259
|
}
|
|
215
|
-
|
|
216
|
-
if (
|
|
217
|
-
options.updateStrategy === 'silentAndNow' ||
|
|
218
|
-
options.updateStrategy === 'silentAndLater'
|
|
219
|
-
) {
|
|
220
|
-
downloadUpdate(info);
|
|
221
|
-
return info;
|
|
222
|
-
}
|
|
223
|
-
alertUpdate(
|
|
224
|
-
'提示',
|
|
225
|
-
'检查到新的版本' + info.name + ',是否下载?\n' + info.description,
|
|
226
|
-
[
|
|
227
|
-
{ text: '取消', style: 'cancel' },
|
|
228
|
-
{
|
|
229
|
-
text: '确定',
|
|
230
|
-
style: 'default',
|
|
231
|
-
onPress: () => {
|
|
232
|
-
downloadUpdate();
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
);
|
|
260
|
+
return info;
|
|
237
261
|
}
|
|
238
|
-
return info;
|
|
239
262
|
},
|
|
240
263
|
[
|
|
241
264
|
client,
|
|
@@ -297,8 +320,8 @@ export const UpdateProvider = ({
|
|
|
297
320
|
checkUpdate({ extra: { toHash: payload.data } }).then(() => {
|
|
298
321
|
if (updateInfoRef.current && updateInfoRef.current.upToDate) {
|
|
299
322
|
Alert.alert(
|
|
300
|
-
'
|
|
301
|
-
'
|
|
323
|
+
client.t('alert_info'),
|
|
324
|
+
client.t('alert_no_update_wait'),
|
|
302
325
|
);
|
|
303
326
|
}
|
|
304
327
|
options.logger = logger;
|
|
@@ -308,7 +331,7 @@ export const UpdateProvider = ({
|
|
|
308
331
|
}
|
|
309
332
|
return false;
|
|
310
333
|
},
|
|
311
|
-
[checkUpdate, options],
|
|
334
|
+
[checkUpdate, options, client],
|
|
312
335
|
);
|
|
313
336
|
|
|
314
337
|
const parseTestQrCode = useCallback(
|
package/src/type.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
export interface
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
description?: string;
|
|
9
|
-
metaInfo?: string;
|
|
10
|
-
config?: {
|
|
11
|
-
rollout?: {
|
|
1
|
+
export interface VersionInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
hash: string;
|
|
4
|
+
description: string;
|
|
5
|
+
metaInfo: string;
|
|
6
|
+
config: {
|
|
7
|
+
rollout: {
|
|
12
8
|
[packageVersion: string]: number;
|
|
13
9
|
};
|
|
14
10
|
[key: string]: any;
|
|
@@ -16,11 +12,27 @@ export interface CheckResult {
|
|
|
16
12
|
pdiff?: string;
|
|
17
13
|
diff?: string;
|
|
18
14
|
full?: string;
|
|
19
|
-
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface RootResult {
|
|
18
|
+
upToDate?: true;
|
|
19
|
+
expired?: true;
|
|
20
|
+
downloadUrl?: string;
|
|
21
|
+
update?: true;
|
|
20
22
|
paused?: 'app' | 'package';
|
|
21
23
|
message?: string;
|
|
24
|
+
paths?: string[];
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
export type CheckResult = RootResult & VersionInfo;
|
|
28
|
+
|
|
29
|
+
export type CheckResultV2 = RootResult & {
|
|
30
|
+
versions?: VersionInfo[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type MixedCheckResult = CheckResult | CheckResultV2;
|
|
34
|
+
|
|
35
|
+
|
|
24
36
|
export interface ProgressData {
|
|
25
37
|
hash: string;
|
|
26
38
|
received: number;
|
package/src/utils.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
|
+
import i18n from './i18n';
|
|
2
3
|
|
|
3
4
|
export function log(...args: any[]) {
|
|
4
|
-
console.log('
|
|
5
|
+
console.log(i18n.t('dev_log_prefix'), ...args);
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export function promiseAny<T>(promises: Promise<T>[]) {
|
|
@@ -14,7 +15,7 @@ export function promiseAny<T>(promises: Promise<T>[]) {
|
|
|
14
15
|
.catch(() => {
|
|
15
16
|
count++;
|
|
16
17
|
if (count === promises.length) {
|
|
17
|
-
reject(new Error('
|
|
18
|
+
reject(new Error(i18n.t('error_all_promises_rejected')));
|
|
18
19
|
}
|
|
19
20
|
});
|
|
20
21
|
});
|
|
@@ -49,7 +50,7 @@ const ping =
|
|
|
49
50
|
return finalUrl;
|
|
50
51
|
}
|
|
51
52
|
log('ping failed', url, status, statusText);
|
|
52
|
-
throw new Error('
|
|
53
|
+
throw new Error(i18n.t('error_ping_failed'));
|
|
53
54
|
})
|
|
54
55
|
.catch(e => {
|
|
55
56
|
pingFinished = true;
|
|
@@ -58,7 +59,7 @@ const ping =
|
|
|
58
59
|
}),
|
|
59
60
|
new Promise((_, reject) =>
|
|
60
61
|
setTimeout(() => {
|
|
61
|
-
reject(new Error('
|
|
62
|
+
reject(new Error(i18n.t('error_ping_timeout')));
|
|
62
63
|
if (!pingFinished) {
|
|
63
64
|
log('ping timeout', url);
|
|
64
65
|
}
|
|
@@ -91,9 +92,7 @@ export const testUrls = async (urls?: string[]) => {
|
|
|
91
92
|
|
|
92
93
|
export const assertWeb = () => {
|
|
93
94
|
if (Platform.OS === 'web') {
|
|
94
|
-
console.warn(
|
|
95
|
-
'react-native-update does not support the Web platform and will not perform any operations',
|
|
96
|
-
);
|
|
95
|
+
console.warn(i18n.t('dev_web_not_supported'));
|
|
97
96
|
return false;
|
|
98
97
|
}
|
|
99
98
|
return true;
|
|
@@ -115,7 +114,12 @@ export const enhancedFetch = async (
|
|
|
115
114
|
if (r.ok) {
|
|
116
115
|
return r;
|
|
117
116
|
}
|
|
118
|
-
throw new Error(
|
|
117
|
+
throw new Error(
|
|
118
|
+
i18n.t('error_http_status', {
|
|
119
|
+
status: r.status,
|
|
120
|
+
statusText: r.statusText,
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
119
123
|
})
|
|
120
124
|
.catch(e => {
|
|
121
125
|
log('fetch error', url, e);
|