1
0
Code Issues Pull Requests Packages Projects Releases Wiki Activity GitHub Gitee

Merge pull request #426 from reactnativecn/v10

V10
This commit is contained in:
Sunny Luo 2024-02-08 22:07:46 +08:00 committed by GitHub
commit 92cfbb3fdf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 5681 additions and 3766 deletions

View File

@ -1,29 +1,4 @@
{
"extends": "eslint-config-airbnb/base",
"parser": "babel-eslint",
"env": {
"browser": true,
"node": true,
"mocha": true
},
"rules": {
// Disable for console/alert
"no-console": 0,
"no-alert": 0,
"indent": [2, 2, {"SwitchCase": 1}]
},
"plugins": [
"import"
],
"settings": {
"import/parser": "babel-eslint",
"import/resolve": {
"moduleDirectory": ["node_modules", "src"]
}
},
"globals": {
"__DEV__": true,
"__OPTION__": true
}
"root": true,
"extends": "@react-native"
}

View File

@ -1,3 +1,8 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
env: {
production: {
plugins: ['react-native-paper/babel'],
},
},
};

View File

@ -284,17 +284,19 @@ PODS:
- React-jsinspector (0.69.8)
- React-logger (0.69.8):
- glog
- react-native-update (9.0.0):
- react-native-safe-area-context (4.8.2):
- React-Core
- react-native-update (10.0.0-beta.1):
- React
- React-Core
- react-native-update/HDiffPatch (= 9.0.0)
- react-native-update/RCTPushy (= 9.0.0)
- react-native-update/HDiffPatch (= 10.0.0-beta.1)
- react-native-update/RCTPushy (= 10.0.0-beta.1)
- SSZipArchive
- react-native-update/HDiffPatch (9.0.0):
- react-native-update/HDiffPatch (10.0.0-beta.1):
- React
- React-Core
- SSZipArchive
- react-native-update/RCTPushy (9.0.0):
- react-native-update/RCTPushy (10.0.0-beta.1):
- React
- React-Core
- SSZipArchive
@ -364,6 +366,8 @@ PODS:
- React-jsi (= 0.69.8)
- React-logger (= 0.69.8)
- React-perflogger (= 0.69.8)
- RNVectorIcons (10.0.3):
- React-Core
- SocketRocket (0.6.0)
- SSZipArchive (2.4.3)
- Yoga (1.14.0)
@ -414,6 +418,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-update (from `../node_modules/react-native-update`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
@ -427,6 +432,7 @@ DEPENDENCIES:
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
@ -487,6 +493,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
React-logger:
:path: "../node_modules/react-native/ReactCommon/logger"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-update:
:path: "../node_modules/react-native-update"
React-perflogger:
@ -513,6 +521,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
RNVectorIcons:
:path: "../node_modules/react-native-vector-icons"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
@ -549,7 +559,8 @@ SPEC CHECKSUMS:
React-jsiexecutor: 2cf8ea3753afb81dfcc386613dbbb0b5d155fb34
React-jsinspector: 0eda09e9cf22bbb5dbb1d23143b03a31acf37d67
React-logger: 5997ab008583826c10ffe4e1ff990363e975639d
react-native-update: 2b5ef06bfeaa668614c8deb7ec4d20dcf56f9278
react-native-safe-area-context: 0ee144a6170530ccc37a0fd9388e28d06f516a89
react-native-update: 767449a2cf592387b9aecb97980ec0536d72c890
React-perflogger: ad1416a715d86b32f456e5d0aed99c3b52f1de37
React-RCTActionSheet: cbf7c6a953982562418ee72a1084ff7b9447b558
React-RCTAnimation: 33df3e25824dd7313edec28dded2745542f9352b
@ -562,6 +573,7 @@ SPEC CHECKSUMS:
React-RCTVibration: 5462287ee85304ba1a00474665ab292e63a41663
React-runtimeexecutor: 9df680f18497367bcf5c15b6b6406c0f2dfa2b6a
ReactCommon: c10f046f3ef8561e7c8e7e9b9dae2ecc9ffc48ef
RNVectorIcons: bc7ee28cadf39c77a49232a14738dfce690f66cd
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
Yoga: d3820731e0ca3a4933f061ad29defaf7726e3251
@ -569,4 +581,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 618d17df10f335f1d113daac849a7997894646b2
COCOAPODS: 1.11.3
COCOAPODS: 1.14.3

View File

@ -1,12 +1,12 @@
const path = require('path');
const extraNodeModules = {
react: path.resolve(__dirname, 'node_modules/react'),
'react-native': path.resolve(__dirname, 'node_modules/react-native'),
'react-native-update': path.resolve(__dirname, '../..'),
'@babel/runtime': path.resolve(__dirname, 'node_modules/@babel/runtime'),
};
const watchFolders = [path.resolve(__dirname, '../..')];
// const extraNodeModules = {
// react: path.resolve(__dirname, 'node_modules/react'),
// 'react-native': path.resolve(__dirname, 'node_modules/react-native'),
// 'react-native-update': path.resolve(__dirname, '../..'),
// '@babel/runtime': path.resolve(__dirname, 'node_modules/@babel/runtime'),
// };
// const watchFolders = [path.resolve(__dirname, '../..')];
module.exports = {
transformer: {
@ -17,8 +17,8 @@ module.exports = {
},
}),
},
resolver: {
extraNodeModules,
},
watchFolders,
// resolver: {
// extraNodeModules,
// },
// watchFolders,
};

View File

@ -16,7 +16,10 @@
"postinstall-postinstall": "^2.1.0",
"react": "18.0.0",
"react-native": "0.69.8",
"react-native-update": "link:../.."
"react-native-paper": "^5.12.1",
"react-native-safe-area-context": "^4.8.2",
"react-native-update": "^10.0.0-beta.3",
"react-native-vector-icons": "^10.0.3"
},
"devDependencies": {
"@babel/core": "^7.21.0",

View File

@ -1,206 +0,0 @@
import React, {Component} from 'react';
import {
StyleSheet,
Platform,
Text,
View,
Alert,
TouchableOpacity,
Linking,
Image,
} from 'react-native';
import {
isFirstTime,
isRolledBack,
packageVersion,
currentVersion,
checkUpdate,
downloadUpdate,
switchVersion,
switchVersionLater,
markSuccess,
downloadAndInstallApk,
cInfo,
} from 'react-native-update';
import TestConsole from './TestConsole';
import _updateConfig from '../update.json';
const {appKey} = _updateConfig[Platform.OS];
export default class App extends Component {
state = {
received: 0,
total: 0,
showTestConsole: false,
};
componentDidMount() {
if (isRolledBack) {
Alert.alert('提示', '刚刚更新失败了,版本被回滚.');
} else if (isFirstTime) {
Alert.alert(
'提示',
'这是当前版本第一次启动,是否要模拟启动失败?将回滚到上一版本',
[
{
text: '是',
onPress: () => {
throw new Error('模拟启动失败,请重启应用');
},
},
{
text: '否',
onPress: () => {
markSuccess();
},
},
],
);
}
}
doUpdate = async info => {
try {
const hash = await downloadUpdate(info, {
onDownloadProgress: ({received, total}) => {
this.setState({
received,
total,
});
},
});
if (!hash) {
return;
}
Alert.alert('提示', '下载完毕,是否重启应用?', [
{
text: '是',
onPress: () => {
switchVersion(hash);
},
},
{text: '否'},
{
text: '下次启动时',
onPress: () => {
switchVersionLater(hash);
},
},
]);
} catch (err) {
Alert.alert('更新失败', err.message);
}
};
checkUpdate = async () => {
let info;
try {
info = await checkUpdate(appKey);
} catch (err) {
Alert.alert('更新检查失败', err.message);
return;
}
if (info.expired) {
Alert.alert('提示', '您的应用版本已更新,点击确定下载安装新版本', [
{
text: '确定',
onPress: () => {
if (info.downloadUrl) {
// apk可直接下载安装
if (
Platform.OS === 'android' &&
info.downloadUrl.endsWith('.apk')
) {
downloadAndInstallApk({
url: info.downloadUrl,
onDownloadProgress: ({received, total}) => {
this.setState({
received,
total,
});
},
});
} else {
Linking.openURL(info.downloadUrl);
}
}
},
},
]);
} else if (info.upToDate) {
Alert.alert('提示', '您的应用版本已是最新.');
} else {
Alert.alert(
'提示',
'检查到新的版本' + info.name + ',是否下载?\n' + info.description,
[
{
text: '是',
onPress: () => {
this.doUpdate(info);
},
},
{text: '否'},
],
);
}
};
render() {
const {received, total, showTestConsole} = this.state;
return (
<View style={styles.container}>
<Text style={styles.welcome}>欢迎使用热更新服务</Text>
<Image
resizeMode={'contain'}
source={require('./assets/shezhi.png')}
style={styles.image}
/>
<Text style={styles.instructions}>
这是版本一 {'\n'}
当前原生包版本号: {packageVersion}
{'\n'}
当前热更新版本Hash: {currentVersion || '(空)'}
{'\n'}
</Text>
<Text>
下载进度{received} / {total}
</Text>
<TouchableOpacity onPress={this.checkUpdate}>
<Text style={styles.instructions}>点击这里检查更新</Text>
</TouchableOpacity>
<TouchableOpacity
testID="testcase"
style={{marginTop: 15}}
onLongPress={() => {
this.setState({showTestConsole: true});
}}>
<Text style={styles.instructions}>
react-native-update版本{cInfo.pushy}
</Text>
</TouchableOpacity>
<TestConsole visible={showTestConsole} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
image: {},
});

View File

@ -0,0 +1,166 @@
import React, {useState} from 'react';
import {
StyleSheet,
Platform,
Text,
View,
TouchableOpacity,
Image,
Switch,
} from 'react-native';
import {Icon, PaperProvider, Snackbar, Banner} from 'react-native-paper';
import TestConsole from './TestConsole';
import _updateConfig from '../update.json';
import {PushyProvider, Pushy, usePushy} from 'react-native-update';
const {appKey} = _updateConfig[Platform.OS];
function App() {
const {
client,
checkUpdate,
downloadUpdate,
switchVersionLater,
switchVersion,
updateInfo,
packageVersion,
currentHash,
progress: {received, total} = {},
} = usePushy();
const [useDefaultAlert, setUseDefaultAlert] = useState(true);
const [showTestConsole, setShowTestConsole] = useState(false);
const [showUpdateBanner, setShowUpdateBanner] = useState(false);
const [showUpdateSnackbar, setShowUpdateSnackbar] = useState(false);
const snackbarVisible =
!useDefaultAlert && showUpdateSnackbar && updateInfo?.update;
return (
<View style={styles.container}>
<Text style={styles.welcome}>使Pushy热更新服务</Text>
<View style={{flexDirection: 'row'}}>
<Text>
{useDefaultAlert ? '当前使用' : '当前不使用'}alert更新提示
</Text>
<Switch
value={useDefaultAlert}
onValueChange={v => {
setUseDefaultAlert(v);
client?.setOptions({
useAlert: v,
});
setShowUpdateSnackbar(!v);
}}
/>
</View>
<Image
resizeMode={'contain'}
source={require('./assets/shezhi.png')}
style={styles.image}
/>
<Text style={styles.instructions}>
{'\n'}
: {packageVersion}
{'\n'}
Hash: {currentHash || '(空)'}
{'\n'}
</Text>
<Text>
{received} / {total}
</Text>
<TouchableOpacity
onPress={() => {
checkUpdate();
setShowUpdateSnackbar(true);
}}>
<Text style={styles.instructions}></Text>
</TouchableOpacity>
<TouchableOpacity
testID="testcase"
style={{marginTop: 15}}
onLongPress={() => {
setShowTestConsole(true);
}}>
<Text style={styles.instructions}>
react-native-update版本{client?.version}
</Text>
</TouchableOpacity>
<TestConsole visible={showTestConsole} />
{snackbarVisible && (
<Snackbar
visible={snackbarVisible}
onDismiss={() => {
setShowUpdateSnackbar(false);
}}
action={{
label: '更新',
onPress: async () => {
setShowUpdateSnackbar(false);
await downloadUpdate();
setShowUpdateBanner(true);
},
}}>
<Text style={{color: 'white'}}>
({updateInfo.name})
</Text>
</Snackbar>
)}
<Banner
style={{width: '100%', position: 'absolute', top: 0}}
visible={showUpdateBanner}
actions={[
{
label: '立即重启',
onPress: switchVersion,
},
{
label: '下次再说',
onPress: () => {
switchVersionLater();
setShowUpdateBanner(false);
},
},
]}
icon={({size}) => (
<Icon name="checkcircleo" size={size} color="#00f" />
)}>
</Banner>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
image: {},
});
const pushyClient = new Pushy({
appKey,
});
export default function Root() {
return (
<PushyProvider client={pushyClient}>
<PaperProvider>
<App />
</PaperProvider>
</PushyProvider>
);
}

View File

@ -1,10 +1,10 @@
{
"ios": {
"appId": 10976,
"appKey": "p5q_rPYNOE0WdCf-i9ZCGPw-DUGUCMvY"
},
"android": {
"appId": 10977,
"appKey": "bUYKfZgtC9VirZsQbAt8qken2Gq2uxe3"
}
}
"ios": {
"appId": 24794,
"appKey": "SqShg4Klnj2hG6LAFMW2PdcgSSuniz0T"
},
"android": {
"appId": 27509,
"appKey": "aQz3Uc2pA7gt_prDaQ4rbWRY"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -57,7 +57,7 @@ public class UpdateModule extends NativeUpdateSpec {
@Override
public void downloadFullUpdate(ReadableMap options, final Promise promise) {
UpdateModuleImpl.downloadFullUpdate(this.updateContext,options,promise);
UpdateModuleImpl.downloadFullUpdate(this.updateContext,options,promise);
}
@Override

View File

@ -1,53 +0,0 @@
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;
export async function updateBackupEndpoints() {
if (backupEndpointsQueryUrl) {
try {
const resp = await fetch(backupEndpointsQueryUrl);
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('fetch remote endpoints failed');
}
}
return backupEndpoints;
}
export function getCheckUrl(APPKEY, endpoint = currentEndpoint) {
return `${endpoint}/checkUpdate/${APPKEY}`;
}
/**
* @param {string} main - The main api endpoint
* @param {string[]} [backups] - The back up endpoints.
* @param {string} [backupQueryUrl] - An url that return a json file containing an array of endpoint.
* like: ["https://backup.api/1", "https://backup.api/2"]
*/
export function setCustomEndpoints({
main,
backups,
backupQueryUrl,
}: {
main: string;
backups?: string[];
backupQueryUrl?: string;
}) {
currentEndpoint = main;
backupEndpointsQueryUrl = null;
if (Array.isArray(backups) && backups.length > 0) {
backupEndpoints = backups;
}
if (typeof backupQueryUrl === 'string') {
backupEndpointsQueryUrl = backupQueryUrl;
}
}

View File

@ -1,2 +0,0 @@
export * from './main';
export * from './simpleUpdate';

View File

@ -1,18 +0,0 @@
export const downloadRootDir = '';
export const packageVersion = '';
export const currentVersion = '';
export const isFirstTime = false;
export const isRolledBack = false;
const noop = () => {};
export const checkUpdate = noop;
export const downloadUpdate = noop;
export const switchVersion = noop;
export const switchVersionLater = noop;
export const markSuccess = noop;
export const downloadAndInstallApk = noop;
export const setCustomEndpoints = noop;
export const getCurrentVersionInfo = noop;
export const simpleUpdate = (app) => app;
export const onPushyEvents = noop;

View File

@ -1,398 +0,0 @@
import {
updateBackupEndpoints,
getCheckUrl,
setCustomEndpoints,
} from './endpoint';
import {
NativeEventEmitter,
NativeModules,
Platform,
PermissionsAndroid,
} from 'react-native';
import {
CheckResult,
EventType,
ProgressData,
UpdateAvailableResult,
UpdateEventsListener,
} from './type';
import { assertRelease, logger } from './utils';
export { setCustomEndpoints };
const {
version: v,
} = require('react-native/Libraries/Core/ReactNativeVersion');
const RNVersion = `${v.major}.${v.minor}.${v.patch}`;
const isTurboModuleEnabled = global.__turboModuleProxy != null;
export const PushyModule = isTurboModuleEnabled
? require('./NativeUpdate').default
: NativeModules.Pushy;
if (!PushyModule) {
throw new Error('react-native-update模块无法加载请对照安装文档检查配置。');
}
const PushyConstants = isTurboModuleEnabled
? PushyModule.getConstants()
: PushyModule;
export const downloadRootDir = PushyConstants.downloadRootDir;
export const packageVersion = PushyConstants.packageVersion;
export const currentVersion = PushyConstants.currentVersion;
export const isFirstTime = PushyConstants.isFirstTime;
const rolledBackVersion = PushyConstants.rolledBackVersion;
export const isRolledBack = typeof rolledBackVersion === 'string';
export const buildTime = PushyConstants.buildTime;
let blockUpdate = PushyConstants.blockUpdate;
let uuid = PushyConstants.uuid;
if (Platform.OS === 'android' && !PushyConstants.isUsingBundleUrl) {
throw new Error(
'react-native-update模块无法加载请对照文档检查Bundle URL的配置',
);
}
function setLocalHashInfo(hash: string, info: Record<string, any>) {
PushyModule.setLocalHashInfo(hash, JSON.stringify(info));
}
async function getLocalHashInfo(hash: string) {
return JSON.parse(await PushyModule.getLocalHashInfo(hash));
}
export async function getCurrentVersionInfo(): Promise<{
name?: string;
description?: string;
metaInfo?: string;
}> {
return currentVersion ? (await getLocalHashInfo(currentVersion)) || {} : {};
}
const eventEmitter = new NativeEventEmitter(PushyModule);
if (!uuid) {
uuid = require('nanoid/non-secure').nanoid();
PushyModule.setUuid(uuid);
}
const noop = () => {};
let reporter: UpdateEventsListener = noop;
export function onPushyEvents(customReporter: UpdateEventsListener) {
reporter = customReporter;
if (isRolledBack) {
report({
type: 'rollback',
data: {
rolledBackVersion,
},
});
}
}
function report({
type,
message = '',
data = {},
}: {
type: EventType;
message?: string;
data?: Record<string, string | number>;
}) {
logger(type + ' ' + message);
reporter({
type,
data: {
currentVersion,
cInfo,
packageVersion,
buildTime,
message,
...data,
},
});
}
logger('uuid: ' + uuid);
export const cInfo = {
pushy: require('../package.json').version,
rn: RNVersion,
os: Platform.OS + ' ' + Platform.Version,
uuid,
};
let lastChecking;
const empty = {};
let lastResult: CheckResult;
export async function checkUpdate(APPKEY: string) {
assertRelease();
const now = Date.now();
if (lastResult && lastChecking && now - lastChecking < 1000 * 60) {
// logger('repeated checking, ignored');
return lastResult;
}
lastChecking = now;
if (blockUpdate && blockUpdate.until > Date.now() / 1000) {
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), fetchPayload);
} catch (e) {
report({
type: 'errorChecking',
message: '无法连接主更新服务器,尝试备用节点',
});
const backupEndpoints = await updateBackupEndpoints();
if (backupEndpoints) {
try {
resp = await Promise.race(
backupEndpoints.map((endpoint) =>
fetch(getCheckUrl(APPKEY, endpoint), fetchPayload),
),
);
} catch {}
}
}
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) {
report({
type: 'errorChecking',
//@ts-ignore
message: result.message,
});
}
return result;
}
function checkOperation(
op: { type: string; reason: string; duration: number }[],
) {
if (!Array.isArray(op)) {
return;
}
op.forEach((action) => {
if (action.type === 'block') {
blockUpdate = {
reason: action.reason,
until: Math.round((Date.now() + action.duration) / 1000),
};
PushyModule.setBlockUpdate(blockUpdate);
}
});
}
let downloadingThrottling = false;
let downloadedHash: string;
export async function downloadUpdate(
options: UpdateAvailableResult,
eventListeners?: {
onDownloadProgress?: (data: ProgressData) => void;
},
) {
assertRelease();
if (!options.update) {
return;
}
if (rolledBackVersion === options.hash) {
logger(`rolledback hash ${rolledBackVersion}, ignored`);
return;
}
if (downloadedHash === options.hash) {
logger(`duplicated downloaded hash ${downloadedHash}, ignored`);
return downloadedHash;
}
if (downloadingThrottling) {
logger('repeated downloading, ignored');
return;
}
downloadingThrottling = true;
setTimeout(() => {
downloadingThrottling = false;
}, 3000);
let progressHandler;
if (eventListeners) {
if (eventListeners.onDownloadProgress) {
const downloadCallback = eventListeners.onDownloadProgress;
progressHandler = eventEmitter.addListener(
'RCTPushyDownloadProgress',
(progressData) => {
if (progressData.hash === options.hash) {
downloadCallback(progressData);
}
},
);
}
}
let succeeded = false;
report({ type: 'downloading' });
if (options.diffUrl) {
logger('downloading diff');
try {
await PushyModule.downloadPatchFromPpk({
updateUrl: options.diffUrl,
hash: options.hash,
originHash: currentVersion,
});
succeeded = true;
} catch (e) {
logger(`diff error: ${e.message}, try pdiff`);
}
}
if (!succeeded && options.pdiffUrl) {
logger('downloading pdiff');
try {
await PushyModule.downloadPatchFromPackage({
updateUrl: options.pdiffUrl,
hash: options.hash,
});
succeeded = true;
} catch (e) {
logger(`pdiff error: ${e.message}, try full patch`);
}
}
if (!succeeded && options.updateUrl) {
logger('downloading full patch');
try {
await PushyModule.downloadFullUpdate({
updateUrl: options.updateUrl,
hash: options.hash,
});
succeeded = true;
} catch (e) {
logger(`full patch error: ${e.message}`);
}
}
progressHandler && progressHandler.remove();
if (!succeeded) {
return report({ type: 'errorUpdate', data: { newVersion: options.hash } });
}
setLocalHashInfo(options.hash, {
name: options.name,
description: options.description,
metaInfo: options.metaInfo,
});
downloadedHash = options.hash;
return options.hash;
}
function assertHash(hash: string) {
if (!downloadedHash) {
logger(`no downloaded hash`);
return;
}
if (hash !== downloadedHash) {
logger(`use downloaded hash ${downloadedHash} first`);
return;
}
return true;
}
let applyingUpdate = false;
export function switchVersion(hash: string) {
assertRelease();
if (assertHash(hash) && !applyingUpdate) {
logger('switchVersion: ' + hash);
applyingUpdate = true;
PushyModule.reloadUpdate({ hash });
}
}
export function switchVersionLater(hash: string) {
assertRelease();
if (assertHash(hash)) {
logger('switchVersionLater: ' + hash);
PushyModule.setNeedUpdate({ hash });
}
}
let marked = false;
export function markSuccess() {
assertRelease();
if (marked) {
logger('repeated markSuccess, ignored');
return;
}
marked = true;
PushyModule.markSuccess();
report({ type: 'markSuccess' });
}
export async function downloadAndInstallApk({
url,
onDownloadProgress,
}: {
url: string;
onDownloadProgress?: (data: ProgressData) => void;
}) {
if (Platform.OS !== 'android') {
return;
}
report({ type: 'downloadingApk' });
if (Platform.Version <= 23) {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
return report({ type: 'rejectStoragePermission' });
}
} catch (err) {
return report({ type: 'errorStoragePermission' });
}
}
let hash = Date.now().toString();
let progressHandler;
if (onDownloadProgress) {
progressHandler = eventEmitter.addListener(
'RCTPushyDownloadProgress',
(progressData: ProgressData) => {
if (progressData.hash === hash) {
onDownloadProgress(progressData);
}
},
);
}
await PushyModule.downloadAndInstallApk({
url,
target: 'update.apk',
hash,
}).catch(() => {
report({ type: 'errowDownloadAndInstallApk' });
});
progressHandler && progressHandler.remove();
}

View File

@ -1,135 +0,0 @@
import React, { PureComponent, ComponentType } from 'react';
import {
Platform,
Alert,
Linking,
AppState,
NativeEventSubscription,
} from 'react-native';
import {
isFirstTime,
isRolledBack,
checkUpdate,
downloadUpdate,
switchVersion,
switchVersionLater,
markSuccess,
downloadAndInstallApk,
onPushyEvents,
} from './main';
import { UpdateEventsListener } from './type';
export function simpleUpdate(
WrappedComponent: ComponentType,
options: { appKey?: string; onPushyEvents?: UpdateEventsListener } = {},
) {
const { appKey, onPushyEvents: eventListeners } = options;
if (!appKey) {
throw new Error('appKey is required for simpleUpdate()');
}
if (typeof eventListeners === 'function') {
onPushyEvents(eventListeners);
}
return __DEV__
? WrappedComponent
: class AppUpdate extends PureComponent {
stateListener: NativeEventSubscription;
componentDidMount() {
if (isRolledBack) {
Alert.alert('抱歉', '刚刚更新遭遇错误,已为您恢复到更新前版本');
} else if (isFirstTime) {
markSuccess();
}
this.stateListener = AppState.addEventListener(
'change',
(nextAppState) => {
if (nextAppState === 'active') {
this.checkUpdate();
}
},
);
this.checkUpdate();
}
componentWillUnmount() {
this.stateListener && this.stateListener.remove();
}
doUpdate = async (info) => {
try {
const hash = await downloadUpdate(info);
if (!hash) {
return;
}
this.stateListener && this.stateListener.remove();
Alert.alert('提示', '下载完毕,是否立即更新?', [
{
text: '以后再说',
style: 'cancel',
onPress: () => {
switchVersionLater(hash);
},
},
{
text: '立即更新',
style: 'default',
onPress: () => {
switchVersion(hash);
},
},
]);
} catch (err) {
Alert.alert('更新失败', err.message);
}
};
checkUpdate = async () => {
let info;
try {
info = await checkUpdate(appKey!);
} catch (err) {
Alert.alert('更新检查失败', err.message);
return;
}
if (info.expired) {
Alert.alert('提示', '您的应用版本已更新,点击确定下载安装新版本', [
{
text: '确定',
onPress: () => {
if (info.downloadUrl) {
if (
Platform.OS === 'android' &&
info.downloadUrl.endsWith('.apk')
) {
downloadAndInstallApk({
url: info.downloadUrl,
});
} else {
Linking.openURL(info.downloadUrl);
}
}
},
},
]);
} else if (info.update) {
Alert.alert(
'提示',
'检查到新的版本' + info.name + ',是否下载?\n' + info.description,
[
{ text: '否', style: 'cancel' },
{
text: '是',
style: 'default',
onPress: () => {
this.doUpdate(info);
},
},
],
);
}
};
render() {
return <WrappedComponent {...this.props} />;
}
};
}

View File

@ -1,8 +1,8 @@
{
"name": "react-native-update",
"version": "9.1.6",
"version": "10.0.0-beta.3",
"description": "react-native hot update",
"main": "lib/index.ts",
"main": "src/index.ts",
"scripts": {
"prepublish": "yarn submodule",
"submodule": "git submodule update --init --recursive",
@ -39,7 +39,8 @@
"url": "https://github.com/reactnativecn/react-native-pushy/issues"
},
"peerDependencies": {
"react-native": ">=0.57.0"
"react": ">=16.8.0",
"react-native": ">=0.59.0"
},
"homepage": "https://github.com/reactnativecn/react-native-pushy#readme",
"dependencies": {
@ -55,17 +56,21 @@
]
},
"devDependencies": {
"@react-native/eslint-config": "^0.73.2",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^29.2.1",
"@types/node": "^20.8.9",
"@types/react": "^18.2.33",
"@types/react": "^18.2.46",
"detox": "^20.5.0",
"eslint": "^8.56.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",
"prettier": "^2",
"react": "18.2.0",
"react-native": "0.73",
"ts-jest": "^29.0.3",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
}
}

View File

@ -1,5 +1,7 @@
require 'json'
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
Pod::Spec.new do |s|
@ -40,5 +42,25 @@ Pod::Spec.new do |s|
'android/jni/lzma/C/Lzma2Dec.{h,c}']
ss.private_header_files = 'ios/RCTPushy/HDiffPatch/**/*.h'
end
install_modules_dependencies(s)
if defined?(install_modules_dependencies()) != nil
install_modules_dependencies(s);
else
if new_arch_enabled
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
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"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
end
end
end

316
src/client.tsx Normal file
View File

@ -0,0 +1,316 @@
import { CheckResult, PushyOptions, ProgressData } from './type';
import { assertRelease, log } from './utils';
import {
EmitterSubscription,
PermissionsAndroid,
Platform,
} from 'react-native';
import {
PushyModule,
buildTime,
cInfo,
pushyNativeEventEmitter,
currentVersion,
packageVersion,
report,
rolledBackVersion,
setLocalHashInfo,
} from './core';
const defaultServer = {
main: 'https://update.react-native.cn/api',
backups: ['https://update.reactnative.cn/api'],
queryUrl:
'https://raw.githubusercontent.com/reactnativecn/react-native-pushy/master/endpoints.json',
};
const empty = {};
export class Pushy {
options: PushyOptions = {
appKey: '',
server: defaultServer,
autoMarkSuccess: true,
useAlert: true,
};
lastChecking: number;
lastResult: CheckResult;
progressHandlers: Record<string, EmitterSubscription> = {};
downloadedHash: string;
marked = false;
applyingUpdate = false;
version = cInfo.pushy;
constructor(options: PushyOptions) {
if (!options.appKey) {
throw new Error('appKey is required');
}
this.setOptions(options);
}
setOptions = (options: Partial<PushyOptions>) => {
for (const [key, value] of Object.entries(options)) {
if (value !== undefined) {
this.options[key] = value;
}
}
};
getCheckUrl = (endpoint: string = this.options.server!.main) => {
return `${endpoint}/checkUpdate/${this.options.appKey}`;
};
assertHash = (hash: string) => {
if (!this.downloadedHash) {
return;
}
if (hash !== this.downloadedHash) {
log(`use downloaded hash ${this.downloadedHash} first`);
return;
}
return true;
};
markSuccess = () => {
assertRelease();
if (this.marked) {
return;
}
this.marked = true;
PushyModule.markSuccess();
report({ type: 'markSuccess' });
};
switchVersion = (hash: string) => {
assertRelease();
if (this.assertHash(hash) && !this.applyingUpdate) {
log('switchVersion: ' + hash);
this.applyingUpdate = true;
PushyModule.reloadUpdate({ hash });
}
};
switchVersionLater = (hash: string) => {
assertRelease();
if (this.assertHash(hash)) {
log('switchVersionLater: ' + hash);
PushyModule.setNeedUpdate({ hash });
}
};
checkUpdate = async () => {
assertRelease();
const now = Date.now();
if (
this.lastResult &&
this.lastChecking &&
now - this.lastChecking < 1000 * 5
) {
return this.lastResult;
}
this.lastChecking = now;
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(this.getCheckUrl(), fetchPayload);
} catch (e) {
report({
type: 'errorChecking',
message: 'Can not connect to update server. Trying backup endpoints.',
});
const backupEndpoints = await this.getBackupEndpoints();
if (backupEndpoints) {
try {
resp = await Promise.race(
backupEndpoints.map((endpoint) =>
fetch(this.getCheckUrl(endpoint), fetchPayload),
),
);
} catch {}
}
}
if (!resp) {
report({
type: 'errorChecking',
message: 'Can not connect to update server. Please check your network.',
});
return this.lastResult || empty;
}
const result: CheckResult = await resp.json();
this.lastResult = result;
if (resp.status !== 200) {
report({
type: 'errorChecking',
message: result.message,
});
}
return result;
};
getBackupEndpoints = async () => {
const { server } = this.options;
if (!server) {
return [];
}
if (server.queryUrl) {
try {
const resp = await fetch(server.queryUrl);
const remoteEndpoints = await resp.json();
log('fetch endpoints:', remoteEndpoints);
if (Array.isArray(remoteEndpoints)) {
server.backups = Array.from(
new Set([...(server.backups || []), ...remoteEndpoints]),
);
}
} catch (e) {
log('failed to fetch endpoints from: ', server.queryUrl);
}
}
return server.backups;
};
downloadUpdate = async (
info: CheckResult,
onDownloadProgress?: (data: ProgressData) => void,
) => {
assertRelease();
const { hash, diffUrl, pdiffUrl, updateUrl, name, description, metaInfo } =
info;
if (!info.update || !hash) {
return;
}
if (rolledBackVersion === hash) {
log(`rolledback hash ${rolledBackVersion}, ignored`);
return;
}
if (this.downloadedHash === hash) {
log(`duplicated downloaded hash ${this.downloadedHash}, ignored`);
return this.downloadedHash;
}
if (this.progressHandlers[hash]) {
return;
}
if (onDownloadProgress) {
this.progressHandlers[hash] = pushyNativeEventEmitter.addListener(
'RCTPushyDownloadProgress',
(progressData) => {
if (progressData.hash === hash) {
onDownloadProgress(progressData);
}
},
);
}
let succeeded = false;
report({ type: 'downloading' });
if (diffUrl) {
log('downloading diff');
try {
await PushyModule.downloadPatchFromPpk({
updateUrl: diffUrl,
hash,
originHash: currentVersion,
});
succeeded = true;
} catch (e) {
log(`diff error: ${e.message}, try pdiff`);
}
}
if (!succeeded && pdiffUrl) {
log('downloading pdiff');
try {
await PushyModule.downloadPatchFromPackage({
updateUrl: pdiffUrl,
hash,
});
succeeded = true;
} catch (e) {
log(`pdiff error: ${e.message}, try full patch`);
}
}
if (!succeeded && updateUrl) {
log('downloading full patch');
try {
await PushyModule.downloadFullUpdate({
updateUrl: updateUrl,
hash,
});
succeeded = true;
} catch (e) {
log(`full patch error: ${e.message}`);
}
}
if (this.progressHandlers[hash]) {
this.progressHandlers[hash].remove();
delete this.progressHandlers[hash];
}
if (!succeeded) {
return report({
type: 'errorUpdate',
data: { newVersion: hash },
});
}
log('downloaded hash:', hash);
setLocalHashInfo(hash, {
name,
description,
metaInfo,
});
this.downloadedHash = hash;
return hash;
};
downloadAndInstallApk = async (
url: string,
onDownloadProgress?: (data: ProgressData) => void,
) => {
if (Platform.OS !== 'android') {
return;
}
report({ type: 'downloadingApk' });
if (Platform.Version <= 23) {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
return report({ type: 'rejectStoragePermission' });
}
} catch (err) {
return report({ type: 'errorStoragePermission' });
}
}
const progressKey = 'downloadingApk';
if (onDownloadProgress) {
this.progressHandlers[progressKey] = pushyNativeEventEmitter.addListener(
'RCTPushyDownloadProgress',
(progressData: ProgressData) => {
if (progressData.hash === progressKey) {
onDownloadProgress(progressData);
}
},
);
}
await PushyModule.downloadAndInstallApk({
url,
target: 'update.apk',
hash: progressKey,
}).catch(() => {
report({ type: 'errowDownloadAndInstallApk' });
});
if (this.progressHandlers[progressKey]) {
this.progressHandlers[progressKey].remove();
delete this.progressHandlers[progressKey];
}
};
}

13
src/client.web.js Normal file
View File

@ -0,0 +1,13 @@
const noop = () => {};
export class Pushy {
constructor() {
console.warn(
'react-native-update is not supported and will do nothing on web.',
);
return new Proxy(this, {
get() {
return noop;
},
});
}
}

36
src/context.ts Normal file
View File

@ -0,0 +1,36 @@
import { createContext, useContext } from 'react';
import { CheckResult, ProgressData } from './type';
import { Pushy } from './client';
const empty = {};
const noop = () => {};
export const defaultContext = {
checkUpdate: () => Promise.resolve(empty),
switchVersion: noop,
switchVersionLater: noop,
markSuccess: noop,
dismissError: noop,
downloadUpdate: noop,
downloadAndInstallApk: noop,
currentHash: '',
packageVersion: '',
};
export const PushyContext = createContext<{
checkUpdate: () => void;
switchVersion: () => void;
switchVersionLater: () => void;
markSuccess: () => void;
dismissError: () => void;
downloadUpdate: () => void;
downloadAndInstallApk: (url: string) => void;
currentHash: string;
packageVersion: string;
client?: Pushy;
progress?: ProgressData;
updateInfo?: CheckResult;
lastError?: Error;
}>(defaultContext);
export const usePushy = () => useContext(PushyContext);

106
src/core.ts Normal file
View File

@ -0,0 +1,106 @@
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
import { EventType, UpdateEventsLogger } from './type';
import { log } from './utils';
const {
version: v,
} = require('react-native/Libraries/Core/ReactNativeVersion');
const RNVersion = `${v.major}.${v.minor}.${v.patch}`;
const isTurboModuleEnabled = global.__turboModuleProxy != null;
export const PushyModule = isTurboModuleEnabled
? require('./turboModuleSpec').default
: NativeModules.Pushy;
if (!PushyModule) {
throw new Error('react-native-update模块无法加载请对照安装文档检查配置。');
}
const PushyConstants = isTurboModuleEnabled
? PushyModule.getConstants()
: PushyModule;
export const downloadRootDir = PushyConstants.downloadRootDir;
export const packageVersion = PushyConstants.packageVersion;
export const currentVersion = PushyConstants.currentVersion;
export const isFirstTime = PushyConstants.isFirstTime;
export const rolledBackVersion = PushyConstants.rolledBackVersion;
export const isRolledBack = typeof rolledBackVersion === 'string';
export const buildTime = PushyConstants.buildTime;
let uuid = PushyConstants.uuid;
if (Platform.OS === 'android' && !PushyConstants.isUsingBundleUrl) {
throw new Error(
'react-native-update模块无法加载请对照文档检查Bundle URL的配置',
);
}
export function setLocalHashInfo(hash: string, info: Record<string, any>) {
PushyModule.setLocalHashInfo(hash, JSON.stringify(info));
}
async function getLocalHashInfo(hash: string) {
return JSON.parse(await PushyModule.getLocalHashInfo(hash));
}
export async function getCurrentVersionInfo(): Promise<{
name?: string;
description?: string;
metaInfo?: string;
}> {
return currentVersion ? (await getLocalHashInfo(currentVersion)) || {} : {};
}
export const pushyNativeEventEmitter = new NativeEventEmitter(PushyModule);
if (!uuid) {
uuid = require('nanoid/non-secure').nanoid();
PushyModule.setUuid(uuid);
}
const noop = () => {};
let reporter: UpdateEventsLogger = noop;
export function onPushyEvents(customReporter: UpdateEventsLogger) {
reporter = customReporter;
if (isRolledBack) {
report({
type: 'rollback',
data: {
rolledBackVersion,
},
});
}
}
export function report({
type,
message = '',
data = {},
}: {
type: EventType;
message?: string;
data?: Record<string, string | number>;
}) {
log(type + ' ' + message);
reporter({
type,
data: {
currentVersion,
cInfo,
packageVersion,
buildTime,
message,
...data,
},
});
}
log('uuid: ' + uuid);
export const cInfo = {
pushy: require('../package.json').version,
rn: RNVersion,
os: Platform.OS + ' ' + Platform.Version,
uuid,
};

3
src/index.ts Normal file
View File

@ -0,0 +1,3 @@
export { Pushy } from './client';
export { PushyContext, usePushy } from './context';
export { PushyProvider } from './provider';

198
src/provider.tsx Normal file
View File

@ -0,0 +1,198 @@
import React, {
ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import {
Alert,
NativeEventSubscription,
AppState,
Platform,
Linking,
} from 'react-native';
import { Pushy } from './client';
import { currentVersion, isFirstTime, packageVersion } from './core';
import { CheckResult, ProgressData } from './type';
import { PushyContext } from './context';
export const PushyProvider = ({
client,
children,
}: {
client: Pushy;
children: ReactNode;
}) => {
const { options } = client;
const stateListener = useRef<NativeEventSubscription>();
const [updateInfo, setUpdateInfo] = useState<CheckResult>();
const [progress, setProgress] = useState<ProgressData>();
const [lastError, setLastError] = useState<Error>();
const dismissError = useCallback(() => {
if (lastError) {
setLastError(undefined);
}
}, [lastError]);
const showAlert = useCallback(
(...args: Parameters<typeof Alert.alert>) => {
if (options.useAlert) {
Alert.alert(...args);
}
},
[options],
);
const switchVersion = useCallback(() => {
if (updateInfo && updateInfo.hash) {
client.switchVersion(updateInfo.hash);
}
}, [client, updateInfo]);
const switchVersionLater = useCallback(() => {
if (updateInfo && updateInfo.hash) {
client.switchVersionLater(updateInfo.hash);
}
}, [client, updateInfo]);
const downloadUpdate = useCallback(async () => {
if (!updateInfo || !updateInfo.update) {
return;
}
try {
const hash = await client.downloadUpdate(updateInfo, setProgress);
if (!hash) {
return;
}
stateListener.current && stateListener.current.remove();
showAlert('提示', '下载完毕,是否立即更新?', [
{
text: '下次再说',
style: 'cancel',
onPress: () => {
client.switchVersionLater(hash);
},
},
{
text: '立即更新',
style: 'default',
onPress: () => {
client.switchVersion(hash);
},
},
]);
} catch (err) {
setLastError(err);
showAlert('更新失败', err.message);
}
}, [client, showAlert, updateInfo]);
const downloadAndInstallApk = useCallback(
(downloadUrl: string) => {
if (Platform.OS === 'android' && downloadUrl) {
client.downloadAndInstallApk(downloadUrl, setProgress);
}
},
[client],
);
const checkUpdate = useCallback(async () => {
let info: CheckResult;
try {
info = await client.checkUpdate();
} catch (err) {
setLastError(err);
showAlert('更新检查失败', err.message);
return;
}
setUpdateInfo(info);
if (info.expired) {
const { downloadUrl } = info;
showAlert('提示', '您的应用版本已更新,点击更新下载安装新版本', [
{
text: '更新',
onPress: () => {
if (downloadUrl) {
if (Platform.OS === 'android' && downloadUrl.endsWith('.apk')) {
downloadAndInstallApk(downloadUrl);
} else {
Linking.openURL(downloadUrl);
}
}
},
},
]);
} else if (info.update) {
showAlert(
'提示',
'检查到新的版本' + info.name + ',是否下载?\n' + info.description,
[
{ text: '取消', style: 'cancel' },
{
text: '确定',
style: 'default',
onPress: () => {
downloadUpdate();
},
},
],
);
}
}, [client, downloadAndInstallApk, downloadUpdate, showAlert]);
const markSuccess = client.markSuccess;
useEffect(() => {
const { strategy, dismissErrorAfter, autoMarkSuccess } = options;
if (isFirstTime && autoMarkSuccess) {
markSuccess();
}
if (strategy === 'both' || strategy === 'onAppResume') {
stateListener.current = AppState.addEventListener(
'change',
(nextAppState) => {
if (nextAppState === 'active') {
checkUpdate();
}
},
);
}
if (strategy === 'both' || strategy === 'onAppStart') {
checkUpdate();
}
let dismissErrorTimer: ReturnType<typeof setTimeout>;
if (typeof dismissErrorAfter === 'number' && dismissErrorAfter > 0) {
dismissErrorTimer = setTimeout(() => {
dismissError();
}, dismissErrorAfter);
}
return () => {
stateListener.current && stateListener.current.remove();
clearTimeout(dismissErrorTimer);
};
}, [checkUpdate, options, dismissError, markSuccess]);
return (
<PushyContext.Provider
value={{
checkUpdate,
switchVersion,
switchVersionLater,
dismissError,
updateInfo,
lastError,
markSuccess,
client,
downloadUpdate,
packageVersion,
currentHash: currentVersion,
progress,
downloadAndInstallApk,
}}
>
{children}
</PushyContext.Provider>
);
};

2
src/provider.web.js Normal file
View File

@ -0,0 +1,2 @@
import { Fragment } from 'react';
export const PushyProvider = Fragment;

View File

@ -9,14 +9,12 @@ export interface Spec extends TurboModule {
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>;
reloadUpdate(options: { hash: string }): Promise<void>;
setNeedUpdate(options: { hash: string }): Promise<void>;
markSuccess(): Promise<void>;

View File

@ -1,34 +1,19 @@
export interface ExpiredResult {
upToDate?: false;
expired: true;
downloadUrl: string;
}
export interface UpTodateResult {
expired?: false;
upToDate: true;
paused?: 'app' | 'package';
}
export interface UpdateAvailableResult {
expired?: false;
upToDate?: false;
update: true;
name: string; // version name
hash: string;
description: string;
metaInfo: string;
pdiffUrl: string;
export interface CheckResult {
upToDate?: true;
expired?: true;
downloadUrl?: string;
update?: true;
name?: string; // version name
hash?: string;
description?: string;
metaInfo?: string;
pdiffUrl?: string;
diffUrl?: string;
updateUrl?: string;
paused?: 'app' | 'package';
message?: string;
}
export type CheckResult =
| ExpiredResult
| UpTodateResult
| UpdateAvailableResult
| {};
export interface ProgressData {
hash: string;
received: number;
@ -62,10 +47,27 @@ export interface EventData {
newVersion?: string;
[key: string]: any;
}
export type UpdateEventsListener = ({
export type UpdateEventsLogger = ({
type,
data,
}: {
type: EventType;
data: EventData;
}) => void;
export interface PushyServerConfig {
main: string;
backups?: string[];
queryUrl?: string;
}
export interface PushyOptions {
appKey: string;
server?: PushyServerConfig;
logger?: UpdateEventsLogger;
useAlert?: boolean;
strategy?: 'onAppStart' | 'onAppResume' | 'both';
autoMarkSuccess?: boolean;
dismissErrorAfter?: number;
}

View File

@ -1,5 +1,5 @@
export function logger(...args: any[]) {
console.log('Pushy: ', ...args);
export function log(...args: any[]) {
console.log('pushy: ', ...args);
}
export function assertRelease() {

2601
yarn.lock

File diff suppressed because it is too large Load Diff