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/client.ts
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DeviceEventEmitter,
|
|
3
|
-
EmitterSubscription,
|
|
3
|
+
type EmitterSubscription,
|
|
4
4
|
Platform,
|
|
5
5
|
} from 'react-native';
|
|
6
6
|
import {
|
|
7
|
-
PushyModule,
|
|
8
7
|
buildTime,
|
|
9
8
|
cInfo,
|
|
10
9
|
currentVersion,
|
|
11
10
|
currentVersionInfo,
|
|
12
|
-
|
|
11
|
+
getBundleHash,
|
|
13
12
|
isFirstTime,
|
|
14
13
|
isRolledBack,
|
|
14
|
+
PushyModule,
|
|
15
15
|
packageVersion,
|
|
16
16
|
pushyNativeEventEmitter,
|
|
17
17
|
rolledBackVersion,
|
|
18
18
|
setLocalHashInfo,
|
|
19
|
+
supportedDiffVersion,
|
|
19
20
|
} from './core';
|
|
21
|
+
import { dedupeEndpoints, executeEndpointFallback } from './endpoint';
|
|
22
|
+
import {
|
|
23
|
+
asUpdateErrorCode,
|
|
24
|
+
toUpdateError,
|
|
25
|
+
UpdateError,
|
|
26
|
+
type UpdateErrorCode,
|
|
27
|
+
} from './error';
|
|
28
|
+
import i18n from './i18n';
|
|
20
29
|
import { PermissionsAndroid } from './permissions';
|
|
21
30
|
import {
|
|
31
|
+
resolveServerEventHash,
|
|
32
|
+
resolveServerEventType,
|
|
33
|
+
truncateDetail,
|
|
34
|
+
} from './telemetry';
|
|
35
|
+
import type {
|
|
22
36
|
BeforeReloadContext,
|
|
23
37
|
CheckResult,
|
|
24
38
|
ClientOptions,
|
|
@@ -38,20 +52,8 @@ import {
|
|
|
38
52
|
noop,
|
|
39
53
|
promiseAny,
|
|
40
54
|
testUrls,
|
|
55
|
+
warn,
|
|
41
56
|
} from './utils';
|
|
42
|
-
import i18n from './i18n';
|
|
43
|
-
import {
|
|
44
|
-
asUpdateErrorCode,
|
|
45
|
-
toUpdateError,
|
|
46
|
-
UpdateError,
|
|
47
|
-
UpdateErrorCode,
|
|
48
|
-
} from './error';
|
|
49
|
-
import { dedupeEndpoints, executeEndpointFallback } from './endpoint';
|
|
50
|
-
import {
|
|
51
|
-
resolveServerEventHash,
|
|
52
|
-
resolveServerEventType,
|
|
53
|
-
truncateDetail,
|
|
54
|
-
} from './telemetry';
|
|
55
57
|
|
|
56
58
|
/**
|
|
57
59
|
* Receives every error the client reports, alongside the report event type.
|
|
@@ -60,13 +62,16 @@ import {
|
|
|
60
62
|
*/
|
|
61
63
|
export type UpdateErrorListener = (
|
|
62
64
|
error: UpdateError,
|
|
63
|
-
eventType: EventType
|
|
65
|
+
eventType: EventType
|
|
64
66
|
) => void;
|
|
65
67
|
|
|
66
68
|
const SERVER_PRESETS = {
|
|
67
69
|
// cn
|
|
68
70
|
Pushy: {
|
|
69
|
-
main: [
|
|
71
|
+
main: [
|
|
72
|
+
'https://update.react-native.cn/api',
|
|
73
|
+
'https://update.reactnative.cn/api',
|
|
74
|
+
],
|
|
70
75
|
queryUrls: [
|
|
71
76
|
'https://gitee.com/sunnylqm/react-native-pushy/raw/master/endpoints.json',
|
|
72
77
|
'https://cdn.jsdelivr.net/gh/reactnativecn/react-native-update@master/endpoints.json',
|
|
@@ -88,15 +93,15 @@ const cloneServerConfig = (server: UpdateServerConfig): UpdateServerConfig => ({
|
|
|
88
93
|
|
|
89
94
|
const excludeConfiguredEndpoints = (
|
|
90
95
|
endpoints: string[],
|
|
91
|
-
configuredEndpoints: string[]
|
|
96
|
+
configuredEndpoints: string[]
|
|
92
97
|
) => {
|
|
93
98
|
const configured = new Set(configuredEndpoints);
|
|
94
|
-
return endpoints.filter(endpoint => !configured.has(endpoint));
|
|
99
|
+
return endpoints.filter((endpoint) => !configured.has(endpoint));
|
|
95
100
|
};
|
|
96
101
|
|
|
97
102
|
assertWeb();
|
|
98
103
|
|
|
99
|
-
const
|
|
104
|
+
const createDefaultClientOptions = (): ClientOptions => ({
|
|
100
105
|
appKey: '',
|
|
101
106
|
autoMarkSuccess: true,
|
|
102
107
|
updateStrategy: __DEV__ ? 'alwaysAlert' : 'alertUpdateAndIgnoreError',
|
|
@@ -104,7 +109,7 @@ const defaultClientOptions: ClientOptions = {
|
|
|
104
109
|
logger: noop,
|
|
105
110
|
debug: false,
|
|
106
111
|
throwError: false,
|
|
107
|
-
};
|
|
112
|
+
});
|
|
108
113
|
|
|
109
114
|
export const sharedState: {
|
|
110
115
|
progressHandlers: Record<string, EmitterSubscription>;
|
|
@@ -142,7 +147,7 @@ const assertHash = (hash: string) => {
|
|
|
142
147
|
|
|
143
148
|
// for China users
|
|
144
149
|
export class Pushy {
|
|
145
|
-
options =
|
|
150
|
+
options: ClientOptions = createDefaultClientOptions();
|
|
146
151
|
clientType: 'Pushy' | 'Cresc' = 'Pushy';
|
|
147
152
|
lastChecking?: number;
|
|
148
153
|
lastRespJson?: Promise<CheckResult>;
|
|
@@ -153,7 +158,7 @@ export class Pushy {
|
|
|
153
158
|
version = cInfo.rnu;
|
|
154
159
|
loggerPromise = (() => {
|
|
155
160
|
let resolve: (value?: unknown) => void = () => {};
|
|
156
|
-
const promise = new Promise(res => {
|
|
161
|
+
const promise = new Promise((res) => {
|
|
157
162
|
resolve = res;
|
|
158
163
|
});
|
|
159
164
|
return {
|
|
@@ -167,14 +172,14 @@ export class Pushy {
|
|
|
167
172
|
this.options.server = cloneServerConfig(SERVER_PRESETS[this.clientType]);
|
|
168
173
|
|
|
169
174
|
i18n.setLocale(
|
|
170
|
-
options.locale ?? (this.clientType === 'Pushy' ? 'zh' : 'en')
|
|
175
|
+
options.locale ?? (this.clientType === 'Pushy' ? 'zh' : 'en')
|
|
171
176
|
);
|
|
172
177
|
|
|
173
178
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
174
179
|
if (!options.appKey) {
|
|
175
180
|
throw new UpdateError(
|
|
176
181
|
i18n.t('error_appkey_required'),
|
|
177
|
-
'APPKEY_REQUIRED'
|
|
182
|
+
'APPKEY_REQUIRED'
|
|
178
183
|
);
|
|
179
184
|
}
|
|
180
185
|
}
|
|
@@ -235,7 +240,7 @@ export class Pushy {
|
|
|
235
240
|
// no logger is ever provided.
|
|
236
241
|
await Promise.race([
|
|
237
242
|
this.loggerPromise.promise,
|
|
238
|
-
new Promise(resolve => setTimeout(resolve, 10 * 1000)),
|
|
243
|
+
new Promise((resolve) => setTimeout(resolve, 10 * 1000)),
|
|
239
244
|
]);
|
|
240
245
|
}
|
|
241
246
|
const { logger = noop, appKey } = this.options;
|
|
@@ -314,7 +319,7 @@ export class Pushy {
|
|
|
314
319
|
detail: truncateDetail(detail),
|
|
315
320
|
}),
|
|
316
321
|
},
|
|
317
|
-
DEFAULT_FETCH_TIMEOUT_MS
|
|
322
|
+
DEFAULT_FETCH_TIMEOUT_MS
|
|
318
323
|
).catch((e: any) => {
|
|
319
324
|
log('telemetry report failed:', e?.message || e);
|
|
320
325
|
});
|
|
@@ -366,7 +371,7 @@ export class Pushy {
|
|
|
366
371
|
{
|
|
367
372
|
message = error.message,
|
|
368
373
|
data,
|
|
369
|
-
}: { message?: string; data?: Record<string, string | number> } = {}
|
|
374
|
+
}: { message?: string; data?: Record<string, string | number> } = {}
|
|
370
375
|
) => {
|
|
371
376
|
this.emittedErrors.add(error);
|
|
372
377
|
this.report({
|
|
@@ -424,20 +429,20 @@ export class Pushy {
|
|
|
424
429
|
}
|
|
425
430
|
try {
|
|
426
431
|
const resp = await promiseAny(
|
|
427
|
-
server.queryUrls.map(queryUrl =>
|
|
428
|
-
fetchWithTimeout(queryUrl, {}, DEFAULT_FETCH_TIMEOUT_MS)
|
|
429
|
-
)
|
|
432
|
+
server.queryUrls.map((queryUrl) =>
|
|
433
|
+
fetchWithTimeout(queryUrl, {}, DEFAULT_FETCH_TIMEOUT_MS)
|
|
434
|
+
)
|
|
430
435
|
);
|
|
431
436
|
const remoteEndpoints = await resp.json();
|
|
432
437
|
log('fetch endpoints:', remoteEndpoints);
|
|
433
438
|
if (Array.isArray(remoteEndpoints)) {
|
|
434
439
|
return excludeConfiguredEndpoints(
|
|
435
440
|
dedupeEndpoints(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
441
|
+
remoteEndpoints.filter(
|
|
442
|
+
(endpoint): endpoint is string => typeof endpoint === 'string'
|
|
443
|
+
)
|
|
439
444
|
),
|
|
440
|
-
this.getConfiguredCheckEndpoints()
|
|
445
|
+
this.getConfiguredCheckEndpoints()
|
|
441
446
|
);
|
|
442
447
|
}
|
|
443
448
|
} catch (e) {
|
|
@@ -447,12 +452,12 @@ export class Pushy {
|
|
|
447
452
|
};
|
|
448
453
|
requestCheckResult = async (
|
|
449
454
|
endpoint: string,
|
|
450
|
-
fetchPayload: Parameters<typeof fetch>[1]
|
|
455
|
+
fetchPayload: Parameters<typeof fetch>[1]
|
|
451
456
|
) => {
|
|
452
457
|
const resp = await fetchWithTimeout(
|
|
453
458
|
this.getCheckUrl(endpoint),
|
|
454
459
|
fetchPayload,
|
|
455
|
-
DEFAULT_FETCH_TIMEOUT_MS
|
|
460
|
+
DEFAULT_FETCH_TIMEOUT_MS
|
|
456
461
|
);
|
|
457
462
|
|
|
458
463
|
if (!resp.ok) {
|
|
@@ -463,7 +468,7 @@ export class Pushy {
|
|
|
463
468
|
statusText: respText,
|
|
464
469
|
}),
|
|
465
470
|
'HTTP_STATUS',
|
|
466
|
-
{ extra: { status: resp.status } }
|
|
471
|
+
{ extra: { status: resp.status } }
|
|
467
472
|
);
|
|
468
473
|
}
|
|
469
474
|
|
|
@@ -473,7 +478,7 @@ export class Pushy {
|
|
|
473
478
|
const { endpoint, value } = await executeEndpointFallback<CheckResult>({
|
|
474
479
|
configuredEndpoints: this.getConfiguredCheckEndpoints(),
|
|
475
480
|
getRemoteEndpoints: this.getRemoteEndpoints,
|
|
476
|
-
tryEndpoint: async currentEndpoint => {
|
|
481
|
+
tryEndpoint: async (currentEndpoint) => {
|
|
477
482
|
try {
|
|
478
483
|
return await this.requestCheckResult(currentEndpoint, fetchPayload);
|
|
479
484
|
} catch (e) {
|
|
@@ -588,6 +593,11 @@ export class Pushy {
|
|
|
588
593
|
this.notifyAfterCheckUpdate({ status: 'skipped' });
|
|
589
594
|
return;
|
|
590
595
|
}
|
|
596
|
+
// 内容寻址的二进制身份,服务端据此精确判定 pdiff 适用性(取代 buildTime
|
|
597
|
+
// 启发式)。同步读 core 里已预取的值:还没算完就省略字段(服务端回退
|
|
598
|
+
// buildTime 启发式),下一次检查自然带上——绝不为它 await、拖慢或复杂化
|
|
599
|
+
// 检查流程。
|
|
600
|
+
const bundleHash = __DEV__ ? '' : getBundleHash();
|
|
591
601
|
const now = Date.now();
|
|
592
602
|
if (
|
|
593
603
|
this.lastRespJson &&
|
|
@@ -617,6 +627,7 @@ export class Pushy {
|
|
|
617
627
|
cInfo,
|
|
618
628
|
// 可消费的 diff 轨道版本(2 = hdiffv2 轨道),服务端据此门控下发
|
|
619
629
|
...(supportedDiffVersion ? { diffV: supportedDiffVersion } : {}),
|
|
630
|
+
...(bundleHash ? { bundleHash } : {}),
|
|
620
631
|
...extra,
|
|
621
632
|
};
|
|
622
633
|
if (__DEV__) {
|
|
@@ -648,6 +659,16 @@ export class Pushy {
|
|
|
648
659
|
|
|
649
660
|
log('checking result:', result);
|
|
650
661
|
|
|
662
|
+
if (result?.bundleStatus === 'unknownBundle') {
|
|
663
|
+
// 服务端判定当前二进制内嵌 bundle 未注册,增量已被降级为全量。只面向
|
|
664
|
+
// 开发者(日志 + 遥测,控制台聚合是主渠道);终端用户无感知。
|
|
665
|
+
warn(this.t('warn_unknown_bundle'));
|
|
666
|
+
this.report({
|
|
667
|
+
type: 'bundleMismatch',
|
|
668
|
+
data: bundleHash ? { bundleHash } : {},
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
651
672
|
this.notifyAfterCheckUpdate({ status: 'completed', result });
|
|
652
673
|
return result;
|
|
653
674
|
} catch (e: any) {
|
|
@@ -666,7 +687,7 @@ export class Pushy {
|
|
|
666
687
|
};
|
|
667
688
|
downloadUpdate = async (
|
|
668
689
|
updateInfo: CheckResult,
|
|
669
|
-
onDownloadProgress?: (data: ProgressData) => void
|
|
690
|
+
onDownloadProgress?: (data: ProgressData) => void
|
|
670
691
|
) => {
|
|
671
692
|
const { hash } = updateInfo;
|
|
672
693
|
if (
|
|
@@ -713,7 +734,7 @@ export class Pushy {
|
|
|
713
734
|
};
|
|
714
735
|
private performDownload = async (
|
|
715
736
|
updateInfo: CheckResult,
|
|
716
|
-
onDownloadProgress?: (data: ProgressData) => void
|
|
737
|
+
onDownloadProgress?: (data: ProgressData) => void
|
|
717
738
|
) => {
|
|
718
739
|
const {
|
|
719
740
|
hash,
|
|
@@ -746,7 +767,9 @@ export class Pushy {
|
|
|
746
767
|
...data,
|
|
747
768
|
progress: computeProgress(data.received, data.total),
|
|
748
769
|
};
|
|
749
|
-
callbacks.forEach(callback =>
|
|
770
|
+
callbacks.forEach((callback) => {
|
|
771
|
+
callback(payload);
|
|
772
|
+
});
|
|
750
773
|
};
|
|
751
774
|
const onNativeProgress = (progressData: ProgressData) => {
|
|
752
775
|
if (progressData.hash === hash) {
|
|
@@ -757,12 +780,12 @@ export class Pushy {
|
|
|
757
780
|
if (Platform.OS === 'harmony') {
|
|
758
781
|
sharedState.progressHandlers[hash] = DeviceEventEmitter.addListener(
|
|
759
782
|
'RCTPushyDownloadProgress',
|
|
760
|
-
onNativeProgress
|
|
783
|
+
onNativeProgress
|
|
761
784
|
);
|
|
762
785
|
} else {
|
|
763
786
|
sharedState.progressHandlers[hash] = pushyNativeEventEmitter.addListener(
|
|
764
787
|
'RCTPushyDownloadProgress',
|
|
765
|
-
onNativeProgress
|
|
788
|
+
onNativeProgress
|
|
766
789
|
);
|
|
767
790
|
}
|
|
768
791
|
const maxRetries = Math.max(0, Math.floor(this.options.maxRetries ?? 3));
|
|
@@ -778,7 +801,10 @@ export class Pushy {
|
|
|
778
801
|
type DownloadStrategy = {
|
|
779
802
|
name: string;
|
|
780
803
|
candidate: string | undefined;
|
|
781
|
-
errorKey:
|
|
804
|
+
errorKey:
|
|
805
|
+
| 'error_diff_failed'
|
|
806
|
+
| 'error_pdiff_failed'
|
|
807
|
+
| 'error_full_patch_failed';
|
|
782
808
|
skipInDev: boolean;
|
|
783
809
|
devNoopWhenNoUrl: boolean;
|
|
784
810
|
run: (url: string) => Promise<void>;
|
|
@@ -790,7 +816,7 @@ export class Pushy {
|
|
|
790
816
|
errorKey: 'error_diff_failed',
|
|
791
817
|
skipInDev: true,
|
|
792
818
|
devNoopWhenNoUrl: false,
|
|
793
|
-
run: url =>
|
|
819
|
+
run: (url) =>
|
|
794
820
|
PushyModule.downloadPatchFromPpk({
|
|
795
821
|
updateUrl: url,
|
|
796
822
|
hash,
|
|
@@ -803,7 +829,7 @@ export class Pushy {
|
|
|
803
829
|
errorKey: 'error_pdiff_failed',
|
|
804
830
|
skipInDev: true,
|
|
805
831
|
devNoopWhenNoUrl: false,
|
|
806
|
-
run: url =>
|
|
832
|
+
run: (url) =>
|
|
807
833
|
PushyModule.downloadPatchFromPackage({
|
|
808
834
|
updateUrl: url,
|
|
809
835
|
hash,
|
|
@@ -815,7 +841,7 @@ export class Pushy {
|
|
|
815
841
|
errorKey: 'error_full_patch_failed',
|
|
816
842
|
skipInDev: false,
|
|
817
843
|
devNoopWhenNoUrl: true,
|
|
818
|
-
run: url =>
|
|
844
|
+
run: (url) =>
|
|
819
845
|
PushyModule.downloadFullUpdate({
|
|
820
846
|
updateUrl: url,
|
|
821
847
|
hash,
|
|
@@ -827,7 +853,7 @@ export class Pushy {
|
|
|
827
853
|
if (attempt > 0) {
|
|
828
854
|
const backoffMs = Math.min(1000 * 2 ** (attempt - 1), 10000);
|
|
829
855
|
log(`retry attempt ${attempt}/${maxRetries}, waiting ${backoffMs}ms`);
|
|
830
|
-
await new Promise(r => setTimeout(r, backoffMs));
|
|
856
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
831
857
|
errorMessages.length = 0;
|
|
832
858
|
lastError = undefined;
|
|
833
859
|
succeeded = '';
|
|
@@ -860,7 +886,7 @@ export class Pushy {
|
|
|
860
886
|
lastError = new UpdateError(
|
|
861
887
|
errorMessage,
|
|
862
888
|
asUpdateErrorCode(e?.code) ?? 'DOWNLOAD_FAILED',
|
|
863
|
-
{ cause: e }
|
|
889
|
+
{ cause: e }
|
|
864
890
|
);
|
|
865
891
|
log(errorMessage);
|
|
866
892
|
}
|
|
@@ -926,7 +952,7 @@ export class Pushy {
|
|
|
926
952
|
};
|
|
927
953
|
downloadAndInstallApk = async (
|
|
928
954
|
url: string,
|
|
929
|
-
onDownloadProgress?: (data: ProgressData) => void
|
|
955
|
+
onDownloadProgress?: (data: ProgressData) => void
|
|
930
956
|
) => {
|
|
931
957
|
if (Platform.OS !== 'android') {
|
|
932
958
|
return;
|
|
@@ -937,7 +963,7 @@ export class Pushy {
|
|
|
937
963
|
if (sharedState.apkStatus === 'downloaded') {
|
|
938
964
|
const err = new UpdateError(
|
|
939
965
|
this.t('error_apk_pending_install'),
|
|
940
|
-
'APK_INSTALL_PENDING'
|
|
966
|
+
'APK_INSTALL_PENDING'
|
|
941
967
|
);
|
|
942
968
|
this.emitError(err, 'errorInstallApk');
|
|
943
969
|
this.throwIfEnabled(err);
|
|
@@ -946,12 +972,12 @@ export class Pushy {
|
|
|
946
972
|
if (Platform.Version <= 23) {
|
|
947
973
|
try {
|
|
948
974
|
const granted = await PermissionsAndroid.request(
|
|
949
|
-
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
|
|
975
|
+
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
|
|
950
976
|
);
|
|
951
977
|
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
|
|
952
978
|
const err = new UpdateError(
|
|
953
979
|
this.t('error_storage_permission_rejected'),
|
|
954
|
-
'STORAGE_PERMISSION_REJECTED'
|
|
980
|
+
'STORAGE_PERMISSION_REJECTED'
|
|
955
981
|
);
|
|
956
982
|
this.emitError(err, 'rejectStoragePermission');
|
|
957
983
|
this.throwIfEnabled(err);
|
|
@@ -978,7 +1004,7 @@ export class Pushy {
|
|
|
978
1004
|
if (progressData.hash === progressKey) {
|
|
979
1005
|
onDownloadProgress(progressData);
|
|
980
1006
|
}
|
|
981
|
-
}
|
|
1007
|
+
}
|
|
982
1008
|
);
|
|
983
1009
|
}
|
|
984
1010
|
try {
|
|
@@ -1045,7 +1071,7 @@ export class Pushy {
|
|
|
1045
1071
|
// native module predates this method.
|
|
1046
1072
|
const err = new UpdateError(
|
|
1047
1073
|
this.t('error_reset_not_supported'),
|
|
1048
|
-
'RESET_FAILED'
|
|
1074
|
+
'RESET_FAILED'
|
|
1049
1075
|
);
|
|
1050
1076
|
this.emitError(err, 'errorReset');
|
|
1051
1077
|
this.throwIfEnabled(err);
|
package/src/context.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createContext, useContext, useMemo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { Pushy, Cresc } from './client';
|
|
2
|
+
import type { Cresc, Pushy } from './client';
|
|
4
3
|
import i18n from './i18n';
|
|
4
|
+
import type { CheckResult, ProgressData } from './type';
|
|
5
5
|
|
|
6
6
|
const noop = () => {};
|
|
7
|
-
const asyncNoop = () => Promise.resolve();
|
|
7
|
+
const asyncNoop = () => Promise.resolve(undefined);
|
|
8
8
|
|
|
9
9
|
export const defaultContext = {
|
|
10
10
|
checkUpdate: asyncNoop,
|
|
@@ -24,12 +24,12 @@ export const defaultContext = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export const UpdateContext = createContext<{
|
|
27
|
-
checkUpdate: () => Promise<
|
|
27
|
+
checkUpdate: () => Promise<undefined | CheckResult>;
|
|
28
28
|
switchVersion: () => Promise<void>;
|
|
29
29
|
switchVersionLater: () => Promise<void>;
|
|
30
30
|
markSuccess: () => void;
|
|
31
31
|
dismissError: () => void;
|
|
32
|
-
downloadUpdate: () => Promise<boolean |
|
|
32
|
+
downloadUpdate: () => Promise<boolean | undefined>;
|
|
33
33
|
downloadAndInstallApk: (url: string) => Promise<void>;
|
|
34
34
|
// @deprecated use currentVersionInfo instead
|
|
35
35
|
getCurrentVersionInfo: () => Promise<{
|
|
@@ -46,7 +46,7 @@ export const UpdateContext = createContext<{
|
|
|
46
46
|
restartApp: () => Promise<void>;
|
|
47
47
|
resetToPackagedBundle: (options?: {
|
|
48
48
|
restart?: boolean;
|
|
49
|
-
}) => Promise<boolean |
|
|
49
|
+
}) => Promise<boolean | undefined>;
|
|
50
50
|
currentHash: string;
|
|
51
51
|
packageVersion: string;
|
|
52
52
|
client?: Pushy | Cresc;
|
|
@@ -58,7 +58,7 @@ export const UpdateContext = createContext<{
|
|
|
58
58
|
// otherwise every tick would re-render all useUpdate() consumers even when
|
|
59
59
|
// they never read progress.
|
|
60
60
|
export const ProgressContext = createContext<ProgressData | undefined>(
|
|
61
|
-
undefined
|
|
61
|
+
undefined
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
/**
|
package/src/core.ts
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
1
|
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
2
|
-
import i18n from './i18n';
|
|
3
2
|
import { UpdateError } from './error';
|
|
3
|
+
import i18n from './i18n';
|
|
4
4
|
import { emptyModule, error, log } from './utils';
|
|
5
5
|
|
|
6
6
|
/* eslint-disable @react-native/no-deep-imports */
|
|
7
7
|
const {
|
|
8
|
-
|
|
8
|
+
version: v,
|
|
9
9
|
} = require('react-native/Libraries/Core/ReactNativeVersion');
|
|
10
10
|
const RNVersion = `${v.major}.${v.minor}.${v.patch}`;
|
|
11
11
|
const isTurboModuleEnabled =
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// https://github.com/facebook/react-native/pull/48362
|
|
13
|
+
(global as any).__turboModuleProxy || (global as any).RN$Bridgeless;
|
|
14
14
|
|
|
15
15
|
export const PushyModule =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
Platform.OS === 'web'
|
|
17
|
+
? emptyModule
|
|
18
|
+
: isTurboModuleEnabled
|
|
19
|
+
? require('./NativePushy').default
|
|
20
|
+
: NativeModules.Pushy;
|
|
21
21
|
|
|
22
22
|
export const UpdateModule = PushyModule;
|
|
23
23
|
|
|
24
24
|
if (!PushyModule) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
throw new UpdateError(
|
|
26
|
+
'Failed to load react-native-update native module, please try to recompile',
|
|
27
|
+
'MODULE_NOT_LOADED'
|
|
28
|
+
);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const PushyConstants = isTurboModuleEnabled
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
? PushyModule.getConstants()
|
|
33
|
+
: PushyModule;
|
|
34
34
|
|
|
35
35
|
export const downloadRootDir: string = PushyConstants.downloadRootDir;
|
|
36
36
|
export const packageVersion: string = PushyConstants.packageVersion;
|
|
37
37
|
export const currentVersion: string = PushyConstants.currentVersion;
|
|
38
38
|
|
|
39
39
|
export function setLocalHashInfo(hash: string, info: Record<string, any>) {
|
|
40
|
-
|
|
40
|
+
PushyModule.setLocalHashInfo(hash, JSON.stringify(info));
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const currentVersionInfoString: string = PushyConstants.currentVersionInfo;
|
|
44
44
|
let _currentVersionInfo: Record<string, any> = {};
|
|
45
45
|
let isDebugChannel = false;
|
|
46
46
|
if (currentVersionInfoString) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
try {
|
|
48
|
+
_currentVersionInfo = JSON.parse(currentVersionInfoString);
|
|
49
|
+
if (_currentVersionInfo.debugChannel) {
|
|
50
|
+
isDebugChannel = true;
|
|
51
|
+
delete _currentVersionInfo.debugChannel;
|
|
52
|
+
setLocalHashInfo(currentVersion, _currentVersionInfo);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
error(
|
|
56
|
+
i18n.t('error_parse_version_info', { info: currentVersionInfoString })
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
59
|
}
|
|
60
60
|
export const currentVersionInfo = _currentVersionInfo;
|
|
61
61
|
|
|
@@ -68,46 +68,73 @@ export const buildTime: string = PushyConstants.buildTime;
|
|
|
68
68
|
// 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道:HBC 变换 +
|
|
69
69
|
// 流式容器);旧原生无此常量时为 0(不上报,服务端只发 baseline)
|
|
70
70
|
export const supportedDiffVersion: number =
|
|
71
|
-
|
|
71
|
+
PushyConstants.supportedDiffVersion || 0;
|
|
72
72
|
let uuid = PushyConstants.uuid;
|
|
73
73
|
|
|
74
|
+
// bundleHash = sha256 of the JS bundle embedded in the binary (the pdiff
|
|
75
|
+
// source). Content identity for the server's pdiff-applicability check,
|
|
76
|
+
// replacing the buildTime heuristic. Natively lazy-computed and cached;
|
|
77
|
+
// prefetched fire-and-forget at module load so the value is usually settled
|
|
78
|
+
// before the first checkUpdate builds its body. Until then (first install,
|
|
79
|
+
// slow device) getBundleHash returns '' and the field is simply omitted — the
|
|
80
|
+
// server falls back to the buildTime heuristic and the next check carries it.
|
|
81
|
+
// Deliberately synchronous at read time: checkUpdate must not await anything
|
|
82
|
+
// between its dedup window and the fetch, and a hash is never worth delaying
|
|
83
|
+
// a check for.
|
|
84
|
+
let bundleHash = '';
|
|
85
|
+
// The JS layer can arrive via hot update onto an older binary whose native
|
|
86
|
+
// module predates the method, hence the feature detect. On web PushyModule is
|
|
87
|
+
// a noop Proxy and the call settles to ''.
|
|
88
|
+
if (typeof PushyModule.getBundleHash === 'function') {
|
|
89
|
+
Promise.resolve(PushyModule.getBundleHash())
|
|
90
|
+
.then((hash: unknown) => {
|
|
91
|
+
bundleHash = String(hash || '');
|
|
92
|
+
})
|
|
93
|
+
.catch((e: any) => {
|
|
94
|
+
log('getBundleHash failed:', e?.message || e);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** '' while unknown (not yet computed, debug build, no embedded bundle, older native). */
|
|
99
|
+
export const getBundleHash = (): string => bundleHash;
|
|
100
|
+
|
|
74
101
|
async function getLocalHashInfo(hash: string) {
|
|
75
|
-
|
|
102
|
+
return JSON.parse(await PushyModule.getLocalHashInfo(hash));
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
// @deprecated use currentVersionInfo instead
|
|
79
106
|
export async function getCurrentVersionInfo(): Promise<{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
107
|
+
name?: string;
|
|
108
|
+
description?: string;
|
|
109
|
+
metaInfo?: string;
|
|
83
110
|
}> {
|
|
84
|
-
|
|
111
|
+
return currentVersion ? (await getLocalHashInfo(currentVersion)) || {} : {};
|
|
85
112
|
}
|
|
86
113
|
|
|
87
114
|
export const pushyNativeEventEmitter = new NativeEventEmitter(PushyModule);
|
|
88
115
|
|
|
89
116
|
if (!uuid) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
117
|
+
uuid = require('nanoid/non-secure').nanoid();
|
|
118
|
+
// If persisting fails the uuid drifts on every launch, which skews gray
|
|
119
|
+
// release bucketing and inflates stats — log it instead of failing silently.
|
|
120
|
+
Promise.resolve(PushyModule.setUuid(uuid)).catch((e: any) => {
|
|
121
|
+
log('setUuid error:', e?.message || e);
|
|
122
|
+
});
|
|
96
123
|
}
|
|
97
124
|
|
|
98
125
|
export const cInfo = {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
126
|
+
rnu: require('../package.json').version,
|
|
127
|
+
rn: RNVersion,
|
|
128
|
+
os: `${Platform.OS} ${Platform.Version}`,
|
|
129
|
+
uuid,
|
|
103
130
|
};
|
|
104
131
|
|
|
105
132
|
log('bootup status', {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
133
|
+
packageVersion,
|
|
134
|
+
currentVersion,
|
|
135
|
+
currentVersionInfo,
|
|
136
|
+
isFirstTime,
|
|
137
|
+
isFirstTimeDebug,
|
|
138
|
+
isDebugChannel,
|
|
139
|
+
cInfo,
|
|
113
140
|
});
|