mirror of
https://gitcode.com/gh_mirrors/re/react-native-pushy.git
synced 2025-11-22 23:46:10 +08:00
Compare commits
27 Commits
v9.0.0-bet
...
fix/zipsli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8622935bdf | ||
|
|
b747b1f356 | ||
|
|
7752581470 | ||
|
|
33eb89d2a7 | ||
|
|
d111bf5a9c | ||
|
|
23346a5f1d | ||
|
|
5aca2104c2 | ||
|
|
fe0a05db3d | ||
|
|
2b287786ff | ||
|
|
7d128900cd | ||
|
|
189e3ec78e | ||
|
|
821722165a | ||
|
|
6cb53ac655 | ||
|
|
7b9a24168a | ||
|
|
c6354bbedc | ||
|
|
b53878c291 | ||
|
|
ab01312f8d | ||
|
|
44784b6d3e | ||
|
|
30c21fed91 | ||
|
|
15af7802ad | ||
|
|
8bed6ef979 | ||
|
|
7d03dc24b7 | ||
|
|
18849ee441 | ||
|
|
3ba1df2020 | ||
|
|
7152ef7304 | ||
|
|
094eb3f267 | ||
|
|
6d01ce5152 |
@@ -1,5 +1,5 @@
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import "RCTPushy.h"
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTRootView.h>
|
||||
@@ -87,7 +87,7 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot";
|
||||
#if DEBUG
|
||||
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
|
||||
#else
|
||||
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
return [RCTPushy bundleURL];
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,19 @@
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
@@ -40,18 +53,5 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
1. 基于阿里云高速 CDN 分发,对比其他服务器在国外的热更新服务,分发更稳定,更新成功率极高。
|
||||
2. 基于 bsdiff/hdiff 算法创建的**超小更新包**,通常版本迭代后在几十 KB 级别(其他全量热更新服务所需流量通常在几十 MB 级别)。
|
||||
3. 始终跟进 RN 最新正式版本,第一时间提供支持。支持 hermes 字节码格式。(暂不支持新架构,会待其相对稳定后跟进)
|
||||
3. 始终跟进 RN 最新正式版本,第一时间提供支持。支持 hermes 字节码格式。支持新架构。
|
||||
4. 跨越多个版本进行更新时,只需要下载**一个更新包**,不需要逐版本依次更新。
|
||||
5. 命令行工具 & 网页双端管理,版本发布过程简单便捷,完全可以集成 CI。
|
||||
6. 支持崩溃回滚,安全可靠。
|
||||
|
||||
1
android/proguard.pro
vendored
1
android/proguard.pro
vendored
@@ -1,2 +1,3 @@
|
||||
-keepnames class cn.reactnative.modules.update.DownloadTask { *; }
|
||||
-keepnames class cn.reactnative.modules.update.UpdateModuleImpl { *; }
|
||||
-keepnames class com.facebook.react.ReactInstanceManager { *; }
|
||||
@@ -1,9 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cn.reactnative.modules.update">
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<application>
|
||||
<meta-data android:name="pushy_build_time" android:value="@string/pushy_build_time" />
|
||||
<provider
|
||||
|
||||
@@ -237,19 +237,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry ze = entries.nextElement();
|
||||
|
||||
String fn = ze.getName();
|
||||
File fmd = new File(param.unzipDirectory, fn);
|
||||
|
||||
if (UpdateContext.DEBUG) {
|
||||
Log.d("RNUpdate", "Unzipping " + fn);
|
||||
}
|
||||
|
||||
if (ze.isDirectory()) {
|
||||
fmd.mkdirs();
|
||||
continue;
|
||||
}
|
||||
|
||||
zipFile.unzipToFile(ze, fmd);
|
||||
zipFile.unzipToPath(ze, param.unzipDirectory);
|
||||
}
|
||||
|
||||
zipFile.close();
|
||||
@@ -324,8 +312,15 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
} else {
|
||||
target = copyList.get((from));
|
||||
}
|
||||
target.add(new File(param.unzipDirectory, to));
|
||||
//copyFromResource(from, new File(param.unzipDirectory, to));
|
||||
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);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -339,18 +334,9 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
fout.close();
|
||||
continue;
|
||||
}
|
||||
File fmd = new File(param.unzipDirectory, fn);
|
||||
|
||||
if (UpdateContext.DEBUG) {
|
||||
Log.d("RNUpdate", "Unzipping " + fn);
|
||||
}
|
||||
|
||||
if (ze.isDirectory()) {
|
||||
fmd.mkdirs();
|
||||
continue;
|
||||
}
|
||||
|
||||
zipFile.unzipToFile(ze, fmd);
|
||||
zipFile.unzipToPath(ze, param.unzipDirectory);
|
||||
}
|
||||
|
||||
zipFile.close();
|
||||
@@ -419,18 +405,8 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
fout.close();
|
||||
continue;
|
||||
}
|
||||
File fmd = new File(param.unzipDirectory, fn);
|
||||
|
||||
if (UpdateContext.DEBUG) {
|
||||
Log.d("RNUpdate", "Unzipping " + fn);
|
||||
}
|
||||
|
||||
if (ze.isDirectory()) {
|
||||
fmd.mkdirs();
|
||||
continue;
|
||||
}
|
||||
|
||||
zipFile.unzipToFile(ze, fmd);
|
||||
zipFile.unzipToPath(ze, param.unzipDirectory);
|
||||
}
|
||||
|
||||
zipFile.close();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.reactnative.modules.update;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
@@ -10,12 +12,15 @@ import java.util.Enumeration;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
|
||||
public class SafeZipFile extends ZipFile {
|
||||
|
||||
public SafeZipFile(File file) throws IOException {
|
||||
super(file);
|
||||
}
|
||||
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
@Override
|
||||
public Enumeration<? extends ZipEntry> entries() {
|
||||
return new SafeZipEntryIterator(super.entries());
|
||||
@@ -43,40 +48,46 @@ public class SafeZipFile extends ZipFile {
|
||||
* avoid ZipperDown
|
||||
*/
|
||||
if (null != name && (name.contains("../") || name.contains("..\\"))) {
|
||||
throw new SecurityException("illegal entry: " + entry.getName());
|
||||
throw new SecurityException("illegal entry: " + name);
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
public void unzipToFile(ZipEntry entry, File output) throws IOException {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = getInputStream(entry);
|
||||
writeOutInputStream(output, inputStream);
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
public void unzipToPath(ZipEntry ze, File targetPath) throws IOException {
|
||||
String name = ze.getName();
|
||||
File target = new File(targetPath, name);
|
||||
|
||||
// Fixing a Zip Path Traversal Vulnerability
|
||||
// https://support.google.com/faqs/answer/9294009
|
||||
String canonicalPath = target.getCanonicalPath();
|
||||
if (!canonicalPath.startsWith(targetPath.getCanonicalPath() + File.separator)) {
|
||||
throw new SecurityException("Illegal name: " + name);
|
||||
}
|
||||
|
||||
if (UpdateContext.DEBUG) {
|
||||
Log.d("RNUpdate", "Unzipping " + name);
|
||||
}
|
||||
|
||||
if (ze.isDirectory()) {
|
||||
target.mkdirs();
|
||||
return;
|
||||
}
|
||||
unzipToFile(ze, target);
|
||||
}
|
||||
|
||||
public void unzipToFile(ZipEntry ze, File target) throws IOException {
|
||||
try (InputStream inputStream = getInputStream(ze)) {
|
||||
try (BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(target));
|
||||
BufferedInputStream input = new BufferedInputStream(inputStream)) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int n;
|
||||
while ((n = input.read(buffer, 0, BUFFER_SIZE)) >= 0) {
|
||||
output.write(buffer, 0, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeOutInputStream(File file, InputStream inputStream) throws IOException {
|
||||
BufferedOutputStream output = null;
|
||||
try {
|
||||
output = new BufferedOutputStream(
|
||||
new FileOutputStream(file));
|
||||
BufferedInputStream input = new BufferedInputStream(inputStream);
|
||||
byte b[] = new byte[8192];
|
||||
int n;
|
||||
while ((n = input.read(b, 0, 8192)) >= 0) {
|
||||
output.write(b, 0, n);
|
||||
}
|
||||
} finally {
|
||||
if (output != null) {
|
||||
output.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,45 @@
|
||||
/**
|
||||
* @format
|
||||
* @flow strict-local
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport';
|
||||
import { TurboModuleRegistry } from 'react-native';
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
getConstants: () => {
|
||||
downloadRootDir: string,
|
||||
packageVersion: string,
|
||||
currentVersion: string,
|
||||
isFirstTime: boolean,
|
||||
rolledBackVersion: string,
|
||||
buildTime: string,
|
||||
blockUpdate: Object,
|
||||
uuid: string,
|
||||
isUsingBundleUrl: boolean,
|
||||
downloadRootDir: string;
|
||||
packageVersion: string;
|
||||
currentVersion: string;
|
||||
isFirstTime: boolean;
|
||||
rolledBackVersion: string;
|
||||
buildTime: string;
|
||||
blockUpdate: Object;
|
||||
uuid: string;
|
||||
isUsingBundleUrl: boolean;
|
||||
};
|
||||
setLocalHashInfo(hash: string, info: string): Promise<void>;
|
||||
getLocalHashInfo(hash: string): Promise<string>;
|
||||
setUuid(uuid: string): Promise<void>;
|
||||
setBlockUpdate(options: { reason: string, until: number }): Promise<void>;
|
||||
setBlockUpdate(options: { reason: string; until: number }): Promise<void>;
|
||||
reloadUpdate(options: { hash: string }): Promise<void>;
|
||||
setNeedUpdate(options: { hash: string }): Promise<void>;
|
||||
markSuccess(): Promise<void>;
|
||||
downloadPatchFromPpk(options: {
|
||||
updateUrl: string,
|
||||
hash: string,
|
||||
originHash: string,
|
||||
updateUrl: string;
|
||||
hash: string;
|
||||
originHash: string;
|
||||
}): Promise<void>;
|
||||
downloadPatchFromPackage(options: {
|
||||
updateUrl: string,
|
||||
hash: string,
|
||||
updateUrl: string;
|
||||
hash: string;
|
||||
}): Promise<void>;
|
||||
downloadFullUpdate(options: {
|
||||
updateUrl: string,
|
||||
hash: string,
|
||||
updateUrl: string;
|
||||
hash: string;
|
||||
}): Promise<void>;
|
||||
downloadAndInstallApk(options: {
|
||||
url: string,
|
||||
target: string,
|
||||
hash: string,
|
||||
url: string;
|
||||
target: string;
|
||||
hash: string;
|
||||
}): Promise<void>;
|
||||
addListener(eventName: string): void;
|
||||
removeListeners(count: number): void;
|
||||
}
|
||||
|
||||
export default (TurboModuleRegistry.get<Spec>('Pushy'): ?Spec);
|
||||
export default TurboModuleRegistry.get<Spec>('Pushy') as Spec | null;
|
||||
@@ -1,75 +1,26 @@
|
||||
import { logger } from './utils';
|
||||
|
||||
let currentEndpoint = 'https://update.react-native.cn/api';
|
||||
let backupEndpoints: string[] = ['https://update.reactnative.cn/api'];
|
||||
let backupEndpointsQueryUrl: string | null = null;
|
||||
|
||||
function ping(url: string, rejectImmediate?: boolean) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = (e) => {
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
if (xhr.status === 200) {
|
||||
resolve(url);
|
||||
} else {
|
||||
rejectImmediate ? reject() : setTimeout(reject, 5000);
|
||||
}
|
||||
};
|
||||
xhr.open('HEAD', url);
|
||||
xhr.send();
|
||||
xhr.timeout = 5000;
|
||||
xhr.ontimeout = reject;
|
||||
});
|
||||
}
|
||||
|
||||
function logger(...args: any[]) {
|
||||
console.log('Pushy: ', ...args);
|
||||
}
|
||||
|
||||
let backupEndpoints: string[] = [];
|
||||
let backupEndpointsQueryUrl: string | null =
|
||||
'https://cdn.jsdelivr.net/gh/reactnativecn/react-native-pushy@master/endpoints.json';
|
||||
|
||||
export async function tryBackupEndpoints() {
|
||||
if (!backupEndpoints.length && !backupEndpointsQueryUrl) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ping(getStatusUrl(), true);
|
||||
logger('current endpoint ok', currentEndpoint);
|
||||
return;
|
||||
} catch (e) {
|
||||
logger('current endpoint failed', currentEndpoint);
|
||||
}
|
||||
if (!backupEndpoints.length && backupEndpointsQueryUrl) {
|
||||
export async function updateBackupEndpoints() {
|
||||
if (backupEndpointsQueryUrl) {
|
||||
try {
|
||||
const resp = await fetch(backupEndpointsQueryUrl);
|
||||
backupEndpoints = await resp.json();
|
||||
logger('get remote endpoints:', backupEndpoints);
|
||||
const remoteEndpoints = await resp.json();
|
||||
if (Array.isArray(remoteEndpoints)) {
|
||||
backupEndpoints = Array.from(
|
||||
new Set([...backupEndpoints, ...remoteEndpoints]),
|
||||
);
|
||||
logger('fetch remote endpoints:', remoteEndpoints);
|
||||
logger('merged backup endpoints:', backupEndpoints);
|
||||
}
|
||||
} catch (e) {
|
||||
logger('get remote endpoints failed');
|
||||
return;
|
||||
logger('fetch remote endpoints failed');
|
||||
}
|
||||
}
|
||||
await pickFatestAvailableEndpoint();
|
||||
}
|
||||
|
||||
async function pickFatestAvailableEndpoint(endpoints = backupEndpoints) {
|
||||
const fastestEndpoint = await Promise.race(
|
||||
endpoints.map(pingAndReturnEndpoint),
|
||||
);
|
||||
if (typeof fastestEndpoint === 'string') {
|
||||
logger(`pick endpoint: ${fastestEndpoint}`);
|
||||
currentEndpoint = fastestEndpoint;
|
||||
} else {
|
||||
logger('all remote endpoints failed');
|
||||
}
|
||||
}
|
||||
|
||||
async function pingAndReturnEndpoint(endpoint = currentEndpoint) {
|
||||
return ping(getStatusUrl(endpoint)).then(() => endpoint);
|
||||
}
|
||||
|
||||
function getStatusUrl(endpoint = currentEndpoint) {
|
||||
return `${endpoint}/status`;
|
||||
return backupEndpoints;
|
||||
}
|
||||
|
||||
export function getCheckUrl(APPKEY, endpoint = currentEndpoint) {
|
||||
@@ -95,7 +46,6 @@ export function setCustomEndpoints({
|
||||
backupEndpointsQueryUrl = null;
|
||||
if (Array.isArray(backups) && backups.length > 0) {
|
||||
backupEndpoints = backups;
|
||||
pickFatestAvailableEndpoint();
|
||||
}
|
||||
if (typeof backupQueryUrl === 'string') {
|
||||
backupEndpointsQueryUrl = backupQueryUrl;
|
||||
|
||||
@@ -14,5 +14,5 @@ export const markSuccess = noop;
|
||||
export const downloadAndInstallApk = noop;
|
||||
export const setCustomEndpoints = noop;
|
||||
export const getCurrentVersionInfo = noop;
|
||||
export const simpleUpdate = noop;
|
||||
export const onEvents = noop;
|
||||
export const simpleUpdate = (app) => app;
|
||||
export const onPushyEvents = noop;
|
||||
|
||||
107
lib/main.ts
107
lib/main.ts
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
tryBackupEndpoints,
|
||||
updateBackupEndpoints,
|
||||
getCheckUrl,
|
||||
setCustomEndpoints,
|
||||
} from './endpoint';
|
||||
@@ -10,11 +10,13 @@ import {
|
||||
PermissionsAndroid,
|
||||
} from 'react-native';
|
||||
import {
|
||||
CheckResult,
|
||||
EventType,
|
||||
ProgressData,
|
||||
UpdateAvailableResult,
|
||||
UpdateEventsListener,
|
||||
} from './type';
|
||||
import { assertRelease, logger } from './utils';
|
||||
export { setCustomEndpoints };
|
||||
const {
|
||||
version: v,
|
||||
@@ -73,14 +75,10 @@ if (!uuid) {
|
||||
PushyModule.setUuid(uuid);
|
||||
}
|
||||
|
||||
function logger(...args: string[]) {
|
||||
console.log('Pushy: ', ...args);
|
||||
}
|
||||
|
||||
const noop = () => {};
|
||||
let reporter: UpdateEventsListener = noop;
|
||||
|
||||
export function onEvents(customReporter: UpdateEventsListener) {
|
||||
export function onPushyEvents(customReporter: UpdateEventsListener) {
|
||||
reporter = customReporter;
|
||||
if (isRolledBack) {
|
||||
report({
|
||||
@@ -124,63 +122,78 @@ export const cInfo = {
|
||||
uuid,
|
||||
};
|
||||
|
||||
function assertRelease() {
|
||||
// @ts-expect-error
|
||||
if (__DEV__) {
|
||||
throw new Error('react-native-update 只能在 RELEASE 版本中运行.');
|
||||
}
|
||||
}
|
||||
|
||||
let checkingThrottling = false;
|
||||
export async function checkUpdate(APPKEY: string, isRetry?: boolean) {
|
||||
let lastChecking;
|
||||
const empty = {};
|
||||
let lastResult: CheckResult;
|
||||
export async function checkUpdate(APPKEY: string) {
|
||||
assertRelease();
|
||||
if (checkingThrottling) {
|
||||
logger('repeated checking, ignored');
|
||||
return;
|
||||
const now = Date.now();
|
||||
if (lastResult && lastChecking && now - lastChecking < 1000 * 60) {
|
||||
// logger('repeated checking, ignored');
|
||||
return lastResult;
|
||||
}
|
||||
checkingThrottling = true;
|
||||
setTimeout(() => {
|
||||
checkingThrottling = false;
|
||||
}, 3000);
|
||||
lastChecking = now;
|
||||
if (blockUpdate && blockUpdate.until > Date.now() / 1000) {
|
||||
return report({
|
||||
report({
|
||||
type: 'errorChecking',
|
||||
message: `热更新已暂停,原因:${blockUpdate.reason}。请在"${new Date(
|
||||
blockUpdate.until * 1000,
|
||||
).toLocaleString()}"之后重试。`,
|
||||
});
|
||||
return lastResult || empty;
|
||||
}
|
||||
report({ type: 'checking' });
|
||||
const fetchPayload = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
packageVersion,
|
||||
hash: currentVersion,
|
||||
buildTime,
|
||||
cInfo,
|
||||
}),
|
||||
};
|
||||
let resp;
|
||||
try {
|
||||
resp = await fetch(getCheckUrl(APPKEY), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
packageVersion,
|
||||
hash: currentVersion,
|
||||
buildTime,
|
||||
cInfo,
|
||||
}),
|
||||
});
|
||||
resp = await fetch(getCheckUrl(APPKEY), fetchPayload);
|
||||
} catch (e) {
|
||||
if (isRetry) {
|
||||
return report({
|
||||
type: 'errorChecking',
|
||||
message: '无法连接更新服务器,请检查网络连接后重试',
|
||||
});
|
||||
report({
|
||||
type: 'errorChecking',
|
||||
message: '无法连接主更新服务器,尝试备用节点',
|
||||
});
|
||||
const backupEndpoints = await updateBackupEndpoints();
|
||||
if (backupEndpoints) {
|
||||
try {
|
||||
resp = await Promise.race(
|
||||
backupEndpoints.map((endpoint) =>
|
||||
fetch(getCheckUrl(APPKEY, endpoint), fetchPayload),
|
||||
),
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
await tryBackupEndpoints();
|
||||
return checkUpdate(APPKEY, true);
|
||||
}
|
||||
const result = await resp.json();
|
||||
if (!resp) {
|
||||
report({
|
||||
type: 'errorChecking',
|
||||
message: '无法连接更新服务器,请检查网络连接后重试',
|
||||
});
|
||||
return lastResult || empty;
|
||||
}
|
||||
const result: CheckResult = await resp.json();
|
||||
|
||||
lastResult = result;
|
||||
// @ts-ignore
|
||||
checkOperation(result.op);
|
||||
|
||||
if (resp.status !== 200) {
|
||||
return report({ type: 'errorChecking', message: result.message });
|
||||
report({
|
||||
type: 'errorChecking',
|
||||
//@ts-ignore
|
||||
message: result.message,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -309,10 +322,12 @@ function assertHash(hash: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let applyingUpdate = false;
|
||||
export function switchVersion(hash: string) {
|
||||
assertRelease();
|
||||
if (assertHash(hash)) {
|
||||
if (assertHash(hash) && !applyingUpdate) {
|
||||
logger('switchVersion: ' + hash);
|
||||
applyingUpdate = true;
|
||||
PushyModule.reloadUpdate({ hash });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Platform, Alert, Linking, AppState } from 'react-native';
|
||||
import React, { PureComponent, ComponentType } from 'react';
|
||||
import {
|
||||
Platform,
|
||||
Alert,
|
||||
Linking,
|
||||
AppState,
|
||||
NativeEventSubscription,
|
||||
} from 'react-native';
|
||||
|
||||
import {
|
||||
isFirstTime,
|
||||
@@ -10,25 +16,25 @@ import {
|
||||
switchVersionLater,
|
||||
markSuccess,
|
||||
downloadAndInstallApk,
|
||||
onEvents,
|
||||
onPushyEvents,
|
||||
} from './main';
|
||||
import { UpdateEventsListener } from './type';
|
||||
|
||||
export function simpleUpdate(
|
||||
WrappedComponent: JSX.Element,
|
||||
options: { appKey?: string; onEvents?: UpdateEventsListener } = {},
|
||||
WrappedComponent: ComponentType,
|
||||
options: { appKey?: string; onPushyEvents?: UpdateEventsListener } = {},
|
||||
) {
|
||||
const { appKey, onEvents: eventListeners } = options;
|
||||
const { appKey, onPushyEvents: eventListeners } = options;
|
||||
if (!appKey) {
|
||||
throw new Error('appKey is required for simpleUpdate()');
|
||||
}
|
||||
if (typeof eventListeners === 'function') {
|
||||
onEvents(eventListeners);
|
||||
onPushyEvents(eventListeners);
|
||||
}
|
||||
// @ts-expect-error
|
||||
return __DEV__
|
||||
? WrappedComponent
|
||||
: class AppUpdate extends PureComponent {
|
||||
stateListener: NativeEventSubscription;
|
||||
componentDidMount() {
|
||||
if (isRolledBack) {
|
||||
Alert.alert('抱歉', '刚刚更新遭遇错误,已为您恢复到更新前版本');
|
||||
@@ -79,7 +85,7 @@ export function simpleUpdate(
|
||||
checkUpdate = async () => {
|
||||
let info;
|
||||
try {
|
||||
info = await checkUpdate(appKey);
|
||||
info = await checkUpdate(appKey!);
|
||||
} catch (err) {
|
||||
Alert.alert('更新检查失败', err.message);
|
||||
return;
|
||||
|
||||
@@ -26,7 +26,8 @@ export interface UpdateAvailableResult {
|
||||
export type CheckResult =
|
||||
| ExpiredResult
|
||||
| UpTodateResult
|
||||
| UpdateAvailableResult;
|
||||
| UpdateAvailableResult
|
||||
| {};
|
||||
|
||||
export interface ProgressData {
|
||||
hash: string;
|
||||
|
||||
9
lib/utils.ts
Normal file
9
lib/utils.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function logger(...args: any[]) {
|
||||
console.log('Pushy: ', ...args);
|
||||
}
|
||||
|
||||
export function assertRelease() {
|
||||
if (__DEV__) {
|
||||
throw new Error('react-native-update 只能在 RELEASE 版本中运行.');
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-update",
|
||||
"version": "9.0.0-beta.2",
|
||||
"version": "9.1.4",
|
||||
"description": "react-native hot update",
|
||||
"main": "lib/index.ts",
|
||||
"scripts": {
|
||||
@@ -39,7 +39,7 @@
|
||||
"url": "https://github.com/reactnativecn/react-native-pushy/issues"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.27.0"
|
||||
"react-native": ">=0.57.0"
|
||||
},
|
||||
"homepage": "https://github.com/reactnativecn/react-native-pushy#readme",
|
||||
"dependencies": {
|
||||
@@ -57,12 +57,15 @@
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/jest": "^29.2.1",
|
||||
"@types/node": "^20.8.9",
|
||||
"@types/react": "^18.2.33",
|
||||
"detox": "^20.5.0",
|
||||
"firebase-tools": "^11.24.1",
|
||||
"fs-extra": "^9.1.0",
|
||||
"jest": "^29.2.1",
|
||||
"pod-install": "^0.1.37",
|
||||
"react-native": "^0.72.6",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.1.3"
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
require 'json'
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
||||
folly_version = '2021.06.28.00-v2'
|
||||
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = package['name']
|
||||
s.version = package['version']
|
||||
@@ -41,18 +40,5 @@ Pod::Spec.new do |s|
|
||||
'android/jni/lzma/C/Lzma2Dec.{h,c}']
|
||||
ss.private_header_files = 'ios/RCTPushy/HDiffPatch/**/*.h'
|
||||
end
|
||||
# This guard prevent to install the dependencies when we run `pod install` in the old architecture.
|
||||
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
||||
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
||||
s.pod_target_xcconfig = {
|
||||
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
||||
}
|
||||
|
||||
s.dependency "React-Codegen"
|
||||
s.dependency "RCT-Folly", folly_version
|
||||
s.dependency "RCTRequired"
|
||||
s.dependency "RCTTypeSafety"
|
||||
s.dependency "ReactCommon/turbomodule/core"
|
||||
end
|
||||
install_modules_dependencies(s)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user