Compare commits
38 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 | ||
![]() |
c771672fcd | ||
![]() |
2978454298 | ||
![]() |
905413e424 | ||
![]() |
ee36fd9334 | ||
![]() |
5b46071834 | ||
![]() |
a6802bdd44 | ||
![]() |
97848e7b13 |
@@ -6,6 +6,7 @@
|
||||
Example
|
||||
android/build
|
||||
.vscode
|
||||
.github/
|
||||
|
||||
# OSX
|
||||
#
|
||||
@@ -45,8 +46,12 @@ node_modules/
|
||||
npm-debug.log
|
||||
Example
|
||||
yarn.lock
|
||||
bun.lock
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
@@ -1,18 +1,19 @@
|
||||
## 运行harmony_use_pushy项目步骤
|
||||
|
||||
### 1. 先在react-native-update根目录执行下面命令同步C++模块。
|
||||
### 1. 在项目根目录执行下面命令安装第三方依赖。
|
||||
```
|
||||
yarn submodule
|
||||
bun install
|
||||
```
|
||||
|
||||
### 2. 在项目根目录执行下面命令安装第三方依赖。
|
||||
### 2. 本地debug 模式
|
||||
```
|
||||
yarn install
|
||||
bun run start
|
||||
```
|
||||

|
||||
|
||||
### 3. 在项目根目录执行下面命令生成bundle包文件。
|
||||
### 3. release 模式: 在项目根目录执行下面命令生成bundle包文件。
|
||||
```
|
||||
yarn build
|
||||
bun run build
|
||||
```
|
||||
说明:这个命令会在harmony/entry/src/main/resources/rawfile目录生成Hbundle.harmony.js和assets文件,同时会基于该内容在.pushy/output目录生成ppk包。
|
||||
|
||||
|
2130
Example/harmony_use_pushy/bun.lock
Normal file
BIN
Example/harmony_use_pushy/debug.png
Normal file
After Width: | Height: | Size: 743 KiB |
@@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* This code was generated by "react-native codegen-harmony"
|
||||
*
|
||||
* Do not edit this file as changes may cause incorrect behavior and will be
|
||||
* lost once the code is regenerated.
|
||||
*
|
||||
* @generatorVersion: 1
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RNOH/Package.h"
|
||||
#include "RNOH/ArkTSTurboModule.h"
|
||||
|
||||
namespace rnoh {
|
||||
|
||||
class RNOHGeneratedPackageTurboModuleFactoryDelegate : public TurboModuleFactoryDelegate {
|
||||
public:
|
||||
SharedTurboModule createTurboModule(Context ctx, const std::string &name) const override {
|
||||
return nullptr;
|
||||
};
|
||||
};
|
||||
|
||||
class GeneratedEventEmitRequestHandler : public EventEmitRequestHandler {
|
||||
public:
|
||||
void handleEvent(Context const &ctx) override {
|
||||
auto eventEmitter = ctx.shadowViewRegistry->getEventEmitter<facebook::react::EventEmitter>(ctx.tag);
|
||||
if (eventEmitter == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> supportedEventNames = {
|
||||
};
|
||||
if (std::find(supportedEventNames.begin(), supportedEventNames.end(), ctx.eventName) != supportedEventNames.end()) {
|
||||
eventEmitter->dispatchEvent(ctx.eventName, ArkJS(ctx.env).getDynamic(ctx.payload));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class RNOHGeneratedPackage : public Package {
|
||||
public:
|
||||
RNOHGeneratedPackage(Package::Context ctx) : Package(ctx){};
|
||||
|
||||
std::unique_ptr<TurboModuleFactoryDelegate> createTurboModuleFactoryDelegate() override {
|
||||
return std::make_unique<RNOHGeneratedPackageTurboModuleFactoryDelegate>();
|
||||
}
|
||||
|
||||
std::vector<facebook::react::ComponentDescriptorProvider> createComponentDescriptorProviders() override {
|
||||
return {
|
||||
};
|
||||
}
|
||||
|
||||
ComponentJSIBinderByString createComponentJSIBinderByName() override {
|
||||
return {
|
||||
};
|
||||
};
|
||||
|
||||
EventEmitRequestHandlers createEventEmitRequestHandlers() override {
|
||||
return {
|
||||
std::make_shared<GeneratedEventEmitRequestHandler>(),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rnoh
|
@@ -1,5 +1,6 @@
|
||||
import { FileJSBundleProvider } from 'pushy/src/main/ets/FileJSBundleProvider';
|
||||
import { ComponentBuilderContext, RNOHCoreContext,RNAbility } from '@rnoh/react-native-openharmony';
|
||||
import { PushyFileJSBundleProvider } from 'pushy/src/main/ets/PushyFileJSBundleProvider';
|
||||
import { ComponentBuilderContext, RNOHCoreContext,RNAbility,
|
||||
MetroJSBundleProvider } from '@rnoh/react-native-openharmony';
|
||||
import {
|
||||
RNApp,
|
||||
AnyJSBundleProvider,
|
||||
@@ -61,9 +62,10 @@ struct Index {
|
||||
},
|
||||
jsBundleProvider: new TraceJSBundleProviderDecorator(
|
||||
new AnyJSBundleProvider([
|
||||
// MetroJSBundleProvider.fromServerIp('127.0.0.1'),
|
||||
// new ResourceJSBundleProvider(rnohCoreContext.uiAbilityContext.resourceManager, 'hermes_bundle.hbc'),
|
||||
new FileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),
|
||||
// local debug mode
|
||||
new MetroJSBundleProvider(),
|
||||
// release mode
|
||||
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-03-09T01:57:42.464Z",
|
||||
"pushy_build_time": "2025-04-30T02:46:33.340Z",
|
||||
"versionName": "1.0.0"
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
export {}
|
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* This code was generated by "react-native codegen-harmony"
|
||||
*
|
||||
* Do not edit this file as changes may cause incorrect behavior and will be
|
||||
* lost once the code is regenerated.
|
||||
*/
|
||||
|
||||
export * from "./ts"
|
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* This code was generated by "react-native codegen-harmony"
|
||||
*
|
||||
* Do not edit this file as changes may cause incorrect behavior and will be
|
||||
* lost once the code is regenerated.
|
||||
*/
|
||||
|
||||
export * as RNC from "./components/ts"
|
||||
export * as TM from "./turboModules/ts"
|
@@ -0,0 +1,5 @@
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
export {}
|
12423
Example/harmony_use_pushy/package-lock.json
generated
@@ -6,7 +6,8 @@
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"lint": "eslint .",
|
||||
"start": "react-native start",
|
||||
"start": "npm run codegen && hdc rport tcp:8081 tcp:8081 && react-native start",
|
||||
"codegen": "react-native codegen-harmony --rnoh-module-path ./harmony/react_native_openharmony",
|
||||
"build": "pushy bundle --platform harmony",
|
||||
"test": "jest",
|
||||
"hdiffFromPPK": "pushy hdiffFromPPK .pushy/output/harmony.1735052610653.ppk .pushy/output/harmony.1735052678646.ppk .pushy/output/hdiff.ppk-patch",
|
||||
@@ -14,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.59",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.72.5",
|
||||
"react-native-update": "file:../../",
|
||||
"@react-native-oh/react-native-harmony": "^0.72.43"
|
||||
"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,
|
||||
|
@@ -22,21 +22,49 @@ def supportsNamespace() {
|
||||
return major >= 8
|
||||
}
|
||||
|
||||
def isExpoProject() {
|
||||
def checkProjectInfo() {
|
||||
def hasExpoModulesCore = rootProject.subprojects.any { it.name == 'expo-modules-core' }
|
||||
|
||||
def packageJsonFile = new File(rootProject.projectDir.parentFile, 'package.json')
|
||||
|
||||
def hasExpoDependency = false
|
||||
def projectVersion = '1.0.0' // Default version
|
||||
|
||||
if (packageJsonFile.exists()) {
|
||||
def packageJson = new groovy.json.JsonSlurper().parseText(packageJsonFile.text)
|
||||
hasExpoDependency = (packageJson.dependencies?.expo != null) ||
|
||||
(packageJson.devDependencies?.expo != null)
|
||||
projectVersion = packageJson.version ?: '1.0.0' // Get project version
|
||||
|
||||
// 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'
|
||||
if (isNewArchitectureEnabled()) {
|
||||
@@ -45,16 +73,16 @@ if (isNewArchitectureEnabled()) {
|
||||
|
||||
if (expoProject) {
|
||||
group = 'expo.modules.pushy'
|
||||
version = '1.0.0'
|
||||
version = projectVersion
|
||||
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
apply from: expoModulesCorePlugin
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
// useExpoPublishing()
|
||||
useCoreDependencies()
|
||||
useExpoPublishing()
|
||||
} else {
|
||||
group = 'cn.reactnative.modules.update'
|
||||
version = '1.0.0'
|
||||
version = projectVersion
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -120,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);
|
||||
}
|
@@ -120,14 +120,13 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(long[]... values) {
|
||||
protected void onProgressUpdate(final long[]... values) {
|
||||
super.onProgressUpdate(values);
|
||||
WritableMap params = Arguments.createMap();
|
||||
params.putDouble("received", (values[0][0]));
|
||||
params.putDouble("total", (values[0][1]));
|
||||
params.putString("hash", this.hash);
|
||||
sendEvent("RCTPushyDownloadProgress", params);
|
||||
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[1024*4];
|
||||
@@ -452,7 +451,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(DownloadTaskParams... params) {
|
||||
protected Void doInBackground(final DownloadTaskParams... params) {
|
||||
int taskType = params[0].type;
|
||||
try {
|
||||
switch (taskType) {
|
||||
|
@@ -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() {
|
||||
|
@@ -24,7 +24,7 @@ public class UpdateModuleImpl {
|
||||
|
||||
public static final String NAME = "Pushy";
|
||||
|
||||
public static void downloadFullUpdate(UpdateContext updateContext, ReadableMap options, Promise promise) {
|
||||
public static void downloadFullUpdate(UpdateContext updateContext, final ReadableMap options, final Promise promise) {
|
||||
String url = options.getString("updateUrl");
|
||||
String hash = options.getString("hash");
|
||||
updateContext.downloadFullUpdate(url, hash, new UpdateContext.DownloadFileListener() {
|
||||
@@ -40,7 +40,7 @@ public class UpdateModuleImpl {
|
||||
});
|
||||
}
|
||||
|
||||
public static void downloadAndInstallApk(UpdateContext updateContext, ReadableMap options, Promise promise) {
|
||||
public static void downloadAndInstallApk(UpdateContext updateContext, final ReadableMap options, final Promise promise) {
|
||||
String url = options.getString("url");
|
||||
String hash = options.getString("hash");
|
||||
String target = options.getString("target");
|
||||
@@ -63,7 +63,7 @@ public class UpdateModuleImpl {
|
||||
UpdateModule.installApk(toInstall);
|
||||
}
|
||||
|
||||
public static void downloadPatchFromPackage(UpdateContext updateContext, ReadableMap options, Promise promise) {
|
||||
public static void downloadPatchFromPackage(UpdateContext updateContext, final ReadableMap options, final Promise promise) {
|
||||
String url = options.getString("updateUrl");
|
||||
String hash = options.getString("hash");
|
||||
updateContext.downloadPatchFromApk(url, hash, new UpdateContext.DownloadFileListener() {
|
||||
@@ -79,7 +79,7 @@ public class UpdateModuleImpl {
|
||||
});
|
||||
}
|
||||
|
||||
public static void downloadPatchFromPpk(UpdateContext updateContext, ReadableMap options, Promise promise) {
|
||||
public static void downloadPatchFromPpk(UpdateContext updateContext, final ReadableMap options, final Promise promise) {
|
||||
try {
|
||||
String url = options.getString("updateUrl");
|
||||
String hash = options.getString("hash");
|
||||
@@ -102,7 +102,7 @@ public class UpdateModuleImpl {
|
||||
}
|
||||
}
|
||||
|
||||
public static void reloadUpdate(UpdateContext updateContext, ReactApplicationContext mContext, ReadableMap options, Promise promise) {
|
||||
public static void reloadUpdate(final UpdateContext updateContext,final ReactApplicationContext mContext, final ReadableMap options, final Promise promise) {
|
||||
final String hash = options.getString("hash");
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
@@ -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);
|
||||
|
||||
@@ -176,7 +185,7 @@ public class UpdateModuleImpl {
|
||||
});
|
||||
}
|
||||
|
||||
public static void restartApp(final ReactApplicationContext mContext, Promise promise) {
|
||||
public static void restartApp(final ReactApplicationContext mContext, final Promise promise) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -206,7 +215,7 @@ public class UpdateModuleImpl {
|
||||
});
|
||||
}
|
||||
|
||||
public static void setNeedUpdate(UpdateContext updateContext, ReadableMap options, Promise promise) {
|
||||
public static void setNeedUpdate(final UpdateContext updateContext, final ReadableMap options, final Promise promise) {
|
||||
final String hash = options.getString("hash");
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
@@ -222,7 +231,7 @@ public class UpdateModuleImpl {
|
||||
});
|
||||
}
|
||||
|
||||
public static void markSuccess(UpdateContext updateContext, Promise promise) {
|
||||
public static void markSuccess(final UpdateContext updateContext, final Promise promise) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -232,7 +241,7 @@ public class UpdateModuleImpl {
|
||||
});
|
||||
}
|
||||
|
||||
public static void setUuid(UpdateContext updateContext, String uuid, Promise promise) {
|
||||
public static void setUuid(final UpdateContext updateContext, final String uuid, final Promise promise) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -253,7 +262,7 @@ public class UpdateModuleImpl {
|
||||
}
|
||||
|
||||
|
||||
public static void setLocalHashInfo(UpdateContext updateContext, final String hash, final String info, Promise promise) {
|
||||
public static void setLocalHashInfo(final UpdateContext updateContext, final String hash, final String info, final Promise promise) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -268,7 +277,7 @@ public class UpdateModuleImpl {
|
||||
});
|
||||
}
|
||||
|
||||
public static void getLocalHashInfo(UpdateContext updateContext, final String hash, Promise promise) {
|
||||
public static void getLocalHashInfo(UpdateContext updateContext, final String hash, final Promise promise) {
|
||||
String value = updateContext.getKv("hash_" + hash);
|
||||
if (check(value)) {
|
||||
promise.resolve(value);
|
||||
|
@@ -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> {
|
@@ -1,10 +1,40 @@
|
||||
import ExpoModulesCore
|
||||
import react_native_update
|
||||
import React
|
||||
|
||||
public final class ExpoPushyReactDelegateHandler: ExpoReactDelegateHandler {
|
||||
private weak var reactDelegate: ExpoReactDelegate?
|
||||
|
||||
public override func bundleURL(reactDelegate: ExpoReactDelegate) -> URL? {
|
||||
return RCTPushy.bundleURL()
|
||||
}
|
||||
#if EXPO_SUPPORTS_BUNDLEURL
|
||||
// This code block compiles only if EXPO_SUPPORTS_BUNDLEURL is defined
|
||||
// For expo-modules-core >= 1.12.0
|
||||
|
||||
// Override bundleURL, which is the primary mechanism for these versions.
|
||||
// Expo's default createBridge implementation should respect this.
|
||||
override public func bundleURL(reactDelegate: ExpoReactDelegate) -> URL? {
|
||||
let bundleURL = RCTPushy.bundleURL()
|
||||
print("PushyHandler: Using bundleURL: \(bundleURL?.absoluteString ?? "nil")")
|
||||
return bundleURL
|
||||
}
|
||||
|
||||
// No createBridge override needed here, rely on default behavior using the bundleURL override.
|
||||
|
||||
#else
|
||||
// This code block compiles only if EXPO_SUPPORTS_BUNDLEURL is NOT defined
|
||||
// For expo-modules-core < 1.12.0
|
||||
|
||||
// No bundleURL override possible here.
|
||||
|
||||
// createBridge is the mechanism to customize the URL here.
|
||||
// We completely override it and do not call super.
|
||||
override public func createBridge(reactDelegate: ExpoReactDelegate, bridgeDelegate: RCTBridgeDelegate, launchOptions: [AnyHashable: Any]?) -> RCTBridge? {
|
||||
let bundleURL = RCTPushy.bundleURL()
|
||||
// Print the URL being provided to the initializer
|
||||
print("PushyHandler: createBridge bundleURL: \(bundleURL?.absoluteString ?? "nil")")
|
||||
|
||||
// Directly create the bridge using the bundleURL initializer.
|
||||
// Pass nil for moduleProvider, assuming default behavior is sufficient.
|
||||
// WARNING: If bundleURL is nil, this initialization might fail silently or crash.
|
||||
return RCTBridge(bundleURL: bundleURL, moduleProvider: nil, launchOptions: launchOptions)
|
||||
}
|
||||
|
||||
#endif // EXPO_SUPPORTS_BUNDLEURL
|
||||
}
|
||||
|
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
|
||||
|
46
package.json
@@ -1,28 +1,29 @@
|
||||
{
|
||||
"name": "react-native-update",
|
||||
"version": "10.28.2",
|
||||
"version": "10.29.8",
|
||||
"description": "react-native hot update",
|
||||
"main": "src/index",
|
||||
"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",
|
||||
"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",
|
||||
@@ -56,23 +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"
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,83 @@
|
||||
require 'json'
|
||||
require 'rubygems' # Required for version comparison
|
||||
|
||||
|
||||
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
||||
|
||||
podspec_dir = File.dirname(__FILE__)
|
||||
|
||||
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.version = package['version']
|
||||
s.summary = package['description']
|
||||
@@ -16,10 +87,9 @@ 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}' }
|
||||
s.source_files = Dir.glob("ios/**/*.{h,m,mm,swift}").reject { |f| f.start_with?("ios/Expo/") }
|
||||
|
||||
s.libraries = 'bz2', 'z'
|
||||
s.vendored_libraries = 'RCTPushy/libRCTPushy.a'
|
||||
s.pod_target_xcconfig = {
|
||||
@@ -33,38 +103,52 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-Core"
|
||||
s.dependency 'SSZipArchive'
|
||||
|
||||
project_root = File.expand_path('../../', __dir__)
|
||||
project_package_json = File.join(project_root, 'package.json')
|
||||
is_expo_project = false
|
||||
|
||||
if (File.exist?(project_package_json))
|
||||
package_json = JSON.parse(File.read(project_package_json))
|
||||
has_expo_dependency = package_json['dependencies'] && package_json['dependencies']['expo']
|
||||
has_expo_modules_core = Dir.exist?('node_modules/expo-modules-core')
|
||||
is_expo_project = has_expo_dependency || has_expo_modules_core
|
||||
if is_expo_project
|
||||
s.dependency 'ExpoModulesCore'
|
||||
end
|
||||
# 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/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
|
||||
|
||||
if is_expo_project
|
||||
# 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|
|
||||
ss.source_files = 'ios/Expo/**/*.{h,m,mm,swift}'
|
||||
if supports_bundle_url_final
|
||||
ss.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'EXPO_SUPPORTS_BUNDLEURL' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,7 +163,7 @@ Pod::Spec.new do |s|
|
||||
s.pod_target_xcconfig = {
|
||||
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
||||
}
|
||||
}.merge(s.pod_target_xcconfig)
|
||||
s.dependency "React-Codegen"
|
||||
s.dependency "RCT-Folly"
|
||||
s.dependency "RCTRequired"
|
||||
|
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();
|
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);
|
||||
});
|
||||
};
|
||||
|