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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#include <vector>
|
|
7
7
|
|
|
8
8
|
#include "archive_patch_core.h"
|
|
9
|
+
#include "digest.h"
|
|
9
10
|
#include "hbc_transform_wire.h"
|
|
10
11
|
#include "patch_core.h"
|
|
11
12
|
#include "state_core.h"
|
|
@@ -890,6 +891,51 @@ bool ExportFunction(
|
|
|
890
891
|
} // namespace
|
|
891
892
|
|
|
892
893
|
|
|
894
|
+
// sha256(Uint8Array | ArrayBuffer) -> 小写 hex。同步:bundleHash 的输入是
|
|
895
|
+
// rawfile 里的 bundle(已整体读入内存,与 pdiff 的读法一致),哈希本身毫秒级。
|
|
896
|
+
static napi_value Sha256Hex(napi_env env, napi_callback_info info) {
|
|
897
|
+
size_t argc = 1;
|
|
898
|
+
napi_value args[1] = {nullptr};
|
|
899
|
+
if (!GetArgCount(env, info, &argc, args) || argc < 1) {
|
|
900
|
+
ThrowError(env, "sha256Hex: missing data argument");
|
|
901
|
+
return nullptr;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
void* data = nullptr;
|
|
905
|
+
size_t length = 0;
|
|
906
|
+
bool is_typedarray = false;
|
|
907
|
+
napi_is_typedarray(env, args[0], &is_typedarray);
|
|
908
|
+
if (is_typedarray) {
|
|
909
|
+
napi_typedarray_type type;
|
|
910
|
+
napi_value arraybuffer = nullptr;
|
|
911
|
+
size_t offset = 0;
|
|
912
|
+
if (napi_get_typedarray_info(
|
|
913
|
+
env, args[0], &type, &length, &data, &arraybuffer, &offset) !=
|
|
914
|
+
napi_ok) {
|
|
915
|
+
ThrowError(env, "sha256Hex: cannot read typed array");
|
|
916
|
+
return nullptr;
|
|
917
|
+
}
|
|
918
|
+
} else {
|
|
919
|
+
bool is_arraybuffer = false;
|
|
920
|
+
napi_is_arraybuffer(env, args[0], &is_arraybuffer);
|
|
921
|
+
if (!is_arraybuffer ||
|
|
922
|
+
napi_get_arraybuffer_info(env, args[0], &data, &length) != napi_ok) {
|
|
923
|
+
ThrowError(env, "sha256Hex: expected Uint8Array or ArrayBuffer");
|
|
924
|
+
return nullptr;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
pushy::digest::Sha256 hasher;
|
|
929
|
+
if (length > 0) {
|
|
930
|
+
hasher.Update(static_cast<const uint8_t*>(data), length);
|
|
931
|
+
}
|
|
932
|
+
const std::string hex = hasher.HexDigest();
|
|
933
|
+
|
|
934
|
+
napi_value result = nullptr;
|
|
935
|
+
napi_create_string_utf8(env, hex.c_str(), hex.size(), &result);
|
|
936
|
+
return result;
|
|
937
|
+
}
|
|
938
|
+
|
|
893
939
|
// 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道)
|
|
894
940
|
static napi_value GetSupportedDiffVersion(napi_env env, napi_callback_info) {
|
|
895
941
|
napi_value result = nullptr;
|
|
@@ -907,6 +953,7 @@ napi_value Init(napi_env env, napi_value exports) {
|
|
|
907
953
|
!ExportFunction(env, exports, "buildCopyGroups", BuildCopyGroups) ||
|
|
908
954
|
!ExportFunction(env, exports, "applyPatchFromFileSource", ApplyPatchFromFileSource) ||
|
|
909
955
|
!ExportFunction(env, exports, "cleanupOldEntries", CleanupOldEntries) ||
|
|
956
|
+
!ExportFunction(env, exports, "sha256Hex", Sha256Hex) ||
|
|
910
957
|
!ExportFunction(env, exports, "getSupportedDiffVersion", GetSupportedDiffVersion)) {
|
|
911
958
|
return nullptr;
|
|
912
959
|
}
|
|
@@ -72,7 +72,7 @@ function toArrayBufferSlice(
|
|
|
72
72
|
offset: number,
|
|
73
73
|
length: number,
|
|
74
74
|
): ArrayBuffer {
|
|
75
|
-
return payload.buffer.slice(
|
|
75
|
+
return (payload.buffer as ArrayBuffer).slice(
|
|
76
76
|
payload.byteOffset + offset,
|
|
77
77
|
payload.byteOffset + offset + length,
|
|
78
78
|
);
|
|
@@ -560,7 +560,7 @@ export class DownloadTask {
|
|
|
560
560
|
'bundle.harmony.js',
|
|
561
561
|
);
|
|
562
562
|
await this.applyBundlePatchFromFileSource(
|
|
563
|
-
originContent,
|
|
563
|
+
originContent.buffer as ArrayBuffer,
|
|
564
564
|
params.unzipDirectory,
|
|
565
565
|
bundlePatchPath,
|
|
566
566
|
`${params.unzipDirectory}/bundle.harmony.js`,
|
|
@@ -80,6 +80,8 @@ interface NativePatchCoreBindings {
|
|
|
80
80
|
keepPrevious: string,
|
|
81
81
|
maxAgeDays: number,
|
|
82
82
|
): Promise<void>;
|
|
83
|
+
/** sha256(小写 hex)。同步:输入是已在内存的 rawfile bundle,哈希毫秒级 */
|
|
84
|
+
sha256Hex(data: Uint8Array): string;
|
|
83
85
|
/** 原生 patch 内核可消费的 diff 轨道版本(2 = hdiffv2 轨道) */
|
|
84
86
|
getSupportedDiffVersion(): number;
|
|
85
87
|
}
|
|
@@ -233,6 +233,17 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
async getBundleHash(): Promise<string> {
|
|
237
|
+
logger.debug(TAG, ',call getBundleHash');
|
|
238
|
+
try {
|
|
239
|
+
return this.context.getBundleHash();
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// 空串 = "未知",服务端回退 buildTime 启发式;该方法永不 reject。
|
|
242
|
+
logger.error(TAG, `getBundleHash failed: ${getErrorMessage(error)}`);
|
|
243
|
+
return '';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
236
247
|
async resetToPackagedBundle(): Promise<void> {
|
|
237
248
|
logger.debug(TAG, ',call resetToPackagedBundle');
|
|
238
249
|
try {
|
|
@@ -632,4 +632,58 @@ export class UpdateContext {
|
|
|
632
632
|
public getIsUsingBundleUrl(): boolean {
|
|
633
633
|
return UpdateContext.isUsingBundleUrl;
|
|
634
634
|
}
|
|
635
|
+
|
|
636
|
+
// bundleHash 缓存:"<packageVersion>|<updateTime>|<sha256hex>"。键标识当前
|
|
637
|
+
// 安装的二进制,每次(覆盖)安装 updateTime 都会变,每个安装只算一次。
|
|
638
|
+
private static readonly KEY_BUNDLE_HASH_CACHE = 'bundleHashCache';
|
|
639
|
+
|
|
640
|
+
private getBundleUpdateTime(): number {
|
|
641
|
+
try {
|
|
642
|
+
const bundleInfo = bundleManager.getBundleInfoForSelfSync(
|
|
643
|
+
this.getBundleFlags(),
|
|
644
|
+
);
|
|
645
|
+
return bundleInfo?.updateTime ?? 0;
|
|
646
|
+
} catch (error) {
|
|
647
|
+
console.error('Failed to get bundle update time:', error);
|
|
648
|
+
return 0;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* bundleHash = 二进制内嵌 JS bundle 的 sha256 —— 标识二进制本身,与当前运行
|
|
654
|
+
* 的热更版本无关。哈希对象与 pdiff 的源读取完全一致:rawfile 的
|
|
655
|
+
* bundle.harmony.js(DownloadTask.doPatchFromApp 同一读法)。懒计算 + 缓存;
|
|
656
|
+
* 永不失败——空串表示"未知",服务端回退 buildTime 启发式。
|
|
657
|
+
*/
|
|
658
|
+
public async getBundleHash(): Promise<string> {
|
|
659
|
+
if (UpdateContext.DEBUG) {
|
|
660
|
+
// debug 下 bundle 由 metro 提供,与 dev 删 buildTime 的行为对齐。
|
|
661
|
+
return '';
|
|
662
|
+
}
|
|
663
|
+
const cachePrefix = `${this.getPackageVersion()}|${this.getBundleUpdateTime()}|`;
|
|
664
|
+
const cached = this.readString(UpdateContext.KEY_BUNDLE_HASH_CACHE);
|
|
665
|
+
if (cached.startsWith(cachePrefix)) {
|
|
666
|
+
return cached.slice(cachePrefix.length);
|
|
667
|
+
}
|
|
668
|
+
let hash = '';
|
|
669
|
+
try {
|
|
670
|
+
// 异步读:TurboModule 跑在 UI 线程,同步读几 MB 会卡主线程一次。C++
|
|
671
|
+
// 哈希本身毫秒级,同步无妨。
|
|
672
|
+
const content =
|
|
673
|
+
await this.context.resourceManager.getRawFileContent('bundle.harmony.js');
|
|
674
|
+
hash = NativePatchCore.sha256Hex(content);
|
|
675
|
+
} catch (error) {
|
|
676
|
+
// rawfile 缺 bundle(未打包 release bundle)是合法的"未知",不是错误。
|
|
677
|
+
console.info('Cannot hash embedded bundle:', error);
|
|
678
|
+
return '';
|
|
679
|
+
}
|
|
680
|
+
if (hash) {
|
|
681
|
+
this.preferences.putSync(
|
|
682
|
+
UpdateContext.KEY_BUNDLE_HASH_CACHE,
|
|
683
|
+
cachePrefix + hash,
|
|
684
|
+
);
|
|
685
|
+
this.flushPreferences('cache bundle hash');
|
|
686
|
+
}
|
|
687
|
+
return hash;
|
|
688
|
+
}
|
|
635
689
|
}
|
package/harmony/pushy.har
CHANGED
|
Binary file
|
package/ios/RCTPushy/RCTPushy.mm
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import "RCTPushyDownloader.h"
|
|
3
3
|
#import "ZipArchive.h"
|
|
4
4
|
#include "../../cpp/patch_core/archive_patch_core.h"
|
|
5
|
+
#include "../../cpp/patch_core/digest.h"
|
|
5
6
|
#include "../../cpp/patch_core/hbc_transform_wire.h"
|
|
6
7
|
#include "../../cpp/patch_core/error_codes.h"
|
|
7
8
|
#include "../../cpp/patch_core/patch_core.h"
|
|
@@ -32,6 +33,10 @@ static NSString *const keyHashInfo = @"REACTNATIVECN_PUSHY_HASH_";
|
|
|
32
33
|
static NSString *const keyFirstLoadMarked = @"REACTNATIVECN_PUSHY_FIRSTLOADMARKED_KEY";
|
|
33
34
|
static NSString *const keyRolledBackMarked = @"REACTNATIVECN_PUSHY_ROLLEDBACKMARKED_KEY";
|
|
34
35
|
static NSString *const KeyPackageUpdatedMarked = @"REACTNATIVECN_PUSHY_ISPACKAGEUPDATEDMARKED_KEY";
|
|
36
|
+
// bundleHash cache: "<cacheKey>|<sha256hex>" where cacheKey identifies the
|
|
37
|
+
// installed binary (packageVersion + embedded bundle size + mtime). Recomputed
|
|
38
|
+
// only when the key changes, i.e. once per install.
|
|
39
|
+
static NSString *const keyBundleHashCache = @"REACTNATIVECN_PUSHY_BUNDLEHASH_KEY";
|
|
35
40
|
static NSString *const PushyErrorDomain = @"cn.reactnative.pushy";
|
|
36
41
|
|
|
37
42
|
// file def
|
|
@@ -512,6 +517,56 @@ RCT_EXPORT_METHOD(restartApp:(RCTPromiseResolveBlock)resolve
|
|
|
512
517
|
resolve(@true);
|
|
513
518
|
}
|
|
514
519
|
|
|
520
|
+
RCT_EXPORT_METHOD(getBundleHash:(RCTPromiseResolveBlock)resolve
|
|
521
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
522
|
+
{
|
|
523
|
+
// bundleHash = sha256 of the bundle embedded in the binary — the identity
|
|
524
|
+
// of the binary itself, not of whatever hot update is currently running.
|
|
525
|
+
// It must hash exactly the bytes pdiff patches from (binaryBundleURL, the
|
|
526
|
+
// pdiff fromBundle). Lazily computed once per install, cached in defaults.
|
|
527
|
+
// Never rejects: an empty string means "unknown" and the server falls back
|
|
528
|
+
// to the buildTime heuristic.
|
|
529
|
+
#if DEBUG
|
|
530
|
+
// Metro serves the bundle in debug; the embedded file (if any) is not what
|
|
531
|
+
// is running. Mirrors the dev behaviour of buildTime.
|
|
532
|
+
resolve(@"");
|
|
533
|
+
#else
|
|
534
|
+
dispatch_async(_fileQueue, ^{
|
|
535
|
+
NSString *path = [[RCTPushy binaryBundleURL] path];
|
|
536
|
+
if (path == nil) {
|
|
537
|
+
resolve(@"");
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
NSDictionary *attributes =
|
|
541
|
+
[[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
|
|
542
|
+
if (attributes == nil) {
|
|
543
|
+
resolve(@"");
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
NSString *cacheKey = [NSString stringWithFormat:@"%@|%llu|%.0f",
|
|
547
|
+
[RCTPushy packageVersion],
|
|
548
|
+
attributes.fileSize,
|
|
549
|
+
[attributes.fileModificationDate timeIntervalSince1970]];
|
|
550
|
+
|
|
551
|
+
NSUserDefaults *defaults = PushyDefaults();
|
|
552
|
+
NSString *cached = [defaults stringForKey:keyBundleHashCache];
|
|
553
|
+
NSString *cachedPrefix = [cacheKey stringByAppendingString:@"|"];
|
|
554
|
+
if ([cached hasPrefix:cachedPrefix]) {
|
|
555
|
+
resolve([cached substringFromIndex:cachedPrefix.length]);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
NSString *hash = PushyFromStdString(
|
|
560
|
+
pushy::digest::Sha256File(PushyToStdString(path))) ?: @"";
|
|
561
|
+
if (hash.length > 0) {
|
|
562
|
+
[defaults setObject:[cachedPrefix stringByAppendingString:hash]
|
|
563
|
+
forKey:keyBundleHashCache];
|
|
564
|
+
}
|
|
565
|
+
resolve(hash);
|
|
566
|
+
});
|
|
567
|
+
#endif
|
|
568
|
+
}
|
|
569
|
+
|
|
515
570
|
RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
|
516
571
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
517
572
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-update",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.49.0",
|
|
4
4
|
"description": "react-native hot update",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"publish:local": "SKIP_NATIVE_BUILD=1 npm publish",
|
|
10
10
|
"postinstall": "node scripts/check-expo-version.js",
|
|
11
11
|
"prepack": "bun submodule && bun lint",
|
|
12
|
-
"lint": "
|
|
12
|
+
"lint": "biome check src && tsc --noEmit && node scripts/check-harmony-types.js",
|
|
13
13
|
"lint:harmony": "node scripts/check-harmony-types.js",
|
|
14
14
|
"submodule": "git submodule update --init --recursive",
|
|
15
15
|
"test": "bun test src/__tests__",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"build:android-release": "cd Example/e2etest && bun && detox build --configuration android.emu.release",
|
|
26
26
|
"test:android-release": "cd Example/e2etest && E2E_PLATFORM=android bun detox test --configuration android.emu.release --headless --record-logs all",
|
|
27
27
|
"test:android-debug": "cd Example/e2etest && E2E_PLATFORM=android detox test --configuration android.emu.debug --headless --record-logs all",
|
|
28
|
+
"smoke:android-debug": "cd Example/e2etest && detox test --configuration android.emu.debug --config e2e/smoke/jest.config.js",
|
|
28
29
|
"e2e:ios": "bun build:ios-release && bun test:ios-release",
|
|
29
30
|
"e2e:android": "bun build:android-release && bun test:android-release",
|
|
30
31
|
"tests:emulator:prepare": "cd .github/workflows/scripts/functions && bun && bun build",
|
|
@@ -63,8 +64,8 @@
|
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
66
|
"@babel/core": "^7.25.8",
|
|
67
|
+
"@biomejs/biome": "^2.5.5",
|
|
66
68
|
"@react-native/babel-preset": "^0.73.21",
|
|
67
|
-
"@react-native/eslint-config": "0.84.1",
|
|
68
69
|
"@react-native/typescript-config": "0.84.1",
|
|
69
70
|
"@types/bun": "^1.3.11",
|
|
70
71
|
"@types/jest": "^30.0.0",
|
|
@@ -72,15 +73,13 @@
|
|
|
72
73
|
"@types/react": "^19.2.14",
|
|
73
74
|
"@types/react-test-renderer": "18.0.7",
|
|
74
75
|
"detox": "^20.50.1",
|
|
75
|
-
"eslint": "^8.57.0",
|
|
76
76
|
"firebase-tools": "^15.11.0",
|
|
77
77
|
"jest": "^30.3.0",
|
|
78
78
|
"pod-install": "^1.0.14",
|
|
79
|
-
"prettier": "^2",
|
|
80
79
|
"react": "18.2.0",
|
|
81
80
|
"react-native": "0.73",
|
|
82
81
|
"react-test-renderer": "18.2.0",
|
|
83
82
|
"ts-jest": "^29.4.6",
|
|
84
|
-
"typescript": "^
|
|
83
|
+
"typescript": "^7.0.2"
|
|
85
84
|
}
|
|
86
85
|
}
|
|
@@ -111,6 +111,7 @@ Pod::Spec.new do |s|
|
|
|
111
111
|
s.subspec 'RCTPushy' do |ss|
|
|
112
112
|
ss.source_files = ['ios/RCTPushy/*.{h,m,mm}',
|
|
113
113
|
'cpp/patch_core/archive_patch_core.{h,cpp}',
|
|
114
|
+
'cpp/patch_core/digest.{h,cpp}',
|
|
114
115
|
'cpp/patch_core/hbc_transform.{h,cpp}',
|
|
115
116
|
'cpp/patch_core/hbc_transform_wire.{h,cpp}',
|
|
116
117
|
'cpp/patch_core/patch_core.{h,cpp}',
|
package/src/NativePushy.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TurboModule, TurboModuleRegistry } from 'react-native';
|
|
1
|
+
import { type TurboModule, TurboModuleRegistry } from 'react-native';
|
|
2
2
|
|
|
3
3
|
export interface Spec extends TurboModule {
|
|
4
4
|
getConstants: () => {
|
|
@@ -20,6 +20,12 @@ export interface Spec extends TurboModule {
|
|
|
20
20
|
restartApp(): Promise<void>;
|
|
21
21
|
setNeedUpdate(options: { hash: string }): Promise<void>;
|
|
22
22
|
markSuccess(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* sha256 of the JS bundle embedded in the binary (the pdiff source), lazily
|
|
25
|
+
* computed and cached natively. Resolves to an empty string when unknown
|
|
26
|
+
* (debug build, no embedded bundle, hash failure) — never rejects.
|
|
27
|
+
*/
|
|
28
|
+
getBundleHash(): Promise<string>;
|
|
23
29
|
resetToPackagedBundle(): Promise<void>;
|
|
24
30
|
downloadPatchFromPpk(options: {
|
|
25
31
|
updateUrl: string;
|