1
0
mirror of https://gitcode.com/gh_mirrors/re/react-native-pushy.git synced 2025-11-22 23:46:10 +08:00
Code Issues Packages Projects Releases Wiki Activity GitHub Gitee

Compare commits

..

16 Commits

Author SHA1 Message Date
sunnylqm
57206dd2f1 Update .gitignore to exclude new harmony package files, remove submodule entries from .gitmodules, and increment version in package.json to 10.34.1. Refactor build-profile.json5 for consistency and update PushyFileJSBundleProvider to handle optional chaining. Remove obsolete pushy.har file and adjust dependencies in oh-package.json5 for clarity. 2025-10-16 00:48:17 +08:00
sunnylqm
4e27d906c3 Reorder properties in CheckResult object for consistency in UpdateProvider component. 2025-09-28 23:23:16 +08:00
sunnylqm
c24f469475 Refactor Pushy class to replace getCurrentVersionInfo with currentVersionInfo for improved clarity and consistency. 2025-09-28 21:57:56 +08:00
sunnylqm
8f8a29eda8 Update version in package.json from 10.34.0-beta.0 to 10.34.0 for stable release. 2025-09-24 16:56:28 +08:00
sunnylqm
a78542b214 Update version to 10.34.0-beta.0 in package.json and modify NDK version for build-lib script; update package manager to yarn@1.22.21. 2025-09-24 15:29:57 +08:00
Sunny Luo
8d9ae57a5f Bump version from 10.32.1 to 10.33.0 2025-09-24 08:28:11 +08:00
Sunny Luo
2502935fc0 Update package.json 2025-09-24 08:27:57 +08:00
波仔糕
8e6d9bf460 update judge logic for v2 (#513)
* update judge logic for v2

* udpate
2025-09-24 08:26:16 +08:00
波仔糕
897f334343 resolve aab package image hot update issue (#512)
* modify harmony download logic to async

* fix harmony image assets load fail issue

* resolve aab package image hot update issue

* update

* udpate
2025-09-23 23:41:40 +08:00
sunnylqm
33bc69c3fb Update react-native-update version to 10.32.0 in package.json for the latest features and improvements. 2025-09-20 12:08:03 +08:00
sunnylqm
5028ce31be Update react-native-update to version 10.32.0 in package.json, refactor error handling in DownloadTask and UpdateContext for improved readability, and enhance type definitions in various files for better TypeScript support. 2025-09-20 12:07:53 +08:00
sunnylqm
d3a4007763 Refactor ProGuard rules for React Native classes to improve reflection handling and maintain compatibility with Expo modules. 2025-09-19 16:03:17 +08:00
sunnylqm
a4f3e3cc38 Update react-native-update to version 10.31.2 in bun.lock, package.json, and Podfile.lock for consistency across dependencies. 2025-09-19 14:58:47 +08:00
sunnylqm
9d51128ed3 Update react-native-update to version 10.31.2 in package.json and enhance currentVersionInfo retrieval in RCTPushy.mm for improved data handling. 2025-09-19 14:50:13 +08:00
sunnylqm
eddb072927 Update Podfile.lock to reflect react-native-update version 10.31.1, ensuring consistency with package dependencies. 2025-09-19 14:42:29 +08:00
sunnylqm
4f9417d620 Update react-native-update to version 10.31.1 in package.json and bun.lock for improved stability and features. 2025-09-19 14:41:52 +08:00
32 changed files with 553 additions and 464 deletions

5
.gitignore vendored
View File

@@ -52,3 +52,8 @@ Example/testHotUpdate/harmony
Example/testHotUpdate/android/app/.cxx Example/testHotUpdate/android/app/.cxx
Example/harmony_use_pushy/libs Example/harmony_use_pushy/libs
**/mcp.json **/mcp.json
harmony/package
**/oh_modules
harmony/pushy/.preview

6
.gitmodules vendored
View File

@@ -4,9 +4,3 @@
[submodule "android/jni/HDiffPatch"] [submodule "android/jni/HDiffPatch"]
path = android/jni/HDiffPatch path = android/jni/HDiffPatch
url = https://github.com/sisong/HDiffPatch.git url = https://github.com/sisong/HDiffPatch.git
[submodule "harmony/src/main/cpp/HDiffPatch"]
path = harmony/src/main/cpp/HDiffPatch
url = https://github.com/sisong/HDiffPatch.git
[submodule "harmony/src/main/cpp/lzma"]
path = harmony/src/main/cpp/lzma
url = https://github.com/sisong/lzma.git

View File

@@ -1,41 +1,49 @@
{ {
"app": { app: {
"signingConfigs": [], signingConfigs: [],
"products": [ products: [
{ {
"name": "default", name: 'default',
"signingConfig": "default", signingConfig: 'default',
"compatibleSdkVersion": "5.0.0(12)", compatibleSdkVersion: '5.0.0(12)',
"runtimeOS": "HarmonyOS", runtimeOS: 'HarmonyOS',
"buildOption": { buildOption: {
"strictMode": { strictMode: {
"caseSensitiveCheck": true, caseSensitiveCheck: true,
"useNormalizedOHMUrl": true useNormalizedOHMUrl: true,
} },
} },
} },
], ],
"buildModeSet": [ buildModeSet: [
{ {
"name": "debug", name: 'debug',
}, },
{ {
"name": "release" name: 'release',
} },
] ],
}, },
"modules": [ modules: [
{ {
"name": "entry", name: 'entry',
"srcPath": "./entry", srcPath: './entry',
"targets": [ targets: [
{ {
"name": "default", name: 'default',
"applyToProducts": [ applyToProducts: ['default'],
"default" },
] ],
} },
] {
} name: 'pushy',
] srcPath: '../../../harmony/pushy',
} targets: [
{
name: 'default',
applyToProducts: ['default'],
},
],
},
],
}

View File

@@ -1,25 +1,26 @@
{ {
"meta": { "meta": {
"stableOrder": true "stableOrder": true,
"enableUnifiedLockfile": false
}, },
"lockfileVersion": 3, "lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": { "specifiers": {
"@rnoh/react-native-openharmony@0.72.38": "@rnoh/react-native-openharmony@0.72.38", "@rnoh/react-native-openharmony@0.72.38": "@rnoh/react-native-openharmony@0.72.38",
"pushy@../../node_modules/react-native-update/harmony": "pushy@../../node_modules/react-native-update/harmony" "pushy@../../node_modules/react-native-update/harmony/pushy": "pushy@../../node_modules/react-native-update/harmony/pushy"
}, },
"packages": { "packages": {
"@rnoh/react-native-openharmony@0.72.38": { "@rnoh/react-native-openharmony@0.72.38": {
"name": "@rnoh/react-native-openharmony", "name": "",
"version": "0.72.38", "version": "0.72.38",
"integrity": "sha512-br5SIrbB0OarSLirenleE7eTOX1lNccMJ7nb/G7qWTyJ7kW4DalmTXVKYpoT2qaOLls1uEE7McD1OjbZZM9jug==", "integrity": "sha512-br5SIrbB0OarSLirenleE7eTOX1lNccMJ7nb/G7qWTyJ7kW4DalmTXVKYpoT2qaOLls1uEE7McD1OjbZZM9jug==",
"resolved": "https://ohpm.openharmony.cn/ohpm/@rnoh/react-native-openharmony/-/react-native-openharmony-0.72.38.har", "resolved": "https://ohpm.openharmony.cn/ohpm/@rnoh/react-native-openharmony/-/react-native-openharmony-0.72.38.har",
"registryType": "ohpm" "registryType": "ohpm"
}, },
"pushy@../../node_modules/react-native-update/harmony": { "pushy@../../node_modules/react-native-update/harmony/pushy": {
"name": "pushy", "name": "pushy",
"version": "3.1.0-0.0.7", "version": "3.1.0-0.0.7",
"resolved": "../../node_modules/react-native-update/harmony", "resolved": "",
"registryType": "local", "registryType": "local",
"dependencies": { "dependencies": {
"@rnoh/react-native-openharmony": "^0.72.38" "@rnoh/react-native-openharmony": "^0.72.38"

View File

@@ -1,13 +1,12 @@
{ {
"name": "entry", name: 'entry',
"version": "1.0.0", version: '1.0.0',
"description": "Please describe the basic information.", description: 'Please describe the basic information.',
"main": "", main: '',
"author": "", author: '',
"license": "", license: '',
"dependencies": { dependencies: {
"@rnoh/react-native-openharmony": "0.72.38", '@rnoh/react-native-openharmony': '0.72.38',
"pushy": "file:../../node_modules/react-native-update/harmony/pushy.har", pushy: 'file:../../node_modules/react-native-update/harmony/pushy',
} },
} }

View File

@@ -1,6 +1,6 @@
import { appTasks } from '@ohos/hvigor-ohos-plugin'; import {appTasks} from '@ohos/hvigor-ohos-plugin';
export default { export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ system: appTasks /* Built-in plugin of Hvigor. It cannot be modified. */,
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ plugins: [] /* Custom plugin to extend the functionality of Hvigor. */,
} };

View File

@@ -12,7 +12,7 @@
"react-native-paper": "^5.14.5", "react-native-paper": "^5.14.5",
"react-native-safe-area-context": "^5.6.1", "react-native-safe-area-context": "^5.6.1",
"react-native-svg": "^15.13.0", "react-native-svg": "^15.13.0",
"react-native-update": "^10.31.0-beta.4", "react-native-update": "^10.31.2",
"react-native-vector-icons": "^10.3.0", "react-native-vector-icons": "^10.3.0",
}, },
"devDependencies": { "devDependencies": {
@@ -1430,7 +1430,7 @@
"react-native-svg": ["react-native-svg@15.13.0", "", { "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/YPK+PAAXg4T0x2d2vYPvqqAhOYid2bRKxUVT7STIyd1p2JxWmsGQkfZxXCkEFN7TwLfIyVlT5RimT91Pj/qXw=="], "react-native-svg": ["react-native-svg@15.13.0", "", { "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/YPK+PAAXg4T0x2d2vYPvqqAhOYid2bRKxUVT7STIyd1p2JxWmsGQkfZxXCkEFN7TwLfIyVlT5RimT91Pj/qXw=="],
"react-native-update": ["react-native-update@10.31.0-beta.4", "", { "dependencies": { "nanoid": "^3.3.3", "react-native-url-polyfill": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-native": ">=0.59.0" } }, "sha512-6TdSq9PdxH07IHFcMkhMaPxA81teike++PZtgVWbL1GJiZJ5MXpLhwsxD2Nd4x+eHJY8pj4wgMBC8iCAfqVJlg=="], "react-native-update": ["react-native-update@10.31.2", "", { "dependencies": { "nanoid": "^3.3.3", "react-native-url-polyfill": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-native": ">=0.59.0" } }, "sha512-doKTDyU/+6eN1jDswSh1HxnM9/XuxYFM65dLadsYVR+JvdLJ0PtbceSauthv4HYI3QXoeGaStkrRCaQAVKvp+w=="],
"react-native-url-polyfill": ["react-native-url-polyfill@2.0.0", "", { "dependencies": { "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "react-native": "*" } }, "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA=="], "react-native-url-polyfill": ["react-native-url-polyfill@2.0.0", "", { "dependencies": { "whatwg-url-without-unicode": "8.0.0-3" }, "peerDependencies": { "react-native": "*" } }, "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA=="],

View File

@@ -1835,7 +1835,7 @@ PODS:
- ReactCommon/turbomodule/core - ReactCommon/turbomodule/core
- SocketRocket - SocketRocket
- Yoga - Yoga
- react-native-update (10.31.0-beta.4): - react-native-update (10.31.2):
- boost - boost
- DoubleConversion - DoubleConversion
- fast_float - fast_float
@@ -1854,7 +1854,7 @@ PODS:
- React-graphics - React-graphics
- React-ImageManager - React-ImageManager
- React-jsi - React-jsi
- react-native-update/RCTPushy (= 10.31.0-beta.4) - react-native-update/RCTPushy (= 10.31.2)
- React-NativeModulesApple - React-NativeModulesApple
- React-RCTFabric - React-RCTFabric
- React-renderercss - React-renderercss
@@ -1866,7 +1866,7 @@ PODS:
- SocketRocket - SocketRocket
- SSZipArchive - SSZipArchive
- Yoga - Yoga
- react-native-update/RCTPushy (10.31.0-beta.4): - react-native-update/RCTPushy (10.31.2):
- boost - boost
- DoubleConversion - DoubleConversion
- fast_float - fast_float
@@ -2800,7 +2800,7 @@ SPEC CHECKSUMS:
React-Mapbuffer: 9d2434a42701d6144ca18f0ca1c4507808ca7696 React-Mapbuffer: 9d2434a42701d6144ca18f0ca1c4507808ca7696
React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b
react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616 react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616
react-native-update: 66be7f3c9572c483d6f43405097cdf8fd5c93d84 react-native-update: 7a05a78c6da651c1961b8407859f44bd0aa9b03a
React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3 React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3
React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d
React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510 React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510

View File

@@ -22,7 +22,7 @@
"react-native-paper": "^5.14.5", "react-native-paper": "^5.14.5",
"react-native-safe-area-context": "^5.6.1", "react-native-safe-area-context": "^5.6.1",
"react-native-svg": "^15.13.0", "react-native-svg": "^15.13.0",
"react-native-update": "^10.31.0-beta.4", "react-native-update": "^10.31.2",
"react-native-vector-icons": "^10.3.0" "react-native-vector-icons": "^10.3.0"
}, },
"devDependencies": { "devDependencies": {

Binary file not shown.

Binary file not shown.

Binary file not shown.

27
android/proguard.pro vendored
View File

@@ -10,28 +10,9 @@
-keepnames class com.facebook.react.devsupport.** { *; } -keepnames class com.facebook.react.devsupport.** { *; }
# Keep fields used in reflection # Keep fields used in reflection
-keepclassmembers class com.facebook.react.ReactInstanceManager { -keepclassmembers class com.facebook.react.ReactActivity { *; }
private JSBundleLoader mBundleLoader; -keepclassmembers class com.facebook.react.ReactInstanceManager { *; }
private String mJSBundleFile; -keepclassmembers class com.facebook.react.ReactDelegate { *; }
} -keepclassmembers class com.facebook.react.ReactHost { *; }
-keepclassmembers class com.facebook.react.ReactDelegate {
private ReactHost mReactHost;
}
-keepclassmembers class com.facebook.react.ReactHost {
private boolean mUseDevSupport;
private ReactHostDelegate mReactHostDelegate;
}
# Keep Expo related classes
-keepnames class expo.modules.ExpoReactHostFactory$ExpoReactHostDelegate { *; } -keepnames class expo.modules.ExpoReactHostFactory$ExpoReactHostDelegate { *; }
# Keep methods used in reflection
-keepclassmembers class com.facebook.react.ReactActivity {
public ReactDelegate getReactDelegate();
}
-keepclassmembers class com.facebook.react.ReactHost {
public void reload(java.lang.String);
}

View File

@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Iterator; import java.util.Iterator;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.CRC32;
import java.util.HashMap; import java.util.HashMap;
import okio.BufferedSink; import okio.BufferedSink;
@@ -100,7 +101,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
if (UpdateContext.DEBUG) { if (UpdateContext.DEBUG) {
Log.d("react-native-update", "Progress " + received + "/" + contentLength); Log.d("react-native-update", "Progress " + received + "/" + contentLength);
} }
int percentage = (int)(received * 100.0 / contentLength + 0.5); int percentage = (int)(received * 100.0 / contentLength + 0.5);
if (percentage > currentPercentage) { if (percentage > currentPercentage) {
currentPercentage = percentage; currentPercentage = percentage;
@@ -198,6 +199,10 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
return fout.toByteArray(); return fout.toByteArray();
} }
private String getCRC32AsDecimal(long crc32Value) {
return String.valueOf(crc32Value & 0xFFFFFFFFL);
}
private void copyFilesWithBlacklist(String current, File from, File to, JSONObject blackList) throws IOException { private void copyFilesWithBlacklist(String current, File from, File to, JSONObject blackList) throws IOException {
File[] files = from.listFiles(); File[] files = from.listFiles();
for (File file : files) { for (File file : files) {
@@ -273,12 +278,41 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
zipFile.close(); zipFile.close();
} }
private void copyFromResourceV2(HashMap<String, ArrayList<File>> resToCopy2) throws IOException {
SafeZipFile zipFile = new SafeZipFile(new File(context.getPackageResourcePath()));
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement();
String fn = ze.getName();
long zipCrc32 = ze.getCrc();
String crc32Decimal = getCRC32AsDecimal(zipCrc32);
ArrayList<File> targets = resToCopy2.get(crc32Decimal);
if (targets != null) {
File lastTarget = null;
for (File target: targets) {
if (UpdateContext.DEBUG) {
Log.d("react-native-update", "Copying from resource " + fn + " to " + target);
}
if (lastTarget != null) {
copyFile(lastTarget, target);
} else {
zipFile.unzipToFile(ze, target);
lastTarget = target;
}
}
}
}
zipFile.close();
}
private void doPatchFromApk(DownloadTaskParams param) throws IOException, JSONException { private void doPatchFromApk(DownloadTaskParams param) throws IOException, JSONException {
downloadFile(param); downloadFile(param);
removeDirectory(param.unzipDirectory); removeDirectory(param.unzipDirectory);
param.unzipDirectory.mkdirs(); param.unzipDirectory.mkdirs();
HashMap<String, ArrayList<File>> copyList = new HashMap<String, ArrayList<File>>(); HashMap<String, ArrayList<File>> copyList = new HashMap<String, ArrayList<File>>();
HashMap<String, ArrayList<File>> copiesv2List = new HashMap<String, ArrayList<File>>();
Boolean isV2 = false;
boolean foundDiff = false; boolean foundDiff = false;
boolean foundBundlePatch = false; boolean foundBundlePatch = false;
@@ -297,29 +331,58 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
JSONObject obj = (JSONObject)new JSONTokener(json).nextValue(); JSONObject obj = (JSONObject)new JSONTokener(json).nextValue();
JSONObject copies = obj.getJSONObject("copies"); JSONObject copies = obj.getJSONObject("copies");
JSONObject copiesv2 = obj.getJSONObject("copiesv2");
Iterator<?> keys = copies.keys(); Iterator<?> keys = copies.keys();
while( keys.hasNext() ) { Iterator<?> keysV2 = copiesv2.keys();
String to = (String)keys.next(); if(keysV2.hasNext()){
String from = copies.getString(to); isV2 = true;
if (from.isEmpty()) { while( keysV2.hasNext() ) {
from = to; String from = (String)keysV2.next();
} String to = copiesv2.getString(from);
ArrayList<File> target = null; if (from.isEmpty()) {
if (!copyList.containsKey(from)) { from = to;
target = new ArrayList<File>(); }
copyList.put(from, target); ArrayList<File> target = null;
} else { if (!copiesv2List.containsKey(from)) {
target = copyList.get((from)); target = new ArrayList<File>();
} copiesv2List.put(from, target);
File toFile = new File(param.unzipDirectory, to); } else {
target = copiesv2List.get((from));
}
File toFile = new File(param.unzipDirectory, to);
// Fixing a Zip Path Traversal Vulnerability // Fixing a Zip Path Traversal Vulnerability
// https://support.google.com/faqs/answer/9294009 // https://support.google.com/faqs/answer/9294009
String canonicalPath = toFile.getCanonicalPath(); String canonicalPath = toFile.getCanonicalPath();
if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) { if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) {
throw new SecurityException("Illegal name: " + to); throw new SecurityException("Illegal name: " + to);
}
target.add(toFile);
}
}else{
while( keys.hasNext() ) {
String to = (String)keys.next();
String from = copies.getString(to);
if (from.isEmpty()) {
from = to;
}
ArrayList<File> target = null;
if (!copyList.containsKey(from)) {
target = new ArrayList<File>();
copyList.put(from, target);
} else {
target = copyList.get((from));
}
File toFile = new File(param.unzipDirectory, to);
// Fixing a Zip Path Traversal Vulnerability
// https://support.google.com/faqs/answer/9294009
String canonicalPath = toFile.getCanonicalPath();
if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) {
throw new SecurityException("Illegal name: " + to);
}
target.add(toFile);
} }
target.add(toFile);
} }
continue; continue;
} }
@@ -348,7 +411,11 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
throw new Error("bundle patch not found"); throw new Error("bundle patch not found");
} }
copyFromResource(copyList); if(isV2){
copyFromResourceV2(copiesv2List);
}else{
copyFromResource(copyList);
}
if (UpdateContext.DEBUG) { if (UpdateContext.DEBUG) {
Log.d("react-native-update", "Unzip finished"); Log.d("react-native-update", "Unzip finished");

Binary file not shown.

View File

@@ -1,6 +1,7 @@
{ {
"meta": { "meta": {
"stableOrder": true "stableOrder": true,
"enableUnifiedLockfile": false
}, },
"lockfileVersion": 3, "lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
@@ -9,7 +10,7 @@
}, },
"packages": { "packages": {
"@rnoh/react-native-openharmony@0.72.38": { "@rnoh/react-native-openharmony@0.72.38": {
"name": "@rnoh/react-native-openharmony", "name": "",
"version": "0.72.38", "version": "0.72.38",
"integrity": "sha512-br5SIrbB0OarSLirenleE7eTOX1lNccMJ7nb/G7qWTyJ7kW4DalmTXVKYpoT2qaOLls1uEE7McD1OjbZZM9jug==", "integrity": "sha512-br5SIrbB0OarSLirenleE7eTOX1lNccMJ7nb/G7qWTyJ7kW4DalmTXVKYpoT2qaOLls1uEE7McD1OjbZZM9jug==",
"resolved": "https://ohpm.openharmony.cn/ohpm/@rnoh/react-native-openharmony/-/react-native-openharmony-0.72.38.har", "resolved": "https://ohpm.openharmony.cn/ohpm/@rnoh/react-native-openharmony/-/react-native-openharmony-0.72.38.har",

View File

@@ -1,5 +1,5 @@
{ {
"license": "ISC", "license": "MIT",
"types": "", "types": "",
"devDependencies": {}, "devDependencies": {},
"name": "pushy", "name": "pushy",
@@ -8,5 +8,6 @@
"version": "3.1.0-0.0.7", "version": "3.1.0-0.0.7",
"dependencies": { "dependencies": {
"@rnoh/react-native-openharmony":"^0.72.38" "@rnoh/react-native-openharmony":"^0.72.38"
} },
"modelVersion": "5.0.0"
} }

View File

@@ -0,0 +1 @@
../../../../../android/jni/HDiffPatch

View File

@@ -0,0 +1 @@
../../../../../android/jni/lzma

View File

@@ -60,13 +60,13 @@ export class DownloadTask {
const exists = fileIo.accessSync(params.targetFile); const exists = fileIo.accessSync(params.targetFile);
if (exists) { if (exists) {
await fileIo.unlink(params.targetFile); await fileIo.unlink(params.targetFile);
}else{ } else {
const targetDir = params.targetFile.substring( const targetDir = params.targetFile.substring(
0, 0,
params.targetFile.lastIndexOf('/'), params.targetFile.lastIndexOf('/'),
); );
const exists = fileIo.accessSync(targetDir); const exists = fileIo.accessSync(targetDir);
if(!exists){ if (!exists) {
await fileIo.mkdir(targetDir); await fileIo.mkdir(targetDir);
} }
} }
@@ -83,7 +83,7 @@ export class DownloadTask {
}, },
}); });
if (response.responseCode > 299) { if (response.responseCode > 299) {
throw new Error(`Server error: ${response.responseCode}`); throw Error(`Server error: ${response.responseCode}`);
} }
const contentLength = parseInt(response.header['content-length'] || '0'); const contentLength = parseInt(response.header['content-length'] || '0');
@@ -108,9 +108,10 @@ export class DownloadTask {
const stats = await fileIo.stat(params.targetFile); const stats = await fileIo.stat(params.targetFile);
const fileSize = stats.size; const fileSize = stats.size;
if (fileSize !== contentLength) { if (fileSize !== contentLength) {
throw new Error(`Download incomplete: expected ${contentLength} bytes but got ${stats.size} bytes`); throw Error(
`Download incomplete: expected ${contentLength} bytes but got ${stats.size} bytes`,
);
} }
} catch (error) { } catch (error) {
console.error('Download failed:', error); console.error('Download failed:', error);
throw error; throw error;
@@ -142,7 +143,7 @@ export class DownloadTask {
bytesRead = await fileIo bytesRead = await fileIo
.read(reader.fd, arrayBuffer) .read(reader.fd, arrayBuffer)
.catch((err: BusinessError) => { .catch((err: BusinessError) => {
throw new Error( throw Error(
`Error reading file: ${err.message}, code: ${err.code}`, `Error reading file: ${err.message}, code: ${err.code}`,
); );
}); });
@@ -154,7 +155,7 @@ export class DownloadTask {
length: bytesRead, length: bytesRead,
}) })
.catch((err: BusinessError) => { .catch((err: BusinessError) => {
throw new Error( throw Error(
`Error writing file: ${err.message}, code: ${err.code}`, `Error writing file: ${err.message}, code: ${err.code}`,
); );
}); });
@@ -295,16 +296,16 @@ export class DownloadTask {
} }
} }
if(fn !== '.DS_Store'){ if (fn !== '.DS_Store') {
await zip.decompressFile(fn, params.unzipDirectory); await zip.decompressFile(fn, params.unzipDirectory);
} }
} }
if (!foundDiff) { if (!foundDiff) {
throw new Error('diff.json not found'); throw Error('diff.json not found');
} }
if (!foundBundlePatch) { if (!foundBundlePatch) {
throw new Error('bundle patch not found'); throw Error('bundle patch not found');
} }
await this.copyFromResource(copyList); await this.copyFromResource(copyList);
} }
@@ -366,12 +367,18 @@ export class DownloadTask {
new Uint8Array(entry.content), new Uint8Array(entry.content),
); );
const outputFile = `${params.unzipDirectory}/bundle.harmony.js`; const outputFile = `${params.unzipDirectory}/bundle.harmony.js`;
const writer = await fileIo.open(outputFile, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY); const writer = await fileIo.open(
outputFile,
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
);
const chunkSize = 4096; const chunkSize = 4096;
let bytesWritten = 0; let bytesWritten = 0;
const totalLength = patched.byteLength; const totalLength = patched.byteLength;
while (bytesWritten < totalLength) { while (bytesWritten < totalLength) {
const chunk = patched.slice(bytesWritten, bytesWritten + chunkSize); const chunk = patched.slice(
bytesWritten,
bytesWritten + chunkSize,
);
await fileIo.write(writer.fd, chunk); await fileIo.write(writer.fd, chunk);
bytesWritten += chunk.byteLength; bytesWritten += chunk.byteLength;
} }
@@ -387,10 +394,10 @@ export class DownloadTask {
} }
if (!foundDiff) { if (!foundDiff) {
throw new Error('diff.json not found'); throw Error('diff.json not found');
} }
if (!foundBundlePatch) { if (!foundBundlePatch) {
throw new Error('bundle patch not found'); throw Error('bundle patch not found');
} }
console.info('Patch from PPK completed'); console.info('Patch from PPK completed');
} }
@@ -478,7 +485,7 @@ export class DownloadTask {
await this.downloadFile(params); await this.downloadFile(params);
break; break;
default: default:
throw new Error(`Unknown task type: ${params.type}`); throw Error(`Unknown task type: ${params.type}`);
} }
params.listener?.onDownloadCompleted(params); params.listener?.onDownloadCompleted(params);

View File

@@ -12,7 +12,7 @@ export class PushyFileJSBundleProvider extends JSBundleProvider {
this.updateContext = new UpdateContext(context); this.updateContext = new UpdateContext(context);
} }
getURL(): string { getURL(): string {
return this.updateContext.getBundleUrl().substring(1); return this.updateContext.getBundleUrl()?.substring(1);
} }
async getBundle(): Promise<ArrayBuffer> { async getBundle(): Promise<ArrayBuffer> {

View File

@@ -9,47 +9,47 @@ import { UpdateModuleImpl } from './UpdateModuleImpl';
import { UpdateContext } from './UpdateContext'; import { UpdateContext } from './UpdateContext';
import { EventHub } from './EventHub'; import { EventHub } from './EventHub';
const TAG = "PushyTurboModule" const TAG = 'PushyTurboModule';
export class PushyTurboModule extends TurboModule { export class PushyTurboModule extends TurboModule {
mUiCtx: common.UIAbilityContext mUiCtx: common.UIAbilityContext;
context: UpdateContext context: UpdateContext;
constructor(protected ctx: TurboModuleContext) { constructor(protected ctx: TurboModuleContext) {
super(ctx); super(ctx);
logger.debug(TAG, ",PushyTurboModule constructor"); logger.debug(TAG, ',PushyTurboModule constructor');
this.mUiCtx = ctx.uiAbilityContext this.mUiCtx = ctx.uiAbilityContext;
this.context = new UpdateContext(this.mUiCtx) this.context = new UpdateContext(this.mUiCtx);
EventHub.getInstance().setRNInstance(ctx.rnInstance) EventHub.getInstance().setRNInstance(ctx.rnInstance);
} }
getConstants(): Object { getConstants(): Object {
logger.debug(TAG, ",call getConstants"); logger.debug(TAG, ',call getConstants');
const context = this.mUiCtx; const context = this.mUiCtx;
const preferencesManager = dataPreferences.getPreferencesSync(context,{ name: 'update' }); const preferencesManager = dataPreferences.getPreferencesSync(context,{ name: 'update' });
const isFirstTime = preferencesManager.getSync("isFirstTime", false) as boolean; const isFirstTime = preferencesManager.getSync('isFirstTime', false) as boolean;
const rolledBackVersion = preferencesManager.getSync("rolledBackVersion", "") as string; const rolledBackVersion = preferencesManager.getSync('rolledBackVersion', '') as string;
const uuid = preferencesManager.getSync("uuid", "") as string; const uuid = preferencesManager.getSync('uuid', '') as string;
const currentVersion = preferencesManager.getSync("currentVersion", "") as string; const currentVersion = preferencesManager.getSync('currentVersion', '') as string;
const currentVersionInfo = this.context.getKv(`hash_${currentVersion}`); const currentVersionInfo = this.context.getKv(`hash_${currentVersion}`);
const buildTime = preferencesManager.getSync("buildTime", "") as string; const buildTime = preferencesManager.getSync('buildTime', '') as string;
const isUsingBundleUrl = this.context.getIsUsingBundleUrl(); const isUsingBundleUrl = this.context.getIsUsingBundleUrl();
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION; let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
let packageVersion = ''; let packageVersion = '';
try { try {
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags); const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
packageVersion = bundleInfo?.versionName || "Unknown" packageVersion = bundleInfo?.versionName || 'Unknown';
} catch (error) { } catch (error) {
console.error("Failed to get bundle info:", error); console.error('Failed to get bundle info:', error);
} }
if (isFirstTime) { if (isFirstTime) {
preferencesManager.deleteSync("isFirstTime"); preferencesManager.deleteSync('isFirstTime');
} }
if (rolledBackVersion) { if (rolledBackVersion) {
preferencesManager.deleteSync("rolledBackVersion"); preferencesManager.deleteSync('rolledBackVersion');
} }
return { return {
@@ -62,12 +62,12 @@ getConstants(): Object {
isFirstTime, isFirstTime,
rolledBackVersion, rolledBackVersion,
uuid, uuid,
} };
} }
setLocalHashInfo(hash: string, info: string): boolean { setLocalHashInfo(hash: string, info: string): boolean {
logger.debug(TAG, ",call setLocalHashInfo"); logger.debug(TAG, ',call setLocalHashInfo');
return UpdateModuleImpl.setLocalHashInfo(this.context, hash, info); return UpdateModuleImpl.setLocalHashInfo(this.context, hash, info);
} }
@@ -76,50 +76,50 @@ getConstants(): Object {
} }
async setUuid(uuid: string): Promise<boolean> { async setUuid(uuid: string): Promise<boolean> {
logger.debug(TAG, `,call setUuid`); logger.debug(TAG, ',call setUuid');
return UpdateModuleImpl.setUuid(this.context,uuid); return UpdateModuleImpl.setUuid(this.context,uuid);
} }
async reloadUpdate(options: { hash: string }): Promise<void> { async reloadUpdate(options: { hash: string }): Promise<void> {
logger.debug(TAG, `,call reloadUpdate`); logger.debug(TAG, ',call reloadUpdate');
return UpdateModuleImpl.reloadUpdate(this.context, this.mUiCtx, options); return UpdateModuleImpl.reloadUpdate(this.context, this.mUiCtx, options);
} }
async setNeedUpdate(options: { hash: string }): Promise<boolean> { async setNeedUpdate(options: { hash: string }): Promise<boolean> {
logger.debug(TAG, `,call setNeedUpdate`); logger.debug(TAG, ',call setNeedUpdate');
return UpdateModuleImpl.setNeedUpdate(this.context, options); return UpdateModuleImpl.setNeedUpdate(this.context, options);
} }
async markSuccess(): Promise<boolean> { async markSuccess(): Promise<boolean> {
logger.debug(TAG, `,call markSuccess`); logger.debug(TAG, ',call markSuccess');
return UpdateModuleImpl.markSuccess(this.context); return UpdateModuleImpl.markSuccess(this.context);
} }
async downloadPatchFromPpk(options: { updateUrl: string; hash: string; originHash: string }): Promise<void> { async downloadPatchFromPpk(options: { updateUrl: string; hash: string; originHash: string }): Promise<void> {
logger.debug(TAG, `,call downloadPatchFromPpk`); logger.debug(TAG, ',call downloadPatchFromPpk');
return UpdateModuleImpl.downloadPatchFromPpk(this.context, options); return UpdateModuleImpl.downloadPatchFromPpk(this.context, options);
} }
async downloadPatchFromPackage(options: { updateUrl: string; hash: string }): Promise<void> { async downloadPatchFromPackage(options: { updateUrl: string; hash: string }): Promise<void> {
logger.debug(TAG, `,call downloadPatchFromPackage`); logger.debug(TAG, ',call downloadPatchFromPackage');
return UpdateModuleImpl.downloadPatchFromPackage(this.context, options); return UpdateModuleImpl.downloadPatchFromPackage(this.context, options);
} }
async downloadFullUpdate(options: { updateUrl: string; hash: string }): Promise<void> { async downloadFullUpdate(options: { updateUrl: string; hash: string }): Promise<void> {
logger.debug(TAG, `,call downloadFullUpdate`); logger.debug(TAG, ',call downloadFullUpdate');
return UpdateModuleImpl.downloadFullUpdate(this.context, options); return UpdateModuleImpl.downloadFullUpdate(this.context, options);
} }
async downloadAndInstallApk(options: { url: string; target: string; hash: string }): Promise<void> { async downloadAndInstallApk(options: { url: string; target: string; hash: string }): Promise<void> {
logger.debug(TAG, `,call downloadAndInstallApk`); logger.debug(TAG, ',call downloadAndInstallApk');
return UpdateModuleImpl.downloadAndInstallApk(this.mUiCtx, options); return UpdateModuleImpl.downloadAndInstallApk(this.mUiCtx, options);
} }
addListener(eventName: string): void { addListener(eventName: string): void {
logger.debug(TAG, `,call addListener`); logger.debug(TAG, ',call addListener');
} }
removeListeners(count: number): void { removeListeners(count: number): void {
logger.debug(TAG, `,call removeListeners`); logger.debug(TAG, ',call removeListeners');
} }
} }

View File

@@ -6,249 +6,276 @@ import common from '@ohos.app.ability.common';
import { DownloadTaskParams } from './DownloadTaskParams'; import { DownloadTaskParams } from './DownloadTaskParams';
export class UpdateContext { export class UpdateContext {
private context: common.UIAbilityContext; private context: common.UIAbilityContext;
private rootDir: string; private rootDir: string;
private preferences: preferences.Preferences; private preferences: preferences.Preferences;
private static DEBUG: boolean = false; private static DEBUG: boolean = false;
private static isUsingBundleUrl: boolean = false; private static isUsingBundleUrl: boolean = false;
constructor(context: common.UIAbilityContext) { constructor(context: common.UIAbilityContext) {
this.context = context; this.context = context;
this.rootDir = context.filesDir + '/_update'; this.rootDir = context.filesDir + '/_update';
try { try {
if (!fileIo.accessSync(this.rootDir)) { if (!fileIo.accessSync(this.rootDir)) {
fileIo.mkdirSync(this.rootDir); fileIo.mkdirSync(this.rootDir);
} }
} catch (e) { } catch (e) {
console.error('Failed to create root directory:', e); console.error('Failed to create root directory:', e);
}
this.initPreferences();
} }
this.initPreferences();
}
private initPreferences() { private initPreferences() {
try { try {
this.preferences = preferences.getPreferencesSync(this.context, {name:'update'}); this.preferences = preferences.getPreferencesSync(this.context, {
const packageVersion = this.getPackageVersion(); name: 'update',
const storedVersion = this.preferences.getSync('packageVersion', ''); });
if(!storedVersion){ const packageVersion = this.getPackageVersion();
this.preferences.putSync('packageVersion', packageVersion); const storedVersion = this.preferences.getSync('packageVersion', '');
this.preferences.flush(); if (!storedVersion) {
} else if (storedVersion && packageVersion !== storedVersion) { this.preferences.putSync('packageVersion', packageVersion);
this.preferences.clear(); this.preferences.flush();
this.preferences.putSync('packageVersion', packageVersion); } else if (storedVersion && packageVersion !== storedVersion) {
this.preferences.flush(); this.preferences.clear();
this.cleanUp(); this.preferences.putSync('packageVersion', packageVersion);
}
} catch (e) {
console.error('Failed to init preferences:', e);
}
}
public setKv(key: string, value: string): void {
this.preferences.putSync(key, value);
this.preferences.flush();
}
public getKv(key: string): string {
return this.preferences.getSync(key, '') as string;
}
public isFirstTime(): boolean {
return this.preferences.getSync('firstTime', false) as boolean;
}
public rolledBackVersion(): string {
return this.preferences.getSync('rolledBackVersion', '') as string;
}
public markSuccess(): void {
this.preferences.putSync('firstTimeOk', true);
const lastVersion = this.preferences.getSync('lastVersion', '') as string;
const curVersion = this.preferences.getSync('currentVersion', '') as string;
if (lastVersion && lastVersion !== curVersion) {
this.preferences.deleteSync('lastVersion');
this.preferences.deleteSync(`hash_${lastVersion}`);
}
this.preferences.flush(); this.preferences.flush();
this.cleanUp(); this.cleanUp();
}
} catch (e) {
console.error('Failed to init preferences:', e);
} }
}
public clearFirstTime(): void { public setKv(key: string, value: string): void {
this.preferences.putSync('firstTime', false); this.preferences.putSync(key, value);
this.preferences.flush(); this.preferences.flush();
this.cleanUp(); }
public getKv(key: string): string {
return this.preferences.getSync(key, '') as string;
}
public isFirstTime(): boolean {
return this.preferences.getSync('firstTime', false) as boolean;
}
public rolledBackVersion(): string {
return this.preferences.getSync('rolledBackVersion', '') as string;
}
public markSuccess(): void {
this.preferences.putSync('firstTimeOk', true);
const lastVersion = this.preferences.getSync('lastVersion', '') as string;
const curVersion = this.preferences.getSync('currentVersion', '') as string;
if (lastVersion && lastVersion !== curVersion) {
this.preferences.deleteSync('lastVersion');
this.preferences.deleteSync(`hash_${lastVersion}`);
} }
this.preferences.flush();
this.cleanUp();
}
public clearRollbackMark(): void { public clearFirstTime(): void {
this.preferences.putSync('rolledBackVersion', null); this.preferences.putSync('firstTime', false);
this.preferences.flush(); this.preferences.flush();
this.cleanUp(); this.cleanUp();
}
public clearRollbackMark(): void {
this.preferences.putSync('rolledBackVersion', null);
this.preferences.flush();
this.cleanUp();
}
public async downloadFullUpdate(
url: string,
hash: string,
listener: DownloadFileListener,
): Promise<void> {
try {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FULL;
params.url = url;
params.hash = hash;
params.listener = listener;
params.targetFile = `${this.rootDir}/${hash}.ppk`;
const downloadTask = new DownloadTask(this.context);
await downloadTask.execute(params);
} catch (e) {
console.error('Failed to download full update:', e);
} }
}
public async downloadFullUpdate(url: string, hash: string, listener: DownloadFileListener): Promise<void> { public async downloadFile(
try { url: string,
const params = new DownloadTaskParams(); hash: string,
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FULL; fileName: string,
params.url = url; listener: DownloadFileListener,
params.hash = hash; ): Promise<void> {
params.listener = listener; const params = new DownloadTaskParams();
params.targetFile = `${this.rootDir}/${hash}.ppk`; params.type = DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD;
const downloadTask = new DownloadTask(this.context); params.url = url;
await downloadTask.execute(params); params.hash = hash;
} catch (e) { params.listener = listener;
console.error('Failed to download full update:', e); params.targetFile = this.rootDir + '/' + fileName;
const downloadTask = new DownloadTask(this.context);
await downloadTask.execute(params);
}
public async downloadPatchFromPpk(
url: string,
hash: string,
originHash: string,
listener: DownloadFileListener,
): Promise<void> {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
params.url = url;
params.hash = hash;
params.originHash = originHash;
params.listener = listener;
params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`;
params.unzipDirectory = `${this.rootDir}/${hash}`;
params.originDirectory = `${this.rootDir}/${params.originHash}`;
const downloadTask = new DownloadTask(this.context);
await downloadTask.execute(params);
}
public async downloadPatchFromPackage(
url: string,
hash: string,
listener: DownloadFileListener,
): Promise<void> {
try {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP;
params.url = url;
params.hash = hash;
params.listener = listener;
params.targetFile = `${this.rootDir}/${hash}.app.patch`;
params.unzipDirectory = `${this.rootDir}/${hash}`;
const downloadTask = new DownloadTask(this.context);
return await downloadTask.execute(params);
} catch (e) {
throw e;
console.error('Failed to download APK patch:', e);
}
}
public switchVersion(hash: string): void {
try {
const bundlePath = `${this.rootDir}/${hash}/bundle.harmony.js`;
if (!fileIo.accessSync(bundlePath)) {
throw Error(`Bundle version ${hash} not found.`);
}
const lastVersion = this.getKv('currentVersion');
this.setKv('currentVersion', hash);
if (lastVersion && lastVersion !== hash) {
this.setKv('lastVersion', lastVersion);
}
this.setKv('firstTime', 'true');
this.setKv('firstTimeOk', 'false');
this.setKv('rolledBackVersion', '');
} catch (e) {
console.error('Failed to switch version:', e);
}
}
public static getBundleUrl(
context: common.UIAbilityContext,
defaultAssetsUrl?: string,
): string {
return new UpdateContext(context).getBundleUrl(defaultAssetsUrl);
}
public getBundleUrl(defaultAssetsUrl?: string): string {
UpdateContext.isUsingBundleUrl = true;
const currentVersion = this.getCurrentVersion();
if (!currentVersion) {
return defaultAssetsUrl;
}
if (!this.isFirstTime()) {
if (!this.preferences.getSync('firstTimeOk', true)) {
return this.rollBack();
}
}
let version = currentVersion;
while (version) {
const bundleFile = `${this.rootDir}/${version}/bundle.harmony.js`;
try {
if (!fileIo.accessSync(bundleFile)) {
console.error(`Bundle version ${version} not found.`);
version = this.rollBack();
continue;
} }
return bundleFile;
} catch (e) {
console.error('Failed to access bundle file:', e);
version = this.rollBack();
}
} }
return defaultAssetsUrl;
}
public async downloadFile(url: string, hash: string, fileName: string, listener: DownloadFileListener): Promise<void> { getPackageVersion(): string {
const params = new DownloadTaskParams(); let bundleFlags =
params.type = DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD; bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
params.url = url; let packageVersion = '';
params.hash = hash; try {
params.listener = listener; const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
params.targetFile = this.rootDir + '/' + fileName; packageVersion = bundleInfo?.versionName || 'Unknown';
} catch (error) {
const downloadTask = new DownloadTask(this.context); console.error('获取包信息失败:', error);
await downloadTask.execute(params);
} }
return packageVersion;
}
public async downloadPatchFromPpk(url: string, hash: string, originHash: string, listener: DownloadFileListener): Promise<void> { public getCurrentVersion(): string {
const params = new DownloadTaskParams(); const currentVersion = this.getKv('currentVersion');
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK; return currentVersion;
params.url = url; }
params.hash = hash;
params.originHash = originHash; private rollBack(): string {
params.listener = listener; const lastVersion = this.preferences.getSync('lastVersion', '') as string;
params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`; const currentVersion = this.preferences.getSync(
params.unzipDirectory = `${this.rootDir}/${hash}`; 'currentVersion',
params.originDirectory = `${this.rootDir}/${params.originHash}`; '',
) as string;
const downloadTask = new DownloadTask(this.context); if (!lastVersion) {
await downloadTask.execute(params); this.preferences.deleteSync('currentVersion');
} else {
this.preferences.putSync('currentVersion', lastVersion);
} }
this.preferences.putSync('firstTimeOk', true);
this.preferences.putSync('firstTime', false);
this.preferences.putSync('rolledBackVersion', currentVersion);
this.preferences.flush();
return lastVersion;
}
public async downloadPatchFromPackage(url: string, hash: string, listener: DownloadFileListener): Promise<void> { private cleanUp(): void {
try { const params = new DownloadTaskParams();
const params = new DownloadTaskParams(); params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP; params.hash = this.preferences.getSync('currentVersion', '') as string;
params.url = url; params.originHash = this.preferences.getSync('lastVersion', '') as string;
params.hash = hash; params.unzipDirectory = this.rootDir;
params.listener = listener; const downloadTask = new DownloadTask(this.context);
params.targetFile = `${this.rootDir}/${hash}.app.patch`; downloadTask.execute(params);
params.unzipDirectory = `${this.rootDir}/${hash}`; }
const downloadTask = new DownloadTask(this.context); public getIsUsingBundleUrl(): boolean {
return await downloadTask.execute(params); return UpdateContext.isUsingBundleUrl;
} catch (e) { }
throw e;
console.error('Failed to download APK patch:', e);
}
}
public switchVersion(hash: string): void {
try {
const bundlePath = `${this.rootDir}/${hash}/bundle.harmony.js`;
if (!fileIo.accessSync(bundlePath)) {
throw new Error(`Bundle version ${hash} not found.`);
}
const lastVersion = this.getKv('currentVersion');
this.setKv('currentVersion', hash);
if (lastVersion && lastVersion !== hash) {
this.setKv('lastVersion', lastVersion);
}
this.setKv('firstTime', 'true');
this.setKv('firstTimeOk', 'false');
this.setKv('rolledBackVersion', "");
} catch (e) {
console.error('Failed to switch version:', e);
}
}
public static getBundleUrl(context: common.UIAbilityContext, defaultAssetsUrl?: string): string {
return new UpdateContext(context).getBundleUrl(defaultAssetsUrl);
}
public getBundleUrl(defaultAssetsUrl?: string): string {
UpdateContext.isUsingBundleUrl = true;
const currentVersion = this.getCurrentVersion();
if (!currentVersion) {
return defaultAssetsUrl;
}
if (!this.isFirstTime()) {
if (!this.preferences.getSync('firstTimeOk', true)) {
return this.rollBack();
}
}
let version = currentVersion;
while (version) {
const bundleFile = `${this.rootDir}/${version}/bundle.harmony.js`;
try {
if (!fileIo.accessSync(bundleFile)) {
console.error(`Bundle version ${version} not found.`);
version = this.rollBack();
continue;
}
return bundleFile;
} catch (e) {
console.error('Failed to access bundle file:', e);
version = this.rollBack();
}
}
return defaultAssetsUrl;
}
getPackageVersion(): string {
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
let packageVersion = '';
try {
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
packageVersion = bundleInfo?.versionName || "Unknown";
} catch (error) {
console.error("获取包信息失败:", error);
}
return packageVersion;
}
public getCurrentVersion() : string {
const currentVersion = this.getKv('currentVersion');
return currentVersion;
}
private rollBack(): string {
const lastVersion = this.preferences.getSync('lastVersion', '') as string;
const currentVersion = this.preferences.getSync('currentVersion', '') as string;
if (!lastVersion) {
this.preferences.deleteSync('currentVersion');
} else {
this.preferences.putSync('currentVersion', lastVersion);
}
this.preferences.putSync('firstTimeOk', true);
this.preferences.putSync('firstTime', false);
this.preferences.putSync('rolledBackVersion', currentVersion);
this.preferences.flush();
return lastVersion;
}
private cleanUp(): void {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
params.hash = this.preferences.getSync('currentVersion', '') as string;
params.originHash = this.preferences.getSync('lastVersion', '') as string;
params.unzipDirectory = this.rootDir;
const downloadTask = new DownloadTask(this.context);
downloadTask.execute(params);
}
public getIsUsingBundleUrl(): boolean {
return UpdateContext.isUsingBundleUrl;
}
} }
export interface DownloadFileListener { export interface DownloadFileListener {
onDownloadCompleted(params: DownloadTaskParams): void; onDownloadCompleted(params: DownloadTaskParams): void;
onDownloadFailed(error: Error): void; onDownloadFailed(error: Error): void;
} }

View File

@@ -4,14 +4,14 @@ import { UpdateContext } from './UpdateContext';
import { DownloadTaskParams } from './DownloadTaskParams'; import { DownloadTaskParams } from './DownloadTaskParams';
import logger from './Logger'; import logger from './Logger';
const TAG = "UpdateModuleImpl"; const TAG = 'UpdateModuleImpl';
export class UpdateModuleImpl { export class UpdateModuleImpl {
static readonly NAME = "Pushy"; static readonly NAME = 'Pushy';
static async downloadFullUpdate( static async downloadFullUpdate(
updateContext: UpdateContext, updateContext: UpdateContext,
options: { updateUrl: string; hash: string } options: { updateUrl: string; hash: string },
): Promise<void> { ): Promise<void> {
try { try {
await updateContext.downloadFullUpdate(options.updateUrl, options.hash, { await updateContext.downloadFullUpdate(options.updateUrl, options.hash, {
@@ -20,7 +20,7 @@ export class UpdateModuleImpl {
}, },
onDownloadFailed: (error: Error) => { onDownloadFailed: (error: Error) => {
return Promise.reject(error); return Promise.reject(error);
} },
}); });
} catch (error) { } catch (error) {
logger.error(TAG, `downloadFullUpdate failed: ${error}`); logger.error(TAG, `downloadFullUpdate failed: ${error}`);
@@ -30,18 +30,18 @@ export class UpdateModuleImpl {
static async downloadAndInstallApk( static async downloadAndInstallApk(
context: common.UIAbilityContext, context: common.UIAbilityContext,
options: { url: string; hash: string; target: string } options: { url: string; hash: string; target: string },
): Promise<void> { ): Promise<void> {
try { try {
const want = { const want = {
action: 'action.system.home', action: 'action.system.home',
parameters: { parameters: {
uri: 'appmarket://details' uri: 'appmarket://details',
} },
}; };
if (!context) { if (!context) {
throw new Error('获取context失败'); throw Error('获取context失败');
} }
await context.startAbility(want); await context.startAbility(want);
@@ -53,17 +53,21 @@ export class UpdateModuleImpl {
static async downloadPatchFromPackage( static async downloadPatchFromPackage(
updateContext: UpdateContext, updateContext: UpdateContext,
options: { updateUrl: string; hash: string } options: { updateUrl: string; hash: string },
): Promise<void> { ): Promise<void> {
try { try {
return await updateContext.downloadPatchFromPackage(options.updateUrl, options.hash, { return await updateContext.downloadPatchFromPackage(
onDownloadCompleted: (params: DownloadTaskParams) => { options.updateUrl,
return Promise.resolve(); options.hash,
{
onDownloadCompleted: (params: DownloadTaskParams) => {
return Promise.resolve();
},
onDownloadFailed: (error: Error) => {
return Promise.reject(error);
},
}, },
onDownloadFailed: (error: Error) => { );
return Promise.reject(error);
}
});
} catch (error) { } catch (error) {
logger.error(TAG, `downloadPatchFromPackage failed: ${error}`); logger.error(TAG, `downloadPatchFromPackage failed: ${error}`);
throw error; throw error;
@@ -72,7 +76,7 @@ export class UpdateModuleImpl {
static async downloadPatchFromPpk( static async downloadPatchFromPpk(
updateContext: UpdateContext, updateContext: UpdateContext,
options: { updateUrl: string; hash: string; originHash: string } options: { updateUrl: string; hash: string; originHash: string },
): Promise<void> { ): Promise<void> {
try { try {
await updateContext.downloadPatchFromPpk( await updateContext.downloadPatchFromPpk(
@@ -85,49 +89,49 @@ export class UpdateModuleImpl {
}, },
onDownloadFailed: (error: Error) => { onDownloadFailed: (error: Error) => {
return Promise.reject(error); return Promise.reject(error);
} },
} },
); );
} catch (error) { } catch (error) {
logger.error(TAG, `downloadPatchFromPpk failed: ${error}`); logger.error(TAG, `downloadPatchFromPpk failed: ${error}`);
throw new Error(`执行报错: ${error.message}`); throw Error(`执行报错: ${error.message}`);
} }
} }
static async reloadUpdate( static async reloadUpdate(
updateContext: UpdateContext, updateContext: UpdateContext,
context: common.UIAbilityContext, context: common.UIAbilityContext,
options: { hash: string } options: { hash: string },
): Promise<void> { ): Promise<void> {
const hash = options.hash; const hash = options.hash;
if (!hash) { if (!hash) {
throw new Error('hash不能为空'); throw Error('hash不能为空');
} }
try { try {
await updateContext.switchVersion(hash); await updateContext.switchVersion(hash);
const bundleInfo = await bundleManager.getBundleInfoForSelf( const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION,
); );
await context.terminateSelf(); await context.terminateSelf();
const want = { const want = {
bundleName: bundleInfo.name, bundleName: bundleInfo.name,
abilityName: context.abilityInfo?.name abilityName: context.abilityInfo?.name,
}; };
await context.startAbility(want); await context.startAbility(want);
} catch (error) { } catch (error) {
logger.error(TAG, `reloadUpdate failed: ${error}`); logger.error(TAG, `reloadUpdate failed: ${error}`);
throw new Error(`pushy:switchVersion failed ${error.message}`); throw Error(`pushy:switchVersion failed ${error.message}`);
} }
} }
static async setNeedUpdate( static async setNeedUpdate(
updateContext: UpdateContext, updateContext: UpdateContext,
options: { hash: string } options: { hash: string },
): Promise<boolean> { ): Promise<boolean> {
const hash = options.hash; const hash = options.hash;
if (!hash) { if (!hash) {
throw new Error('hash不能为空'); throw Error('hash不能为空');
} }
try { try {
@@ -135,7 +139,7 @@ export class UpdateModuleImpl {
return true; return true;
} catch (error) { } catch (error) {
logger.error(TAG, `setNeedUpdate failed: ${error}`); logger.error(TAG, `setNeedUpdate failed: ${error}`);
throw new Error(`switchVersionLater failed: ${error.message}`); throw Error(`switchVersionLater failed: ${error.message}`);
} }
} }
@@ -145,20 +149,20 @@ export class UpdateModuleImpl {
return true; return true;
} catch (error) { } catch (error) {
logger.error(TAG, `markSuccess failed: ${error}`); logger.error(TAG, `markSuccess failed: ${error}`);
throw new Error(`执行报错: ${error.message}`); throw Error(`执行报错: ${error.message}`);
} }
} }
static async setUuid( static async setUuid(
updateContext: UpdateContext, updateContext: UpdateContext,
uuid: string uuid: string,
): Promise<boolean> { ): Promise<boolean> {
try { try {
await updateContext.setKv('uuid', uuid); await updateContext.setKv('uuid', uuid);
return true; return true;
} catch (error) { } catch (error) {
logger.error(TAG, `setUuid failed: ${error}`); logger.error(TAG, `setUuid failed: ${error}`);
throw new Error(`执行报错: ${error.message}`); throw Error(`执行报错: ${error.message}`);
} }
} }
@@ -174,17 +178,14 @@ export class UpdateModuleImpl {
static setLocalHashInfo( static setLocalHashInfo(
updateContext: UpdateContext, updateContext: UpdateContext,
hash: string, hash: string,
info: string info: string,
): boolean { ): boolean {
updateContext.setKv(`hash_${hash}`, info); updateContext.setKv(`hash_${hash}`, info);
return true; return true;
} }
static getLocalHashInfo( static getLocalHashInfo(updateContext: UpdateContext, hash: string): string {
updateContext: UpdateContext,
hash: string
): string {
const value = updateContext.getKv(`hash_${hash}`); const value = updateContext.getKv(`hash_${hash}`);
return value; return value;
} }
} }

View File

@@ -178,7 +178,9 @@ RCT_EXPORT_MODULE(RCTPushy);
NSDictionary *pushyInfo = [defaults dictionaryForKey:keyPushyInfo]; NSDictionary *pushyInfo = [defaults dictionaryForKey:keyPushyInfo];
NSString *currentVersion = [pushyInfo objectForKey:paramCurrentVersion]; NSString *currentVersion = [pushyInfo objectForKey:paramCurrentVersion];
ret[@"currentVersion"] = currentVersion; ret[@"currentVersion"] = currentVersion;
ret[@"currentVersionInfo"] = [defaults objectForKey:[keyHashInfo stringByAppendingString:currentVersion]]; if (currentVersion != nil) {
ret[@"currentVersionInfo"] = [defaults objectForKey:[keyHashInfo stringByAppendingString:currentVersion]];
}
// clear isFirstTimemarked // clear isFirstTimemarked
if (ret[@"isFirstTime"]) { if (ret[@"isFirstTime"]) {

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-native-update", "name": "react-native-update",
"version": "10.31.1", "version": "10.34.1",
"description": "react-native hot update", "description": "react-native hot update",
"main": "src/index", "main": "src/index",
"scripts": { "scripts": {
@@ -9,7 +9,7 @@
"lint": "eslint \"src/*.@(ts|tsx|js|jsx)\" && tsc --noEmit", "lint": "eslint \"src/*.@(ts|tsx|js|jsx)\" && tsc --noEmit",
"submodule": "git submodule update --init --recursive", "submodule": "git submodule update --init --recursive",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build-lib": "bun submodule && $ANDROID_HOME/ndk/20.1.5948944/ndk-build NDK_PROJECT_PATH=android APP_BUILD_SCRIPT=android/jni/Android.mk NDK_APPLICATION_MK=android/jni/Application.mk NDK_LIBS_OUT=android/lib", "build:so": "bun submodule && $ANDROID_HOME/ndk/28.2.13676358/ndk-build NDK_PROJECT_PATH=android APP_BUILD_SCRIPT=android/jni/Android.mk NDK_APPLICATION_MK=android/jni/Application.mk NDK_LIBS_OUT=android/lib",
"build:ios-debug": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.debug", "build:ios-debug": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.debug",
"build:ios-release": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.release", "build:ios-release": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.release",
"test:ios-debug": "cd Example/testHotUpdate && detox test --configuration ios.sim.debug", "test:ios-debug": "cd Example/testHotUpdate && detox test --configuration ios.sim.debug",
@@ -72,6 +72,5 @@
"react-native": "0.73", "react-native": "0.73",
"ts-jest": "^29.3.2", "ts-jest": "^29.3.2",
"typescript": "^5.6.3" "typescript": "^5.6.3"
}, }
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
} }

View File

@@ -8,7 +8,7 @@ import {
buildTime, buildTime,
cInfo, cInfo,
currentVersion, currentVersion,
getCurrentVersionInfo, currentVersionInfo,
isFirstTime, isFirstTime,
isRolledBack, isRolledBack,
packageVersion, packageVersion,
@@ -93,7 +93,6 @@ export class Pushy {
clientType: 'Pushy' | 'Cresc' = 'Pushy'; clientType: 'Pushy' | 'Cresc' = 'Pushy';
lastChecking?: number; lastChecking?: number;
lastRespJson?: Promise<CheckResult>; lastRespJson?: Promise<CheckResult>;
lastRespText?: Promise<string>;
version = cInfo.rnu; version = cInfo.rnu;
loggerPromise = (() => { loggerPromise = (() => {
@@ -116,7 +115,7 @@ export class Pushy {
if (Platform.OS === 'ios' || Platform.OS === 'android') { if (Platform.OS === 'ios' || Platform.OS === 'android') {
if (!options.appKey) { if (!options.appKey) {
throw new Error(i18n.t('error_appkey_required')); throw Error(i18n.t('error_appkey_required'));
} }
} }
@@ -164,7 +163,6 @@ export class Pushy {
log(type + ' ' + message); log(type + ' ' + message);
await this.loggerPromise.promise; await this.loggerPromise.promise;
const { logger = noop, appKey } = this.options; const { logger = noop, appKey } = this.options;
const info = await getCurrentVersionInfo();
const overridePackageVersion = this.options.overridePackageVersion; const overridePackageVersion = this.options.overridePackageVersion;
logger({ logger({
type, type,
@@ -176,7 +174,7 @@ export class Pushy {
overridePackageVersion, overridePackageVersion,
buildTime, buildTime,
message, message,
...info, ...currentVersionInfo,
...data, ...data,
}, },
}); });
@@ -293,10 +291,10 @@ export class Pushy {
), ),
); );
} catch (err: any) { } catch (err: any) {
this.throwIfEnabled(new Error('errorCheckingUseBackup')); this.throwIfEnabled(Error('errorCheckingUseBackup'));
} }
} else { } else {
this.throwIfEnabled(new Error('errorCheckingGetBackup')); this.throwIfEnabled(Error('errorCheckingGetBackup'));
} }
} }
if (!resp) { if (!resp) {
@@ -304,21 +302,21 @@ export class Pushy {
type: 'errorChecking', type: 'errorChecking',
message: this.t('error_cannot_connect_server'), message: this.t('error_cannot_connect_server'),
}); });
this.throwIfEnabled(new Error('errorChecking')); this.throwIfEnabled(Error('errorChecking'));
return this.lastRespJson ? await this.lastRespJson : emptyObj; return this.lastRespJson ? await this.lastRespJson : emptyObj;
} }
if (resp.status !== 200) { if (!resp.ok) {
const respText = await resp.text();
const errorMessage = this.t('error_http_status', { const errorMessage = this.t('error_http_status', {
status: resp.status, status: resp.status,
statusText: resp.statusText, statusText: respText,
}); });
this.report({ this.report({
type: 'errorChecking', type: 'errorChecking',
message: errorMessage, message: errorMessage,
}); });
this.throwIfEnabled(new Error(errorMessage)); this.throwIfEnabled(Error(errorMessage));
log('error checking response:', resp.status, await resp.text());
return this.lastRespJson ? await this.lastRespJson : emptyObj; return this.lastRespJson ? await this.lastRespJson : emptyObj;
} }
this.lastRespJson = resp.json(); this.lastRespJson = resp.json();
@@ -435,7 +433,7 @@ export class Pushy {
message: e.message, message: e.message,
}); });
errorMessages.push(errorMessage); errorMessages.push(errorMessage);
lastError = new Error(errorMessage); lastError = Error(errorMessage);
log(errorMessage); log(errorMessage);
} }
} }
@@ -454,7 +452,7 @@ export class Pushy {
message: e.message, message: e.message,
}); });
errorMessages.push(errorMessage); errorMessages.push(errorMessage);
lastError = new Error(errorMessage); lastError = Error(errorMessage);
log(errorMessage); log(errorMessage);
} }
} }
@@ -474,7 +472,7 @@ export class Pushy {
message: e.message, message: e.message,
}); });
errorMessages.push(errorMessage); errorMessages.push(errorMessage);
lastError = new Error(errorMessage); lastError = Error(errorMessage);
log(errorMessage); log(errorMessage);
} }
} else if (__DEV__) { } else if (__DEV__) {
@@ -532,7 +530,7 @@ export class Pushy {
} }
if (sharedState.apkStatus === 'downloaded') { if (sharedState.apkStatus === 'downloaded') {
this.report({ type: 'errorInstallApk' }); this.report({ type: 'errorInstallApk' });
this.throwIfEnabled(new Error('errorInstallApk')); this.throwIfEnabled(Error('errorInstallApk'));
return; return;
} }
if (Platform.Version <= 23) { if (Platform.Version <= 23) {
@@ -542,7 +540,7 @@ export class Pushy {
); );
if (granted !== PermissionsAndroid.RESULTS.GRANTED) { if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
this.report({ type: 'rejectStoragePermission' }); this.report({ type: 'rejectStoragePermission' });
this.throwIfEnabled(new Error('rejectStoragePermission')); this.throwIfEnabled(Error('rejectStoragePermission'));
return; return;
} }
} catch (e: any) { } catch (e: any) {
@@ -575,7 +573,7 @@ export class Pushy {
}).catch(() => { }).catch(() => {
sharedState.apkStatus = null; sharedState.apkStatus = null;
this.report({ type: 'errorDownloadAndInstallApk' }); this.report({ type: 'errorDownloadAndInstallApk' });
this.throwIfEnabled(new Error('errorDownloadAndInstallApk')); this.throwIfEnabled(Error('errorDownloadAndInstallApk'));
}); });
sharedState.apkStatus = 'downloaded'; sharedState.apkStatus = 'downloaded';
if (sharedState.progressHandlers[progressKey]) { if (sharedState.progressHandlers[progressKey]) {

View File

@@ -18,7 +18,7 @@ export const PushyModule =
export const UpdateModule = PushyModule; export const UpdateModule = PushyModule;
if (!PushyModule) { if (!PushyModule) {
throw new Error( throw Error(
'Failed to load react-native-update native module, please try to recompile', 'Failed to load react-native-update native module, please try to recompile',
); );
} }

View File

@@ -16,9 +16,9 @@ import { Pushy, Cresc, sharedState } from './client';
import { currentVersion, packageVersion, getCurrentVersionInfo, currentVersionInfo } from './core'; import { currentVersion, packageVersion, getCurrentVersionInfo, currentVersionInfo } from './core';
import { import {
CheckResult, CheckResult,
MixedCheckResult,
ProgressData, ProgressData,
UpdateTestPayload, UpdateTestPayload,
VersionInfo,
} from './type'; } from './type';
import { UpdateContext } from './context'; import { UpdateContext } from './context';
import { URL } from 'react-native-url-polyfill'; import { URL } from 'react-native-url-polyfill';
@@ -163,7 +163,7 @@ export const UpdateProvider = ({
return; return;
} }
lastChecking.current = now; lastChecking.current = now;
let rootInfo: MixedCheckResult | undefined; let rootInfo: CheckResult | undefined;
try { try {
rootInfo = await client.checkUpdate(extra); rootInfo = await client.checkUpdate(extra);
} catch (e: any) { } catch (e: any) {
@@ -175,12 +175,12 @@ export const UpdateProvider = ({
if (!rootInfo) { if (!rootInfo) {
return; return;
} }
const versions = rootInfo.versions || [rootInfo as CheckResult]; const versions = [rootInfo.expVersion, rootInfo].filter(Boolean) as VersionInfo[];
delete rootInfo.versions; delete rootInfo.expVersion;
for (const versionInfo of versions) { for (const versionInfo of versions) {
const info: CheckResult = { const info: CheckResult = {
...versionInfo,
...rootInfo, ...rootInfo,
...versionInfo,
}; };
const rollout = info.config?.rollout?.[packageVersion]; const rollout = info.config?.rollout?.[packageVersion];
if (info.update && rollout) { if (info.update && rollout) {
@@ -242,7 +242,7 @@ export const UpdateProvider = ({
alertUpdate( alertUpdate(
client.t('alert_title'), client.t('alert_title'),
client.t('alert_new_version_found', { client.t('alert_new_version_found', {
name: info.name, name: info.name!,
description: info.description, description: info.description,
}), }),
[ [

View File

@@ -24,14 +24,10 @@ interface RootResult {
paths?: string[]; paths?: string[];
} }
export type CheckResult = RootResult & VersionInfo; export type CheckResult = RootResult &
Partial<VersionInfo> & {
export type CheckResultV2 = RootResult & { expVersion?: VersionInfo;
versions?: VersionInfo[]; };
};
export type MixedCheckResult = CheckResult | CheckResultV2;
export interface ProgressData { export interface ProgressData {
hash: string; hash: string;

View File

@@ -17,7 +17,7 @@ export function promiseAny<T>(promises: Promise<T>[]) {
.catch(() => { .catch(() => {
count++; count++;
if (count === promises.length) { if (count === promises.length) {
reject(new Error(i18n.t('error_all_promises_rejected'))); reject(Error(i18n.t('error_all_promises_rejected')));
} }
}); });
}); });
@@ -51,7 +51,7 @@ const ping = isWeb
return finalUrl; return finalUrl;
} }
log('ping failed', url, status, statusText); log('ping failed', url, status, statusText);
throw new Error(i18n.t('error_ping_failed')); throw Error(i18n.t('error_ping_failed'));
}) })
.catch(e => { .catch(e => {
pingFinished = true; pingFinished = true;
@@ -60,7 +60,7 @@ const ping = isWeb
}), }),
new Promise((_, reject) => new Promise((_, reject) =>
setTimeout(() => { setTimeout(() => {
reject(new Error(i18n.t('error_ping_timeout'))); reject(Error(i18n.t('error_ping_timeout')));
if (!pingFinished) { if (!pingFinished) {
log('ping timeout', url); log('ping timeout', url);
} }
@@ -115,7 +115,7 @@ export const enhancedFetch = async (
if (r.ok) { if (r.ok) {
return r; return r;
} }
throw new Error( throw Error(
i18n.t('error_http_status', { i18n.t('error_http_status', {
status: r.status, status: r.status,
statusText: r.statusText, statusText: r.statusText,