Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
40742e16d8 | ||
![]() |
598ae1a506 | ||
![]() |
e5424591d1 | ||
![]() |
2cf7336b6a | ||
![]() |
7eac48ab5d | ||
![]() |
18d9b75545 | ||
![]() |
e8ec85c65f | ||
![]() |
48a776d506 | ||
![]() |
8ad526148f | ||
![]() |
cea39f1746 | ||
![]() |
aa56c2ec0f | ||
![]() |
00a989d567 | ||
![]() |
83ca3a6c05 | ||
![]() |
257f2697e0 | ||
![]() |
77aa345f11 | ||
![]() |
66332d007a | ||
![]() |
37849b1730 | ||
![]() |
c771672fcd | ||
![]() |
2978454298 |
@@ -6,6 +6,7 @@
|
|||||||
Example
|
Example
|
||||||
android/build
|
android/build
|
||||||
.vscode
|
.vscode
|
||||||
|
.github/
|
||||||
|
|
||||||
# OSX
|
# OSX
|
||||||
#
|
#
|
||||||
@@ -52,3 +53,5 @@ endpoints.json
|
|||||||
endpoints_cresc.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",
|
"name": "harmony_use_pushy",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-oh/react-native-harmony": "^0.72.43",
|
"@react-native-oh/react-native-harmony": "^0.72.59",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-native": "0.72.5",
|
"react-native": "0.72.5",
|
||||||
"react-native-update": "^10.26.4",
|
"react-native-update": "^10.26.4",
|
||||||
|
@@ -36,10 +36,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'pushy',
|
|
||||||
srcPath: '../node_modules/react-native-update/harmony',
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@@ -7,7 +7,7 @@
|
|||||||
"license": "",
|
"license": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rnoh/react-native-openharmony": "0.72.38",
|
"@rnoh/react-native-openharmony": "0.72.38",
|
||||||
"pushy": "file:../../node_modules/react-native-update/harmony"
|
"pushy": "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,
|
import { ComponentBuilderContext, RNOHCoreContext,RNAbility,
|
||||||
MetroJSBundleProvider } from '@rnoh/react-native-openharmony';
|
MetroJSBundleProvider } from '@rnoh/react-native-openharmony';
|
||||||
import {
|
import {
|
||||||
@@ -65,7 +65,7 @@ struct Index {
|
|||||||
// local debug mode
|
// local debug mode
|
||||||
new MetroJSBundleProvider(),
|
new MetroJSBundleProvider(),
|
||||||
// release mode
|
// release mode
|
||||||
new FileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),
|
new PushyFileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),
|
||||||
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')
|
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')
|
||||||
]),
|
]),
|
||||||
this.rnohCoreContext.logger),
|
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"
|
"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"
|
"hash": "pushy hash /Users/yanbo.he/Desktop/HarmonyOS/react-native-pushy/Example/harmony_use_pushy/.pushy/output/harmony.1735048297258.ppk"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-oh/react-native-harmony": "^0.72.43",
|
"@react-native-oh/react-native-harmony": "^0.72.59",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-native": "0.72.5",
|
"react-native": "0.72.5",
|
||||||
"react-native-update": "^10.26.4"
|
"react-native-update": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
|
4
Example/testHotUpdate/.gitignore
vendored
@@ -62,3 +62,7 @@ buck-out/
|
|||||||
# Ruby / CocoaPods
|
# Ruby / CocoaPods
|
||||||
/ios/Pods/
|
/ios/Pods/
|
||||||
/vendor/bundle/
|
/vendor/bundle/
|
||||||
|
|
||||||
|
# react-native-update
|
||||||
|
.update
|
||||||
|
.pushy
|
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@@ -17,33 +17,33 @@
|
|||||||
"form-data": "^4.0.2",
|
"form-data": "^4.0.2",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-native": "0.78.0",
|
"react-native": "0.79.2",
|
||||||
"react-native-camera-kit": "^14.2.0",
|
"react-native-camera-kit": "^15.0.1",
|
||||||
"react-native-paper": "^5.13.1",
|
"react-native-paper": "^5.14.5",
|
||||||
"react-native-safe-area-context": "^5.3.0",
|
"react-native-safe-area-context": "^5.4.1",
|
||||||
"react-native-svg": "^15.11.2",
|
"react-native-svg": "^15.12.0",
|
||||||
"react-native-update": "^10.26.4",
|
"react-native-update": "^10.28.11",
|
||||||
"react-native-vector-icons": "^10.2.0"
|
"react-native-vector-icons": "^10.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.27.3",
|
||||||
"@babel/preset-env": "^7.26.0",
|
"@babel/preset-env": "^7.27.2",
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.27.3",
|
||||||
"@react-native-community/cli": "15.0.1",
|
"@react-native-community/cli": "18.0.0",
|
||||||
"@react-native-community/cli-platform-android": "15.0.1",
|
"@react-native-community/cli-platform-android": "18.0.0",
|
||||||
"@react-native-community/cli-platform-ios": "15.0.1",
|
"@react-native-community/cli-platform-ios": "18.0.0",
|
||||||
"@react-native/babel-preset": "0.78.0",
|
"@react-native/babel-preset": "0.79.2",
|
||||||
"@react-native/eslint-config": "0.78.0",
|
"@react-native/eslint-config": "0.79.2",
|
||||||
"@react-native/metro-config": "0.78.0",
|
"@react-native/metro-config": "0.79.2",
|
||||||
"@react-native/typescript-config": "0.78.0",
|
"@react-native/typescript-config": "0.79.2",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-test-renderer": "^19.0.0",
|
"@types/react-test-renderer": "^19.0.0",
|
||||||
"detox": "^20.32.0",
|
"detox": "^20.39.0",
|
||||||
"eslint": "^8.19.0",
|
"eslint": "^8.19.0",
|
||||||
"jest": "^29.6.3",
|
"jest": "^29.6.3",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"react-test-renderer": "19.0.0",
|
"react-test-renderer": "19.0.0",
|
||||||
"typescript": "5.8.2"
|
"typescript": "5.8.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
@@ -22,21 +22,49 @@ def supportsNamespace() {
|
|||||||
return major >= 8
|
return major >= 8
|
||||||
}
|
}
|
||||||
|
|
||||||
def isExpoProject() {
|
def checkProjectInfo() {
|
||||||
def hasExpoModulesCore = rootProject.subprojects.any { it.name == 'expo-modules-core' }
|
def hasExpoModulesCore = rootProject.subprojects.any { it.name == 'expo-modules-core' }
|
||||||
|
|
||||||
def packageJsonFile = new File(rootProject.projectDir.parentFile, 'package.json')
|
def packageJsonFile = new File(rootProject.projectDir.parentFile, 'package.json')
|
||||||
|
|
||||||
def hasExpoDependency = false
|
def hasExpoDependency = false
|
||||||
|
def projectVersion = '1.0.0' // Default version
|
||||||
|
|
||||||
if (packageJsonFile.exists()) {
|
if (packageJsonFile.exists()) {
|
||||||
def packageJson = new groovy.json.JsonSlurper().parseText(packageJsonFile.text)
|
def packageJson = new groovy.json.JsonSlurper().parseText(packageJsonFile.text)
|
||||||
hasExpoDependency = (packageJson.dependencies?.expo != null) ||
|
projectVersion = packageJson.version ?: '1.0.0' // Get project version
|
||||||
(packageJson.devDependencies?.expo != null)
|
|
||||||
|
// Check for expo dependency and version >= 50
|
||||||
|
String expoVersionString = packageJson.dependencies?.expo ?: packageJson.devDependencies?.expo
|
||||||
|
boolean expoVersionIsHighEnough = false
|
||||||
|
if (expoVersionString) {
|
||||||
|
try {
|
||||||
|
// Extract the first number sequence as the major version
|
||||||
|
def matcher = (expoVersionString =~ /(\d+)/)
|
||||||
|
if (matcher.find()) {
|
||||||
|
int majorVersion = matcher[0][0].toInteger()
|
||||||
|
if (majorVersion >= 50) {
|
||||||
|
expoVersionIsHighEnough = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// Handle error if version parsing fails, maybe log a warning
|
||||||
|
println "Warning: Could not parse Expo version string: ${expoVersionString}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasExpoDependency = expoVersionIsHighEnough // Update based on version check
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasExpoModulesCore || hasExpoDependency
|
def isExpo = hasExpoModulesCore && hasExpoDependency
|
||||||
|
|
||||||
|
// Return a map containing both pieces of information
|
||||||
|
return [isExpo: isExpo, version: projectVersion]
|
||||||
}
|
}
|
||||||
|
|
||||||
def expoProject = isExpoProject()
|
// Get project info map
|
||||||
|
def projectInfo = checkProjectInfo()
|
||||||
|
// Extract info into variables
|
||||||
|
def projectVersion = projectInfo.version
|
||||||
|
def expoProject = projectInfo.isExpo
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
if (isNewArchitectureEnabled()) {
|
if (isNewArchitectureEnabled()) {
|
||||||
@@ -45,16 +73,16 @@ if (isNewArchitectureEnabled()) {
|
|||||||
|
|
||||||
if (expoProject) {
|
if (expoProject) {
|
||||||
group = 'expo.modules.pushy'
|
group = 'expo.modules.pushy'
|
||||||
version = '1.0.0'
|
version = projectVersion
|
||||||
|
|
||||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||||
apply from: expoModulesCorePlugin
|
apply from: expoModulesCorePlugin
|
||||||
applyKotlinExpoModulesCorePlugin()
|
applyKotlinExpoModulesCorePlugin()
|
||||||
|
// useExpoPublishing()
|
||||||
useCoreDependencies()
|
useCoreDependencies()
|
||||||
useExpoPublishing()
|
|
||||||
} else {
|
} else {
|
||||||
group = 'cn.reactnative.modules.update'
|
group = 'cn.reactnative.modules.update'
|
||||||
version = '1.0.0'
|
version = projectVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -120,7 +148,6 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.facebook.react:react-native:+'
|
implementation 'com.facebook.react:react-native:+'
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
|
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
|
||||||
implementation 'com.jakewharton:process-phoenix:3.0.0'
|
|
||||||
}
|
}
|
||||||
if (isNewArchitectureEnabled()) {
|
if (isNewArchitectureEnabled()) {
|
||||||
react {
|
react {
|
||||||
|
@@ -152,8 +152,13 @@ public class UpdateModuleImpl {
|
|||||||
reactHostDelegateField.setAccessible(true);
|
reactHostDelegateField.setAccessible(true);
|
||||||
Object reactHostDelegate = reactHostDelegateField.get(reactHost);
|
Object reactHostDelegate = reactHostDelegateField.get(reactHost);
|
||||||
|
|
||||||
|
String bundleFieldName = "jsBundleLoader";
|
||||||
|
if (reactHostDelegate.getClass().getCanonicalName().equals("expo.modules.ExpoReactHostFactory.ExpoReactHostDelegate")) {
|
||||||
|
bundleFieldName = "_jsBundleLoader";
|
||||||
|
}
|
||||||
|
|
||||||
// Modify the jsBundleLoader field
|
// Modify the jsBundleLoader field
|
||||||
Field jsBundleLoaderField = reactHostDelegate.getClass().getDeclaredField("jsBundleLoader");
|
Field jsBundleLoaderField = reactHostDelegate.getClass().getDeclaredField(bundleFieldName);
|
||||||
jsBundleLoaderField.setAccessible(true);
|
jsBundleLoaderField.setAccessible(true);
|
||||||
jsBundleLoaderField.set(reactHostDelegate, loader);
|
jsBundleLoaderField.set(reactHostDelegate, loader);
|
||||||
|
|
||||||
|
@@ -20,7 +20,6 @@ import com.facebook.react.bridge.UiThreadUtil;
|
|||||||
import com.facebook.react.bridge.JSBundleLoader;
|
import com.facebook.react.bridge.JSBundleLoader;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
import com.jakewharton.processphoenix.ProcessPhoenix;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
@@ -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
@@ -3,7 +3,7 @@ import fileIo from '@ohos.file.fs';
|
|||||||
import common from '@ohos.app.ability.common';
|
import common from '@ohos.app.ability.common';
|
||||||
import { UpdateContext } from './UpdateContext';
|
import { UpdateContext } from './UpdateContext';
|
||||||
|
|
||||||
export class FileJSBundleProvider extends JSBundleProvider {
|
export class PushyFileJSBundleProvider extends JSBundleProvider {
|
||||||
private updateContext: UpdateContext;
|
private updateContext: UpdateContext;
|
||||||
private filePath: string = ''
|
private filePath: string = ''
|
||||||
|
|
49
package.json
@@ -1,28 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "react-native-update",
|
"name": "react-native-update",
|
||||||
"version": "10.28.4",
|
"version": "10.29.0",
|
||||||
"description": "react-native hot update",
|
"description": "react-native hot update",
|
||||||
"main": "src/index",
|
"main": "src/index",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepack": "yarn submodule && yarn lint",
|
"postinstall": "node scripts/check-expo-version.js",
|
||||||
|
"prepack": "bun submodule && bun lint",
|
||||||
"lint": "eslint \"src/*.@(ts|tsx|js|jsx)\" && tsc --noEmit",
|
"lint": "eslint \"src/*.@(ts|tsx|js|jsx)\" && tsc --noEmit",
|
||||||
"submodule": "git submodule update --init --recursive",
|
"submodule": "git submodule update --init --recursive",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build-lib": "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-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 && yarn && detox build --configuration ios.sim.debug",
|
"build:ios-debug": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.debug",
|
||||||
"build:ios-release": "cd Example/testHotUpdate && yarn && detox build --configuration ios.sim.release",
|
"build:ios-release": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.release",
|
||||||
"test:ios-debug": "cd Example/testHotUpdate && detox test --configuration ios.sim.debug",
|
"test:ios-debug": "cd Example/testHotUpdate && detox test --configuration ios.sim.debug",
|
||||||
"test:ios-release": "cd Example/testHotUpdate && yarn detox test --configuration ios.sim.release",
|
"test:ios-release": "cd Example/testHotUpdate && bun detox test --configuration ios.sim.release",
|
||||||
"build:android-debug": "cd Example/testHotUpdate && yarn && detox build --configuration android.emu.debug",
|
"build:android-debug": "cd Example/testHotUpdate && bun && detox build --configuration android.emu.debug",
|
||||||
"build:android-release": "cd Example/testHotUpdate && yarn && detox build --configuration android.emu.release",
|
"build:android-release": "cd Example/testHotUpdate && bun && detox build --configuration android.emu.release",
|
||||||
"test:android-release": "cd Example/testHotUpdate && yarn detox test --configuration android.emu.release --headless --record-logs all",
|
"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",
|
"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:ios": "bun build:ios-release && bun test:ios-release",
|
||||||
"e2e:android": "npm run build:android-release && npm run test:android-release",
|
"e2e:android": "bun build:android-release && bun test:android-release",
|
||||||
"tests:emulator:prepare": "cd .github/workflows/scripts/functions && yarn && yarn build",
|
"tests:emulator:prepare": "cd .github/workflows/scripts/functions && bun && bun build",
|
||||||
"tests:emulator:start-ci": "yarn tests:emulator:prepare && cd ./.github/workflows/scripts && ./start-firebase-emulator.sh",
|
"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: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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -56,24 +57,20 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.8",
|
"@babel/core": "^7.25.8",
|
||||||
"@react-native/babel-preset": "^0.73.21",
|
"@react-native/babel-preset": "^0.73.21",
|
||||||
"@react-native/eslint-config": "^0.73.2",
|
"@react-native/eslint-config": "0.79.1",
|
||||||
"@react-native/typescript-config": "^0.74.0",
|
"@react-native/typescript-config": "0.79.1",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/jest": "^29.5.13",
|
"@types/node": "^22.15.2",
|
||||||
"@types/node": "^22.7.6",
|
|
||||||
"@types/react": "^18.3.11",
|
"@types/react": "^18.3.11",
|
||||||
"detox": "^20.27.3",
|
"detox": "^20.37.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-ft-flow": "^3.0.7",
|
|
||||||
"firebase-tools": "^13.22.1",
|
"firebase-tools": "^13.22.1",
|
||||||
"fs-extra": "^11.2.0",
|
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"pod-install": "^0.2.2",
|
"pod-install": "^0.3.7",
|
||||||
"prettier": "^2",
|
"prettier": "^2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-native": "0.73",
|
"react-native": "0.73",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.3.2",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.6.3"
|
||||||
},
|
}
|
||||||
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
|
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,83 @@
|
|||||||
require 'json'
|
require 'json'
|
||||||
require 'rubygems' # Required for version comparison
|
require 'rubygems' # Required for version comparison
|
||||||
|
|
||||||
|
|
||||||
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
|
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
|
||||||
|
|
||||||
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
||||||
|
|
||||||
podspec_dir = File.dirname(__FILE__)
|
podspec_dir = File.dirname(__FILE__)
|
||||||
|
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
|
|
||||||
|
is_expo_in_podfile = false
|
||||||
|
begin
|
||||||
|
# Check Podfile for use_expo_modules!
|
||||||
|
podfile_path = File.join(Pod::Config.instance.installation_root, 'Podfile')
|
||||||
|
if File.exist?(podfile_path)
|
||||||
|
podfile_content = File.read(podfile_path)
|
||||||
|
is_expo_in_podfile = podfile_content.include?('use_expo_modules!')
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|
||||||
|
# Final check
|
||||||
|
valid_expo_project = is_version_sufficient
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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.name = package['name']
|
||||||
s.version = package['version']
|
s.version = package['version']
|
||||||
s.summary = package['description']
|
s.summary = package['description']
|
||||||
@@ -17,10 +87,16 @@ Pod::Spec.new do |s|
|
|||||||
s.homepage = package['homepage']
|
s.homepage = package['homepage']
|
||||||
|
|
||||||
s.cocoapods_version = '>= 1.6.0'
|
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}' }
|
s.source = { :git => 'https://github.com/reactnativecn/react-native-update.git', :tag => '#{s.version}' }
|
||||||
s.source_files = Dir.glob("ios/**/*.{h,m,mm,swift}").reject { |f| f.start_with?("ios/Expo/") }
|
|
||||||
|
# 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.libraries = 'bz2', 'z'
|
s.libraries = 'bz2', 'z'
|
||||||
s.vendored_libraries = 'RCTPushy/libRCTPushy.a'
|
s.vendored_libraries = 'RCTPushy/libRCTPushy.a'
|
||||||
s.pod_target_xcconfig = {
|
s.pod_target_xcconfig = {
|
||||||
@@ -34,51 +110,14 @@ Pod::Spec.new do |s|
|
|||||||
s.dependency "React-Core"
|
s.dependency "React-Core"
|
||||||
s.dependency 'SSZipArchive'
|
s.dependency 'SSZipArchive'
|
||||||
|
|
||||||
is_expo_project = false
|
# Conditionally add Expo dependency
|
||||||
expo_dependency_added = false
|
if valid_expo_project
|
||||||
supports_bundle_url_final = false
|
|
||||||
|
|
||||||
# Use CocoaPods mechanism to find Podfile
|
|
||||||
begin
|
|
||||||
podfile_path = File.join(Pod::Config.instance.installation_root, 'Podfile')
|
|
||||||
if File.exist?(podfile_path)
|
|
||||||
podfile_content = File.read(podfile_path)
|
|
||||||
is_expo_project = podfile_content.include?('use_expo_modules!')
|
|
||||||
end
|
|
||||||
rescue
|
|
||||||
# Silently skip if CocoaPods config is not available
|
|
||||||
end
|
|
||||||
|
|
||||||
if is_expo_project
|
|
||||||
s.dependency 'ExpoModulesCore'
|
s.dependency 'ExpoModulesCore'
|
||||||
expo_dependency_added = true
|
|
||||||
|
|
||||||
# Current directory is in node_modules/react-native-update, so parent is node_modules
|
|
||||||
expo_core_package_json_path = File.join(__dir__, '..', 'expo-modules-core', 'package.json')
|
|
||||||
|
|
||||||
if File.exist?(expo_core_package_json_path)
|
|
||||||
begin
|
|
||||||
core_package_json = JSON.parse(File.read(expo_core_package_json_path))
|
|
||||||
installed_version_str = core_package_json['version']
|
|
||||||
|
|
||||||
if installed_version_str
|
|
||||||
begin
|
|
||||||
installed_version = Gem::Version.new(installed_version_str)
|
|
||||||
target_version = Gem::Version.new('1.12.0')
|
|
||||||
supports_bundle_url_final = installed_version >= target_version
|
|
||||||
rescue ArgumentError
|
|
||||||
# Silently skip version parsing errors
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue JSON::ParserError, StandardError
|
|
||||||
# Silently skip file reading and parsing errors
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
s.subspec 'RCTPushy' do |ss|
|
s.subspec 'RCTPushy' do |ss|
|
||||||
ss.source_files = 'ios/RCTPushy/*.{h,m,mm,swift}'
|
ss.source_files = 'ios/RCTPushy/*.{h,m,mm,swift}'
|
||||||
ss.public_header_files = ['ios/RCTPushy/RCTPushy.h']
|
ss.public_header_files = ['ios/RCTPushy/*.h']
|
||||||
end
|
end
|
||||||
|
|
||||||
s.subspec 'HDiffPatch' do |ss|
|
s.subspec 'HDiffPatch' do |ss|
|
||||||
@@ -91,7 +130,31 @@ Pod::Spec.new do |s|
|
|||||||
ss.public_header_files = 'ios/RCTPushy/HDiffPatch/**/*.h'
|
ss.public_header_files = 'ios/RCTPushy/HDiffPatch/**/*.h'
|
||||||
end
|
end
|
||||||
|
|
||||||
if expo_dependency_added
|
# Conditionally add Expo subspec and check ExpoModulesCore version
|
||||||
|
if valid_expo_project
|
||||||
|
supports_bundle_url_final = false # Default
|
||||||
|
|
||||||
|
# 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
|
||||||
|
rescue ArgumentError
|
||||||
|
# If Gem::Version fails parsing, supports_bundle_url_final remains false.
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
s.subspec 'Expo' do |ss|
|
s.subspec 'Expo' do |ss|
|
||||||
ss.source_files = 'ios/Expo/**/*.{h,m,mm,swift}'
|
ss.source_files = 'ios/Expo/**/*.{h,m,mm,swift}'
|
||||||
if supports_bundle_url_final
|
if supports_bundle_url_final
|
||||||
|
92
scripts/check-expo-version.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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');
|
||||||
|
|
||||||
|
const projectRoot = path.resolve(__dirname, '..'); // react-native-update module root
|
||||||
|
const expoConfigPath = path.resolve(projectRoot, 'expo-module.config.json');
|
||||||
|
|
||||||
|
function getExpoMajorVersion() {
|
||||||
|
let resolvedExpoPackagePath;
|
||||||
|
try {
|
||||||
|
// Use require.resolve to find expo's package.json from the host project's perspective
|
||||||
|
resolvedExpoPackagePath = require.resolve('expo/package.json', {
|
||||||
|
paths: [path.resolve(projectRoot, '..', '..')],
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
'Expo not found in project node_modules (via require.resolve).',
|
||||||
|
);
|
||||||
|
return null; // Expo not found or resolvable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the resolved path actually exists (belt-and-suspenders)
|
||||||
|
if (!fs.existsSync(resolvedExpoPackagePath)) {
|
||||||
|
console.log(
|
||||||
|
`Expo package.json path resolved to ${resolvedExpoPackagePath}, but file does not exist.`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const packageJson = JSON.parse(
|
||||||
|
fs.readFileSync(resolvedExpoPackagePath, 'utf8'),
|
||||||
|
);
|
||||||
|
const version = packageJson.version;
|
||||||
|
if (!version) {
|
||||||
|
console.log('Expo package.json does not contain a version.');
|
||||||
|
return null; // Version not found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the first number sequence as the major version
|
||||||
|
const match = version.match(/\d+/);
|
||||||
|
if (!match) {
|
||||||
|
console.log(
|
||||||
|
`Could not parse major version from Expo version string: ${version}`,
|
||||||
|
);
|
||||||
|
return null; // Cannot parse version
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(match[0], 10);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading or parsing Expo package.json:', error);
|
||||||
|
return null; // Error during processing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAndCleanExpoConfig() {
|
||||||
|
const majorVersion = getExpoMajorVersion();
|
||||||
|
|
||||||
|
// Condition: Expo not found OR major version is less than 50
|
||||||
|
if (majorVersion === null || majorVersion < 50) {
|
||||||
|
if (fs.existsSync(expoConfigPath)) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(expoConfigPath);
|
||||||
|
console.log(
|
||||||
|
`Expo version (${
|
||||||
|
majorVersion !== null ? majorVersion : 'not found'
|
||||||
|
}) is < 50 or Expo not found. Deleted ${expoConfigPath}`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to delete ${expoConfigPath}:`, error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`Expo version (${
|
||||||
|
majorVersion !== null ? majorVersion : 'not found'
|
||||||
|
}) is < 50 or Expo not found. ${expoConfigPath} does not exist, no action needed.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`Expo version (${majorVersion}) is >= 50. Kept ${expoConfigPath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAndCleanExpoConfig();
|
@@ -1,33 +1,33 @@
|
|||||||
import { CheckResult, ClientOptions, ProgressData, EventType } from './type';
|
|
||||||
import {
|
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,
|
assertWeb,
|
||||||
emptyObj,
|
emptyObj,
|
||||||
|
enhancedFetch,
|
||||||
joinUrls,
|
joinUrls,
|
||||||
log,
|
log,
|
||||||
noop,
|
noop,
|
||||||
promiseAny,
|
promiseAny,
|
||||||
testUrls,
|
testUrls,
|
||||||
} from './utils';
|
} 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 = {
|
const SERVER_PRESETS = {
|
||||||
// cn
|
// cn
|
||||||
@@ -170,10 +170,12 @@ export class Pushy {
|
|||||||
getCheckUrl = (endpoint: string = this.options.server!.main) => {
|
getCheckUrl = (endpoint: string = this.options.server!.main) => {
|
||||||
return `${endpoint}/checkUpdate/${this.options.appKey}`;
|
return `${endpoint}/checkUpdate/${this.options.appKey}`;
|
||||||
};
|
};
|
||||||
assertDebug = () => {
|
assertDebug = (matter: string) => {
|
||||||
if (__DEV__ && !this.options.debug) {
|
if (__DEV__ && !this.options.debug) {
|
||||||
console.info(
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -188,7 +190,7 @@ export class Pushy {
|
|||||||
this.report({ type: 'markSuccess' });
|
this.report({ type: 'markSuccess' });
|
||||||
};
|
};
|
||||||
switchVersion = async (hash: string) => {
|
switchVersion = async (hash: string) => {
|
||||||
if (!assertDev('switchVersion()')) {
|
if (!this.assertDebug('switchVersion()')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (assertHash(hash) && !sharedState.applyingUpdate) {
|
if (assertHash(hash) && !sharedState.applyingUpdate) {
|
||||||
@@ -199,7 +201,7 @@ export class Pushy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
switchVersionLater = async (hash: string) => {
|
switchVersionLater = async (hash: string) => {
|
||||||
if (!assertDev('switchVersionLater()')) {
|
if (!this.assertDebug('switchVersionLater()')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (assertHash(hash)) {
|
if (assertHash(hash)) {
|
||||||
@@ -208,7 +210,7 @@ export class Pushy {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
checkUpdate = async (extra?: Record<string, any>) => {
|
checkUpdate = async (extra?: Record<string, any>) => {
|
||||||
if (!this.assertDebug()) {
|
if (!this.assertDebug('checkUpdate()')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!assertWeb()) {
|
if (!assertWeb()) {
|
||||||
@@ -261,7 +263,7 @@ export class Pushy {
|
|||||||
type: 'checking',
|
type: 'checking',
|
||||||
message: this.options.appKey + ': ' + stringifyBody,
|
message: this.options.appKey + ': ' + stringifyBody,
|
||||||
});
|
});
|
||||||
resp = await fetch(this.getCheckUrl(), fetchPayload);
|
resp = await enhancedFetch(this.getCheckUrl(), fetchPayload);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.report({
|
this.report({
|
||||||
type: 'errorChecking',
|
type: 'errorChecking',
|
||||||
@@ -272,7 +274,7 @@ export class Pushy {
|
|||||||
try {
|
try {
|
||||||
resp = await promiseAny(
|
resp = await promiseAny(
|
||||||
backupEndpoints.map(endpoint =>
|
backupEndpoints.map(endpoint =>
|
||||||
fetch(this.getCheckUrl(endpoint), fetchPayload),
|
enhancedFetch(this.getCheckUrl(endpoint), fetchPayload),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -393,7 +395,7 @@ export class Pushy {
|
|||||||
let lastError: any;
|
let lastError: any;
|
||||||
let errorMessages: string[] = [];
|
let errorMessages: string[] = [];
|
||||||
const diffUrl = await testUrls(joinUrls(paths, diff));
|
const diffUrl = await testUrls(joinUrls(paths, diff));
|
||||||
if (diffUrl) {
|
if (diffUrl && !__DEV__) {
|
||||||
log('downloading diff');
|
log('downloading diff');
|
||||||
try {
|
try {
|
||||||
await PushyModule.downloadPatchFromPpk({
|
await PushyModule.downloadPatchFromPpk({
|
||||||
@@ -406,15 +408,12 @@ export class Pushy {
|
|||||||
const errorMessage = `diff error: ${e.message}`;
|
const errorMessage = `diff error: ${e.message}`;
|
||||||
errorMessages.push(errorMessage);
|
errorMessages.push(errorMessage);
|
||||||
lastError = new Error(errorMessage);
|
lastError = new Error(errorMessage);
|
||||||
if (__DEV__) {
|
|
||||||
succeeded = 'diff';
|
|
||||||
} else {
|
|
||||||
log(errorMessage);
|
log(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (!succeeded) {
|
||||||
const pdiffUrl = await testUrls(joinUrls(paths, pdiff));
|
const pdiffUrl = await testUrls(joinUrls(paths, pdiff));
|
||||||
if (!succeeded && pdiffUrl) {
|
if (pdiffUrl && !__DEV__) {
|
||||||
log('downloading pdiff');
|
log('downloading pdiff');
|
||||||
try {
|
try {
|
||||||
await PushyModule.downloadPatchFromPackage({
|
await PushyModule.downloadPatchFromPackage({
|
||||||
@@ -426,15 +425,13 @@ export class Pushy {
|
|||||||
const errorMessage = `pdiff error: ${e.message}`;
|
const errorMessage = `pdiff error: ${e.message}`;
|
||||||
errorMessages.push(errorMessage);
|
errorMessages.push(errorMessage);
|
||||||
lastError = new Error(errorMessage);
|
lastError = new Error(errorMessage);
|
||||||
if (__DEV__) {
|
|
||||||
succeeded = 'pdiff';
|
|
||||||
} else {
|
|
||||||
log(errorMessage);
|
log(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!succeeded) {
|
||||||
const fullUrl = await testUrls(joinUrls(paths, full));
|
const fullUrl = await testUrls(joinUrls(paths, full));
|
||||||
if (!succeeded && fullUrl) {
|
if (fullUrl) {
|
||||||
log('downloading full patch');
|
log('downloading full patch');
|
||||||
try {
|
try {
|
||||||
await PushyModule.downloadFullUpdate({
|
await PushyModule.downloadFullUpdate({
|
||||||
@@ -446,20 +443,21 @@ export class Pushy {
|
|||||||
const errorMessage = `full patch error: ${e.message}`;
|
const errorMessage = `full patch error: ${e.message}`;
|
||||||
errorMessages.push(errorMessage);
|
errorMessages.push(errorMessage);
|
||||||
lastError = new Error(errorMessage);
|
lastError = new Error(errorMessage);
|
||||||
if (__DEV__) {
|
|
||||||
succeeded = 'full';
|
|
||||||
} else {
|
|
||||||
log(errorMessage);
|
log(errorMessage);
|
||||||
}
|
}
|
||||||
|
} else if (__DEV__) {
|
||||||
|
log(
|
||||||
|
`当前是开发环境,无法执行增量式热更新,重启不会生效。
|
||||||
|
如果需要在开发环境中测试可生效的全量热更新(但也会在再次重启后重新连接 metro),
|
||||||
|
请打开“忽略时间戳”开关再重试。`,
|
||||||
|
);
|
||||||
|
succeeded = 'full';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sharedState.progressHandlers[hash]) {
|
if (sharedState.progressHandlers[hash]) {
|
||||||
sharedState.progressHandlers[hash].remove();
|
sharedState.progressHandlers[hash].remove();
|
||||||
delete sharedState.progressHandlers[hash];
|
delete sharedState.progressHandlers[hash];
|
||||||
}
|
}
|
||||||
if (__DEV__) {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
if (!succeeded) {
|
if (!succeeded) {
|
||||||
this.report({
|
this.report({
|
||||||
type: 'errorUpdate',
|
type: 'errorUpdate',
|
||||||
|
@@ -251,7 +251,7 @@ export const UpdateProvider = ({
|
|||||||
const markSuccess = client.markSuccess;
|
const markSuccess = client.markSuccess;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!client.assertDebug()) {
|
if (!client.assertDebug('checkUpdate()')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { checkStrategy, dismissErrorAfter, autoMarkSuccess } = options;
|
const { checkStrategy, dismissErrorAfter, autoMarkSuccess } = options;
|
||||||
|
31
src/utils.ts
@@ -40,13 +40,13 @@ const ping =
|
|||||||
: async (url: string) => {
|
: async (url: string) => {
|
||||||
let pingFinished = false;
|
let pingFinished = false;
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
fetch(url, {
|
enhancedFetch(url, {
|
||||||
method: 'HEAD',
|
method: 'HEAD',
|
||||||
})
|
})
|
||||||
.then(({ status, statusText }) => {
|
.then(({ status, statusText, url: finalUrl }) => {
|
||||||
pingFinished = true;
|
pingFinished = true;
|
||||||
if (status === 200) {
|
if (status === 200) {
|
||||||
return url;
|
return finalUrl;
|
||||||
}
|
}
|
||||||
log('ping failed', url, status, statusText);
|
log('ping failed', url, status, statusText);
|
||||||
throw new Error('Ping failed');
|
throw new Error('Ping failed');
|
||||||
@@ -69,7 +69,7 @@ const ping =
|
|||||||
|
|
||||||
export function joinUrls(paths: string[], fileName?: string) {
|
export function joinUrls(paths: string[], fileName?: string) {
|
||||||
if (fileName) {
|
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;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const assertDev = (matter: string) => {
|
// export const isAndroid70AndBelow = () => {
|
||||||
if (__DEV__) {
|
// // android 7.0 and below devices do not support letsencrypt cert
|
||||||
console.warn(
|
// // https://letsencrypt.org/2023/07/10/cross-sign-expiration/
|
||||||
`${matter} is not supported in development environment; no action taken.`,
|
// return Platform.OS === 'android' && Platform.Version <= 24;
|
||||||
);
|
// };
|
||||||
return false;
|
|
||||||
}
|
export const enhancedFetch = async (
|
||||||
return true;
|
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);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|