mirror of
https://gitcode.com/gh_mirrors/re/react-native-pushy.git
synced 2025-11-22 23:46:10 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d9ae57a5f | ||
|
|
2502935fc0 | ||
|
|
8e6d9bf460 | ||
|
|
897f334343 | ||
|
|
33bc69c3fb | ||
|
|
5028ce31be | ||
|
|
d3a4007763 | ||
|
|
a4f3e3cc38 | ||
|
|
9d51128ed3 | ||
|
|
eddb072927 | ||
|
|
4f9417d620 | ||
|
|
6f2314d3c9 |
@@ -12,7 +12,7 @@
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-safe-area-context": "^5.6.1",
|
||||
"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",
|
||||
},
|
||||
"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-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=="],
|
||||
|
||||
|
||||
@@ -37,8 +37,14 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>For taking photos</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>For saving photos</string>
|
||||
<key>RCTNewArchEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
@@ -53,10 +59,5 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>For taking photos</string>
|
||||
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>For saving photos</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-safe-area-context": "^5.6.1",
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
/* eslint-disable react-native/no-inline-styles */
|
||||
import React, {useRef, useState} from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
Platform,
|
||||
@@ -19,15 +19,14 @@ import {
|
||||
Modal,
|
||||
Portal,
|
||||
} from 'react-native-paper';
|
||||
import {Camera} from 'react-native-camera-kit';
|
||||
import {LocalSvg} from 'react-native-svg/css';
|
||||
|
||||
import { Camera } from 'react-native-camera-kit';
|
||||
import { LocalSvg } from 'react-native-svg/css';
|
||||
|
||||
import TestConsole from './TestConsole';
|
||||
|
||||
import _updateConfig from '../update.json';
|
||||
import {UpdateProvider, Pushy, Cresc, useUpdate} from 'react-native-update';
|
||||
const {appKey} = _updateConfig[Platform.OS];
|
||||
import { UpdateProvider, Pushy, Cresc, useUpdate } from 'react-native-update';
|
||||
const { appKey } = _updateConfig[Platform.OS];
|
||||
|
||||
function App() {
|
||||
const {
|
||||
@@ -54,8 +53,8 @@ function App() {
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>欢迎使用Pushy热更新服务</Text>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Text style={styles.welcome}>欢迎22使用Pushy热更新服务</Text>
|
||||
<View style={{ flexDirection: 'row' }}>
|
||||
<Text>
|
||||
{useDefaultAlert ? '当前使用' : '当前不使用'}默认的alert更新提示
|
||||
</Text>
|
||||
@@ -74,9 +73,9 @@ function App() {
|
||||
<Portal>
|
||||
<Modal visible={showCamera} onDismiss={() => setShowCamera(false)}>
|
||||
<Camera
|
||||
style={{minHeight: 320}}
|
||||
style={{ minHeight: 320 }}
|
||||
scanBarcode={true}
|
||||
onReadCode={({nativeEvent: {codeStringValue}}) => {
|
||||
onReadCode={({ nativeEvent: { codeStringValue } }) => {
|
||||
// 防止重复扫码
|
||||
if (lastParsedCode.current === codeStringValue) {
|
||||
return;
|
||||
@@ -94,7 +93,7 @@ function App() {
|
||||
/>
|
||||
</Modal>
|
||||
</Portal>
|
||||
<View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
|
||||
<Text>png:</Text>
|
||||
<Image
|
||||
resizeMode={'contain'}
|
||||
@@ -102,11 +101,11 @@ function App() {
|
||||
style={styles.image}
|
||||
/>
|
||||
</View>
|
||||
<View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
|
||||
<Text>svg:</Text>
|
||||
<LocalSvg
|
||||
asset={require('./assets/react-logo.svg')}
|
||||
style={{width: 30, height: 30}}
|
||||
style={{ width: 30, height: 30 }}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.instructions}>
|
||||
@@ -124,16 +123,18 @@ function App() {
|
||||
onPress={() => {
|
||||
checkUpdate();
|
||||
setShowUpdateSnackbar(true);
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Text style={styles.instructions}>点击这里检查更新</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
testID="testcase"
|
||||
style={{marginTop: 15}}
|
||||
style={{ marginTop: 15 }}
|
||||
onLongPress={() => {
|
||||
setShowTestConsole(true);
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Text style={styles.instructions}>
|
||||
react-native-update版本:{client?.version}
|
||||
</Text>
|
||||
@@ -155,14 +156,15 @@ function App() {
|
||||
await downloadUpdate();
|
||||
setShowUpdateBanner(true);
|
||||
},
|
||||
}}>
|
||||
<Text style={{color: 'white'}}>
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: 'white' }}>
|
||||
有新版本({updateInfo.name})可用,是否更新?
|
||||
</Text>
|
||||
</Snackbar>
|
||||
)}
|
||||
<Banner
|
||||
style={{width: '100%', position: 'absolute', top: 0}}
|
||||
style={{ width: '100%', position: 'absolute', top: 0 }}
|
||||
visible={showUpdateBanner}
|
||||
actions={[
|
||||
{
|
||||
@@ -177,9 +179,10 @@ function App() {
|
||||
},
|
||||
},
|
||||
]}
|
||||
icon={({size}) => (
|
||||
icon={({ size }) => (
|
||||
<Icon name="checkcircleo" size={size} color="#00f" />
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
更新已完成,是否立即重启?
|
||||
</Banner>
|
||||
</View>
|
||||
|
||||
27
android/proguard.pro
vendored
27
android/proguard.pro
vendored
@@ -10,28 +10,9 @@
|
||||
-keepnames class com.facebook.react.devsupport.** { *; }
|
||||
|
||||
# Keep fields used in reflection
|
||||
-keepclassmembers class com.facebook.react.ReactInstanceManager {
|
||||
private JSBundleLoader mBundleLoader;
|
||||
private String mJSBundleFile;
|
||||
}
|
||||
-keepclassmembers class com.facebook.react.ReactActivity { *; }
|
||||
-keepclassmembers class com.facebook.react.ReactInstanceManager { *; }
|
||||
-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 { *; }
|
||||
|
||||
# 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);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.HashMap;
|
||||
|
||||
import okio.BufferedSink;
|
||||
@@ -198,6 +199,10 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
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 {
|
||||
File[] files = from.listFiles();
|
||||
for (File file : files) {
|
||||
@@ -273,12 +278,41 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
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 {
|
||||
downloadFile(param);
|
||||
|
||||
removeDirectory(param.unzipDirectory);
|
||||
param.unzipDirectory.mkdirs();
|
||||
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 foundBundlePatch = false;
|
||||
@@ -297,7 +331,35 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
JSONObject obj = (JSONObject)new JSONTokener(json).nextValue();
|
||||
|
||||
JSONObject copies = obj.getJSONObject("copies");
|
||||
JSONObject copiesv2 = obj.getJSONObject("copiesv2");
|
||||
Iterator<?> keys = copies.keys();
|
||||
Iterator<?> keysV2 = copiesv2.keys();
|
||||
if(keysV2.hasNext()){
|
||||
isV2 = true;
|
||||
while( keysV2.hasNext() ) {
|
||||
String from = (String)keysV2.next();
|
||||
String to = copiesv2.getString(from);
|
||||
if (from.isEmpty()) {
|
||||
from = to;
|
||||
}
|
||||
ArrayList<File> target = null;
|
||||
if (!copiesv2List.containsKey(from)) {
|
||||
target = new ArrayList<File>();
|
||||
copiesv2List.put(from, target);
|
||||
} else {
|
||||
target = copiesv2List.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);
|
||||
}
|
||||
}else{
|
||||
while( keys.hasNext() ) {
|
||||
String to = (String)keys.next();
|
||||
String from = copies.getString(to);
|
||||
@@ -321,6 +383,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
}
|
||||
target.add(toFile);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (fn.equals("index.bundlejs.patch")) {
|
||||
@@ -348,7 +411,11 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
throw new Error("bundle patch not found");
|
||||
}
|
||||
|
||||
if(isV2){
|
||||
copyFromResourceV2(copiesv2List);
|
||||
}else{
|
||||
copyFromResource(copyList);
|
||||
}
|
||||
|
||||
if (UpdateContext.DEBUG) {
|
||||
Log.d("react-native-update", "Unzip finished");
|
||||
|
||||
@@ -60,13 +60,13 @@ export class DownloadTask {
|
||||
const exists = fileIo.accessSync(params.targetFile);
|
||||
if (exists) {
|
||||
await fileIo.unlink(params.targetFile);
|
||||
}else{
|
||||
} else {
|
||||
const targetDir = params.targetFile.substring(
|
||||
0,
|
||||
params.targetFile.lastIndexOf('/'),
|
||||
);
|
||||
const exists = fileIo.accessSync(targetDir);
|
||||
if(!exists){
|
||||
if (!exists) {
|
||||
await fileIo.mkdir(targetDir);
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export class DownloadTask {
|
||||
},
|
||||
});
|
||||
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');
|
||||
@@ -108,9 +108,10 @@ export class DownloadTask {
|
||||
const stats = await fileIo.stat(params.targetFile);
|
||||
const fileSize = stats.size;
|
||||
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) {
|
||||
console.error('Download failed:', error);
|
||||
throw error;
|
||||
@@ -142,7 +143,7 @@ export class DownloadTask {
|
||||
bytesRead = await fileIo
|
||||
.read(reader.fd, arrayBuffer)
|
||||
.catch((err: BusinessError) => {
|
||||
throw new Error(
|
||||
throw Error(
|
||||
`Error reading file: ${err.message}, code: ${err.code}`,
|
||||
);
|
||||
});
|
||||
@@ -154,7 +155,7 @@ export class DownloadTask {
|
||||
length: bytesRead,
|
||||
})
|
||||
.catch((err: BusinessError) => {
|
||||
throw new Error(
|
||||
throw Error(
|
||||
`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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundDiff) {
|
||||
throw new Error('diff.json not found');
|
||||
throw Error('diff.json not found');
|
||||
}
|
||||
if (!foundBundlePatch) {
|
||||
throw new Error('bundle patch not found');
|
||||
throw Error('bundle patch not found');
|
||||
}
|
||||
await this.copyFromResource(copyList);
|
||||
}
|
||||
@@ -366,12 +367,18 @@ export class DownloadTask {
|
||||
new Uint8Array(entry.content),
|
||||
);
|
||||
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;
|
||||
let bytesWritten = 0;
|
||||
const totalLength = patched.byteLength;
|
||||
while (bytesWritten < totalLength) {
|
||||
const chunk = patched.slice(bytesWritten, bytesWritten + chunkSize);
|
||||
const chunk = patched.slice(
|
||||
bytesWritten,
|
||||
bytesWritten + chunkSize,
|
||||
);
|
||||
await fileIo.write(writer.fd, chunk);
|
||||
bytesWritten += chunk.byteLength;
|
||||
}
|
||||
@@ -387,10 +394,10 @@ export class DownloadTask {
|
||||
}
|
||||
|
||||
if (!foundDiff) {
|
||||
throw new Error('diff.json not found');
|
||||
throw Error('diff.json not found');
|
||||
}
|
||||
if (!foundBundlePatch) {
|
||||
throw new Error('bundle patch not found');
|
||||
throw Error('bundle patch not found');
|
||||
}
|
||||
console.info('Patch from PPK completed');
|
||||
}
|
||||
@@ -478,7 +485,7 @@ export class DownloadTask {
|
||||
await this.downloadFile(params);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown task type: ${params.type}`);
|
||||
throw Error(`Unknown task type: ${params.type}`);
|
||||
}
|
||||
|
||||
params.listener?.onDownloadCompleted(params);
|
||||
|
||||
@@ -28,10 +28,12 @@ export class UpdateContext {
|
||||
|
||||
private initPreferences() {
|
||||
try {
|
||||
this.preferences = preferences.getPreferencesSync(this.context, {name:'update'});
|
||||
this.preferences = preferences.getPreferencesSync(this.context, {
|
||||
name: 'update',
|
||||
});
|
||||
const packageVersion = this.getPackageVersion();
|
||||
const storedVersion = this.preferences.getSync('packageVersion', '');
|
||||
if(!storedVersion){
|
||||
if (!storedVersion) {
|
||||
this.preferences.putSync('packageVersion', packageVersion);
|
||||
this.preferences.flush();
|
||||
} else if (storedVersion && packageVersion !== storedVersion) {
|
||||
@@ -87,7 +89,11 @@ export class UpdateContext {
|
||||
this.cleanUp();
|
||||
}
|
||||
|
||||
public async downloadFullUpdate(url: string, hash: string, listener: DownloadFileListener): Promise<void> {
|
||||
public async downloadFullUpdate(
|
||||
url: string,
|
||||
hash: string,
|
||||
listener: DownloadFileListener,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const params = new DownloadTaskParams();
|
||||
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FULL;
|
||||
@@ -102,7 +108,12 @@ export class UpdateContext {
|
||||
}
|
||||
}
|
||||
|
||||
public async downloadFile(url: string, hash: string, fileName: string, listener: DownloadFileListener): Promise<void> {
|
||||
public async downloadFile(
|
||||
url: string,
|
||||
hash: string,
|
||||
fileName: string,
|
||||
listener: DownloadFileListener,
|
||||
): Promise<void> {
|
||||
const params = new DownloadTaskParams();
|
||||
params.type = DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD;
|
||||
params.url = url;
|
||||
@@ -114,7 +125,12 @@ export class UpdateContext {
|
||||
await downloadTask.execute(params);
|
||||
}
|
||||
|
||||
public async downloadPatchFromPpk(url: string, hash: string, originHash: string, listener: DownloadFileListener): Promise<void> {
|
||||
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;
|
||||
@@ -129,7 +145,11 @@ export class UpdateContext {
|
||||
await downloadTask.execute(params);
|
||||
}
|
||||
|
||||
public async downloadPatchFromPackage(url: string, hash: string, listener: DownloadFileListener): Promise<void> {
|
||||
public async downloadPatchFromPackage(
|
||||
url: string,
|
||||
hash: string,
|
||||
listener: DownloadFileListener,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const params = new DownloadTaskParams();
|
||||
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP;
|
||||
@@ -151,7 +171,7 @@ export class UpdateContext {
|
||||
try {
|
||||
const bundlePath = `${this.rootDir}/${hash}/bundle.harmony.js`;
|
||||
if (!fileIo.accessSync(bundlePath)) {
|
||||
throw new Error(`Bundle version ${hash} not found.`);
|
||||
throw Error(`Bundle version ${hash} not found.`);
|
||||
}
|
||||
|
||||
const lastVersion = this.getKv('currentVersion');
|
||||
@@ -162,13 +182,16 @@ export class UpdateContext {
|
||||
|
||||
this.setKv('firstTime', 'true');
|
||||
this.setKv('firstTimeOk', 'false');
|
||||
this.setKv('rolledBackVersion', "");
|
||||
this.setKv('rolledBackVersion', '');
|
||||
} catch (e) {
|
||||
console.error('Failed to switch version:', e);
|
||||
}
|
||||
}
|
||||
|
||||
public static getBundleUrl(context: common.UIAbilityContext, defaultAssetsUrl?: string): string {
|
||||
public static getBundleUrl(
|
||||
context: common.UIAbilityContext,
|
||||
defaultAssetsUrl?: string,
|
||||
): string {
|
||||
return new UpdateContext(context).getBundleUrl(defaultAssetsUrl);
|
||||
}
|
||||
|
||||
@@ -202,25 +225,29 @@ export class UpdateContext {
|
||||
}
|
||||
|
||||
getPackageVersion(): string {
|
||||
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
|
||||
let bundleFlags =
|
||||
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
|
||||
let packageVersion = '';
|
||||
try {
|
||||
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
|
||||
packageVersion = bundleInfo?.versionName || "Unknown";
|
||||
packageVersion = bundleInfo?.versionName || 'Unknown';
|
||||
} catch (error) {
|
||||
console.error("获取包信息失败:", error);
|
||||
console.error('获取包信息失败:', error);
|
||||
}
|
||||
return packageVersion;
|
||||
}
|
||||
|
||||
public getCurrentVersion() : string {
|
||||
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;
|
||||
const currentVersion = this.preferences.getSync(
|
||||
'currentVersion',
|
||||
'',
|
||||
) as string;
|
||||
if (!lastVersion) {
|
||||
this.preferences.deleteSync('currentVersion');
|
||||
} else {
|
||||
|
||||
@@ -4,14 +4,14 @@ import { UpdateContext } from './UpdateContext';
|
||||
import { DownloadTaskParams } from './DownloadTaskParams';
|
||||
import logger from './Logger';
|
||||
|
||||
const TAG = "UpdateModuleImpl";
|
||||
const TAG = 'UpdateModuleImpl';
|
||||
|
||||
export class UpdateModuleImpl {
|
||||
static readonly NAME = "Pushy";
|
||||
static readonly NAME = 'Pushy';
|
||||
|
||||
static async downloadFullUpdate(
|
||||
updateContext: UpdateContext,
|
||||
options: { updateUrl: string; hash: string }
|
||||
options: { updateUrl: string; hash: string },
|
||||
): Promise<void> {
|
||||
try {
|
||||
await updateContext.downloadFullUpdate(options.updateUrl, options.hash, {
|
||||
@@ -20,7 +20,7 @@ export class UpdateModuleImpl {
|
||||
},
|
||||
onDownloadFailed: (error: Error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(TAG, `downloadFullUpdate failed: ${error}`);
|
||||
@@ -30,18 +30,18 @@ export class UpdateModuleImpl {
|
||||
|
||||
static async downloadAndInstallApk(
|
||||
context: common.UIAbilityContext,
|
||||
options: { url: string; hash: string; target: string }
|
||||
options: { url: string; hash: string; target: string },
|
||||
): Promise<void> {
|
||||
try {
|
||||
const want = {
|
||||
action: 'action.system.home',
|
||||
parameters: {
|
||||
uri: 'appmarket://details'
|
||||
}
|
||||
uri: 'appmarket://details',
|
||||
},
|
||||
};
|
||||
|
||||
if (!context) {
|
||||
throw new Error('获取context失败');
|
||||
throw Error('获取context失败');
|
||||
}
|
||||
|
||||
await context.startAbility(want);
|
||||
@@ -53,17 +53,21 @@ export class UpdateModuleImpl {
|
||||
|
||||
static async downloadPatchFromPackage(
|
||||
updateContext: UpdateContext,
|
||||
options: { updateUrl: string; hash: string }
|
||||
options: { updateUrl: string; hash: string },
|
||||
): Promise<void> {
|
||||
try {
|
||||
return await updateContext.downloadPatchFromPackage(options.updateUrl, options.hash, {
|
||||
return await updateContext.downloadPatchFromPackage(
|
||||
options.updateUrl,
|
||||
options.hash,
|
||||
{
|
||||
onDownloadCompleted: (params: DownloadTaskParams) => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
onDownloadFailed: (error: Error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(TAG, `downloadPatchFromPackage failed: ${error}`);
|
||||
throw error;
|
||||
@@ -72,7 +76,7 @@ export class UpdateModuleImpl {
|
||||
|
||||
static async downloadPatchFromPpk(
|
||||
updateContext: UpdateContext,
|
||||
options: { updateUrl: string; hash: string; originHash: string }
|
||||
options: { updateUrl: string; hash: string; originHash: string },
|
||||
): Promise<void> {
|
||||
try {
|
||||
await updateContext.downloadPatchFromPpk(
|
||||
@@ -85,49 +89,49 @@ export class UpdateModuleImpl {
|
||||
},
|
||||
onDownloadFailed: (error: Error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(TAG, `downloadPatchFromPpk failed: ${error}`);
|
||||
throw new Error(`执行报错: ${error.message}`);
|
||||
throw Error(`执行报错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
static async reloadUpdate(
|
||||
updateContext: UpdateContext,
|
||||
context: common.UIAbilityContext,
|
||||
options: { hash: string }
|
||||
options: { hash: string },
|
||||
): Promise<void> {
|
||||
const hash = options.hash;
|
||||
if (!hash) {
|
||||
throw new Error('hash不能为空');
|
||||
throw Error('hash不能为空');
|
||||
}
|
||||
|
||||
try {
|
||||
await updateContext.switchVersion(hash);
|
||||
const bundleInfo = await bundleManager.getBundleInfoForSelf(
|
||||
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION
|
||||
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION,
|
||||
);
|
||||
await context.terminateSelf();
|
||||
const want = {
|
||||
bundleName: bundleInfo.name,
|
||||
abilityName: context.abilityInfo?.name
|
||||
abilityName: context.abilityInfo?.name,
|
||||
};
|
||||
await context.startAbility(want);
|
||||
} catch (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(
|
||||
updateContext: UpdateContext,
|
||||
options: { hash: string }
|
||||
options: { hash: string },
|
||||
): Promise<boolean> {
|
||||
const hash = options.hash;
|
||||
if (!hash) {
|
||||
throw new Error('hash不能为空');
|
||||
throw Error('hash不能为空');
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -135,7 +139,7 @@ export class UpdateModuleImpl {
|
||||
return true;
|
||||
} catch (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;
|
||||
} catch (error) {
|
||||
logger.error(TAG, `markSuccess failed: ${error}`);
|
||||
throw new Error(`执行报错: ${error.message}`);
|
||||
throw Error(`执行报错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
static async setUuid(
|
||||
updateContext: UpdateContext,
|
||||
uuid: string
|
||||
uuid: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await updateContext.setKv('uuid', uuid);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(TAG, `setUuid failed: ${error}`);
|
||||
throw new Error(`执行报错: ${error.message}`);
|
||||
throw Error(`执行报错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,16 +178,13 @@ export class UpdateModuleImpl {
|
||||
static setLocalHashInfo(
|
||||
updateContext: UpdateContext,
|
||||
hash: string,
|
||||
info: string
|
||||
info: string,
|
||||
): boolean {
|
||||
updateContext.setKv(`hash_${hash}`, info);
|
||||
return true;
|
||||
}
|
||||
|
||||
static getLocalHashInfo(
|
||||
updateContext: UpdateContext,
|
||||
hash: string
|
||||
): string {
|
||||
static getLocalHashInfo(updateContext: UpdateContext, hash: string): string {
|
||||
const value = updateContext.getKv(`hash_${hash}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -178,7 +178,9 @@ RCT_EXPORT_MODULE(RCTPushy);
|
||||
NSDictionary *pushyInfo = [defaults dictionaryForKey:keyPushyInfo];
|
||||
NSString *currentVersion = [pushyInfo objectForKey:paramCurrentVersion];
|
||||
ret[@"currentVersion"] = currentVersion;
|
||||
ret[@"currentVersionInfo"] = [pushyInfo objectForKey:[keyHashInfo stringByAppendingString:currentVersion]];
|
||||
if (currentVersion != nil) {
|
||||
ret[@"currentVersionInfo"] = [defaults objectForKey:[keyHashInfo stringByAppendingString:currentVersion]];
|
||||
}
|
||||
|
||||
// clear isFirstTimemarked
|
||||
if (ret[@"isFirstTime"]) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-update",
|
||||
"version": "10.31.0",
|
||||
"version": "10.33.0",
|
||||
"description": "react-native hot update",
|
||||
"main": "src/index",
|
||||
"scripts": {
|
||||
@@ -72,6 +72,5 @@
|
||||
"react-native": "0.73",
|
||||
"ts-jest": "^29.3.2",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,6 @@ export class Pushy {
|
||||
clientType: 'Pushy' | 'Cresc' = 'Pushy';
|
||||
lastChecking?: number;
|
||||
lastRespJson?: Promise<CheckResult>;
|
||||
lastRespText?: Promise<string>;
|
||||
|
||||
version = cInfo.rnu;
|
||||
loggerPromise = (() => {
|
||||
@@ -116,7 +115,7 @@ export class Pushy {
|
||||
|
||||
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
||||
if (!options.appKey) {
|
||||
throw new Error(i18n.t('error_appkey_required'));
|
||||
throw Error(i18n.t('error_appkey_required'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,10 +292,10 @@ export class Pushy {
|
||||
),
|
||||
);
|
||||
} catch (err: any) {
|
||||
this.throwIfEnabled(new Error('errorCheckingUseBackup'));
|
||||
this.throwIfEnabled(Error('errorCheckingUseBackup'));
|
||||
}
|
||||
} else {
|
||||
this.throwIfEnabled(new Error('errorCheckingGetBackup'));
|
||||
this.throwIfEnabled(Error('errorCheckingGetBackup'));
|
||||
}
|
||||
}
|
||||
if (!resp) {
|
||||
@@ -304,21 +303,21 @@ export class Pushy {
|
||||
type: 'errorChecking',
|
||||
message: this.t('error_cannot_connect_server'),
|
||||
});
|
||||
this.throwIfEnabled(new Error('errorChecking'));
|
||||
this.throwIfEnabled(Error('errorChecking'));
|
||||
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', {
|
||||
status: resp.status,
|
||||
statusText: resp.statusText,
|
||||
statusText: respText,
|
||||
});
|
||||
this.report({
|
||||
type: 'errorChecking',
|
||||
message: errorMessage,
|
||||
});
|
||||
this.throwIfEnabled(new Error(errorMessage));
|
||||
log('error checking response:', resp.status, await resp.text());
|
||||
this.throwIfEnabled(Error(errorMessage));
|
||||
return this.lastRespJson ? await this.lastRespJson : emptyObj;
|
||||
}
|
||||
this.lastRespJson = resp.json();
|
||||
@@ -435,7 +434,7 @@ export class Pushy {
|
||||
message: e.message,
|
||||
});
|
||||
errorMessages.push(errorMessage);
|
||||
lastError = new Error(errorMessage);
|
||||
lastError = Error(errorMessage);
|
||||
log(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -454,7 +453,7 @@ export class Pushy {
|
||||
message: e.message,
|
||||
});
|
||||
errorMessages.push(errorMessage);
|
||||
lastError = new Error(errorMessage);
|
||||
lastError = Error(errorMessage);
|
||||
log(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -474,7 +473,7 @@ export class Pushy {
|
||||
message: e.message,
|
||||
});
|
||||
errorMessages.push(errorMessage);
|
||||
lastError = new Error(errorMessage);
|
||||
lastError = Error(errorMessage);
|
||||
log(errorMessage);
|
||||
}
|
||||
} else if (__DEV__) {
|
||||
@@ -532,7 +531,7 @@ export class Pushy {
|
||||
}
|
||||
if (sharedState.apkStatus === 'downloaded') {
|
||||
this.report({ type: 'errorInstallApk' });
|
||||
this.throwIfEnabled(new Error('errorInstallApk'));
|
||||
this.throwIfEnabled(Error('errorInstallApk'));
|
||||
return;
|
||||
}
|
||||
if (Platform.Version <= 23) {
|
||||
@@ -542,7 +541,7 @@ export class Pushy {
|
||||
);
|
||||
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
|
||||
this.report({ type: 'rejectStoragePermission' });
|
||||
this.throwIfEnabled(new Error('rejectStoragePermission'));
|
||||
this.throwIfEnabled(Error('rejectStoragePermission'));
|
||||
return;
|
||||
}
|
||||
} catch (e: any) {
|
||||
@@ -575,7 +574,7 @@ export class Pushy {
|
||||
}).catch(() => {
|
||||
sharedState.apkStatus = null;
|
||||
this.report({ type: 'errorDownloadAndInstallApk' });
|
||||
this.throwIfEnabled(new Error('errorDownloadAndInstallApk'));
|
||||
this.throwIfEnabled(Error('errorDownloadAndInstallApk'));
|
||||
});
|
||||
sharedState.apkStatus = 'downloaded';
|
||||
if (sharedState.progressHandlers[progressKey]) {
|
||||
|
||||
@@ -18,7 +18,7 @@ export const PushyModule =
|
||||
export const UpdateModule = PushyModule;
|
||||
|
||||
if (!PushyModule) {
|
||||
throw new Error(
|
||||
throw Error(
|
||||
'Failed to load react-native-update native module, please try to recompile',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ import { Pushy, Cresc, sharedState } from './client';
|
||||
import { currentVersion, packageVersion, getCurrentVersionInfo, currentVersionInfo } from './core';
|
||||
import {
|
||||
CheckResult,
|
||||
MixedCheckResult,
|
||||
ProgressData,
|
||||
UpdateTestPayload,
|
||||
VersionInfo,
|
||||
} from './type';
|
||||
import { UpdateContext } from './context';
|
||||
import { URL } from 'react-native-url-polyfill';
|
||||
@@ -163,7 +163,7 @@ export const UpdateProvider = ({
|
||||
return;
|
||||
}
|
||||
lastChecking.current = now;
|
||||
let rootInfo: MixedCheckResult | undefined;
|
||||
let rootInfo: CheckResult | undefined;
|
||||
try {
|
||||
rootInfo = await client.checkUpdate(extra);
|
||||
} catch (e: any) {
|
||||
@@ -175,8 +175,8 @@ export const UpdateProvider = ({
|
||||
if (!rootInfo) {
|
||||
return;
|
||||
}
|
||||
const versions = rootInfo.versions || [rootInfo as CheckResult];
|
||||
delete rootInfo.versions;
|
||||
const versions = [rootInfo.expVersion, rootInfo].filter(Boolean) as VersionInfo[];
|
||||
delete rootInfo.expVersion;
|
||||
for (const versionInfo of versions) {
|
||||
const info: CheckResult = {
|
||||
...versionInfo,
|
||||
@@ -242,7 +242,7 @@ export const UpdateProvider = ({
|
||||
alertUpdate(
|
||||
client.t('alert_title'),
|
||||
client.t('alert_new_version_found', {
|
||||
name: info.name,
|
||||
name: info.name!,
|
||||
description: info.description,
|
||||
}),
|
||||
[
|
||||
|
||||
12
src/type.ts
12
src/type.ts
@@ -24,14 +24,10 @@ interface RootResult {
|
||||
paths?: string[];
|
||||
}
|
||||
|
||||
export type CheckResult = RootResult & VersionInfo;
|
||||
|
||||
export type CheckResultV2 = RootResult & {
|
||||
versions?: VersionInfo[];
|
||||
};
|
||||
|
||||
export type MixedCheckResult = CheckResult | CheckResultV2;
|
||||
|
||||
export type CheckResult = RootResult &
|
||||
Partial<VersionInfo> & {
|
||||
expVersion?: VersionInfo;
|
||||
};
|
||||
|
||||
export interface ProgressData {
|
||||
hash: string;
|
||||
|
||||
@@ -17,7 +17,7 @@ export function promiseAny<T>(promises: Promise<T>[]) {
|
||||
.catch(() => {
|
||||
count++;
|
||||
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;
|
||||
}
|
||||
log('ping failed', url, status, statusText);
|
||||
throw new Error(i18n.t('error_ping_failed'));
|
||||
throw Error(i18n.t('error_ping_failed'));
|
||||
})
|
||||
.catch(e => {
|
||||
pingFinished = true;
|
||||
@@ -60,7 +60,7 @@ const ping = isWeb
|
||||
}),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => {
|
||||
reject(new Error(i18n.t('error_ping_timeout')));
|
||||
reject(Error(i18n.t('error_ping_timeout')));
|
||||
if (!pingFinished) {
|
||||
log('ping timeout', url);
|
||||
}
|
||||
@@ -115,7 +115,7 @@ export const enhancedFetch = async (
|
||||
if (r.ok) {
|
||||
return r;
|
||||
}
|
||||
throw new Error(
|
||||
throw Error(
|
||||
i18n.t('error_http_status', {
|
||||
status: r.status,
|
||||
statusText: r.statusText,
|
||||
|
||||
Reference in New Issue
Block a user