react-native-update 10.44.0 → 10.46.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/android/jni/Android.mk +0 -1
- 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/DownloadTask.java +10 -1
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +24 -0
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +2 -1
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +2 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +19 -4
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +13 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +15 -15
- package/cpp/patch_core/error_codes.h +43 -0
- package/cpp/patch_core/patch_core.cpp +16 -1
- package/cpp/patch_core/patch_core.h +6 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +77 -0
- package/harmony/pushy/src/main/cpp/pushy.cpp +1 -93
- package/harmony/pushy/src/main/ets/DownloadTask.ts +13 -10
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +0 -4
- package/harmony/pushy/src/main/ets/UpdateContext.ts +20 -2
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +223 -132
- package/ios/RCTPushy/RCTPushyDownloader.mm +22 -1
- package/package.json +7 -3
- package/src/client.ts +151 -53
- package/src/context.ts +21 -7
- package/src/core.ts +8 -2
- package/src/endpoint.ts +4 -2
- package/src/error.ts +68 -0
- package/src/index.ts +10 -1
- package/src/locales/en.ts +5 -1
- package/src/locales/zh.ts +3 -1
- package/src/provider.tsx +101 -54
- package/src/type.ts +6 -0
- package/src/utils.ts +56 -16
- package/android/jni/DownloadTask.c +0 -56
- package/android/jni/cn_reactnative_modules_update_DownloadTask.h +0 -21
package/src/client.ts
CHANGED
|
@@ -39,8 +39,19 @@ import {
|
|
|
39
39
|
testUrls,
|
|
40
40
|
} from './utils';
|
|
41
41
|
import i18n from './i18n';
|
|
42
|
+
import { toUpdateError, UpdateError, UpdateErrorCode } from './error';
|
|
42
43
|
import { dedupeEndpoints, executeEndpointFallback } from './endpoint';
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Receives every error the client reports, alongside the report event type.
|
|
47
|
+
* The UpdateProvider subscribes to surface errors as lastError/Alert; user
|
|
48
|
+
* code can subscribe too. Listeners run before any throwError rethrow.
|
|
49
|
+
*/
|
|
50
|
+
export type UpdateErrorListener = (
|
|
51
|
+
error: UpdateError,
|
|
52
|
+
eventType: EventType,
|
|
53
|
+
) => void;
|
|
54
|
+
|
|
44
55
|
const SERVER_PRESETS = {
|
|
45
56
|
// cn
|
|
46
57
|
Pushy: {
|
|
@@ -142,7 +153,10 @@ export class Pushy {
|
|
|
142
153
|
|
|
143
154
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
144
155
|
if (!options.appKey) {
|
|
145
|
-
throw
|
|
156
|
+
throw new UpdateError(
|
|
157
|
+
i18n.t('error_appkey_required'),
|
|
158
|
+
'APPKEY_REQUIRED',
|
|
159
|
+
);
|
|
146
160
|
}
|
|
147
161
|
}
|
|
148
162
|
|
|
@@ -184,36 +198,88 @@ export class Pushy {
|
|
|
184
198
|
report = async ({
|
|
185
199
|
type,
|
|
186
200
|
message = '',
|
|
201
|
+
code,
|
|
187
202
|
data = {},
|
|
188
203
|
}: {
|
|
189
204
|
type: EventType;
|
|
190
205
|
message?: string;
|
|
206
|
+
code?: UpdateErrorCode;
|
|
191
207
|
data?: Record<string, string | number>;
|
|
192
208
|
}) => {
|
|
193
|
-
log(`${type} ${message}`);
|
|
194
|
-
|
|
209
|
+
log(`${type} ${code ? `[${code}] ` : ''}${message}`);
|
|
210
|
+
if (this.options.logger === noop) {
|
|
211
|
+
// Wait briefly for a logger to arrive via setOptions (e.g. the rollback
|
|
212
|
+
// report fires in the constructor before the user configures one), but
|
|
213
|
+
// give up after a bound instead of retaining the closure forever when
|
|
214
|
+
// no logger is ever provided.
|
|
215
|
+
await Promise.race([
|
|
216
|
+
this.loggerPromise.promise,
|
|
217
|
+
new Promise(resolve => setTimeout(resolve, 10 * 1000)),
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
195
220
|
const { logger = noop, appKey } = this.options;
|
|
196
221
|
const overridePackageVersion = this.options.overridePackageVersion;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
222
|
+
try {
|
|
223
|
+
logger({
|
|
224
|
+
type,
|
|
225
|
+
data: {
|
|
226
|
+
appKey,
|
|
227
|
+
currentVersion,
|
|
228
|
+
cInfo,
|
|
229
|
+
packageVersion,
|
|
230
|
+
overridePackageVersion,
|
|
231
|
+
buildTime,
|
|
232
|
+
message,
|
|
233
|
+
code,
|
|
234
|
+
...currentVersionInfo,
|
|
235
|
+
...data,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
} catch (e: any) {
|
|
239
|
+
// A user-provided logger must never break the update flow, and report()
|
|
240
|
+
// calls are fire-and-forget so a throw here would be an unhandled
|
|
241
|
+
// rejection.
|
|
242
|
+
log('logger error:', e?.message || e);
|
|
243
|
+
}
|
|
211
244
|
};
|
|
212
245
|
throwIfEnabled = (e: Error) => {
|
|
213
246
|
if (this.options.throwError) {
|
|
214
247
|
throw e;
|
|
215
248
|
}
|
|
216
249
|
};
|
|
250
|
+
private errorListeners = new Set<UpdateErrorListener>();
|
|
251
|
+
/**
|
|
252
|
+
* Subscribe to every error the client reports (regardless of throwError).
|
|
253
|
+
* Returns an unsubscribe function.
|
|
254
|
+
*/
|
|
255
|
+
onError = (listener: UpdateErrorListener) => {
|
|
256
|
+
this.errorListeners.add(listener);
|
|
257
|
+
return () => {
|
|
258
|
+
this.errorListeners.delete(listener);
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Single exit point for errors: reports to the logger (with the stable
|
|
263
|
+
* code) and notifies onError listeners. Whether to also throw stays with
|
|
264
|
+
* the caller (throwIfEnabled or an unconditional rethrow).
|
|
265
|
+
*/
|
|
266
|
+
private emitError = (
|
|
267
|
+
error: UpdateError,
|
|
268
|
+
type: EventType,
|
|
269
|
+
{
|
|
270
|
+
message = error.message,
|
|
271
|
+
data,
|
|
272
|
+
}: { message?: string; data?: Record<string, string | number> } = {},
|
|
273
|
+
) => {
|
|
274
|
+
this.report({ type, message, code: error.code, data });
|
|
275
|
+
for (const listener of this.errorListeners) {
|
|
276
|
+
try {
|
|
277
|
+
listener(error, type);
|
|
278
|
+
} catch (e: any) {
|
|
279
|
+
log('onError listener error:', e?.message || e);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
217
283
|
notifyAfterCheckUpdate = (state: UpdateCheckState) => {
|
|
218
284
|
const { afterCheckUpdate } = this.options;
|
|
219
285
|
if (!afterCheckUpdate) {
|
|
@@ -286,11 +352,13 @@ export class Pushy {
|
|
|
286
352
|
|
|
287
353
|
if (!resp.ok) {
|
|
288
354
|
const respText = await resp.text();
|
|
289
|
-
throw
|
|
355
|
+
throw new UpdateError(
|
|
290
356
|
this.t('error_http_status', {
|
|
291
357
|
status: resp.status,
|
|
292
358
|
statusText: respText,
|
|
293
359
|
}),
|
|
360
|
+
'HTTP_STATUS',
|
|
361
|
+
{ extra: { status: resp.status } },
|
|
294
362
|
);
|
|
295
363
|
}
|
|
296
364
|
|
|
@@ -332,7 +400,13 @@ export class Pushy {
|
|
|
332
400
|
if (sharedState.marked || __DEV__ || !isFirstTime) {
|
|
333
401
|
return;
|
|
334
402
|
}
|
|
335
|
-
|
|
403
|
+
try {
|
|
404
|
+
await Promise.resolve(PushyModule.markSuccess());
|
|
405
|
+
} catch (e) {
|
|
406
|
+
const err = toUpdateError(e, 'MARK_SUCCESS_FAILED');
|
|
407
|
+
this.emitError(err, 'errorMarkSuccess');
|
|
408
|
+
throw err;
|
|
409
|
+
}
|
|
336
410
|
sharedState.marked = true;
|
|
337
411
|
this.report({ type: 'markSuccess' });
|
|
338
412
|
};
|
|
@@ -350,7 +424,11 @@ export class Pushy {
|
|
|
350
424
|
}
|
|
351
425
|
} catch (e) {
|
|
352
426
|
sharedState.applyingUpdate = false;
|
|
353
|
-
|
|
427
|
+
const err = toUpdateError(e, 'SWITCH_VERSION_FAILED');
|
|
428
|
+
this.emitError(err, 'errorSwitchVersion', {
|
|
429
|
+
data: { newVersion: hash },
|
|
430
|
+
});
|
|
431
|
+
throw err;
|
|
354
432
|
}
|
|
355
433
|
try {
|
|
356
434
|
return await PushyModule.reloadUpdate({ hash });
|
|
@@ -358,7 +436,11 @@ export class Pushy {
|
|
|
358
436
|
// reloadUpdate can reject (e.g. bundle missing); reset the flag so a
|
|
359
437
|
// later retry is not permanently blocked by a stuck applyingUpdate.
|
|
360
438
|
sharedState.applyingUpdate = false;
|
|
361
|
-
|
|
439
|
+
const err = toUpdateError(e, 'SWITCH_VERSION_FAILED');
|
|
440
|
+
this.emitError(err, 'errorSwitchVersion', {
|
|
441
|
+
data: { newVersion: hash },
|
|
442
|
+
});
|
|
443
|
+
throw err;
|
|
362
444
|
}
|
|
363
445
|
}
|
|
364
446
|
};
|
|
@@ -369,7 +451,15 @@ export class Pushy {
|
|
|
369
451
|
}
|
|
370
452
|
if (assertHash(hash)) {
|
|
371
453
|
log(`switchVersionLater: ${hash}`);
|
|
372
|
-
|
|
454
|
+
try {
|
|
455
|
+
return await PushyModule.setNeedUpdate({ hash });
|
|
456
|
+
} catch (e) {
|
|
457
|
+
const err = toUpdateError(e, 'SWITCH_VERSION_FAILED');
|
|
458
|
+
this.emitError(err, 'errorSwitchVersion', {
|
|
459
|
+
data: { newVersion: hash },
|
|
460
|
+
});
|
|
461
|
+
throw err;
|
|
462
|
+
}
|
|
373
463
|
}
|
|
374
464
|
};
|
|
375
465
|
checkUpdate = async (extra?: Record<string, any>) => {
|
|
@@ -440,31 +530,18 @@ export class Pushy {
|
|
|
440
530
|
return result;
|
|
441
531
|
} catch (e: any) {
|
|
442
532
|
this.lastRespJson = previousRespJson;
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
type: 'errorChecking',
|
|
447
|
-
message: errorMessage,
|
|
533
|
+
const err = toUpdateError(e, 'CHECK_FAILED');
|
|
534
|
+
this.emitError(err, 'errorChecking', {
|
|
535
|
+
message: err.message || this.t('error_cannot_connect_server'),
|
|
448
536
|
});
|
|
449
|
-
this.notifyAfterCheckUpdate({ status: 'error', error:
|
|
450
|
-
this.throwIfEnabled(
|
|
537
|
+
this.notifyAfterCheckUpdate({ status: 'error', error: err });
|
|
538
|
+
this.throwIfEnabled(err);
|
|
451
539
|
// Fall back to the previous successful response if we have one; otherwise
|
|
452
540
|
// return undefined so callers can distinguish "check failed" from a real
|
|
453
541
|
// empty result and avoid overwriting the last good updateInfo.
|
|
454
542
|
return previousRespJson ? await previousRespJson : undefined;
|
|
455
543
|
}
|
|
456
544
|
};
|
|
457
|
-
getBackupEndpoints = async () => {
|
|
458
|
-
const { server } = this.options;
|
|
459
|
-
if (!server) {
|
|
460
|
-
return [];
|
|
461
|
-
}
|
|
462
|
-
const remoteEndpoints = await this.getRemoteEndpoints();
|
|
463
|
-
return excludeConfiguredEndpoints(
|
|
464
|
-
dedupeEndpoints(remoteEndpoints),
|
|
465
|
-
this.getConfiguredCheckEndpoints(),
|
|
466
|
-
);
|
|
467
|
-
};
|
|
468
545
|
downloadUpdate = async (
|
|
469
546
|
updateInfo: CheckResult,
|
|
470
547
|
onDownloadProgress?: (data: ProgressData) => void,
|
|
@@ -664,14 +741,22 @@ export class Pushy {
|
|
|
664
741
|
delete sharedState.progressHandlers[hash];
|
|
665
742
|
}
|
|
666
743
|
if (!succeeded) {
|
|
744
|
+
const message = errorMessages.join(';');
|
|
745
|
+
if (lastError) {
|
|
746
|
+
const err = toUpdateError(lastError, 'DOWNLOAD_FAILED');
|
|
747
|
+
this.emitError(err, 'errorUpdate', {
|
|
748
|
+
message,
|
|
749
|
+
data: { newVersion: hash },
|
|
750
|
+
});
|
|
751
|
+
throw err;
|
|
752
|
+
}
|
|
753
|
+
// No download URL was even attempted (e.g. dev without a full URL):
|
|
754
|
+
// report for diagnostics but there is no error object to surface.
|
|
667
755
|
this.report({
|
|
668
756
|
type: 'errorUpdate',
|
|
669
757
|
data: { newVersion: hash },
|
|
670
|
-
message
|
|
758
|
+
message,
|
|
671
759
|
});
|
|
672
|
-
if (lastError) {
|
|
673
|
-
throw lastError;
|
|
674
|
-
}
|
|
675
760
|
return;
|
|
676
761
|
} else {
|
|
677
762
|
const duration = Date.now() - patchStartTime;
|
|
@@ -712,8 +797,12 @@ export class Pushy {
|
|
|
712
797
|
return;
|
|
713
798
|
}
|
|
714
799
|
if (sharedState.apkStatus === 'downloaded') {
|
|
715
|
-
|
|
716
|
-
|
|
800
|
+
const err = new UpdateError(
|
|
801
|
+
this.t('error_apk_pending_install'),
|
|
802
|
+
'APK_INSTALL_PENDING',
|
|
803
|
+
);
|
|
804
|
+
this.emitError(err, 'errorInstallApk');
|
|
805
|
+
this.throwIfEnabled(err);
|
|
717
806
|
return;
|
|
718
807
|
}
|
|
719
808
|
if (Platform.Version <= 23) {
|
|
@@ -722,13 +811,18 @@ export class Pushy {
|
|
|
722
811
|
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
|
|
723
812
|
);
|
|
724
813
|
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
|
|
725
|
-
|
|
726
|
-
|
|
814
|
+
const err = new UpdateError(
|
|
815
|
+
this.t('error_storage_permission_rejected'),
|
|
816
|
+
'STORAGE_PERMISSION_REJECTED',
|
|
817
|
+
);
|
|
818
|
+
this.emitError(err, 'rejectStoragePermission');
|
|
819
|
+
this.throwIfEnabled(err);
|
|
727
820
|
return;
|
|
728
821
|
}
|
|
729
822
|
} catch (e: any) {
|
|
730
|
-
|
|
731
|
-
this.
|
|
823
|
+
const err = toUpdateError(e, 'STORAGE_PERMISSION_ERROR');
|
|
824
|
+
this.emitError(err, 'errorStoragePermission');
|
|
825
|
+
this.throwIfEnabled(err);
|
|
732
826
|
return;
|
|
733
827
|
}
|
|
734
828
|
}
|
|
@@ -756,10 +850,14 @@ export class Pushy {
|
|
|
756
850
|
hash: progressKey,
|
|
757
851
|
});
|
|
758
852
|
sharedState.apkStatus = 'downloaded';
|
|
759
|
-
} catch {
|
|
853
|
+
} catch (e) {
|
|
760
854
|
sharedState.apkStatus = null;
|
|
761
|
-
|
|
762
|
-
|
|
855
|
+
// Keep the native error (message/stack) instead of discarding it.
|
|
856
|
+
const err = toUpdateError(e, 'APK_DOWNLOAD_FAILED');
|
|
857
|
+
this.emitError(err, 'errorDownloadAndInstallApk', {
|
|
858
|
+
message: err.message || this.t('error_apk_download_failed'),
|
|
859
|
+
});
|
|
860
|
+
this.throwIfEnabled(err);
|
|
763
861
|
} finally {
|
|
764
862
|
if (sharedState.progressHandlers[progressKey]) {
|
|
765
863
|
sharedState.progressHandlers[progressKey].remove();
|
package/src/context.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react';
|
|
1
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
2
2
|
import { CheckResult, ProgressData } from './type';
|
|
3
3
|
import { Pushy, Cresc } from './client';
|
|
4
4
|
import i18n from './i18n';
|
|
@@ -46,21 +46,35 @@ export const UpdateContext = createContext<{
|
|
|
46
46
|
currentHash: string;
|
|
47
47
|
packageVersion: string;
|
|
48
48
|
client?: Pushy | Cresc;
|
|
49
|
-
progress?: ProgressData;
|
|
50
49
|
updateInfo?: CheckResult;
|
|
51
50
|
lastError?: Error;
|
|
52
51
|
}>(defaultContext);
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
// Download progress ticks at high frequency, so it lives in its own context;
|
|
54
|
+
// otherwise every tick would re-render all useUpdate() consumers even when
|
|
55
|
+
// they never read progress.
|
|
56
|
+
export const ProgressContext = createContext<ProgressData | undefined>(
|
|
57
|
+
undefined,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Subscribe to download progress only. Components that render a progress bar
|
|
62
|
+
* should prefer this over useUpdate() so the rest of the tree is not
|
|
63
|
+
* re-rendered on every progress event.
|
|
64
|
+
*/
|
|
65
|
+
export const useUpdateProgress = () => useContext(ProgressContext);
|
|
66
|
+
|
|
67
|
+
export const useUpdate = () => {
|
|
55
68
|
const context = useContext(UpdateContext);
|
|
69
|
+
const progress = useContext(ProgressContext);
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
if (__DEV__ && !context.client) {
|
|
72
|
+
// 检查是否在 UpdateProvider 内部使用
|
|
59
73
|
throw new Error(i18n.t('error_use_update_outside_provider'));
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
return context;
|
|
63
|
-
}
|
|
76
|
+
return useMemo(() => ({ ...context, progress }), [context, progress]);
|
|
77
|
+
};
|
|
64
78
|
|
|
65
79
|
/** @deprecated Please use `useUpdate` instead */
|
|
66
80
|
export const usePushy = useUpdate;
|
package/src/core.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
2
2
|
import i18n from './i18n';
|
|
3
|
+
import { UpdateError } from './error';
|
|
3
4
|
import { emptyModule, error, log } from './utils';
|
|
4
5
|
|
|
5
6
|
/* eslint-disable @react-native/no-deep-imports */
|
|
@@ -21,8 +22,9 @@ export const PushyModule =
|
|
|
21
22
|
export const UpdateModule = PushyModule;
|
|
22
23
|
|
|
23
24
|
if (!PushyModule) {
|
|
24
|
-
throw
|
|
25
|
+
throw new UpdateError(
|
|
25
26
|
'Failed to load react-native-update native module, please try to recompile',
|
|
27
|
+
'MODULE_NOT_LOADED',
|
|
26
28
|
);
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -82,7 +84,11 @@ export const pushyNativeEventEmitter = new NativeEventEmitter(PushyModule);
|
|
|
82
84
|
|
|
83
85
|
if (!uuid) {
|
|
84
86
|
uuid = require('nanoid/non-secure').nanoid();
|
|
85
|
-
|
|
87
|
+
// If persisting fails the uuid drifts on every launch, which skews gray
|
|
88
|
+
// release bucketing and inflates stats — log it instead of failing silently.
|
|
89
|
+
Promise.resolve(PushyModule.setUuid(uuid)).catch((e: any) => {
|
|
90
|
+
log('setUuid error:', e?.message || e);
|
|
91
|
+
});
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
export const cInfo = {
|
package/src/endpoint.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { UpdateError } from './error';
|
|
2
|
+
|
|
1
3
|
export interface EndpointAttemptSuccess<T> {
|
|
2
4
|
endpoint: string;
|
|
3
5
|
value: T;
|
|
@@ -47,7 +49,7 @@ export const pickRandomEndpoint = (
|
|
|
47
49
|
random: () => number = Math.random,
|
|
48
50
|
) => {
|
|
49
51
|
if (!endpoints.length) {
|
|
50
|
-
throw new
|
|
52
|
+
throw new UpdateError('No endpoints configured', 'NO_ENDPOINTS');
|
|
51
53
|
}
|
|
52
54
|
return endpoints[Math.floor(random() * endpoints.length)];
|
|
53
55
|
};
|
|
@@ -120,7 +122,7 @@ export async function executeEndpointFallback<T>({
|
|
|
120
122
|
let candidates = dedupeEndpoints(configuredEndpoints);
|
|
121
123
|
|
|
122
124
|
if (!candidates.length) {
|
|
123
|
-
throw new
|
|
125
|
+
throw new UpdateError('No endpoints configured', 'NO_ENDPOINTS');
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
const firstEndpoint = pickRandomEndpoint(candidates, random);
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable, machine-readable error codes. Unlike messages (which are localized
|
|
3
|
+
* and vary across platforms), codes are safe to aggregate on in a logger.
|
|
4
|
+
*
|
|
5
|
+
* The native-originated codes (second group) are defined in
|
|
6
|
+
* cpp/patch_core/error_codes.h — the single source of truth shared by the
|
|
7
|
+
* Android/iOS/Harmony modules — and flow through promise rejections onto the
|
|
8
|
+
* `code` property, which toUpdateError() preserves.
|
|
9
|
+
*/
|
|
10
|
+
export type UpdateErrorCode =
|
|
11
|
+
// JS-layer codes
|
|
12
|
+
| 'MODULE_NOT_LOADED'
|
|
13
|
+
| 'APPKEY_REQUIRED'
|
|
14
|
+
| 'NO_ENDPOINTS'
|
|
15
|
+
| 'HTTP_STATUS'
|
|
16
|
+
| 'CHECK_FAILED'
|
|
17
|
+
| 'DOWNLOAD_FAILED'
|
|
18
|
+
| 'SWITCH_VERSION_FAILED'
|
|
19
|
+
| 'MARK_SUCCESS_FAILED'
|
|
20
|
+
| 'APK_INSTALL_PENDING'
|
|
21
|
+
| 'STORAGE_PERMISSION_REJECTED'
|
|
22
|
+
| 'STORAGE_PERMISSION_ERROR'
|
|
23
|
+
| 'APK_DOWNLOAD_FAILED'
|
|
24
|
+
// Native codes (see cpp/patch_core/error_codes.h)
|
|
25
|
+
| 'INVALID_OPTIONS'
|
|
26
|
+
| 'PATCH_FAILED'
|
|
27
|
+
| 'FILE_OPERATION_FAILED'
|
|
28
|
+
| 'RESTART_FAILED'
|
|
29
|
+
| 'INVALID_HASH_INFO'
|
|
30
|
+
| 'UNSUPPORTED_PLATFORM';
|
|
31
|
+
|
|
32
|
+
export class UpdateError extends Error {
|
|
33
|
+
code: UpdateErrorCode;
|
|
34
|
+
cause?: unknown;
|
|
35
|
+
extra?: Record<string, string | number>;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
message: string,
|
|
39
|
+
code: UpdateErrorCode,
|
|
40
|
+
options?: { cause?: unknown; extra?: Record<string, string | number> },
|
|
41
|
+
) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = 'UpdateError';
|
|
44
|
+
this.code = code;
|
|
45
|
+
this.cause = options?.cause;
|
|
46
|
+
this.extra = options?.extra;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Attach a code to an unknown thrown value. An existing Error keeps its
|
|
52
|
+
* identity (message, stack, and any code already assigned upstream) so callers
|
|
53
|
+
* comparing the caught error to the original still match; non-Error values are
|
|
54
|
+
* wrapped.
|
|
55
|
+
*/
|
|
56
|
+
export const toUpdateError = (
|
|
57
|
+
e: unknown,
|
|
58
|
+
code: UpdateErrorCode,
|
|
59
|
+
): UpdateError => {
|
|
60
|
+
if (e instanceof Error) {
|
|
61
|
+
const err = e as UpdateError;
|
|
62
|
+
if (!err.code) {
|
|
63
|
+
err.code = code;
|
|
64
|
+
}
|
|
65
|
+
return err;
|
|
66
|
+
}
|
|
67
|
+
return new UpdateError(String(e ?? code), code);
|
|
68
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export { Pushy, Cresc } from './client';
|
|
2
|
-
export {
|
|
2
|
+
export type { UpdateErrorListener } from './client';
|
|
3
|
+
export { UpdateError } from './error';
|
|
4
|
+
export type { UpdateErrorCode } from './error';
|
|
5
|
+
export {
|
|
6
|
+
ProgressContext,
|
|
7
|
+
UpdateContext,
|
|
8
|
+
usePushy,
|
|
9
|
+
useUpdate,
|
|
10
|
+
useUpdateProgress,
|
|
11
|
+
} from './context';
|
|
3
12
|
export { PushyProvider, UpdateProvider } from './provider';
|
|
4
13
|
export { PushyModule, UpdateModule } from './core';
|
package/src/locales/en.ts
CHANGED
|
@@ -19,7 +19,6 @@ export default {
|
|
|
19
19
|
time_remaining: 'Time remaining: {{time}}',
|
|
20
20
|
|
|
21
21
|
// Error messages
|
|
22
|
-
error_code: 'Error code: {{code}}',
|
|
23
22
|
error_message: 'Error message: {{message}}',
|
|
24
23
|
retry_count: 'Retry attempt: {{count}}/{{max}}',
|
|
25
24
|
|
|
@@ -57,6 +56,11 @@ export default {
|
|
|
57
56
|
error_ping_failed: 'Ping failed',
|
|
58
57
|
error_ping_timeout: 'Ping timeout',
|
|
59
58
|
error_http_status: '{{status}} {{statusText}}',
|
|
59
|
+
error_apk_pending_install:
|
|
60
|
+
'The APK has been downloaded, please complete the installation in the system installer',
|
|
61
|
+
error_storage_permission_rejected:
|
|
62
|
+
'Storage permission denied, unable to download the APK',
|
|
63
|
+
error_apk_download_failed: 'Failed to download or install the APK',
|
|
60
64
|
|
|
61
65
|
// Development messages
|
|
62
66
|
dev_debug_disabled:
|
package/src/locales/zh.ts
CHANGED
|
@@ -19,7 +19,6 @@ export default {
|
|
|
19
19
|
time_remaining: '剩余时间: {{time}}',
|
|
20
20
|
|
|
21
21
|
// Error messages
|
|
22
|
-
error_code: '错误代码: {{code}}',
|
|
23
22
|
error_message: '错误信息: {{message}}',
|
|
24
23
|
retry_count: '重试次数: {{count}}/{{max}}',
|
|
25
24
|
|
|
@@ -54,6 +53,9 @@ export default {
|
|
|
54
53
|
error_ping_failed: 'Ping 失败',
|
|
55
54
|
error_ping_timeout: 'Ping 超时',
|
|
56
55
|
error_http_status: '{{status}} {{statusText}}',
|
|
56
|
+
error_apk_pending_install: '安装包已下载完成,请在系统安装界面完成安装',
|
|
57
|
+
error_storage_permission_rejected: '存储权限被拒绝,无法下载安装包',
|
|
58
|
+
error_apk_download_failed: '安装包下载或安装失败',
|
|
57
59
|
|
|
58
60
|
// Development messages
|
|
59
61
|
dev_debug_disabled:
|