Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a913e8c10e | ||
![]() |
c5f458291a | ||
![]() |
9699632a43 | ||
![]() |
80e42f5dba | ||
![]() |
9b718b8f75 | ||
![]() |
99e3431844 | ||
![]() |
d7b5562ab7 | ||
![]() |
6a0a5b2d49 | ||
![]() |
7023ff57ca | ||
![]() |
17e21d79cf | ||
![]() |
1cab582bd0 | ||
![]() |
7da5a165fd | ||
![]() |
d5194a1ad1 | ||
![]() |
ebc9b97e70 | ||
![]() |
40742e16d8 | ||
![]() |
598ae1a506 | ||
![]() |
e5424591d1 | ||
![]() |
2cf7336b6a | ||
![]() |
7eac48ab5d | ||
![]() |
18d9b75545 | ||
![]() |
e8ec85c65f | ||
![]() |
48a776d506 | ||
![]() |
8ad526148f | ||
![]() |
cea39f1746 | ||
![]() |
aa56c2ec0f | ||
![]() |
00a989d567 | ||
![]() |
83ca3a6c05 | ||
![]() |
257f2697e0 | ||
![]() |
77aa345f11 | ||
![]() |
66332d007a | ||
![]() |
37849b1730 |
@@ -6,6 +6,7 @@
|
||||
Example
|
||||
android/build
|
||||
.vscode
|
||||
.github/
|
||||
|
||||
# OSX
|
||||
#
|
||||
@@ -51,4 +52,6 @@ domains.json
|
||||
endpoints.json
|
||||
endpoints_cresc.json
|
||||
|
||||
tea.yaml
|
||||
tea.yaml
|
||||
|
||||
e2e/
|
@@ -1 +0,0 @@
|
||||
nodeLinker: node-modules
|
36
Example/expoUsePushy/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
expo-env.d.ts
|
||||
|
||||
# Native
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
221
Example/expoUsePushy/App.tsx
Normal file
@@ -0,0 +1,221 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
/* eslint-disable react-native/no-inline-styles */
|
||||
import React, {useState} from 'react';
|
||||
import {StyleSheet, Text, View, TouchableOpacity, Image} from 'react-native';
|
||||
|
||||
import TestConsole from './TestConsole';
|
||||
|
||||
import _updateConfig from './update.json';
|
||||
import {PushyProvider, Pushy, usePushy} from 'react-native-update';
|
||||
const {appKey} = _updateConfig.android;
|
||||
|
||||
function Home() {
|
||||
const {
|
||||
client,
|
||||
checkUpdate,
|
||||
downloadUpdate,
|
||||
switchVersionLater,
|
||||
switchVersion,
|
||||
updateInfo,
|
||||
packageVersion,
|
||||
currentHash,
|
||||
progress: {received, total} = {},
|
||||
} = usePushy();
|
||||
const [useDefaultAlert, setUseDefaultAlert] = useState(false);
|
||||
const [showTestConsole, setShowTestConsole] = useState(false);
|
||||
const [showUpdateBanner, setShowUpdateBanner] = useState(false);
|
||||
const [showUpdateSnackbar, setShowUpdateSnackbar] = useState(false);
|
||||
// if (updateInfo) {
|
||||
// updateInfo!.name = 'name';
|
||||
// updateInfo!.update = true;
|
||||
// }
|
||||
const snackbarVisible =
|
||||
!useDefaultAlert && showUpdateSnackbar && updateInfo?.update;
|
||||
|
||||
if (showTestConsole) {
|
||||
return (
|
||||
<TestConsole visible={true} onClose={() => setShowTestConsole(false)} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>欢迎使用Pushy热更新服务</Text>
|
||||
{/* <Text style={styles.welcome}>😁hdiffFromAPP更新成功!!!</Text> */}
|
||||
{/* <Text style={styles.welcome}>😁hdiffFromPPk更新成功!!!</Text> */}
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
client?.setOptions({
|
||||
updateStrategy: !useDefaultAlert ? null : 'alwaysAlert',
|
||||
});
|
||||
setShowUpdateSnackbar(useDefaultAlert);
|
||||
setUseDefaultAlert(!useDefaultAlert);
|
||||
}}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<View
|
||||
style={{
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: '#999',
|
||||
backgroundColor: useDefaultAlert ? 'blue' : 'white',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
{useDefaultAlert && <Text style={{color: 'white'}}>✓</Text>}
|
||||
</View>
|
||||
<Text style={{marginLeft: 8}}>
|
||||
{' '}
|
||||
{useDefaultAlert ? '当前使用' : '当前不使用'}默认的alert更新提示
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</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}}
|
||||
onPress={() => {
|
||||
setShowTestConsole(true);
|
||||
}}>
|
||||
<Text style={styles.instructions}>
|
||||
react-native-update版本:{client?.version}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
{snackbarVisible && (
|
||||
<View style={styles.overlay}>
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
backgroundColor: '#333',
|
||||
padding: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<Text style={{color: 'white'}}>
|
||||
有新版本({updateInfo.name})可用,是否更新?
|
||||
</Text>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowUpdateSnackbar(false)}
|
||||
style={{marginRight: 10}}>
|
||||
<Text style={{color: 'white'}}>取消</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={async () => {
|
||||
setShowUpdateSnackbar(false);
|
||||
await downloadUpdate();
|
||||
setShowUpdateBanner(true);
|
||||
}}>
|
||||
<Text style={{color: '#2196F3'}}>更新</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{showUpdateBanner && (
|
||||
<View style={styles.overlay}>
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
backgroundColor: '#fff',
|
||||
padding: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#eee',
|
||||
}}>
|
||||
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
||||
<Text>更新已完成,是否立即重启?</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
marginTop: 10,
|
||||
}}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
switchVersionLater();
|
||||
setShowUpdateBanner(false);
|
||||
}}
|
||||
style={{marginRight: 20}}>
|
||||
<Text style={{color: '#2196F3'}}>下次再说</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={switchVersion}>
|
||||
<Text style={{color: '#2196F3'}}>立即重启</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
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,
|
||||
debug: true,
|
||||
});
|
||||
|
||||
export default function HomeScreen() {
|
||||
return (
|
||||
<PushyProvider client={pushyClient}>
|
||||
<Home />
|
||||
</PushyProvider>
|
||||
);
|
||||
}
|
274
Example/expoUsePushy/TestConsole.js
Normal file
@@ -0,0 +1,274 @@
|
||||
/* eslint-disable react-native/no-inline-styles */
|
||||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import {useCallback, useMemo, useState} from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
TextInput,
|
||||
Button,
|
||||
StyleSheet,
|
||||
SafeAreaView,
|
||||
Text,
|
||||
View,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
|
||||
import {PushyModule} from 'react-native-update';
|
||||
const Hash = '9D5CE6EBA420717BE7E7D308B11F8207681B066C951D68F3994D19828F342474';
|
||||
const UUID = '00000000-0000-0000-0000-000000000000';
|
||||
const DownloadUrl = 'https://localhost:3000/diff.ppk-patch';
|
||||
const AppPatchDownloadUrl = 'https://github.com/bozaigao/test_pushy_server/raw/refs/heads/main/hdiff.app-patch';
|
||||
const AppPatchHash = 'f5ba92c7c04250d4b8a446c8267ef459';
|
||||
const PPKDownloadUrl = 'https://github.com/bozaigao/test_pushy_server/raw/refs/heads/main/hdiff.ppk-patch';
|
||||
const PPKPatchHash = '6b3d26b7d868d1f67aedadb7f0b342d9';
|
||||
const OriginHash = 'f5ba92c7c04250d4b8a446c8267ef459';
|
||||
|
||||
|
||||
const CustomDialog = ({title, visible, onConfirm}) => {
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.dialog}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<TouchableOpacity
|
||||
testID="done"
|
||||
style={styles.button}
|
||||
onPress={onConfirm}>
|
||||
<Text style={styles.buttonText}>确认</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
export default function TestConsole({visible, onClose}) {
|
||||
const [text, setText] = useState('');
|
||||
const [running, setRunning] = useState(false);
|
||||
const [options, setOptions] = useState();
|
||||
const [alertVisible, setAlertVisible] = useState(false);
|
||||
const [alertMsg, setAlertMsg] = useState('');
|
||||
const NativeTestMethod = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
name: 'setLocalHashInfo',
|
||||
invoke: () => {
|
||||
setText(
|
||||
`setLocalHashInfo\n${Hash}\n{\"version\":\"1.0.0\",\"size\":\"19M\"}`,
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'getLocalHashInfo',
|
||||
invoke: () => {
|
||||
setText(`getLocalHashInfo\n${Hash}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'setUuid',
|
||||
invoke: () => {
|
||||
setText(`setUuid\n${UUID}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'reloadUpdate',
|
||||
invoke: () => {
|
||||
setText('reloadUpdate');
|
||||
setOptions({hash: Hash});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'setNeedUpdateForApp',
|
||||
invoke: () => {
|
||||
setText('setNeedUpdate');
|
||||
setOptions({hash: AppPatchHash});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'setNeedUpdateForPPK',
|
||||
invoke: () => {
|
||||
setText('setNeedUpdate');
|
||||
setOptions({hash: PPKPatchHash});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'markSuccess',
|
||||
invoke: () => {
|
||||
setText('markSuccess');
|
||||
setOptions(undefined);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'downloadPatchFromPpk',
|
||||
invoke: () => {
|
||||
setText('downloadPatchFromPpk');
|
||||
setOptions({updateUrl: PPKDownloadUrl, hash: PPKPatchHash, originHash: OriginHash});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'downloadPatchFromPackage',
|
||||
invoke: () => {
|
||||
setText('downloadPatchFromPackage');
|
||||
setOptions({updateUrl: AppPatchDownloadUrl, hash: AppPatchHash});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'downloadFullUpdate',
|
||||
invoke: () => {
|
||||
setText('downloadFullUpdate');
|
||||
setOptions({updateUrl: DownloadUrl, hash: Hash});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'downloadAndInstallApk',
|
||||
invoke: () => {
|
||||
setText('downloadAndInstallApk');
|
||||
setOptions({url: DownloadUrl, target: Hash, hash: Hash});
|
||||
},
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
const renderTestView = useCallback(() => {
|
||||
const views = [];
|
||||
for (let i = 0; i < NativeTestMethod.length; i++) {
|
||||
views.push(
|
||||
<TouchableOpacity
|
||||
key={i}
|
||||
testID={NativeTestMethod[i].name}
|
||||
onPress={() => {
|
||||
NativeTestMethod[i].invoke();
|
||||
}}>
|
||||
<Text>{NativeTestMethod[i].name}</Text>
|
||||
</TouchableOpacity>,
|
||||
);
|
||||
}
|
||||
return <View>{views}</View>;
|
||||
}, [NativeTestMethod]);
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{flex: 1, padding: 10}}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: 10,
|
||||
}}>
|
||||
<Text>调试Pushy方法(方法名,参数,值换行)</Text>
|
||||
<Button title="关闭" onPress={() => onClose()} />
|
||||
</View>
|
||||
<TextInput
|
||||
autoCorrect={false}
|
||||
autoCapitalize="none"
|
||||
style={{
|
||||
borderWidth: StyleSheet.hairlineWidth * 4,
|
||||
borderColor: 'black',
|
||||
height: '30%',
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
padding: 10,
|
||||
fontSize: 20,
|
||||
}}
|
||||
textAlignVertical="top"
|
||||
multiline={true}
|
||||
value={text}
|
||||
onChangeText={setText}
|
||||
/>
|
||||
{running && <ActivityIndicator />}
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
backgroundColor: 'rgb(0,140,237)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingTop: 10,
|
||||
paddingBottom: 10,
|
||||
marginBottom: 5,
|
||||
}}
|
||||
testID="submit"
|
||||
onPress={async () => {
|
||||
setRunning(true);
|
||||
try {
|
||||
const inputs = text.split('\n');
|
||||
const methodName = inputs[0];
|
||||
let params = [];
|
||||
if (inputs.length === 1) {
|
||||
if (options) {
|
||||
await PushyModule[methodName](options);
|
||||
} else {
|
||||
await PushyModule[methodName]();
|
||||
}
|
||||
} else {
|
||||
if (inputs.length === 2) {
|
||||
params = [inputs[1]];
|
||||
} else {
|
||||
params = [inputs[1], inputs[2]];
|
||||
console.log({inputs, params});
|
||||
}
|
||||
await PushyModule[methodName](...params);
|
||||
}
|
||||
setAlertVisible(true);
|
||||
setAlertMsg('done');
|
||||
} catch (e) {
|
||||
setAlertVisible(true);
|
||||
setAlertMsg(e.message);
|
||||
}
|
||||
setRunning(false);
|
||||
}}>
|
||||
<Text style={{color: 'white'}}>执行</Text>
|
||||
</TouchableOpacity>
|
||||
<Button title="重置" onPress={() => setText('')} />
|
||||
{renderTestView()}
|
||||
<CustomDialog
|
||||
title={alertMsg}
|
||||
visible={alertVisible}
|
||||
onConfirm={() => {
|
||||
setAlertVisible(false);
|
||||
}}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
overlay: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
dialog: {
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 10,
|
||||
padding: 20,
|
||||
width: '80%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 20,
|
||||
},
|
||||
button: {
|
||||
backgroundColor: '#2196F3',
|
||||
borderRadius: 5,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
buttonText: {
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
29
Example/expoUsePushy/app.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "expoUsePushy",
|
||||
"slug": "expoUsePushy",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"newArchEnabled": true,
|
||||
"splash": {
|
||||
"image": "./assets/splash-icon.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.anonymous.expoUsePushy"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"favicon": "./assets/favicon.png"
|
||||
}
|
||||
}
|
||||
}
|
BIN
Example/expoUsePushy/assets/adaptive-icon.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
Example/expoUsePushy/assets/favicon.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Example/expoUsePushy/assets/icon.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
Example/expoUsePushy/assets/shezhi.png
Normal file
After Width: | Height: | Size: 696 B |
BIN
Example/expoUsePushy/assets/shezhi@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Example/expoUsePushy/assets/shezhi@3x.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Example/expoUsePushy/assets/splash-icon.png
Normal file
After Width: | Height: | Size: 17 KiB |
1765
Example/expoUsePushy/bun.lock
Normal file
8
Example/expoUsePushy/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from './App';
|
||||
|
||||
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
||||
// It also ensures that whether you load the app in Expo Go or in a native build,
|
||||
// the environment is set up appropriately
|
||||
registerRootComponent(App);
|
24
Example/expoUsePushy/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "expousepushy",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "~52.0.46",
|
||||
"expo-status-bar": "~2.0.1",
|
||||
"react": "18.3.1",
|
||||
"react-native": "0.76.9",
|
||||
"react-native-update": "^10.28.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@types/react": "~18.3.12",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
6
Example/expoUsePushy/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
}
|
||||
}
|
14
Example/expoUsePushy/update.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ios": {
|
||||
"appId": 29439,
|
||||
"appKey": "jNA71vpFHTDpEqeZd9yx87zj"
|
||||
},
|
||||
"android": {
|
||||
"appId": 29413,
|
||||
"appKey": "vdZWPXU6eyaPE6Avk96-YvwK"
|
||||
},
|
||||
"harmony": {
|
||||
"appId": 29140,
|
||||
"appKey": "JLklGflGIRbY-cMebjQwm1J1"
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@
|
||||
"": {
|
||||
"name": "harmony_use_pushy",
|
||||
"dependencies": {
|
||||
"@react-native-oh/react-native-harmony": "^0.72.43",
|
||||
"@react-native-oh/react-native-harmony": "^0.72.59",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.72.5",
|
||||
"react-native-update": "^10.26.4",
|
||||
|
@@ -36,10 +36,6 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'pushy',
|
||||
srcPath: '../node_modules/react-native-update/harmony',
|
||||
}
|
||||
]
|
||||
}
|
@@ -7,7 +7,7 @@
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"@rnoh/react-native-openharmony": "0.72.38",
|
||||
"pushy": "file:../../node_modules/react-native-update/harmony"
|
||||
"pushy": "file:../../node_modules/react-native-update/harmony/pushy.har",
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FileJSBundleProvider } from 'pushy/src/main/ets/FileJSBundleProvider';
|
||||
import { PushyFileJSBundleProvider } from 'pushy/src/main/ets/PushyFileJSBundleProvider';
|
||||
import { ComponentBuilderContext, RNOHCoreContext,RNAbility,
|
||||
MetroJSBundleProvider } from '@rnoh/react-native-openharmony';
|
||||
import {
|
||||
@@ -65,7 +65,7 @@ struct Index {
|
||||
// local debug mode
|
||||
new MetroJSBundleProvider(),
|
||||
// release mode
|
||||
new FileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),
|
||||
new PushyFileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),
|
||||
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')
|
||||
]),
|
||||
this.rnohCoreContext.logger),
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"pushy_build_time": "2025-04-12T11:12:43.423Z",
|
||||
"pushy_build_time": "2025-04-30T02:46:33.340Z",
|
||||
"versionName": "1.0.0"
|
||||
}
|
@@ -15,10 +15,10 @@
|
||||
"hash": "pushy hash /Users/yanbo.he/Desktop/HarmonyOS/react-native-pushy/Example/harmony_use_pushy/.pushy/output/harmony.1735048297258.ppk"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-oh/react-native-harmony": "^0.72.43",
|
||||
"@react-native-oh/react-native-harmony": "^0.72.59",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.72.5",
|
||||
"react-native-update": "^10.26.4"
|
||||
"react-native-update": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
|
4
Example/testHotUpdate/.gitignore
vendored
@@ -62,3 +62,7 @@ buck-out/
|
||||
# Ruby / CocoaPods
|
||||
/ios/Pods/
|
||||
/vendor/bundle/
|
||||
|
||||
# react-native-update
|
||||
.update
|
||||
.pushy
|
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
@@ -2,12 +2,14 @@
|
||||
#import "RCTPushy.h"
|
||||
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
self.moduleName = @"AwesomeProject";
|
||||
self.dependencyProvider = [RCTAppDependencyProvider new];
|
||||
// You can add your custom initial props in the dictionary below.
|
||||
// They will be passed down to the ViewController used by React Native.
|
||||
self.initialProps = @{};
|
||||
|
@@ -14,36 +14,36 @@
|
||||
"dev:harmony": "react-native bundle-harmony --dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": "^4.0.2",
|
||||
"form-data": "^4.0.3",
|
||||
"patch-package": "^8.0.0",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.78.0",
|
||||
"react-native-camera-kit": "^14.2.0",
|
||||
"react-native-paper": "^5.13.1",
|
||||
"react-native-safe-area-context": "^5.3.0",
|
||||
"react-native-svg": "^15.11.2",
|
||||
"react-native-update": "^10.26.4",
|
||||
"react-native": "0.79.2",
|
||||
"react-native-camera-kit": "^15.1.0",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-safe-area-context": "^5.5.0",
|
||||
"react-native-svg": "^15.12.0",
|
||||
"react-native-update": "^10.29.4",
|
||||
"react-native-vector-icons": "^10.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/preset-env": "^7.26.0",
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@react-native-community/cli": "15.0.1",
|
||||
"@react-native-community/cli-platform-android": "15.0.1",
|
||||
"@react-native-community/cli-platform-ios": "15.0.1",
|
||||
"@react-native/babel-preset": "0.78.0",
|
||||
"@react-native/eslint-config": "0.78.0",
|
||||
"@react-native/metro-config": "0.78.0",
|
||||
"@react-native/typescript-config": "0.78.0",
|
||||
"@babel/core": "^7.27.3",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/runtime": "^7.27.3",
|
||||
"@react-native-community/cli": "18.0.0",
|
||||
"@react-native-community/cli-platform-android": "18.0.0",
|
||||
"@react-native-community/cli-platform-ios": "18.0.0",
|
||||
"@react-native/babel-preset": "0.79.2",
|
||||
"@react-native/eslint-config": "0.79.2",
|
||||
"@react-native/metro-config": "0.79.2",
|
||||
"@react-native/typescript-config": "0.79.2",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-test-renderer": "^19.0.0",
|
||||
"detox": "^20.32.0",
|
||||
"detox": "^20.39.0",
|
||||
"eslint": "^8.19.0",
|
||||
"jest": "^29.6.3",
|
||||
"prettier": "2.8.8",
|
||||
"react-test-renderer": "19.0.0",
|
||||
"typescript": "5.8.2"
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -52,4 +52,4 @@
|
||||
"detox",
|
||||
"dtrace-provider"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@ function App() {
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>欢迎使用Pushy热更新服务</Text>
|
||||
<Text style={styles.welcome}>欢迎xxx使用Pushy热更新服务</Text>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Text>
|
||||
{useDefaultAlert ? '当前使用' : '当前不使用'}默认的alert更新提示
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ios": {
|
||||
"appId": 24794,
|
||||
"appKey": "SqShg4Klnj2hG6LAFMW2PdcgSSuniz0T"
|
||||
"appId": 28943,
|
||||
"appKey": "d-OmPxIBivPrDfKhLHjxN-HS"
|
||||
},
|
||||
"android": {
|
||||
"appId": 27509,
|
||||
|
@@ -78,7 +78,7 @@ if (expoProject) {
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
useExpoPublishing()
|
||||
// useExpoPublishing()
|
||||
useCoreDependencies()
|
||||
} else {
|
||||
group = 'cn.reactnative.modules.update'
|
||||
@@ -148,7 +148,6 @@ repositories {
|
||||
dependencies {
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
|
||||
implementation 'com.jakewharton:process-phoenix:3.0.0'
|
||||
}
|
||||
if (isNewArchitectureEnabled()) {
|
||||
react {
|
||||
|
36
android/proguard.pro
vendored
@@ -1,3 +1,37 @@
|
||||
# Keep our update module classes
|
||||
-keepnames class cn.reactnative.modules.update.DownloadTask { *; }
|
||||
-keepnames class cn.reactnative.modules.update.UpdateModuleImpl { *; }
|
||||
-keepnames class com.facebook.react.ReactInstanceManager { *; }
|
||||
-keepnames class cn.reactnative.modules.update.** { *; }
|
||||
|
||||
# Keep React Native classes
|
||||
-keepnames class com.facebook.react.ReactInstanceManager { *; }
|
||||
-keepnames class com.facebook.react.** { *; }
|
||||
-keepnames class com.facebook.react.bridge.** { *; }
|
||||
-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.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);
|
||||
}
|
@@ -169,17 +169,19 @@ public class UpdateContext {
|
||||
}
|
||||
|
||||
public void markSuccess() {
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putBoolean("firstTimeOk", true);
|
||||
String lastVersion = sp.getString("lastVersion", null);
|
||||
String curVersion = sp.getString("currentVersion", null);
|
||||
if (lastVersion != null && !lastVersion.equals(curVersion)) {
|
||||
editor.remove("lastVersion");
|
||||
editor.remove("hash_" + lastVersion);
|
||||
}
|
||||
editor.apply();
|
||||
if (!BuildConfig.DEBUG) {
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putBoolean("firstTimeOk", true);
|
||||
String lastVersion = sp.getString("lastVersion", null);
|
||||
String curVersion = sp.getString("currentVersion", null);
|
||||
if (lastVersion != null && !lastVersion.equals(curVersion)) {
|
||||
editor.remove("lastVersion");
|
||||
editor.remove("hash_" + lastVersion);
|
||||
}
|
||||
editor.apply();
|
||||
|
||||
this.cleanUp();
|
||||
this.cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearFirstTime() {
|
||||
|
@@ -147,13 +147,22 @@ public class UpdateModuleImpl {
|
||||
reactHostField.setAccessible(true);
|
||||
Object reactHost = reactHostField.get(reactDelegate);
|
||||
|
||||
Field devSupport = reactHost.getClass().getDeclaredField("mUseDevSupport");
|
||||
devSupport.setAccessible(true);
|
||||
devSupport.set(reactHost, false);
|
||||
|
||||
// Access the mReactHostDelegate field
|
||||
Field reactHostDelegateField = reactHost.getClass().getDeclaredField("mReactHostDelegate");
|
||||
reactHostDelegateField.setAccessible(true);
|
||||
Object reactHostDelegate = reactHostDelegateField.get(reactHost);
|
||||
|
||||
String bundleFieldName = "jsBundleLoader";
|
||||
if (reactHostDelegate.getClass().getCanonicalName().equals("expo.modules.ExpoReactHostFactory.ExpoReactHostDelegate")) {
|
||||
bundleFieldName = "_jsBundleLoader";
|
||||
}
|
||||
|
||||
// Modify the jsBundleLoader field
|
||||
Field jsBundleLoaderField = reactHostDelegate.getClass().getDeclaredField("jsBundleLoader");
|
||||
Field jsBundleLoaderField = reactHostDelegate.getClass().getDeclaredField(bundleFieldName);
|
||||
jsBundleLoaderField.setAccessible(true);
|
||||
jsBundleLoaderField.set(reactHostDelegate, loader);
|
||||
|
||||
|
@@ -4,6 +4,8 @@ import static androidx.core.content.FileProvider.getUriForFile;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
@@ -17,6 +19,7 @@ import java.util.Map;
|
||||
public class UpdateModule extends NativePushySpec {
|
||||
UpdateContext updateContext;
|
||||
public static ReactApplicationContext mContext;
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
public UpdateModule(ReactApplicationContext reactContext, UpdateContext updateContext) {
|
||||
super(reactContext);
|
||||
this.updateContext = updateContext;
|
||||
@@ -38,12 +41,22 @@ public class UpdateModule extends NativePushySpec {
|
||||
boolean isFirstTime = updateContext.isFirstTime();
|
||||
constants.put("isFirstTime", isFirstTime);
|
||||
if (isFirstTime) {
|
||||
updateContext.clearFirstTime();
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateContext.clearFirstTime();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
String rolledBackVersion = updateContext.rolledBackVersion();
|
||||
constants.put("rolledBackVersion", rolledBackVersion);
|
||||
if (rolledBackVersion != null) {
|
||||
updateContext.clearRollbackMark();
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateContext.clearRollbackMark();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
constants.put("uuid", updateContext.getKv("uuid"));
|
||||
return constants;
|
||||
|
@@ -20,7 +20,6 @@ import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.bridge.JSBundleLoader;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.jakewharton.processphoenix.ProcessPhoenix;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
|
@@ -1 +1 @@
|
||||
["https://pushy-koa-qgbgqmcpis.cn-beijing.fcapp.run", "https://p.reactnative.cn/api"]
|
||||
["https://p.reactnative.cn/api"]
|
||||
|
@@ -1 +0,0 @@
|
||||
../../../../../harmony/oh_modules/.ohpm/@rnoh+react-native-openharmony@0.72.38/oh_modules/@rnoh/react-native-openharmony
|
BIN
harmony/pushy.har
Normal file
@@ -73,7 +73,7 @@ static jsi::Value _hostFunction_PushyTurboModule_downloadPatchFromPpk(
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"downloadPatchFromPpk", args, count));
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).callAsync(rt,"downloadPatchFromPpk", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_downloadPatchFromPackage(
|
||||
@@ -82,7 +82,7 @@ static jsi::Value _hostFunction_PushyTurboModule_downloadPatchFromPackage(
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"downloadPatchFromPackage", args, count));
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).callAsync(rt,"downloadPatchFromPackage", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_downloadFullUpdate(
|
||||
@@ -91,7 +91,7 @@ static jsi::Value _hostFunction_PushyTurboModule_downloadFullUpdate(
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"downloadFullUpdate", args, count));
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).callAsync(rt,"downloadFullUpdate", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_downloadAndInstallApk(
|
||||
@@ -100,7 +100,7 @@ static jsi::Value _hostFunction_PushyTurboModule_downloadAndInstallApk(
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"downloadAndInstallApk", args, count));
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).callAsync(rt,"downloadAndInstallApk", args, count));
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import fileIo from '@ohos.file.fs';
|
||||
import common from '@ohos.app.ability.common';
|
||||
import { UpdateContext } from './UpdateContext';
|
||||
|
||||
export class FileJSBundleProvider extends JSBundleProvider {
|
||||
export class PushyFileJSBundleProvider extends JSBundleProvider {
|
||||
private updateContext: UpdateContext;
|
||||
private filePath: string = ''
|
||||
|
||||
@@ -12,7 +12,7 @@ export class FileJSBundleProvider extends JSBundleProvider {
|
||||
this.updateContext = new UpdateContext(context);
|
||||
}
|
||||
getURL(): string {
|
||||
return this.updateContext.getBundleUrl();
|
||||
return this.updateContext.getBundleUrl().substring(1);
|
||||
}
|
||||
|
||||
async getBundle(): Promise<ArrayBuffer> {
|
2
ios/ImportReact.h
Normal file
@@ -0,0 +1,2 @@
|
||||
@import React;
|
||||
|
@@ -93,8 +93,7 @@ RCT_EXPORT_MODULE(RCTPushy);
|
||||
BOOL needRollback = (!ignoreRollback && isFirstTime == NO && isFirstLoadOK == NO) || loadVersion.length<=0;
|
||||
if (needRollback) {
|
||||
loadVersion = [self rollback];
|
||||
}
|
||||
else if (isFirstTime && !ignoreRollback){
|
||||
} else if (isFirstTime && !ignoreRollback){
|
||||
// bundleURL may be called many times, ignore rollbacks before process restarted again.
|
||||
ignoreRollback = true;
|
||||
|
||||
@@ -302,34 +301,34 @@ RCT_EXPORT_METHOD(setNeedUpdate:(NSDictionary *)options
|
||||
|
||||
|
||||
resolve(@true);
|
||||
}else{
|
||||
} else {
|
||||
reject(@"执行报错", nil, nil);
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(reloadUpdate:(NSDictionary *)options
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject)
|
||||
rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
@try {
|
||||
NSString *hash = options[@"hash"];
|
||||
if (hash.length) {
|
||||
[self setNeedUpdate:options resolver:resolve rejecter:reject];
|
||||
|
||||
// reload in earlier version
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.bridge setValue:[[self class] bundleURL] forKey:@"bundleURL"];
|
||||
[self.bridge reload];
|
||||
});
|
||||
|
||||
#if __has_include("RCTReloadCommand.h")
|
||||
// reload 0.62+
|
||||
RCTReloadCommandSetBundleURL([[self class] bundleURL]);
|
||||
RCTTriggerReloadCommandListeners(@"pushy reload");
|
||||
#endif
|
||||
|
||||
resolve(@true);
|
||||
}else{
|
||||
// 只在 setNeedUpdate 成功后 resolve
|
||||
[self setNeedUpdate:options resolver:^(id result) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
#if __has_include("RCTReloadCommand.h")
|
||||
// reload 0.62+
|
||||
RCTReloadCommandSetBundleURL([[self class] bundleURL]);
|
||||
RCTTriggerReloadCommandListeners(@"pushy reloadUpdate");
|
||||
#else
|
||||
[self.bridge reload];
|
||||
#endif
|
||||
});
|
||||
resolve(@true);
|
||||
} rejecter:^(NSString *code, NSString *message, NSError *error) {
|
||||
reject(code, message, error);
|
||||
}];
|
||||
} else {
|
||||
reject(@"执行报错", nil, nil);
|
||||
}
|
||||
}
|
||||
@@ -343,13 +342,14 @@ RCT_EXPORT_METHOD(restartApp:(RCTPromiseResolveBlock)resolve
|
||||
{
|
||||
@try {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.bridge reload];
|
||||
#if __has_include("RCTReloadCommand.h")
|
||||
// reload 0.62+
|
||||
RCTReloadCommandSetBundleURL([[self class] bundleURL]);
|
||||
RCTTriggerReloadCommandListeners(@"pushy restartApp");
|
||||
#else
|
||||
[self.bridge reload];
|
||||
#endif
|
||||
});
|
||||
#if __has_include("RCTReloadCommand.h")
|
||||
// reload 0.62+
|
||||
RCTReloadCommandSetBundleURL([[self class] bundleURL]);
|
||||
RCTTriggerReloadCommandListeners(@"pushy restartApp");
|
||||
#endif
|
||||
|
||||
resolve(@true);
|
||||
}
|
||||
@@ -542,7 +542,15 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
|
||||
callback([self errorWithMessage:ERROR_HDIFFPATCH]);
|
||||
}
|
||||
};
|
||||
[_fileManager hdiffFileAtPath:bundlePatch fromOrigin:bundleOrigin toDestination:destination completionHandler:completionHandler];
|
||||
|
||||
@try {
|
||||
[_fileManager hdiffFileAtPath:bundlePatch fromOrigin:bundleOrigin toDestination:destination completionHandler:completionHandler];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSLog(@"Pushy _dopatch error: exception occurred during hdiffFileAtPath: %@, reason: %@",
|
||||
exception.name, exception.reason);
|
||||
callback([self errorWithMessage:ERROR_HDIFFPATCH]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)patch:(NSString *)hash fromBundle:(NSString *)bundleOrigin source:(NSString *)sourceOrigin callback:(void (^)(NSError *error))callback
|
||||
|
48
package.json
@@ -1,29 +1,29 @@
|
||||
{
|
||||
"name": "react-native-update",
|
||||
"version": "10.28.5",
|
||||
"version": "10.29.8",
|
||||
"description": "react-native hot update",
|
||||
"main": "src/index",
|
||||
"scripts": {
|
||||
"postinstall": "node scripts/check-expo-version.js",
|
||||
"prepack": "yarn submodule && yarn lint",
|
||||
"prepack": "bun submodule && bun lint",
|
||||
"lint": "eslint \"src/*.@(ts|tsx|js|jsx)\" && tsc --noEmit",
|
||||
"submodule": "git submodule update --init --recursive",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build-lib": "yarn submodule && $ANDROID_HOME/ndk/20.1.5948944/ndk-build NDK_PROJECT_PATH=android APP_BUILD_SCRIPT=android/jni/Android.mk NDK_APPLICATION_MK=android/jni/Application.mk NDK_LIBS_OUT=android/lib",
|
||||
"build:ios-debug": "cd Example/testHotUpdate && yarn && detox build --configuration ios.sim.debug",
|
||||
"build:ios-release": "cd Example/testHotUpdate && yarn && detox build --configuration ios.sim.release",
|
||||
"build-lib": "bun submodule && $ANDROID_HOME/ndk/20.1.5948944/ndk-build NDK_PROJECT_PATH=android APP_BUILD_SCRIPT=android/jni/Android.mk NDK_APPLICATION_MK=android/jni/Application.mk NDK_LIBS_OUT=android/lib",
|
||||
"build:ios-debug": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.debug",
|
||||
"build:ios-release": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.release",
|
||||
"test:ios-debug": "cd Example/testHotUpdate && detox test --configuration ios.sim.debug",
|
||||
"test:ios-release": "cd Example/testHotUpdate && yarn detox test --configuration ios.sim.release",
|
||||
"build:android-debug": "cd Example/testHotUpdate && yarn && detox build --configuration android.emu.debug",
|
||||
"build:android-release": "cd Example/testHotUpdate && yarn && detox build --configuration android.emu.release",
|
||||
"test:android-release": "cd Example/testHotUpdate && yarn detox test --configuration android.emu.release --headless --record-logs all",
|
||||
"test:ios-release": "cd Example/testHotUpdate && bun detox test --configuration ios.sim.release",
|
||||
"build:android-debug": "cd Example/testHotUpdate && bun && detox build --configuration android.emu.debug",
|
||||
"build:android-release": "cd Example/testHotUpdate && bun && detox build --configuration android.emu.release",
|
||||
"test:android-release": "cd Example/testHotUpdate && bun detox test --configuration android.emu.release --headless --record-logs all",
|
||||
"test:android-debug": "cd Example/testHotUpdate && detox test --configuration android.emu.debug --headless --record-logs all",
|
||||
"e2e:ios": "npm run build:ios-release && npm run test:ios-release",
|
||||
"e2e:android": "npm run build:android-release && npm run test:android-release",
|
||||
"tests:emulator:prepare": "cd .github/workflows/scripts/functions && yarn && yarn build",
|
||||
"tests:emulator:start-ci": "yarn tests:emulator:prepare && cd ./.github/workflows/scripts && ./start-firebase-emulator.sh",
|
||||
"e2e:ios": "bun build:ios-release && bun test:ios-release",
|
||||
"e2e:android": "bun build:android-release && bun test:android-release",
|
||||
"tests:emulator:prepare": "cd .github/workflows/scripts/functions && bun && bun build",
|
||||
"tests:emulator:start-ci": "bun tests:emulator:prepare && cd ./.github/workflows/scripts && ./start-firebase-emulator.sh",
|
||||
"tests:packager:jet-ci": "cd Example/testHotUpdate && cross-env TMPDIR=$HOME/.metro REACT_DEBUGGER=\"echo nope\" node_modules/.bin/react-native start --no-interactive",
|
||||
"tests:ios:pod:install": "cd Example/testHotUpdate && yarn && yarn pod-install"
|
||||
"tests:ios:pod:install": "cd Example/testHotUpdate && bun && bun pod-install"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -57,24 +57,20 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.8",
|
||||
"@react-native/babel-preset": "^0.73.21",
|
||||
"@react-native/eslint-config": "^0.73.2",
|
||||
"@react-native/typescript-config": "^0.74.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/jest": "^29.5.13",
|
||||
"@types/node": "^22.7.6",
|
||||
"@react-native/eslint-config": "0.79.1",
|
||||
"@react-native/typescript-config": "0.79.1",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.15.2",
|
||||
"@types/react": "^18.3.11",
|
||||
"detox": "^20.27.3",
|
||||
"detox": "^20.37.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-ft-flow": "^3.0.7",
|
||||
"firebase-tools": "^13.22.1",
|
||||
"fs-extra": "^11.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"pod-install": "^0.2.2",
|
||||
"pod-install": "^0.3.7",
|
||||
"prettier": "^2",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.73",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-jest": "^29.3.2",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,6 @@ podspec_dir = File.dirname(__FILE__)
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
is_expo_in_podfile = false
|
||||
is_version_sufficient = false
|
||||
begin
|
||||
# Check Podfile for use_expo_modules!
|
||||
podfile_path = File.join(Pod::Config.instance.installation_root, 'Podfile')
|
||||
@@ -17,24 +16,67 @@ Pod::Spec.new do |s|
|
||||
podfile_content = File.read(podfile_path)
|
||||
is_expo_in_podfile = podfile_content.include?('use_expo_modules!')
|
||||
end
|
||||
# Check root package.json for Expo version >= 50
|
||||
root_package_json_path = File.join(podspec_dir, '..', '..', 'package.json')
|
||||
if File.exist?(root_package_json_path)
|
||||
pkg_json = JSON.parse(File.read(root_package_json_path))
|
||||
expo_version_string = pkg_json['dependencies']&.[]('expo') || pkg_json['devDependencies']&.[]('expo')
|
||||
if expo_version_string
|
||||
match = expo_version_string.match(/\d+/)
|
||||
rescue => e
|
||||
# Silently ignore errors during check
|
||||
end
|
||||
|
||||
# Determine final validity by checking Podfile presence AND Expo version
|
||||
valid_expo_project = false # Default
|
||||
if is_expo_in_podfile
|
||||
# Only check expo version if use_expo_modules! is present
|
||||
is_version_sufficient = false
|
||||
begin
|
||||
expo_version_str = `node --print \"require('expo/package.json').version\"`.strip
|
||||
if expo_version_str && !expo_version_str.empty?
|
||||
match = expo_version_str.match(/^\d+/)
|
||||
if match
|
||||
major_version = match[0].to_i
|
||||
is_version_sufficient = major_version >= 50
|
||||
end
|
||||
end
|
||||
rescue
|
||||
# Node command failed, version remains insufficient
|
||||
end
|
||||
rescue => e
|
||||
# Silently ignore errors during check
|
||||
|
||||
# Final check
|
||||
valid_expo_project = is_version_sufficient
|
||||
end
|
||||
# Determine final validity
|
||||
valid_expo_project = is_expo_in_podfile && is_version_sufficient
|
||||
|
||||
# Set platform based on whether it's a valid Expo project and if we can parse its target
|
||||
final_ios_deployment_target = '11.0' # Default target
|
||||
|
||||
if valid_expo_project
|
||||
# --- Try to find and parse ExpoModulesCore.podspec only if it's an Expo project ---
|
||||
parsed_expo_ios_target = nil
|
||||
expo_modules_core_podspec_path = begin
|
||||
package_json_path = `node -p "require.resolve('expo-modules-core/package.json')"`.strip
|
||||
File.join(File.dirname(package_json_path), 'ExpoModulesCore.podspec') if $?.success? && package_json_path && !package_json_path.empty?
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
if expo_modules_core_podspec_path && File.exist?(expo_modules_core_podspec_path)
|
||||
begin
|
||||
content = File.read(expo_modules_core_podspec_path)
|
||||
match = content.match(/s\.platforms\s*=\s*\{[\s\S]*?:ios\s*=>\s*'([^\']+)'/) # Match within s.platforms hash
|
||||
if match && match[1]
|
||||
parsed_expo_ios_target = match[1]
|
||||
else
|
||||
match = content.match(/s\.platform\s*=\s*:ios,\s*'([^\']+)'/) # Fallback to s.platform = :ios, 'version'
|
||||
if match && match[1]
|
||||
parsed_expo_ios_target = match[1]
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
# Pod::UI.warn "Failed to read or parse ExpoModulesCore.podspec content: #{e.message}"
|
||||
end
|
||||
end
|
||||
if parsed_expo_ios_target
|
||||
final_ios_deployment_target = parsed_expo_ios_target
|
||||
end
|
||||
end
|
||||
|
||||
s.platforms = { :ios => final_ios_deployment_target }
|
||||
|
||||
s.name = package['name']
|
||||
s.version = package['version']
|
||||
@@ -45,16 +87,8 @@ Pod::Spec.new do |s|
|
||||
s.homepage = package['homepage']
|
||||
|
||||
s.cocoapods_version = '>= 1.6.0'
|
||||
s.platform = :ios, "8.0"
|
||||
s.platforms = { :ios => "11.0" }
|
||||
s.source = { :git => 'https://github.com/reactnativecn/react-native-update.git', :tag => '#{s.version}' }
|
||||
|
||||
# Conditionally set source files
|
||||
if valid_expo_project
|
||||
s.source_files = Dir.glob("ios/**/*.{h,m,mm,swift}") # Include Expo files
|
||||
else
|
||||
s.source_files = Dir.glob("ios/**/*.{h,m,mm,swift}").reject { |f| f.start_with?("ios/Expo/") } # Exclude Expo files
|
||||
end
|
||||
s.source = { :git => 'https://github.com/reactnativecn/react-native-update.git', :tag => '#{s.version}' }
|
||||
|
||||
s.libraries = 'bz2', 'z'
|
||||
s.vendored_libraries = 'RCTPushy/libRCTPushy.a'
|
||||
@@ -71,41 +105,43 @@ Pod::Spec.new do |s|
|
||||
|
||||
# Conditionally add Expo dependency
|
||||
if valid_expo_project
|
||||
s.public_header_files = ['ios/ImportReact.h']
|
||||
s.dependency 'ExpoModulesCore'
|
||||
end
|
||||
|
||||
s.subspec 'RCTPushy' do |ss|
|
||||
ss.source_files = 'ios/RCTPushy/*.{h,m,mm,swift}'
|
||||
ss.public_header_files = ['ios/RCTPushy/*.h']
|
||||
end
|
||||
|
||||
s.subspec 'HDiffPatch' do |ss|
|
||||
ss.source_files = ['ios/RCTPushy/HDiffPatch/**/*.{h,m,c}',
|
||||
ss.source_files = ['ios/RCTPushy/**/*.{h,m,mm,c}',
|
||||
'android/jni/hpatch.{h,c}',
|
||||
'android/jni/HDiffPatch/libHDiffPatch/HPatch/*.{h,c}',
|
||||
'android/jni/HDiffPatch/file_for_patch.{h,c}',
|
||||
'android/jni/lzma/C/LzmaDec.{h,c}',
|
||||
'android/jni/lzma/C/Lzma2Dec.{h,c}']
|
||||
ss.public_header_files = 'ios/RCTPushy/HDiffPatch/**/*.h'
|
||||
ss.public_header_files = ['ios/RCTPushy/**/*.h']
|
||||
end
|
||||
|
||||
# Conditionally add Expo subspec and check ExpoModulesCore version
|
||||
if valid_expo_project
|
||||
supports_bundle_url_final = false # Default
|
||||
begin
|
||||
# Check installed ExpoModulesCore version for bundle URL support
|
||||
expo_core_package_json_path = File.join(podspec_dir, '..', 'expo-modules-core', 'package.json')
|
||||
if File.exist?(expo_core_package_json_path)
|
||||
core_package_json = JSON.parse(File.read(expo_core_package_json_path))
|
||||
installed_version_str = core_package_json['version']
|
||||
if installed_version_str
|
||||
installed_version = Gem::Version.new(installed_version_str)
|
||||
|
||||
# 1. Try executing node to get the version string
|
||||
expo_modules_core_version_str = begin
|
||||
# Use node to directly require expo-modules-core/package.json and get its version
|
||||
`node --print \"require('expo-modules-core/package.json').version\"` # Execute, keep raw output
|
||||
rescue
|
||||
# Node command failed (e.g., node not found, package not found). Return empty string.
|
||||
''
|
||||
end
|
||||
|
||||
# 2. Process the obtained version string (if not empty)
|
||||
if expo_modules_core_version_str && !expo_modules_core_version_str.empty?
|
||||
begin
|
||||
# Compare versions using Gem::Version (handles trailing newline)
|
||||
installed_version = Gem::Version.new(expo_modules_core_version_str)
|
||||
target_version = Gem::Version.new('1.12.0')
|
||||
supports_bundle_url_final = installed_version >= target_version
|
||||
end
|
||||
end
|
||||
rescue JSON::ParserError, Errno::ENOENT, ArgumentError, StandardError => e
|
||||
# Pod::UI.warn "Could not check ExpoModulesCore version: #{e.message}"
|
||||
rescue ArgumentError
|
||||
# If Gem::Version fails parsing, supports_bundle_url_final remains false.
|
||||
end
|
||||
end
|
||||
|
||||
s.subspec 'Expo' do |ss|
|
||||
|
@@ -1,3 +1,10 @@
|
||||
const ownPackageJson = require('../package.json');
|
||||
|
||||
if (process.env.npm_package_name === ownPackageJson.name) {
|
||||
console.log('Skipping postinstall during local development.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
|
143
src/client.ts
@@ -1,33 +1,33 @@
|
||||
import { CheckResult, ClientOptions, ProgressData, EventType } from './type';
|
||||
import {
|
||||
assertDev,
|
||||
DeviceEventEmitter,
|
||||
EmitterSubscription,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import {
|
||||
PushyModule,
|
||||
buildTime,
|
||||
cInfo,
|
||||
currentVersion,
|
||||
getCurrentVersionInfo,
|
||||
isFirstTime,
|
||||
isRolledBack,
|
||||
packageVersion,
|
||||
pushyNativeEventEmitter,
|
||||
rolledBackVersion,
|
||||
setLocalHashInfo,
|
||||
} from './core';
|
||||
import { PermissionsAndroid } from './permissions';
|
||||
import { CheckResult, ClientOptions, EventType, ProgressData } from './type';
|
||||
import {
|
||||
assertWeb,
|
||||
emptyObj,
|
||||
enhancedFetch,
|
||||
joinUrls,
|
||||
log,
|
||||
noop,
|
||||
promiseAny,
|
||||
testUrls,
|
||||
} from './utils';
|
||||
import {
|
||||
EmitterSubscription,
|
||||
Platform,
|
||||
DeviceEventEmitter,
|
||||
} from 'react-native';
|
||||
import { PermissionsAndroid } from './permissions';
|
||||
import {
|
||||
PushyModule,
|
||||
buildTime,
|
||||
cInfo,
|
||||
pushyNativeEventEmitter,
|
||||
currentVersion,
|
||||
packageVersion,
|
||||
rolledBackVersion,
|
||||
setLocalHashInfo,
|
||||
isFirstTime,
|
||||
isRolledBack,
|
||||
getCurrentVersionInfo,
|
||||
} from './core';
|
||||
|
||||
const SERVER_PRESETS = {
|
||||
// cn
|
||||
@@ -148,6 +148,7 @@ export class Pushy {
|
||||
await this.loggerPromise.promise;
|
||||
const { logger = noop, appKey } = this.options;
|
||||
const info = await getCurrentVersionInfo();
|
||||
const overridePackageVersion = this.options.overridePackageVersion;
|
||||
logger({
|
||||
type,
|
||||
data: {
|
||||
@@ -155,6 +156,7 @@ export class Pushy {
|
||||
currentVersion,
|
||||
cInfo,
|
||||
packageVersion,
|
||||
overridePackageVersion,
|
||||
buildTime,
|
||||
message,
|
||||
...info,
|
||||
@@ -170,10 +172,12 @@ export class Pushy {
|
||||
getCheckUrl = (endpoint: string = this.options.server!.main) => {
|
||||
return `${endpoint}/checkUpdate/${this.options.appKey}`;
|
||||
};
|
||||
assertDebug = () => {
|
||||
assertDebug = (matter: string) => {
|
||||
if (__DEV__ && !this.options.debug) {
|
||||
console.info(
|
||||
'You are currently in the development environment and have not enabled debug mode. The hot update check will not be performed. If you need to debug hot updates in the development environment, please set debug to true in the client.',
|
||||
`You are currently in the development environment and have not enabled debug mode.
|
||||
${matter} will not be performed.
|
||||
If you need to debug ${matter} in the development environment, please set debug to true in the client.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -188,7 +192,7 @@ export class Pushy {
|
||||
this.report({ type: 'markSuccess' });
|
||||
};
|
||||
switchVersion = async (hash: string) => {
|
||||
if (!assertDev('switchVersion()')) {
|
||||
if (!this.assertDebug('switchVersion()')) {
|
||||
return;
|
||||
}
|
||||
if (assertHash(hash) && !sharedState.applyingUpdate) {
|
||||
@@ -199,7 +203,7 @@ export class Pushy {
|
||||
};
|
||||
|
||||
switchVersionLater = async (hash: string) => {
|
||||
if (!assertDev('switchVersionLater()')) {
|
||||
if (!this.assertDebug('switchVersionLater()')) {
|
||||
return;
|
||||
}
|
||||
if (assertHash(hash)) {
|
||||
@@ -208,7 +212,7 @@ export class Pushy {
|
||||
}
|
||||
};
|
||||
checkUpdate = async (extra?: Record<string, any>) => {
|
||||
if (!this.assertDebug()) {
|
||||
if (!this.assertDebug('checkUpdate()')) {
|
||||
return;
|
||||
}
|
||||
if (!assertWeb()) {
|
||||
@@ -231,7 +235,7 @@ export class Pushy {
|
||||
}
|
||||
this.lastChecking = now;
|
||||
const fetchBody = {
|
||||
packageVersion,
|
||||
packageVersion: this.options.overridePackageVersion || packageVersion,
|
||||
hash: currentVersion,
|
||||
buildTime,
|
||||
cInfo,
|
||||
@@ -261,7 +265,7 @@ export class Pushy {
|
||||
type: 'checking',
|
||||
message: this.options.appKey + ': ' + stringifyBody,
|
||||
});
|
||||
resp = await fetch(this.getCheckUrl(), fetchPayload);
|
||||
resp = await enhancedFetch(this.getCheckUrl(), fetchPayload);
|
||||
} catch (e: any) {
|
||||
this.report({
|
||||
type: 'errorChecking',
|
||||
@@ -272,7 +276,7 @@ export class Pushy {
|
||||
try {
|
||||
resp = await promiseAny(
|
||||
backupEndpoints.map(endpoint =>
|
||||
fetch(this.getCheckUrl(endpoint), fetchPayload),
|
||||
enhancedFetch(this.getCheckUrl(endpoint), fetchPayload),
|
||||
),
|
||||
);
|
||||
} catch (err: any) {
|
||||
@@ -389,11 +393,16 @@ export class Pushy {
|
||||
}
|
||||
}
|
||||
let succeeded = '';
|
||||
this.report({ type: 'downloading' });
|
||||
this.report({
|
||||
type: 'downloading',
|
||||
data: {
|
||||
newVersion: hash,
|
||||
},
|
||||
});
|
||||
let lastError: any;
|
||||
let errorMessages: string[] = [];
|
||||
const diffUrl = await testUrls(joinUrls(paths, diff));
|
||||
if (diffUrl) {
|
||||
if (diffUrl && !__DEV__) {
|
||||
log('downloading diff');
|
||||
try {
|
||||
await PushyModule.downloadPatchFromPpk({
|
||||
@@ -406,60 +415,56 @@ export class Pushy {
|
||||
const errorMessage = `diff error: ${e.message}`;
|
||||
errorMessages.push(errorMessage);
|
||||
lastError = new Error(errorMessage);
|
||||
if (__DEV__) {
|
||||
succeeded = 'diff';
|
||||
} else {
|
||||
log(errorMessage);
|
||||
}
|
||||
log(errorMessage);
|
||||
}
|
||||
}
|
||||
const pdiffUrl = await testUrls(joinUrls(paths, pdiff));
|
||||
if (!succeeded && pdiffUrl) {
|
||||
log('downloading pdiff');
|
||||
try {
|
||||
await PushyModule.downloadPatchFromPackage({
|
||||
updateUrl: pdiffUrl,
|
||||
hash,
|
||||
});
|
||||
succeeded = 'pdiff';
|
||||
} catch (e: any) {
|
||||
const errorMessage = `pdiff error: ${e.message}`;
|
||||
errorMessages.push(errorMessage);
|
||||
lastError = new Error(errorMessage);
|
||||
if (__DEV__) {
|
||||
if (!succeeded) {
|
||||
const pdiffUrl = await testUrls(joinUrls(paths, pdiff));
|
||||
if (pdiffUrl && !__DEV__) {
|
||||
log('downloading pdiff');
|
||||
try {
|
||||
await PushyModule.downloadPatchFromPackage({
|
||||
updateUrl: pdiffUrl,
|
||||
hash,
|
||||
});
|
||||
succeeded = 'pdiff';
|
||||
} else {
|
||||
} catch (e: any) {
|
||||
const errorMessage = `pdiff error: ${e.message}`;
|
||||
errorMessages.push(errorMessage);
|
||||
lastError = new Error(errorMessage);
|
||||
log(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
const fullUrl = await testUrls(joinUrls(paths, full));
|
||||
if (!succeeded && fullUrl) {
|
||||
log('downloading full patch');
|
||||
try {
|
||||
await PushyModule.downloadFullUpdate({
|
||||
updateUrl: fullUrl,
|
||||
hash,
|
||||
});
|
||||
succeeded = 'full';
|
||||
} catch (e: any) {
|
||||
const errorMessage = `full patch error: ${e.message}`;
|
||||
errorMessages.push(errorMessage);
|
||||
lastError = new Error(errorMessage);
|
||||
if (__DEV__) {
|
||||
if (!succeeded) {
|
||||
const fullUrl = await testUrls(joinUrls(paths, full));
|
||||
if (fullUrl) {
|
||||
log('downloading full patch');
|
||||
try {
|
||||
await PushyModule.downloadFullUpdate({
|
||||
updateUrl: fullUrl,
|
||||
hash,
|
||||
});
|
||||
succeeded = 'full';
|
||||
} else {
|
||||
} catch (e: any) {
|
||||
const errorMessage = `full patch error: ${e.message}`;
|
||||
errorMessages.push(errorMessage);
|
||||
lastError = new Error(errorMessage);
|
||||
log(errorMessage);
|
||||
}
|
||||
} else if (__DEV__) {
|
||||
log(
|
||||
`当前是开发环境,无法执行增量式热更新,重启不会生效。
|
||||
如果需要在开发环境中测试可生效的全量热更新(但也会在再次重启后重新连接 metro),
|
||||
请打开“忽略时间戳”开关再重试。`,
|
||||
);
|
||||
succeeded = 'full';
|
||||
}
|
||||
}
|
||||
if (sharedState.progressHandlers[hash]) {
|
||||
sharedState.progressHandlers[hash].remove();
|
||||
delete sharedState.progressHandlers[hash];
|
||||
}
|
||||
if (__DEV__) {
|
||||
return hash;
|
||||
}
|
||||
if (!succeeded) {
|
||||
this.report({
|
||||
type: 'errorUpdate',
|
||||
|
@@ -251,7 +251,7 @@ export const UpdateProvider = ({
|
||||
const markSuccess = client.markSuccess;
|
||||
|
||||
useEffect(() => {
|
||||
if (!client.assertDebug()) {
|
||||
if (!client.assertDebug('checkUpdate()')) {
|
||||
return;
|
||||
}
|
||||
const { checkStrategy, dismissErrorAfter, autoMarkSuccess } = options;
|
||||
|
@@ -93,6 +93,7 @@ export interface ClientOptions {
|
||||
beforeDownloadUpdate?: (info: CheckResult) => Promise<boolean>;
|
||||
afterDownloadUpdate?: (info: CheckResult) => Promise<boolean>;
|
||||
onPackageExpired?: (info: CheckResult) => Promise<boolean>;
|
||||
overridePackageVersion?: string;
|
||||
}
|
||||
|
||||
export interface UpdateTestPayload {
|
||||
|
31
src/utils.ts
@@ -40,13 +40,13 @@ const ping =
|
||||
: async (url: string) => {
|
||||
let pingFinished = false;
|
||||
return Promise.race([
|
||||
fetch(url, {
|
||||
enhancedFetch(url, {
|
||||
method: 'HEAD',
|
||||
})
|
||||
.then(({ status, statusText }) => {
|
||||
.then(({ status, statusText, url: finalUrl }) => {
|
||||
pingFinished = true;
|
||||
if (status === 200) {
|
||||
return url;
|
||||
return finalUrl;
|
||||
}
|
||||
log('ping failed', url, status, statusText);
|
||||
throw new Error('Ping failed');
|
||||
@@ -69,7 +69,7 @@ const ping =
|
||||
|
||||
export function joinUrls(paths: string[], fileName?: string) {
|
||||
if (fileName) {
|
||||
return paths.map(path => 'https://' + path + '/' + fileName);
|
||||
return paths.map(path => `https://${path}/${fileName}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,12 +99,19 @@ export const assertWeb = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const assertDev = (matter: string) => {
|
||||
if (__DEV__) {
|
||||
console.warn(
|
||||
`${matter} is not supported in development environment; no action taken.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// export const isAndroid70AndBelow = () => {
|
||||
// // android 7.0 and below devices do not support letsencrypt cert
|
||||
// // https://letsencrypt.org/2023/07/10/cross-sign-expiration/
|
||||
// return Platform.OS === 'android' && Platform.Version <= 24;
|
||||
// };
|
||||
|
||||
export const enhancedFetch = async (
|
||||
url: string,
|
||||
params: Parameters<typeof fetch>[1],
|
||||
) => {
|
||||
return fetch(url, params).catch(e => {
|
||||
log('fetch error', url, e);
|
||||
log('trying fallback to http');
|
||||
return fetch(url.replace('https', 'http'), params);
|
||||
});
|
||||
};
|
||||
|