1
0
mirror of https://gitcode.com/gh_mirrors/re/react-native-pushy.git synced 2025-11-22 15:36:10 +08:00
Code Issues Packages Projects Releases Wiki Activity GitHub Gitee

Compare commits

...

82 Commits

Author SHA1 Message Date
sunnylqm
1b4c03924a fix harmony build 2025-10-23 11:58:57 +08:00
sunnylqm
d458371f54 fix harmony build 2025-10-23 11:30:13 +08:00
sunnylqm
84381e5ed7 Increment version in package.json to 10.34.7 and update podspec to simplify public header files for Expo dependency. 2025-10-22 16:03:54 +08:00
sunnylqm
8970fd406d bump 2025-10-22 14:53:59 +08:00
sunnylqm
34d6fef493 revert react header 2025-10-22 14:19:40 +08:00
sunnylqm
84b71e33a8 support expo 54 2025-10-21 22:23:49 +08:00
sunnylqm
6cd99dece0 Increment version in package.json to 10.34.5, remove obsolete ImportReact.h file, and update podspec to eliminate unnecessary public header files for Expo dependency. 2025-10-21 11:57:09 +08:00
sunnylqm
d726f3602b Update app.json to include package name, modify index.js to use expo-router, and upgrade dependencies in package.json for improved compatibility and features. 2025-10-19 17:40:18 +08:00
sunnylqm
da21c99bcc Update react-native-update dependency to version 10.34.4 in package.json, bun.lock, and Podfile.lock. Add additional flags for Folly configuration in Xcode project settings. 2025-10-18 23:18:23 +08:00
sunnylqm
1f8748375c Enhance Pushy class to support locale configuration in i18n initialization by adding optional locale property to ClientOptions interface. 2025-10-17 16:03:54 +08:00
sunnylqm
fa731cd583 Increment version in package.json to 10.34.4. 2025-10-16 12:56:53 +08:00
sunnylqm
d077dcb6d3 Update submodule references for HDiffPatch and lzma to v3.1.1 commits. 2025-10-16 12:49:59 +08:00
sunnylqm
59b60fdc6d Update .gitmodules to reflect new submodule paths for HDiffPatch and lzma, and increment version in package.json to 10.34.3. 2025-10-16 11:35:39 +08:00
sunnylqm
c768705221 Update .gitmodules to add new submodules for HDiffPatch and lzma, and increment version in package.json to 10.34.2. 2025-10-16 11:13:22 +08:00
sunnylqm
57206dd2f1 Update .gitignore to exclude new harmony package files, remove submodule entries from .gitmodules, and increment version in package.json to 10.34.1. Refactor build-profile.json5 for consistency and update PushyFileJSBundleProvider to handle optional chaining. Remove obsolete pushy.har file and adjust dependencies in oh-package.json5 for clarity. 2025-10-16 00:48:17 +08:00
sunnylqm
4e27d906c3 Reorder properties in CheckResult object for consistency in UpdateProvider component. 2025-09-28 23:23:16 +08:00
sunnylqm
c24f469475 Refactor Pushy class to replace getCurrentVersionInfo with currentVersionInfo for improved clarity and consistency. 2025-09-28 21:57:56 +08:00
sunnylqm
8f8a29eda8 Update version in package.json from 10.34.0-beta.0 to 10.34.0 for stable release. 2025-09-24 16:56:28 +08:00
sunnylqm
a78542b214 Update version to 10.34.0-beta.0 in package.json and modify NDK version for build-lib script; update package manager to yarn@1.22.21. 2025-09-24 15:29:57 +08:00
Sunny Luo
8d9ae57a5f Bump version from 10.32.1 to 10.33.0 2025-09-24 08:28:11 +08:00
Sunny Luo
2502935fc0 Update package.json 2025-09-24 08:27:57 +08:00
波仔糕
8e6d9bf460 update judge logic for v2 (#513)
* update judge logic for v2

* udpate
2025-09-24 08:26:16 +08:00
波仔糕
897f334343 resolve aab package image hot update issue (#512)
* modify harmony download logic to async

* fix harmony image assets load fail issue

* resolve aab package image hot update issue

* update

* udpate
2025-09-23 23:41:40 +08:00
sunnylqm
33bc69c3fb Update react-native-update version to 10.32.0 in package.json for the latest features and improvements. 2025-09-20 12:08:03 +08:00
sunnylqm
5028ce31be Update react-native-update to version 10.32.0 in package.json, refactor error handling in DownloadTask and UpdateContext for improved readability, and enhance type definitions in various files for better TypeScript support. 2025-09-20 12:07:53 +08:00
sunnylqm
d3a4007763 Refactor ProGuard rules for React Native classes to improve reflection handling and maintain compatibility with Expo modules. 2025-09-19 16:03:17 +08:00
sunnylqm
a4f3e3cc38 Update react-native-update to version 10.31.2 in bun.lock, package.json, and Podfile.lock for consistency across dependencies. 2025-09-19 14:58:47 +08:00
sunnylqm
9d51128ed3 Update react-native-update to version 10.31.2 in package.json and enhance currentVersionInfo retrieval in RCTPushy.mm for improved data handling. 2025-09-19 14:50:13 +08:00
sunnylqm
eddb072927 Update Podfile.lock to reflect react-native-update version 10.31.1, ensuring consistency with package dependencies. 2025-09-19 14:42:29 +08:00
sunnylqm
4f9417d620 Update react-native-update to version 10.31.1 in package.json and bun.lock for improved stability and features. 2025-09-19 14:41:52 +08:00
sunnylqm
6f2314d3c9 Update version to 10.31.1, enhance iOS Info.plist with camera and photo library usage descriptions, and refactor code for improved readability in index.tsx. Adjust currentVersionInfo retrieval in RCTPushy.mm for better data handling. 2025-09-19 14:31:07 +08:00
sunnylqm
a6e9ece559 bump 10.31.0 2025-09-17 21:46:28 +08:00
sunnylqm
78430e2ec2 support 0.81 android 2025-09-17 16:36:15 +08:00
sunnylqm
ec5b9e1938 Update dependencies and versioning: bump react-native-update to 10.31.0-beta.3, update form-data and react-native-camera-kit versions, and enhance TypeScript types for better compatibility. 2025-09-17 11:25:51 +08:00
sunnylqm
53dfb45ca2 Update Android build configuration: change React Native architectures and bump version name to 1.81.4 2025-09-17 10:22:04 +08:00
sunnylqm
4a62e89c73 Update gradle-wrapper.jar to the latest version 2025-09-17 10:07:30 +08:00
sunnylqm
655f4c8cf5 update to 0.81.4 2025-09-16 16:56:49 +08:00
sunnylqm
3732c196a1 Implement localized message for disabled incremental hot update in development environment 2025-09-16 12:51:02 +08:00
sunnylqm
bfb520bd07 add currentversioninfo 2025-09-15 23:46:19 +08:00
sunnylqm
f7be8a4d71 Update dependencies in package.json and bun.lock, enhance .gitignore to exclude mcp.json, and refactor provider and utils for improved web support. 2025-09-12 23:28:27 +08:00
sunnylqm
4383a66274 update TypeScript configuration to exclude additional directories: harmony and Example 2025-09-12 00:32:36 +08:00
sunny.ll
a82b75f51f bump version to 10.31.0-beta.1, update TypeScript configuration to include .ts and .tsx files, and initialize stateListener with undefined 2025-09-10 23:07:33 +08:00
sunnylqm
584f698329 bump version to 10.31.0-beta.0 and integrate i18n for improved localization support 2025-09-04 10:24:52 +08:00
sunnylqm
e58903a634 support multiple versions 2025-09-04 00:24:35 +08:00
sunnylqm
41e1028b2d fix first reload 2025-09-03 13:58:57 +08:00
sunnylqm
11d40ce5f2 bump version to 10.30.3 and remove unnecessary code in RCTPushy.mm 2025-09-01 17:03:20 +08:00
sunnylqm
4a1d4d5a50 bump version to 10.30.2 and improve error handling in enhancedFetch function 2025-08-30 11:53:52 +08:00
sunnylqm
02517a9eb0 bump version to 10.30.1 and enhance error handling in enhancedFetch function 2025-08-30 11:14:01 +08:00
sunnylqm
f7309f699f update version to 10.30.0, enhance version and build time tracking in UpdateContext and RCTPushy, and add new entry to .gitignore 2025-08-28 18:45:00 +08:00
sunnylqm
a224113998 bump version to 10.29.9 2025-08-28 14:35:18 +08:00
sunnylqm
f2ede92ea1 add debug resolution for markSuccess method in RCTPushy 2025-08-28 14:33:36 +08:00
sunnylqm
a913e8c10e bump version to 10.29.8 and include new version data in downloading report 2025-08-25 17:18:59 +08:00
sunnylqm
c5f458291a update welcome message in App component and add overridePackageVersion option to ClientOptions 2025-08-22 16:39:33 +08:00
sunnylqm
9699632a43 remove deprecated endpoint from endpoints.json 2025-07-27 10:32:24 +08:00
sunnylqm
80e42f5dba bump version to 10.29.7 and add error handling for hdiffFileAtPath in RCTPushy 2025-07-21 20:57:16 +08:00
sunnylqm
9b718b8f75 support expo 53 2025-06-29 11:52:05 +08:00
sunnylqm
99e3431844 simplify subspec 2025-06-29 00:20:22 +08:00
sunnylqm
d7b5562ab7 improve iOS reload handling 2025-06-28 21:40:06 +08:00
sunnylqm
6a0a5b2d49 fix ios reload 2025-06-27 23:23:59 +08:00
Sunny Luo
7023ff57ca bump 10.29.4 2025-06-25 15:23:08 +08:00
波仔糕
17e21d79cf fix harmony image assets load fail issue (#505)
* modify harmony download logic to async

* fix harmony image assets load fail issue
2025-06-25 15:22:47 +08:00
Sunny Luo
1cab582bd0 Update package.json 2025-06-17 22:29:46 +08:00
波仔糕
7da5a165fd modify harmony download logic to async (#502) 2025-06-17 22:29:28 +08:00
sunnylqm
d5194a1ad1 bump version to 10.29.2 and add delayed execution for clearing first-time and rollback marks in UpdateModule 2025-06-16 12:00:19 +08:00
sunnylqm
ebc9b97e70 fix proguard 2025-06-14 23:34:54 +08:00
sunnylqm
40742e16d8 fix expo reload 2025-06-05 16:04:35 +08:00
sunnylqm
598ae1a506 bump example rn 0.79.2 2025-06-05 14:48:18 +08:00
sunnylqm
e5424591d1 fallback for all 2025-05-21 11:46:40 +08:00
sunnylqm
2cf7336b6a add fallback for android <= 7.0 2025-05-21 11:12:01 +08:00
Sunny Luo
7eac48ab5d 更新 package.json 2025-05-12 14:24:17 +08:00
波仔糕
18d9b75545 update pushy reference method (#499)
* update pushy reference method

* update
2025-05-12 14:23:45 +08:00
sunnylqm
e8ec85c65f cleanup 2025-05-01 20:25:26 +08:00
sunnylqm
48a776d506 cleanup expo android config 2025-04-30 15:50:07 +08:00
Sunny Luo
8ad526148f 更新 package.json 2025-04-30 14:12:16 +08:00
波仔糕
cea39f1746 fix FileJSBundleProvider conflict issue (#496)
* fix FileJSBundleProvider conflics issue

* udpate
2025-04-30 14:11:50 +08:00
sunnylqm
aa56c2ec0f improve podspec detection 2025-04-30 11:56:58 +08:00
sunnylqm
00a989d567 do not specify ios platform version 2025-04-29 22:01:45 +08:00
sunnylqm
83ca3a6c05 update deps 2025-04-26 23:09:23 +08:00
sunnylqm
257f2697e0 skip postinstall during dev/publish 2025-04-26 22:52:32 +08:00
sunnylqm
77aa345f11 cleanup 2025-04-26 21:57:58 +08:00
sunnylqm
66332d007a cleanup 2025-04-26 21:54:12 +08:00
波仔糕
37849b1730 add expoUsePushy demo (#495)
* add expoUsePushy demo

* update
2025-04-26 21:36:02 +08:00
113 changed files with 6799 additions and 2480 deletions

8
.gitignore vendored
View File

@@ -51,3 +51,11 @@ android/bin
Example/testHotUpdate/harmony
Example/testHotUpdate/android/app/.cxx
Example/harmony_use_pushy/libs
**/mcp.json
harmony/package
**/oh_modules
harmony/pushy/.preview
Example/harmony_use_pushy/harmony/entry/src/main/resources/rawfile/meta.json
**/.hvigor

6
.gitmodules vendored
View File

@@ -4,9 +4,3 @@
[submodule "android/jni/HDiffPatch"]
path = android/jni/HDiffPatch
url = https://github.com/sisong/HDiffPatch.git
[submodule "harmony/src/main/cpp/HDiffPatch"]
path = harmony/src/main/cpp/HDiffPatch
url = https://github.com/sisong/HDiffPatch.git
[submodule "harmony/src/main/cpp/lzma"]
path = harmony/src/main/cpp/lzma
url = https://github.com/sisong/lzma.git

View File

@@ -6,6 +6,7 @@
Example
android/build
.vscode
.github/
# OSX
#
@@ -51,4 +52,6 @@ domains.json
endpoints.json
endpoints_cresc.json
tea.yaml
tea.yaml
e2e/

View File

@@ -1 +0,0 @@
nodeLinker: node-modules

36
Example/expoUsePushy/.gitignore vendored Normal file
View 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

View 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>
);
}

View 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',
},
});

View File

@@ -0,0 +1,30 @@
{
"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"
},
"package": "com.anonymous.expoUsePushy"
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
import 'expo-router/entry';

View File

@@ -0,0 +1,50 @@
{
"name": "expousepushy",
"main": "index.js",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint"
},
"dependencies": {
"@expo/vector-icons": "^14.1.0",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"expo": "~53.0.22",
"expo-blur": "~14.1.5",
"expo-constants": "~17.1.7",
"expo-font": "~13.3.2",
"expo-haptics": "~14.1.4",
"expo-image": "~2.4.0",
"expo-linking": "~7.1.7",
"expo-router": "~5.1.5",
"expo-splash-screen": "~0.30.10",
"expo-status-bar": "~2.2.3",
"expo-symbols": "~0.4.5",
"expo-system-ui": "~5.0.11",
"expo-web-browser": "~14.2.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.6",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.4",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.11.1",
"react-native-update": "^10.34.4",
"react-native-web": "~0.20.0",
"react-native-webview": "13.13.5"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~19.0.10",
"typescript": "~5.8.3",
"eslint": "^9.25.0",
"eslint-config-expo": "~9.2.0"
},
"private": true
}

View File

@@ -0,0 +1,6 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
}
}

View File

@@ -0,0 +1,14 @@
{
"ios": {
"appId": 29439,
"appKey": "jNA71vpFHTDpEqeZd9yx87zj"
},
"android": {
"appId": 29413,
"appKey": "vdZWPXU6eyaPE6Avk96-YvwK"
},
"harmony": {
"appId": 29140,
"appKey": "JLklGflGIRbY-cMebjQwm1J1"
}
}

View File

@@ -4,7 +4,7 @@
"": {
"name": "harmony_use_pushy",
"dependencies": {
"@react-native-oh/react-native-harmony": "^0.72.43",
"@react-native-oh/react-native-harmony": "^0.72.59",
"react": "18.2.0",
"react-native": "0.72.5",
"react-native-update": "^10.26.4",

View File

@@ -1,45 +1,43 @@
{
"app": {
"signingConfigs": [],
"products": [
app: {
signingConfigs: [],
products: [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
name: 'default',
signingConfig: 'default',
compatibleSdkVersion: '5.0.0(12)',
runtimeOS: 'HarmonyOS',
buildOption: {
strictMode: {
caseSensitiveCheck: true,
useNormalizedOHMUrl: true,
},
},
},
],
"buildModeSet": [
buildModeSet: [
{
"name": "debug",
name: 'debug',
},
{
"name": "release"
}
]
name: 'release',
},
],
},
"modules": [
modules: [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
name: 'entry',
srcPath: './entry',
targets: [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
name: 'default',
applyToProducts: ['default'],
},
],
},
{
name: 'pushy',
srcPath: '../node_modules/react-native-update/harmony',
}
]
}
srcPath: '../node_modules/react-native-update/harmony/pushy',
},
],
}

View File

@@ -1,25 +1,26 @@
{
"meta": {
"stableOrder": true
"stableOrder": true,
"enableUnifiedLockfile": false
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"@rnoh/react-native-openharmony@0.72.38": "@rnoh/react-native-openharmony@0.72.38",
"pushy@../../node_modules/react-native-update/harmony": "pushy@../../node_modules/react-native-update/harmony"
"pushy@../../node_modules/react-native-update/harmony/pushy": "pushy@../../node_modules/react-native-update/harmony/pushy"
},
"packages": {
"@rnoh/react-native-openharmony@0.72.38": {
"name": "@rnoh/react-native-openharmony",
"name": "",
"version": "0.72.38",
"integrity": "sha512-br5SIrbB0OarSLirenleE7eTOX1lNccMJ7nb/G7qWTyJ7kW4DalmTXVKYpoT2qaOLls1uEE7McD1OjbZZM9jug==",
"resolved": "https://ohpm.openharmony.cn/ohpm/@rnoh/react-native-openharmony/-/react-native-openharmony-0.72.38.har",
"registryType": "ohpm"
},
"pushy@../../node_modules/react-native-update/harmony": {
"pushy@../../node_modules/react-native-update/harmony/pushy": {
"name": "pushy",
"version": "3.1.0-0.0.7",
"resolved": "../../node_modules/react-native-update/harmony",
"resolved": "",
"registryType": "local",
"dependencies": {
"@rnoh/react-native-openharmony": "^0.72.38"

View File

@@ -1,13 +1,12 @@
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"@rnoh/react-native-openharmony": "0.72.38",
"pushy": "file:../../node_modules/react-native-update/harmony"
}
name: 'entry',
version: '1.0.0',
description: 'Please describe the basic information.',
main: '',
author: '',
license: '',
dependencies: {
'@rnoh/react-native-openharmony': '0.72.38',
pushy: 'file:../../node_modules/react-native-update/harmony/pushy',
},
}

View File

@@ -1,4 +1,4 @@
import { FileJSBundleProvider } from 'pushy/src/main/ets/FileJSBundleProvider';
import { PushyFileJSBundleProvider } from 'pushy/src/main/ets/PushyFileJSBundleProvider';
import { ComponentBuilderContext, RNOHCoreContext,RNAbility,
MetroJSBundleProvider } from '@rnoh/react-native-openharmony';
import {
@@ -65,7 +65,7 @@ struct Index {
// local debug mode
new MetroJSBundleProvider(),
// release mode
new FileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),
new PushyFileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')
]),
this.rnohCoreContext.logger),

View File

@@ -1,4 +0,0 @@
{
"pushy_build_time": "2025-04-12T11:12:43.423Z",
"versionName": "1.0.0"
}

View File

@@ -1,6 +1,6 @@
import { appTasks } from '@ohos/hvigor-ohos-plugin';
import {appTasks} from '@ohos/hvigor-ohos-plugin';
export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}
system: appTasks /* Built-in plugin of Hvigor. It cannot be modified. */,
plugins: [] /* Custom plugin to extend the functionality of Hvigor. */,
};

View File

@@ -1,23 +1,16 @@
{
"meta": {
"stableOrder": true
"stableOrder": true,
"enableUnifiedLockfile": false
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0",
"@ohos/hypium@1.0.19": "@ohos/hypium@1.0.19"
},
"packages": {
"@ohos/hamock@1.0.0": {
"name": "@ohos/hamock",
"version": "1.0.0",
"integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==",
"resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har",
"registryType": "ohpm"
},
"@ohos/hypium@1.0.19": {
"name": "@ohos/hypium",
"name": "",
"version": "1.0.19",
"integrity": "sha512-cEjDgLFCm3cWZDeRXk7agBUkPqjWxUo6AQeiu0gEkb3J8ESqlduQLSIXeo3cCsm8U/asL7iKjF85ZyOuufAGSQ==",
"resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.19.har",

View File

@@ -1,20 +1,18 @@
{
"modelVersion": "5.0.0",
"description": "Please describe the basic information.",
"dependencies": {
modelVersion: '5.0.0',
description: 'Please describe the basic information.',
dependencies: {},
devDependencies: {
'@ohos/hypium': '1.0.19'
},
"devDependencies": {
"@ohos/hypium": "1.0.19",
"@ohos/hamock": "1.0.0"
arkTs: {
compilerOptions: {
noImplicitAny: false,
suppressImplicitAnyIndexErrors: true,
strict: false,
},
},
"arkTs": {
"compilerOptions": {
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": true,
"strict": false
}
overrides: {
'@rnoh/react-native-openharmony': '0.72.38',
},
"overrides": {
"@rnoh/react-native-openharmony": "0.72.38"
}
}

View File

@@ -15,10 +15,10 @@
"hash": "pushy hash /Users/yanbo.he/Desktop/HarmonyOS/react-native-pushy/Example/harmony_use_pushy/.pushy/output/harmony.1735048297258.ppk"
},
"dependencies": {
"@react-native-oh/react-native-harmony": "^0.72.43",
"@react-native-oh/react-native-harmony": "^0.72.59",
"react": "18.2.0",
"react-native": "0.72.5",
"react-native-update": "^10.26.4"
"react-native-update": "latest"
},
"devDependencies": {
"@babel/core": "^7.20.0",

View File

@@ -62,3 +62,7 @@ buck-out/
# Ruby / CocoaPods
/ios/Pods/
/vendor/bundle/
# react-native-update
.update
.pushy

View File

@@ -1,7 +1,5 @@
module.exports = {
arrowParens: 'avoid',
bracketSameLine: true,
bracketSpacing: false,
singleQuote: true,
trailingComma: 'all',
};

View File

@@ -81,7 +81,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
versionName "1.81.4"
}
signingConfigs {
debug {

View File

@@ -5,13 +5,11 @@ import cn.reactnative.modules.update.UpdateContext
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
class MainApplication : Application(), ReactApplication {
@@ -39,10 +37,6 @@ class MainApplication : Application(), ReactApplication {
override fun onCreate() {
super.onCreate()
SoLoader.init(this, OpenSourceMergedSoMapping)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
loadReactNative(this)
}
}

View File

@@ -1,11 +1,11 @@
buildscript {
ext {
buildToolsVersion = "35.0.0"
buildToolsVersion = "36.0.0"
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 35
compileSdkVersion = 36
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
kotlinVersion = "2.0.21"
kotlinVersion = "2.1.20"
}
repositories {
google()

View File

@@ -25,7 +25,8 @@ android.useAndroidX=true
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
reactNativeArchitectures=arm64-v8a
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in

View File

@@ -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.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -205,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
@@ -248,4 +248,4 @@ eval "set -- $(
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
exec "$JAVACMD" "$@"

View File

@@ -1,3 +1,8 @@
@REM Copyright (c) Meta Platforms, Inc. and affiliates.
@REM
@REM This source code is licensed under the MIT license found in the
@REM LICENSE file in the root directory of this source tree.
@rem
@rem Copyright 2015 the original author or authors.
@rem
@@ -70,11 +75,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
@@ -91,4 +96,4 @@ exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:omega

File diff suppressed because it is too large Load Diff

View File

@@ -403,6 +403,8 @@
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
@@ -475,6 +477,8 @@
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";

View File

@@ -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 = @{};

View File

@@ -37,8 +37,14 @@
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>For taking photos</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSPhotoLibraryUsageDescription</key>
<string>For saving photos</string>
<key>RCTNewArchEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
@@ -53,10 +59,5 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSCameraUsageDescription</key>
<string>For taking photos</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>For saving photos</string>
</dict>
</plist>

File diff suppressed because it is too large Load Diff

View File

@@ -10,46 +10,45 @@
"test:e2e": "detox test --configuration android.emu.debug",
"lint": "eslint .",
"postinstall": "patch-package",
"apk": "cd android && ./gradlew assembleRelease",
"dev:harmony": "react-native bundle-harmony --dev"
"apk": "cd android && ./gradlew assembleRelease"
},
"dependencies": {
"form-data": "^4.0.2",
"form-data": "^4.0.4",
"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-vector-icons": "^10.2.0"
"react": "19.1.0",
"react-native": "0.81.4",
"react-native-camera-kit": "^16.1.2",
"react-native-paper": "^5.14.5",
"react-native-safe-area-context": "^5.6.1",
"react-native-svg": "^15.13.0",
"react-native-update": "^10.34.4",
"react-native-vector-icons": "^10.3.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",
"@types/react": "^19.0.0",
"@types/react-test-renderer": "^19.0.0",
"detox": "^20.32.0",
"eslint": "^8.19.0",
"@babel/core": "^7.27.3",
"@babel/preset-env": "^7.27.2",
"@babel/runtime": "^7.27.3",
"@react-native-community/cli": "20.0.0",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.81.4",
"@react-native/eslint-config": "0.81.4",
"@react-native/metro-config": "0.81.4",
"@react-native/typescript-config": "0.81.4",
"@types/react": "^19.1.13",
"@types/react-test-renderer": "^19.1.0",
"detox": "^20.41.2",
"eslint": "^9.35.0",
"jest": "^29.6.3",
"prettier": "2.8.8",
"react-test-renderer": "19.0.0",
"typescript": "5.8.2"
"react-test-renderer": "19.1.1",
"typescript": "5.8.3"
},
"engines": {
"node": ">=18"
"node": ">=20"
},
"trustedDependencies": [
"detox",
"dtrace-provider"
]
}
}

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/no-unstable-nested-components */
/* eslint-disable react-native/no-inline-styles */
import React, {useRef, useState} from 'react';
import React, { useRef, useState } from 'react';
import {
StyleSheet,
Platform,
@@ -19,14 +19,14 @@ import {
Modal,
Portal,
} from 'react-native-paper';
import {Camera} from 'react-native-camera-kit';
import {LocalSvg} from 'react-native-svg/css';
import { Camera } from 'react-native-camera-kit';
import { LocalSvg } from 'react-native-svg/css';
import TestConsole from './TestConsole';
import _updateConfig from '../update.json';
import {UpdateProvider, Pushy, Cresc, useUpdate} from 'react-native-update';
const {appKey} = _updateConfig[Platform.OS];
import { UpdateProvider, Pushy, Cresc, useUpdate } from 'react-native-update';
const { appKey } = _updateConfig[Platform.OS];
function App() {
const {
@@ -39,7 +39,8 @@ function App() {
packageVersion,
currentHash,
parseTestQrCode,
progress: {received, total} = {},
progress: { received, total } = {},
currentVersionInfo,
} = useUpdate();
const [useDefaultAlert, setUseDefaultAlert] = useState(true);
const [showTestConsole, setShowTestConsole] = useState(false);
@@ -52,8 +53,8 @@ function App() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>使Pushy热更新服务</Text>
<View style={{flexDirection: 'row'}}>
<Text style={styles.welcome}>22使Pushy热更新服务</Text>
<View style={{ flexDirection: 'row' }}>
<Text>
{useDefaultAlert ? '当前使用' : '当前不使用'}alert更新提示
</Text>
@@ -72,9 +73,9 @@ function App() {
<Portal>
<Modal visible={showCamera} onDismiss={() => setShowCamera(false)}>
<Camera
style={{minHeight: 320}}
style={{ minHeight: 320 }}
scanBarcode={true}
onReadCode={({nativeEvent: {codeStringValue}}) => {
onReadCode={({ nativeEvent: { codeStringValue } }) => {
// 防止重复扫码
if (lastParsedCode.current === codeStringValue) {
return;
@@ -92,7 +93,7 @@ function App() {
/>
</Modal>
</Portal>
<View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
<Text>png:</Text>
<Image
resizeMode={'contain'}
@@ -100,11 +101,11 @@ function App() {
style={styles.image}
/>
</View>
<View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
<Text>svg:</Text>
<LocalSvg
asset={require('./assets/react-logo.svg')}
style={{width: 30, height: 30}}
style={{ width: 30, height: 30 }}
/>
</View>
<Text style={styles.instructions}>
@@ -113,6 +114,7 @@ function App() {
{'\n'}
Hash: {currentHash || '(空)'}
{'\n'}
: {JSON.stringify(currentVersionInfo) || '(空)'}
</Text>
<Text>
{received} / {total}
@@ -121,16 +123,18 @@ function App() {
onPress={() => {
checkUpdate();
setShowUpdateSnackbar(true);
}}>
}}
>
<Text style={styles.instructions}></Text>
</TouchableOpacity>
<TouchableOpacity
testID="testcase"
style={{marginTop: 15}}
style={{ marginTop: 15 }}
onLongPress={() => {
setShowTestConsole(true);
}}>
}}
>
<Text style={styles.instructions}>
react-native-update版本{client?.version}
</Text>
@@ -152,14 +156,15 @@ function App() {
await downloadUpdate();
setShowUpdateBanner(true);
},
}}>
<Text style={{color: 'white'}}>
}}
>
<Text style={{ color: 'white' }}>
({updateInfo.name})
</Text>
</Snackbar>
)}
<Banner
style={{width: '100%', position: 'absolute', top: 0}}
style={{ width: '100%', position: 'absolute', top: 0 }}
visible={showUpdateBanner}
actions={[
{
@@ -174,9 +179,10 @@ function App() {
},
},
]}
icon={({size}) => (
icon={({ size }) => (
<Icon name="checkcircleo" size={size} color="#00f" />
)}>
)}
>
</Banner>
</View>
@@ -224,4 +230,4 @@ export default function Root() {
</PaperProvider>
</UpdateProvider>
);
}
}

View File

@@ -0,0 +1,5 @@
{
"extends": "@react-native/typescript-config",
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["**/node_modules", "**/Pods"]
}

View File

@@ -1,7 +1,7 @@
{
"ios": {
"appId": 24794,
"appKey": "SqShg4Klnj2hG6LAFMW2PdcgSSuniz0T"
"appId": 28943,
"appKey": "d-OmPxIBivPrDfKhLHjxN-HS"
},
"android": {
"appId": 27509,

View File

@@ -78,7 +78,7 @@ if (expoProject) {
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useExpoPublishing()
// useExpoPublishing()
useCoreDependencies()
} else {
group = 'cn.reactnative.modules.update'
@@ -101,7 +101,7 @@ android {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 27)
versionCode 1
versionName "1.0"
versionName "1.81.4"
consumerProguardFiles "proguard.pro"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}
@@ -148,7 +148,6 @@ repositories {
dependencies {
implementation 'com.facebook.react:react-native:+'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
implementation 'com.jakewharton:process-phoenix:3.0.0'
}
if (isNewArchitectureEnabled()) {
react {

Binary file not shown.

Binary file not shown.

Binary file not shown.

17
android/proguard.pro vendored
View File

@@ -1,3 +1,18 @@
# 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.ReactActivity { *; }
-keepclassmembers class com.facebook.react.ReactInstanceManager { *; }
-keepclassmembers class com.facebook.react.ReactDelegate { *; }
-keepclassmembers class com.facebook.react.ReactHost { *; }
-keepnames class expo.modules.ExpoReactHostFactory$ExpoReactHostDelegate { *; }

View File

@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.CRC32;
import java.util.HashMap;
import okio.BufferedSink;
@@ -100,7 +101,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
if (UpdateContext.DEBUG) {
Log.d("react-native-update", "Progress " + received + "/" + contentLength);
}
int percentage = (int)(received * 100.0 / contentLength + 0.5);
if (percentage > currentPercentage) {
currentPercentage = percentage;
@@ -198,6 +199,10 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
return fout.toByteArray();
}
private String getCRC32AsDecimal(long crc32Value) {
return String.valueOf(crc32Value & 0xFFFFFFFFL);
}
private void copyFilesWithBlacklist(String current, File from, File to, JSONObject blackList) throws IOException {
File[] files = from.listFiles();
for (File file : files) {
@@ -273,12 +278,41 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
zipFile.close();
}
private void copyFromResourceV2(HashMap<String, ArrayList<File>> resToCopy2) throws IOException {
SafeZipFile zipFile = new SafeZipFile(new File(context.getPackageResourcePath()));
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement();
String fn = ze.getName();
long zipCrc32 = ze.getCrc();
String crc32Decimal = getCRC32AsDecimal(zipCrc32);
ArrayList<File> targets = resToCopy2.get(crc32Decimal);
if (targets != null) {
File lastTarget = null;
for (File target: targets) {
if (UpdateContext.DEBUG) {
Log.d("react-native-update", "Copying from resource " + fn + " to " + target);
}
if (lastTarget != null) {
copyFile(lastTarget, target);
} else {
zipFile.unzipToFile(ze, target);
lastTarget = target;
}
}
}
}
zipFile.close();
}
private void doPatchFromApk(DownloadTaskParams param) throws IOException, JSONException {
downloadFile(param);
removeDirectory(param.unzipDirectory);
param.unzipDirectory.mkdirs();
HashMap<String, ArrayList<File>> copyList = new HashMap<String, ArrayList<File>>();
HashMap<String, ArrayList<File>> copiesv2List = new HashMap<String, ArrayList<File>>();
Boolean isV2 = false;
boolean foundDiff = false;
boolean foundBundlePatch = false;
@@ -297,29 +331,58 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
JSONObject obj = (JSONObject)new JSONTokener(json).nextValue();
JSONObject copies = obj.getJSONObject("copies");
JSONObject copiesv2 = obj.getJSONObject("copiesv2");
Iterator<?> keys = copies.keys();
while( keys.hasNext() ) {
String to = (String)keys.next();
String from = copies.getString(to);
if (from.isEmpty()) {
from = to;
}
ArrayList<File> target = null;
if (!copyList.containsKey(from)) {
target = new ArrayList<File>();
copyList.put(from, target);
} else {
target = copyList.get((from));
}
File toFile = new File(param.unzipDirectory, to);
Iterator<?> keysV2 = copiesv2.keys();
if(keysV2.hasNext()){
isV2 = true;
while( keysV2.hasNext() ) {
String from = (String)keysV2.next();
String to = copiesv2.getString(from);
if (from.isEmpty()) {
from = to;
}
ArrayList<File> target = null;
if (!copiesv2List.containsKey(from)) {
target = new ArrayList<File>();
copiesv2List.put(from, target);
} else {
target = copiesv2List.get((from));
}
File toFile = new File(param.unzipDirectory, to);
// Fixing a Zip Path Traversal Vulnerability
// https://support.google.com/faqs/answer/9294009
String canonicalPath = toFile.getCanonicalPath();
if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) {
throw new SecurityException("Illegal name: " + to);
// Fixing a Zip Path Traversal Vulnerability
// https://support.google.com/faqs/answer/9294009
String canonicalPath = toFile.getCanonicalPath();
if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) {
throw new SecurityException("Illegal name: " + to);
}
target.add(toFile);
}
}else{
while( keys.hasNext() ) {
String to = (String)keys.next();
String from = copies.getString(to);
if (from.isEmpty()) {
from = to;
}
ArrayList<File> target = null;
if (!copyList.containsKey(from)) {
target = new ArrayList<File>();
copyList.put(from, target);
} else {
target = copyList.get((from));
}
File toFile = new File(param.unzipDirectory, to);
// Fixing a Zip Path Traversal Vulnerability
// https://support.google.com/faqs/answer/9294009
String canonicalPath = toFile.getCanonicalPath();
if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) {
throw new SecurityException("Illegal name: " + to);
}
target.add(toFile);
}
target.add(toFile);
}
continue;
}
@@ -348,7 +411,11 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
throw new Error("bundle patch not found");
}
copyFromResource(copyList);
if(isV2){
copyFromResourceV2(copiesv2List);
}else{
copyFromResource(copyList);
}
if (UpdateContext.DEBUG) {
Log.d("react-native-update", "Unzip finished");

View File

@@ -36,10 +36,28 @@ public class UpdateContext {
this.sp = context.getSharedPreferences("update", Context.MODE_PRIVATE);
String packageVersion = getPackageVersion();
if (!packageVersion.equals(this.sp.getString("packageVersion", null))) {
String buildTime = getBuildTime();
String storedPackageVersion = this.sp.getString("packageVersion", null);
String storedBuildTime = this.sp.getString("buildTime", null);
// If stored versions don't exist, write current versions first
if (storedPackageVersion == null || storedBuildTime == null) {
SharedPreferences.Editor editor = sp.edit();
editor.putString("packageVersion", packageVersion);
editor.putString("buildTime", buildTime);
editor.apply();
storedPackageVersion = packageVersion;
storedBuildTime = buildTime;
}
boolean packageVersionChanged = !packageVersion.equals(storedPackageVersion);
boolean buildTimeChanged = !buildTime.equals(storedBuildTime);
if (packageVersionChanged || buildTimeChanged) {
SharedPreferences.Editor editor = sp.edit();
editor.clear();
editor.putString("packageVersion", packageVersion);
editor.putString("buildTime", buildTime);
editor.apply();
this.cleanUp();
@@ -169,17 +187,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() {

View File

@@ -23,6 +23,38 @@ import java.util.Map;
public class UpdateModuleImpl {
public static final String NAME = "Pushy";
/**
* 获取字段的兼容性方法尝试带m前缀和不带m前缀的字段名
* @param clazz 目标类
* @param fieldName 基础字段名不带m前缀
* @return 找到的字段对象
* @throws NoSuchFieldException 如果两种命名都找不到字段
*/
private static Field getCompatibleField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
// 首先尝试带m前缀的字段名
try {
return clazz.getDeclaredField("m" + capitalize(fieldName));
} catch (NoSuchFieldException e) {
// 如果找不到带m前缀的尝试不带m前缀的
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e2) {
// 如果都找不到,抛出异常并包含两种尝试的信息
throw new NoSuchFieldException("Field not found with either name: m" + capitalize(fieldName) + " or " + fieldName);
}
}
}
/**
* 首字母大写的辅助方法
*/
private static String capitalize(String str) {
if (str == null || str.length() == 0) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
public static void downloadFullUpdate(UpdateContext updateContext, final ReadableMap options, final Promise promise) {
String url = options.getString("updateUrl");
@@ -143,17 +175,26 @@ public class UpdateModuleImpl {
ReactDelegate reactDelegate = (ReactDelegate)
getReactDelegateMethod.invoke(currentActivity);
Field reactHostField = ReactDelegate.class.getDeclaredField("mReactHost");
Field reactHostField = getCompatibleField(ReactDelegate.class, "reactHost");
reactHostField.setAccessible(true);
Object reactHost = reactHostField.get(reactDelegate);
// Access the mReactHostDelegate field
Field reactHostDelegateField = reactHost.getClass().getDeclaredField("mReactHostDelegate");
Field devSupport = getCompatibleField(reactHost.getClass(), "useDevSupport");
devSupport.setAccessible(true);
devSupport.set(reactHost, false);
// Access the ReactHostDelegate field (compatible with mReactHostDelegate/reactHostDelegate)
Field reactHostDelegateField = getCompatibleField(reactHost.getClass(), "reactHostDelegate");
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);

View File

@@ -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;
@@ -32,18 +35,30 @@ public class UpdateModule extends NativePushySpec {
final Map<String, Object> constants = new HashMap<>();
constants.put("downloadRootDir", updateContext.getRootDir());
constants.put("packageVersion", updateContext.getPackageVersion());
constants.put("currentVersion", updateContext.getCurrentVersion());
String currentVersion = updateContext.getCurrentVersion();
constants.put("currentVersion", currentVersion);
constants.put("currentVersionInfo", updateContext.getKv("hash_" + currentVersion));
constants.put("buildTime", updateContext.getBuildTime());
constants.put("isUsingBundleUrl", updateContext.getIsUsingBundleUrl());
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;

View File

@@ -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;
@@ -49,7 +48,9 @@ public class UpdateModule extends ReactContextBaseJavaModule {
final Map<String, Object> constants = new HashMap<>();
constants.put("downloadRootDir", updateContext.getRootDir());
constants.put("packageVersion", updateContext.getPackageVersion());
constants.put("currentVersion", updateContext.getCurrentVersion());
String currentVersion = updateContext.getCurrentVersion();
constants.put("currentVersion", currentVersion);
constants.put("currentVersionInfo", updateContext.getKv("hash_" + currentVersion));
constants.put("buildTime", updateContext.getBuildTime());
constants.put("isUsingBundleUrl", updateContext.getIsUsingBundleUrl());
boolean isFirstTime = updateContext.isFirstTime();

610
bun.lock Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
["https://pushy-koa-qgbgqmcpis.cn-beijing.fcapp.run", "https://p.reactnative.cn/api"]
["https://p.reactnative.cn/api"]

View File

@@ -1 +0,0 @@
../../../../../harmony/oh_modules/.ohpm/@rnoh+react-native-openharmony@0.72.38/oh_modules/@rnoh/react-native-openharmony

View File

@@ -1,6 +1,7 @@
{
"meta": {
"stableOrder": true
"stableOrder": true,
"enableUnifiedLockfile": false
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
@@ -9,7 +10,7 @@
},
"packages": {
"@rnoh/react-native-openharmony@0.72.38": {
"name": "@rnoh/react-native-openharmony",
"name": "",
"version": "0.72.38",
"integrity": "sha512-br5SIrbB0OarSLirenleE7eTOX1lNccMJ7nb/G7qWTyJ7kW4DalmTXVKYpoT2qaOLls1uEE7McD1OjbZZM9jug==",
"resolved": "https://ohpm.openharmony.cn/ohpm/@rnoh/react-native-openharmony/-/react-native-openharmony-0.72.38.har",

View File

@@ -1,5 +1,5 @@
{
"license": "ISC",
"license": "MIT",
"types": "",
"devDependencies": {},
"name": "pushy",
@@ -8,5 +8,6 @@
"version": "3.1.0-0.0.7",
"dependencies": {
"@rnoh/react-native-openharmony":"^0.72.38"
}
},
"modelVersion": "5.0.0"
}

View File

@@ -1,11 +1,13 @@
cmake_minimum_required(VERSION 3.13)
project(rnupdate)
set(HDIFFPATCH_DIR ${CMAKE_CURRENT_SOURCE_DIR}/HDiffPatch)
set(LZMA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lzma)
# Point to android/jni directory for shared source code
set(ANDROID_JNI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../node_modules/react-native-update/android/jni)
set(HDIFFPATCH_DIR ${ANDROID_JNI_DIR}/HDiffPatch)
set(LZMA_DIR ${ANDROID_JNI_DIR}/lzma)
set(HDP_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/pushy.c
${CMAKE_CURRENT_SOURCE_DIR}/hpatch.c
${ANDROID_JNI_DIR}/hpatch.c
${HDIFFPATCH_DIR}/libHDiffPatch/HPatch/patch.c
${HDIFFPATCH_DIR}/file_for_patch.c
${LZMA_DIR}/C/LzmaDec.c
@@ -20,6 +22,7 @@ add_library(rnupdate SHARED
target_include_directories(rnupdate PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${ANDROID_JNI_DIR}
${HDIFFPATCH_DIR}
${HDIFFPATCH_DIR}/libHDiffPatch/HPatch
${LZMA_DIR}/C

View 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));
}

View File

@@ -60,13 +60,13 @@ export class DownloadTask {
const exists = fileIo.accessSync(params.targetFile);
if (exists) {
await fileIo.unlink(params.targetFile);
}else{
} else {
const targetDir = params.targetFile.substring(
0,
params.targetFile.lastIndexOf('/'),
);
const exists = fileIo.accessSync(targetDir);
if(!exists){
if (!exists) {
await fileIo.mkdir(targetDir);
}
}
@@ -83,7 +83,7 @@ export class DownloadTask {
},
});
if (response.responseCode > 299) {
throw new Error(`Server error: ${response.responseCode}`);
throw Error(`Server error: ${response.responseCode}`);
}
const contentLength = parseInt(response.header['content-length'] || '0');
@@ -108,9 +108,10 @@ export class DownloadTask {
const stats = await fileIo.stat(params.targetFile);
const fileSize = stats.size;
if (fileSize !== contentLength) {
throw new Error(`Download incomplete: expected ${contentLength} bytes but got ${stats.size} bytes`);
throw Error(
`Download incomplete: expected ${contentLength} bytes but got ${stats.size} bytes`,
);
}
} catch (error) {
console.error('Download failed:', error);
throw error;
@@ -142,7 +143,7 @@ export class DownloadTask {
bytesRead = await fileIo
.read(reader.fd, arrayBuffer)
.catch((err: BusinessError) => {
throw new Error(
throw Error(
`Error reading file: ${err.message}, code: ${err.code}`,
);
});
@@ -154,7 +155,7 @@ export class DownloadTask {
length: bytesRead,
})
.catch((err: BusinessError) => {
throw new Error(
throw Error(
`Error writing file: ${err.message}, code: ${err.code}`,
);
});
@@ -295,16 +296,16 @@ export class DownloadTask {
}
}
if(fn !== '.DS_Store'){
if (fn !== '.DS_Store') {
await zip.decompressFile(fn, params.unzipDirectory);
}
}
if (!foundDiff) {
throw new Error('diff.json not found');
throw Error('diff.json not found');
}
if (!foundBundlePatch) {
throw new Error('bundle patch not found');
throw Error('bundle patch not found');
}
await this.copyFromResource(copyList);
}
@@ -366,12 +367,18 @@ export class DownloadTask {
new Uint8Array(entry.content),
);
const outputFile = `${params.unzipDirectory}/bundle.harmony.js`;
const writer = await fileIo.open(outputFile, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
const writer = await fileIo.open(
outputFile,
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
);
const chunkSize = 4096;
let bytesWritten = 0;
const totalLength = patched.byteLength;
while (bytesWritten < totalLength) {
const chunk = patched.slice(bytesWritten, bytesWritten + chunkSize);
const chunk = patched.slice(
bytesWritten,
bytesWritten + chunkSize,
);
await fileIo.write(writer.fd, chunk);
bytesWritten += chunk.byteLength;
}
@@ -387,10 +394,10 @@ export class DownloadTask {
}
if (!foundDiff) {
throw new Error('diff.json not found');
throw Error('diff.json not found');
}
if (!foundBundlePatch) {
throw new Error('bundle patch not found');
throw Error('bundle patch not found');
}
console.info('Patch from PPK completed');
}
@@ -478,7 +485,7 @@ export class DownloadTask {
await this.downloadFile(params);
break;
default:
throw new Error(`Unknown task type: ${params.type}`);
throw Error(`Unknown task type: ${params.type}`);
}
params.listener?.onDownloadCompleted(params);

View File

@@ -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> {

View File

@@ -9,50 +9,52 @@ import { UpdateModuleImpl } from './UpdateModuleImpl';
import { UpdateContext } from './UpdateContext';
import { EventHub } from './EventHub';
const TAG = "PushyTurboModule"
const TAG = 'PushyTurboModule';
export class PushyTurboModule extends TurboModule {
mUiCtx: common.UIAbilityContext
context: UpdateContext
mUiCtx: common.UIAbilityContext;
context: UpdateContext;
constructor(protected ctx: TurboModuleContext) {
super(ctx);
logger.debug(TAG, ",PushyTurboModule constructor");
this.mUiCtx = ctx.uiAbilityContext
this.context = new UpdateContext(this.mUiCtx)
EventHub.getInstance().setRNInstance(ctx.rnInstance)
logger.debug(TAG, ',PushyTurboModule constructor');
this.mUiCtx = ctx.uiAbilityContext;
this.context = new UpdateContext(this.mUiCtx);
EventHub.getInstance().setRNInstance(ctx.rnInstance);
}
getConstants(): Object {
logger.debug(TAG, ",call getConstants");
logger.debug(TAG, ',call getConstants');
const context = this.mUiCtx;
const preferencesManager = dataPreferences.getPreferencesSync(context,{ name: 'update' });
const isFirstTime = preferencesManager.getSync("isFirstTime", false) as boolean;
const rolledBackVersion = preferencesManager.getSync("rolledBackVersion", "") as string;
const uuid = preferencesManager.getSync("uuid", "") as string;
const currentVersion = preferencesManager.getSync("currentVersion", "") as string;
const buildTime = preferencesManager.getSync("buildTime", "") as string;
const isFirstTime = preferencesManager.getSync('isFirstTime', false) as boolean;
const rolledBackVersion = preferencesManager.getSync('rolledBackVersion', '') as string;
const uuid = preferencesManager.getSync('uuid', '') as string;
const currentVersion = preferencesManager.getSync('currentVersion', '') as string;
const currentVersionInfo = this.context.getKv(`hash_${currentVersion}`);
const buildTime = preferencesManager.getSync('buildTime', '') as string;
const isUsingBundleUrl = this.context.getIsUsingBundleUrl();
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
let packageVersion = '';
try {
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
packageVersion = bundleInfo?.versionName || "Unknown"
packageVersion = bundleInfo?.versionName || 'Unknown';
} catch (error) {
console.error("Failed to get bundle info:", error);
console.error('Failed to get bundle info:', error);
}
if (isFirstTime) {
preferencesManager.deleteSync("isFirstTime");
preferencesManager.deleteSync('isFirstTime');
}
if (rolledBackVersion) {
preferencesManager.deleteSync("rolledBackVersion");
preferencesManager.deleteSync('rolledBackVersion');
}
return {
downloadRootDir: `${context.filesDir}/_update`,
currentVersionInfo,
packageVersion,
currentVersion,
buildTime,
@@ -60,64 +62,64 @@ getConstants(): Object {
isFirstTime,
rolledBackVersion,
uuid,
}
};
}
async setLocalHashInfo(hash: string, info: string): Promise<boolean> {
logger.debug(TAG, ",call setLocalHashInfo");
return UpdateModuleImpl.setLocalHashInfo(this.context,hash,info);
setLocalHashInfo(hash: string, info: string): boolean {
logger.debug(TAG, ',call setLocalHashInfo');
return UpdateModuleImpl.setLocalHashInfo(this.context, hash, info);
}
async getLocalHashInfo(hash: string): Promise<string> {
return UpdateModuleImpl.getLocalHashInfo(this.context,hash);
getLocalHashInfo(hash: string): string {
return UpdateModuleImpl.getLocalHashInfo(this.context, hash);
}
async setUuid(uuid: string): Promise<boolean> {
logger.debug(TAG, `,call setUuid`);
logger.debug(TAG, ',call setUuid');
return UpdateModuleImpl.setUuid(this.context,uuid);
}
async reloadUpdate(options: { hash: string }): Promise<void> {
logger.debug(TAG, `,call reloadUpdate`);
logger.debug(TAG, ',call reloadUpdate');
return UpdateModuleImpl.reloadUpdate(this.context, this.mUiCtx, options);
}
async setNeedUpdate(options: { hash: string }): Promise<boolean> {
logger.debug(TAG, `,call setNeedUpdate`);
logger.debug(TAG, ',call setNeedUpdate');
return UpdateModuleImpl.setNeedUpdate(this.context, options);
}
async markSuccess(): Promise<boolean> {
logger.debug(TAG, `,call markSuccess`);
logger.debug(TAG, ',call markSuccess');
return UpdateModuleImpl.markSuccess(this.context);
}
async downloadPatchFromPpk(options: { updateUrl: string; hash: string; originHash: string }): Promise<void> {
logger.debug(TAG, `,call downloadPatchFromPpk`);
logger.debug(TAG, ',call downloadPatchFromPpk');
return UpdateModuleImpl.downloadPatchFromPpk(this.context, options);
}
async downloadPatchFromPackage(options: { updateUrl: string; hash: string }): Promise<void> {
logger.debug(TAG, `,call downloadPatchFromPackage`);
logger.debug(TAG, ',call downloadPatchFromPackage');
return UpdateModuleImpl.downloadPatchFromPackage(this.context, options);
}
async downloadFullUpdate(options: { updateUrl: string; hash: string }): Promise<void> {
logger.debug(TAG, `,call downloadFullUpdate`);
logger.debug(TAG, ',call downloadFullUpdate');
return UpdateModuleImpl.downloadFullUpdate(this.context, options);
}
async downloadAndInstallApk(options: { url: string; target: string; hash: string }): Promise<void> {
logger.debug(TAG, `,call downloadAndInstallApk`);
logger.debug(TAG, ',call downloadAndInstallApk');
return UpdateModuleImpl.downloadAndInstallApk(this.mUiCtx, options);
}
addListener(eventName: string): void {
logger.debug(TAG, `,call addListener`);
logger.debug(TAG, ',call addListener');
}
removeListeners(count: number): void {
logger.debug(TAG, `,call removeListeners`);
logger.debug(TAG, ',call removeListeners');
}
}
}

View File

@@ -0,0 +1,281 @@
import preferences from '@ohos.data.preferences';
import bundleManager from '@ohos.bundle.bundleManager';
import fileIo from '@ohos.file.fs';
import { DownloadTask } from './DownloadTask';
import common from '@ohos.app.ability.common';
import { DownloadTaskParams } from './DownloadTaskParams';
export class UpdateContext {
private context: common.UIAbilityContext;
private rootDir: string;
private preferences: preferences.Preferences;
private static DEBUG: boolean = false;
private static isUsingBundleUrl: boolean = false;
constructor(context: common.UIAbilityContext) {
this.context = context;
this.rootDir = context.filesDir + '/_update';
try {
if (!fileIo.accessSync(this.rootDir)) {
fileIo.mkdirSync(this.rootDir);
}
} catch (e) {
console.error('Failed to create root directory:', e);
}
this.initPreferences();
}
private initPreferences() {
try {
this.preferences = preferences.getPreferencesSync(this.context, {
name: 'update',
});
const packageVersion = this.getPackageVersion();
const storedVersion = this.preferences.getSync('packageVersion', '');
if (!storedVersion) {
this.preferences.putSync('packageVersion', packageVersion);
this.preferences.flush();
} else if (storedVersion && packageVersion !== storedVersion) {
this.preferences.clear();
this.preferences.putSync('packageVersion', packageVersion);
this.preferences.flush();
this.cleanUp();
}
} catch (e) {
console.error('Failed to init preferences:', e);
}
}
public setKv(key: string, value: string): void {
this.preferences.putSync(key, value);
this.preferences.flush();
}
public getKv(key: string): string {
return this.preferences.getSync(key, '') as string;
}
public isFirstTime(): boolean {
return this.preferences.getSync('firstTime', false) as boolean;
}
public rolledBackVersion(): string {
return this.preferences.getSync('rolledBackVersion', '') as string;
}
public markSuccess(): void {
this.preferences.putSync('firstTimeOk', true);
const lastVersion = this.preferences.getSync('lastVersion', '') as string;
const curVersion = this.preferences.getSync('currentVersion', '') as string;
if (lastVersion && lastVersion !== curVersion) {
this.preferences.deleteSync('lastVersion');
this.preferences.deleteSync(`hash_${lastVersion}`);
}
this.preferences.flush();
this.cleanUp();
}
public clearFirstTime(): void {
this.preferences.putSync('firstTime', false);
this.preferences.flush();
this.cleanUp();
}
public clearRollbackMark(): void {
this.preferences.putSync('rolledBackVersion', null);
this.preferences.flush();
this.cleanUp();
}
public async downloadFullUpdate(
url: string,
hash: string,
listener: DownloadFileListener,
): Promise<void> {
try {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FULL;
params.url = url;
params.hash = hash;
params.listener = listener;
params.targetFile = `${this.rootDir}/${hash}.ppk`;
const downloadTask = new DownloadTask(this.context);
await downloadTask.execute(params);
} catch (e) {
console.error('Failed to download full update:', e);
}
}
public async downloadFile(
url: string,
hash: string,
fileName: string,
listener: DownloadFileListener,
): Promise<void> {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD;
params.url = url;
params.hash = hash;
params.listener = listener;
params.targetFile = this.rootDir + '/' + fileName;
const downloadTask = new DownloadTask(this.context);
await downloadTask.execute(params);
}
public async downloadPatchFromPpk(
url: string,
hash: string,
originHash: string,
listener: DownloadFileListener,
): Promise<void> {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
params.url = url;
params.hash = hash;
params.originHash = originHash;
params.listener = listener;
params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`;
params.unzipDirectory = `${this.rootDir}/${hash}`;
params.originDirectory = `${this.rootDir}/${params.originHash}`;
const downloadTask = new DownloadTask(this.context);
await downloadTask.execute(params);
}
public async downloadPatchFromPackage(
url: string,
hash: string,
listener: DownloadFileListener,
): Promise<void> {
try {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP;
params.url = url;
params.hash = hash;
params.listener = listener;
params.targetFile = `${this.rootDir}/${hash}.app.patch`;
params.unzipDirectory = `${this.rootDir}/${hash}`;
const downloadTask = new DownloadTask(this.context);
return await downloadTask.execute(params);
} catch (e) {
throw e;
console.error('Failed to download APK patch:', e);
}
}
public switchVersion(hash: string): void {
try {
const bundlePath = `${this.rootDir}/${hash}/bundle.harmony.js`;
if (!fileIo.accessSync(bundlePath)) {
throw Error(`Bundle version ${hash} not found.`);
}
const lastVersion = this.getKv('currentVersion');
this.setKv('currentVersion', hash);
if (lastVersion && lastVersion !== hash) {
this.setKv('lastVersion', lastVersion);
}
this.setKv('firstTime', 'true');
this.setKv('firstTimeOk', 'false');
this.setKv('rolledBackVersion', '');
} catch (e) {
console.error('Failed to switch version:', e);
}
}
public static getBundleUrl(
context: common.UIAbilityContext,
defaultAssetsUrl?: string,
): string {
return new UpdateContext(context).getBundleUrl(defaultAssetsUrl);
}
public getBundleUrl(defaultAssetsUrl?: string): string {
UpdateContext.isUsingBundleUrl = true;
const currentVersion = this.getCurrentVersion();
if (!currentVersion) {
return defaultAssetsUrl;
}
if (!this.isFirstTime()) {
if (!this.preferences.getSync('firstTimeOk', true)) {
return this.rollBack();
}
}
let version = currentVersion;
while (version) {
const bundleFile = `${this.rootDir}/${version}/bundle.harmony.js`;
try {
if (!fileIo.accessSync(bundleFile)) {
console.error(`Bundle version ${version} not found.`);
version = this.rollBack();
continue;
}
return bundleFile;
} catch (e) {
console.error('Failed to access bundle file:', e);
version = this.rollBack();
}
}
return defaultAssetsUrl;
}
getPackageVersion(): string {
let bundleFlags =
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
let packageVersion = '';
try {
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
packageVersion = bundleInfo?.versionName || 'Unknown';
} catch (error) {
console.error('获取包信息失败:', error);
}
return packageVersion;
}
public getCurrentVersion(): string {
const currentVersion = this.getKv('currentVersion');
return currentVersion;
}
private rollBack(): string {
const lastVersion = this.preferences.getSync('lastVersion', '') as string;
const currentVersion = this.preferences.getSync(
'currentVersion',
'',
) as string;
if (!lastVersion) {
this.preferences.deleteSync('currentVersion');
} else {
this.preferences.putSync('currentVersion', lastVersion);
}
this.preferences.putSync('firstTimeOk', true);
this.preferences.putSync('firstTime', false);
this.preferences.putSync('rolledBackVersion', currentVersion);
this.preferences.flush();
return lastVersion;
}
private cleanUp(): void {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
params.hash = this.preferences.getSync('currentVersion', '') as string;
params.originHash = this.preferences.getSync('lastVersion', '') as string;
params.unzipDirectory = this.rootDir;
const downloadTask = new DownloadTask(this.context);
downloadTask.execute(params);
}
public getIsUsingBundleUrl(): boolean {
return UpdateContext.isUsingBundleUrl;
}
}
export interface DownloadFileListener {
onDownloadCompleted(params: DownloadTaskParams): void;
onDownloadFailed(error: Error): void;
}

View File

@@ -4,14 +4,14 @@ import { UpdateContext } from './UpdateContext';
import { DownloadTaskParams } from './DownloadTaskParams';
import logger from './Logger';
const TAG = "UpdateModuleImpl";
const TAG = 'UpdateModuleImpl';
export class UpdateModuleImpl {
static readonly NAME = "Pushy";
static readonly NAME = 'Pushy';
static async downloadFullUpdate(
updateContext: UpdateContext,
options: { updateUrl: string; hash: string }
updateContext: UpdateContext,
options: { updateUrl: string; hash: string },
): Promise<void> {
try {
await updateContext.downloadFullUpdate(options.updateUrl, options.hash, {
@@ -20,7 +20,7 @@ export class UpdateModuleImpl {
},
onDownloadFailed: (error: Error) => {
return Promise.reject(error);
}
},
});
} catch (error) {
logger.error(TAG, `downloadFullUpdate failed: ${error}`);
@@ -30,18 +30,18 @@ export class UpdateModuleImpl {
static async downloadAndInstallApk(
context: common.UIAbilityContext,
options: { url: string; hash: string; target: string }
options: { url: string; hash: string; target: string },
): Promise<void> {
try {
const want = {
action: 'action.system.home',
parameters: {
uri: 'appmarket://details'
}
uri: 'appmarket://details',
},
};
if (!context) {
throw new Error('获取context失败');
throw Error('获取context失败');
}
await context.startAbility(want);
@@ -53,17 +53,21 @@ export class UpdateModuleImpl {
static async downloadPatchFromPackage(
updateContext: UpdateContext,
options: { updateUrl: string; hash: string }
options: { updateUrl: string; hash: string },
): Promise<void> {
try {
return await updateContext.downloadPatchFromPackage(options.updateUrl, options.hash, {
onDownloadCompleted: (params: DownloadTaskParams) => {
return Promise.resolve();
return await updateContext.downloadPatchFromPackage(
options.updateUrl,
options.hash,
{
onDownloadCompleted: (params: DownloadTaskParams) => {
return Promise.resolve();
},
onDownloadFailed: (error: Error) => {
return Promise.reject(error);
},
},
onDownloadFailed: (error: Error) => {
return Promise.reject(error);
}
});
);
} catch (error) {
logger.error(TAG, `downloadPatchFromPackage failed: ${error}`);
throw error;
@@ -72,7 +76,7 @@ export class UpdateModuleImpl {
static async downloadPatchFromPpk(
updateContext: UpdateContext,
options: { updateUrl: string; hash: string; originHash: string }
options: { updateUrl: string; hash: string; originHash: string },
): Promise<void> {
try {
await updateContext.downloadPatchFromPpk(
@@ -85,49 +89,49 @@ export class UpdateModuleImpl {
},
onDownloadFailed: (error: Error) => {
return Promise.reject(error);
}
}
},
},
);
} catch (error) {
logger.error(TAG, `downloadPatchFromPpk failed: ${error}`);
throw new Error(`执行报错: ${error.message}`);
throw Error(`执行报错: ${error.message}`);
}
}
static async reloadUpdate(
updateContext: UpdateContext,
context: common.UIAbilityContext,
options: { hash: string }
options: { hash: string },
): Promise<void> {
const hash = options.hash;
if (!hash) {
throw new Error('hash不能为空');
throw Error('hash不能为空');
}
try {
await updateContext.switchVersion(hash);
const bundleInfo = await bundleManager.getBundleInfoForSelf(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION,
);
await context.terminateSelf();
const want = {
bundleName: bundleInfo.name,
abilityName: context.abilityInfo?.name
abilityName: context.abilityInfo?.name,
};
await context.startAbility(want);
} catch (error) {
logger.error(TAG, `reloadUpdate failed: ${error}`);
throw new Error(`pushy:switchVersion failed ${error.message}`);
throw Error(`pushy:switchVersion failed ${error.message}`);
}
}
static async setNeedUpdate(
updateContext: UpdateContext,
options: { hash: string }
options: { hash: string },
): Promise<boolean> {
const hash = options.hash;
if (!hash) {
throw new Error('hash不能为空');
throw Error('hash不能为空');
}
try {
@@ -135,7 +139,7 @@ export class UpdateModuleImpl {
return true;
} catch (error) {
logger.error(TAG, `setNeedUpdate failed: ${error}`);
throw new Error(`switchVersionLater failed: ${error.message}`);
throw Error(`switchVersionLater failed: ${error.message}`);
}
}
@@ -145,20 +149,20 @@ export class UpdateModuleImpl {
return true;
} catch (error) {
logger.error(TAG, `markSuccess failed: ${error}`);
throw new Error(`执行报错: ${error.message}`);
throw Error(`执行报错: ${error.message}`);
}
}
static async setUuid(
updateContext: UpdateContext,
uuid: string
uuid: string,
): Promise<boolean> {
try {
await updateContext.setKv('uuid', uuid);
return true;
} catch (error) {
logger.error(TAG, `setUuid failed: ${error}`);
throw new Error(`执行报错: ${error.message}`);
throw Error(`执行报错: ${error.message}`);
}
}
@@ -171,27 +175,17 @@ export class UpdateModuleImpl {
}
}
static async setLocalHashInfo(
static setLocalHashInfo(
updateContext: UpdateContext,
hash: string,
info: string
): Promise<boolean> {
if (!this.checkJson(info)) {
await updateContext.setKv(`hash_${hash}`, info);
throw new Error('校验报错:json字符串格式错误');
}
await updateContext.setKv(`hash_${hash}`, info);
info: string,
): boolean {
updateContext.setKv(`hash_${hash}`, info);
return true;
}
static async getLocalHashInfo(
updateContext: UpdateContext,
hash: string
): Promise<string> {
const value = await updateContext.getKv(`hash_${hash}`);
if (!this.checkJson(value)) {
throw new Error('校验报错:json字符串格式错误');
}
static getLocalHashInfo(updateContext: UpdateContext, hash: string): string {
const value = updateContext.getKv(`hash_${hash}`);
return value;
}
}
}

View File

@@ -1,137 +0,0 @@
// hpatch.c
// Copyright 2021 housisong, All rights reserved
#include "hpatch.h"
#include "HDiffPatch/libHDiffPatch/HPatch/patch.h"
#include "HDiffPatch/file_for_patch.h"
//#define _CompressPlugin_zlib
//#define _CompressPlugin_bz2
#define _CompressPlugin_lzma
#define _CompressPlugin_lzma2
#define _IsNeedIncludeDefaultCompressHead 0
#include "lzma/C/LzmaDec.h"
#include "lzma/C/Lzma2Dec.h"
#include "HDiffPatch/decompress_plugin_demo.h"
#define kMaxLoadMemOldSize ((1<<20)*8)
#define _check(v,errorType) do{ \
if (!(v)){ if (result==kHPatch_ok) result=errorType; if (!_isInClear){ goto _clear; }; } }while(0)
int hpatch_getInfo_by_mem(hpatch_singleCompressedDiffInfo* out_patinfo,
const uint8_t* pat,size_t patsize){
hpatch_TStreamInput patStream;
mem_as_hStreamInput(&patStream,pat,pat+patsize);
if (!getSingleCompressedDiffInfo(out_patinfo,&patStream,0))
return kHPatch_error_info;//data error;
return kHPatch_ok; //ok
}
static hpatch_TDecompress* getDecompressPlugin(const char* compressType){
#ifdef _CompressPlugin_zlib
if (zlibDecompressPlugin.is_can_open(compressType))
return &zlibDecompressPlugin;
#endif
#ifdef _CompressPlugin_bz2
if (bz2DecompressPlugin.is_can_open(compressType))
return &bz2DecompressPlugin;
#endif
#ifdef _CompressPlugin_lzma
if (lzmaDecompressPlugin.is_can_open(compressType))
return &lzmaDecompressPlugin;
#endif
#ifdef _CompressPlugin_lzma2
if (lzma2DecompressPlugin.is_can_open(compressType))
return &lzma2DecompressPlugin;
#endif
return 0;
}
static int hpatch_by_stream(const hpatch_TStreamInput* old,hpatch_BOOL isLoadOldAllToMem,const hpatch_TStreamInput* pat,
hpatch_TStreamOutput* out_new,const hpatch_singleCompressedDiffInfo* patInfo){
int result=kHPatch_ok;
int _isInClear=hpatch_FALSE;
hpatch_TDecompress* decompressPlugin=0;
uint8_t* temp_cache=0;
size_t temp_cache_size;
hpatch_singleCompressedDiffInfo _patinfo;
hpatch_TStreamInput _old;
{// info
if (!patInfo){
_check(getSingleCompressedDiffInfo(&_patinfo,pat,0),kHPatch_error_info);
patInfo=&_patinfo;
}
_check(old->streamSize==patInfo->oldDataSize,kHPatch_error_old_size);
_check(out_new->streamSize>=patInfo->newDataSize,kHPatch_error_new_size);
out_new->streamSize=patInfo->newDataSize;
if (strlen(patInfo->compressType)>0){
decompressPlugin=getDecompressPlugin(patInfo->compressType);
_check(decompressPlugin,kHPatch_error_compressType);
}
}
{// mem
size_t mem_size;
size_t oldSize=(size_t)old->streamSize;
isLoadOldAllToMem=isLoadOldAllToMem&&(old->streamSize<=kMaxLoadMemOldSize);
temp_cache_size=patInfo->stepMemSize+hpatch_kFileIOBufBetterSize*3;
mem_size=temp_cache_size+(isLoadOldAllToMem?oldSize:0);
temp_cache=malloc(mem_size);
_check(temp_cache,kHPatch_error_malloc);
if (isLoadOldAllToMem){//load old to mem
uint8_t* oldMem=temp_cache+temp_cache_size;
_check(old->read(old,0,oldMem,oldMem+oldSize),kHPatch_error_old_fread);
mem_as_hStreamInput(&_old,oldMem,oldMem+oldSize);
old=&_old;
}
}
_check(patch_single_compressed_diff(out_new,old,pat,patInfo->diffDataPos,
patInfo->uncompressedSize,decompressPlugin,patInfo->coverCount,
patInfo->stepMemSize,temp_cache,temp_cache+temp_cache_size),kHPatch_error_patch);
_clear:
_isInClear=hpatch_TRUE;
if (temp_cache){ free(temp_cache); temp_cache=0; }
return result;
}
int hpatch_by_mem(const uint8_t* old,size_t oldsize,uint8_t* newBuf,size_t newsize,
const uint8_t* pat,size_t patsize,const hpatch_singleCompressedDiffInfo* patInfo){
hpatch_TStreamInput oldStream;
hpatch_TStreamInput patStream;
hpatch_TStreamOutput newStream;
mem_as_hStreamInput(&oldStream,old,old+oldsize);
mem_as_hStreamInput(&patStream,pat,pat+patsize);
mem_as_hStreamOutput(&newStream,newBuf,newBuf+newsize);
return hpatch_by_stream(&oldStream,hpatch_FALSE,&patStream,&newStream,patInfo);
}
int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfile){
int result=kHPatch_ok;
int _isInClear=hpatch_FALSE;
int patch_result;
hpatch_TFileStreamInput oldStream;
hpatch_TFileStreamInput patStream;
hpatch_TFileStreamOutput newStream;
hpatch_TFileStreamInput_init(&oldStream);
hpatch_TFileStreamInput_init(&patStream);
hpatch_TFileStreamOutput_init(&newStream);
_check(hpatch_TFileStreamInput_open(&oldStream,oldfile),kHPatch_error_old_fopen);
_check(hpatch_TFileStreamInput_open(&patStream,patchfile),kHPatch_error_pat_fopen);
_check(hpatch_TFileStreamOutput_open(&newStream,newfile,~(hpatch_StreamPos_t)0),kHPatch_error_new_fopen);
patch_result=hpatch_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base,0);
if (patch_result!=kHPatch_ok){
_check(!oldStream.fileError,kHPatch_error_old_fread);
_check(!patStream.fileError,kHPatch_error_pat_fread);
_check(!newStream.fileError,kHPatch_error_new_fwrite);
_check(hpatch_FALSE,patch_result);
}
_clear:
_isInClear=hpatch_TRUE;
_check(hpatch_TFileStreamInput_close(&oldStream),kHPatch_error_old_fclose);
_check(hpatch_TFileStreamInput_close(&patStream),kHPatch_error_pat_fclose);
_check(hpatch_TFileStreamOutput_close(&newStream),kHPatch_error_new_fclose);
return result;
}

View File

@@ -1,44 +0,0 @@
// hpatch.h
// import HDiffPatch, support patchData created by "hdiffz -SD -c-lzma2 oldfile newfile patchfile"
// Copyright 2021 housisong, All rights reserved
#ifndef HDIFFPATCH_PATCH_H
#define HDIFFPATCH_PATCH_H
# include <stdint.h> //for uint8_t
#include "HDiffPatch/libHDiffPatch/HPatch/patch_types.h" //for hpatch_singleCompressedDiffInfo
#ifdef __cplusplus
extern "C" {
#endif
//result
enum {
kHPatch_ok = 0,
kHPatch_error_malloc =-1,
kHPatch_error_info =-2,
kHPatch_error_compressType =-3,
kHPatch_error_patch =-4,
kHPatch_error_old_fopen =-5,
kHPatch_error_old_fread =-6,
kHPatch_error_old_fclose =-7,
kHPatch_error_pat_fopen =-8,
kHPatch_error_pat_fread =-9,
kHPatch_error_pat_fclose =-10,
kHPatch_error_new_fopen =-11,
kHPatch_error_new_fwrite =-12,
kHPatch_error_new_fclose =-13,
kHPatch_error_old_size =-14,
kHPatch_error_new_size =-15,
};
int hpatch_getInfo_by_mem(hpatch_singleCompressedDiffInfo* out_patinfo,
const uint8_t* pat,size_t patsize);
//patInfo can NULL
int hpatch_by_mem(const uint8_t* old,size_t oldsize, uint8_t* newBuf,size_t newsize,
const uint8_t* pat,size_t patsize,const hpatch_singleCompressedDiffInfo* patInfo);
int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfile);
#ifdef __cplusplus
}
#endif
#endif //HDIFFPATCH_PATCH_H

View File

@@ -1,254 +0,0 @@
import preferences from '@ohos.data.preferences';
import bundleManager from '@ohos.bundle.bundleManager';
import fileIo from '@ohos.file.fs';
import { DownloadTask } from './DownloadTask';
import common from '@ohos.app.ability.common';
import { DownloadTaskParams } from './DownloadTaskParams';
export class UpdateContext {
private context: common.UIAbilityContext;
private rootDir: string;
private preferences: preferences.Preferences;
private static DEBUG: boolean = false;
private static isUsingBundleUrl: boolean = false;
constructor(context: common.UIAbilityContext) {
this.context = context;
this.rootDir = context.filesDir + '/_update';
try {
if (!fileIo.accessSync(this.rootDir)) {
fileIo.mkdirSync(this.rootDir);
}
} catch (e) {
console.error('Failed to create root directory:', e);
}
this.initPreferences();
}
private initPreferences() {
try {
this.preferences = preferences.getPreferencesSync(this.context, {name:'update'});
const packageVersion = this.getPackageVersion();
const storedVersion = this.preferences.getSync('packageVersion', '');
if(!storedVersion){
this.preferences.putSync('packageVersion', packageVersion);
this.preferences.flush();
} else if (storedVersion && packageVersion !== storedVersion) {
this.preferences.clear();
this.preferences.putSync('packageVersion', packageVersion);
this.preferences.flush();
this.cleanUp();
}
} catch (e) {
console.error('Failed to init preferences:', e);
}
}
public setKv(key: string, value: string): void {
this.preferences.putSync(key, value);
this.preferences.flush();
}
public getKv(key: string): string {
return this.preferences.getSync(key, '') as string;
}
public isFirstTime(): boolean {
return this.preferences.getSync('firstTime', false) as boolean;
}
public rolledBackVersion(): string {
return this.preferences.getSync('rolledBackVersion', '') as string;
}
public markSuccess(): void {
this.preferences.putSync('firstTimeOk', true);
const lastVersion = this.preferences.getSync('lastVersion', '') as string;
const curVersion = this.preferences.getSync('currentVersion', '') as string;
if (lastVersion && lastVersion !== curVersion) {
this.preferences.deleteSync('lastVersion');
this.preferences.deleteSync(`hash_${lastVersion}`);
}
this.preferences.flush();
this.cleanUp();
}
public clearFirstTime(): void {
this.preferences.putSync('firstTime', false);
this.preferences.flush();
this.cleanUp();
}
public clearRollbackMark(): void {
this.preferences.putSync('rolledBackVersion', null);
this.preferences.flush();
this.cleanUp();
}
public async downloadFullUpdate(url: string, hash: string, listener: DownloadFileListener): Promise<void> {
try {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FULL;
params.url = url;
params.hash = hash;
params.listener = listener;
params.targetFile = `${this.rootDir}/${hash}.ppk`;
const downloadTask = new DownloadTask(this.context);
await downloadTask.execute(params);
} catch (e) {
console.error('Failed to download full update:', e);
}
}
public async downloadFile(url: string, hash: string, fileName: string, listener: DownloadFileListener): Promise<void> {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD;
params.url = url;
params.hash = hash;
params.listener = listener;
params.targetFile = this.rootDir + '/' + fileName;
const downloadTask = new DownloadTask(this.context);
await downloadTask.execute(params);
}
public async downloadPatchFromPpk(url: string, hash: string, originHash: string, listener: DownloadFileListener): Promise<void> {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
params.url = url;
params.hash = hash;
params.originHash = originHash;
params.listener = listener;
params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`;
params.unzipDirectory = `${this.rootDir}/${hash}`;
params.originDirectory = `${this.rootDir}/${params.originHash}`;
const downloadTask = new DownloadTask(this.context);
await downloadTask.execute(params);
}
public async downloadPatchFromPackage(url: string, hash: string, listener: DownloadFileListener): Promise<void> {
try {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP;
params.url = url;
params.hash = hash;
params.listener = listener;
params.targetFile = `${this.rootDir}/${hash}.app.patch`;
params.unzipDirectory = `${this.rootDir}/${hash}`;
const downloadTask = new DownloadTask(this.context);
return await downloadTask.execute(params);
} catch (e) {
throw e;
console.error('Failed to download APK patch:', e);
}
}
public switchVersion(hash: string): void {
try {
const bundlePath = `${this.rootDir}/${hash}/bundle.harmony.js`;
if (!fileIo.accessSync(bundlePath)) {
throw new Error(`Bundle version ${hash} not found.`);
}
const lastVersion = this.getKv('currentVersion');
this.setKv('currentVersion', hash);
if (lastVersion && lastVersion !== hash) {
this.setKv('lastVersion', lastVersion);
}
this.setKv('firstTime', 'true');
this.setKv('firstTimeOk', 'false');
this.setKv('rolledBackVersion', "");
} catch (e) {
console.error('Failed to switch version:', e);
}
}
public static getBundleUrl(context: common.UIAbilityContext, defaultAssetsUrl?: string): string {
return new UpdateContext(context).getBundleUrl(defaultAssetsUrl);
}
public getBundleUrl(defaultAssetsUrl?: string): string {
UpdateContext.isUsingBundleUrl = true;
const currentVersion = this.getCurrentVersion();
if (!currentVersion) {
return defaultAssetsUrl;
}
if (!this.isFirstTime()) {
if (!this.preferences.getSync('firstTimeOk', true)) {
return this.rollBack();
}
}
let version = currentVersion;
while (version) {
const bundleFile = `${this.rootDir}/${version}/bundle.harmony.js`;
try {
if (!fileIo.accessSync(bundleFile)) {
console.error(`Bundle version ${version} not found.`);
version = this.rollBack();
continue;
}
return bundleFile;
} catch (e) {
console.error('Failed to access bundle file:', e);
version = this.rollBack();
}
}
return defaultAssetsUrl;
}
getPackageVersion(): string {
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
let packageVersion = '';
try {
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
packageVersion = bundleInfo?.versionName || "Unknown";
} catch (error) {
console.error("获取包信息失败:", error);
}
return packageVersion;
}
public getCurrentVersion() : string {
const currentVersion = this.getKv('currentVersion');
return currentVersion;
}
private rollBack(): string {
const lastVersion = this.preferences.getSync('lastVersion', '') as string;
const currentVersion = this.preferences.getSync('currentVersion', '') as string;
if (!lastVersion) {
this.preferences.deleteSync('currentVersion');
} else {
this.preferences.putSync('currentVersion', lastVersion);
}
this.preferences.putSync('firstTimeOk', true);
this.preferences.putSync('firstTime', false);
this.preferences.putSync('rolledBackVersion', currentVersion);
this.preferences.flush();
return lastVersion;
}
private cleanUp(): void {
const params = new DownloadTaskParams();
params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
params.hash = this.preferences.getSync('currentVersion', '') as string;
params.originHash = this.preferences.getSync('lastVersion', '') as string;
params.unzipDirectory = this.rootDir;
const downloadTask = new DownloadTask(this.context);
downloadTask.execute(params);
}
public getIsUsingBundleUrl(): boolean {
return UpdateContext.isUsingBundleUrl;
}
}
export interface DownloadFileListener {
onDownloadCompleted(params: DownloadTaskParams): void;
onDownloadFailed(error: Error): void;
}

2
ios/ImportReact.h Normal file
View File

@@ -0,0 +1,2 @@
@import React;

View File

@@ -16,6 +16,7 @@
static NSString *const keyPushyInfo = @"REACTNATIVECN_PUSHY_INFO_KEY";
static NSString *const paramPackageVersion = @"packageVersion";
static NSString *const paramBuildTime = @"buildTime";
static NSString *const paramLastVersion = @"lastVersion";
static NSString *const paramCurrentVersion = @"currentVersion";
static NSString *const paramIsFirstTime = @"isFirstTime";
@@ -70,20 +71,36 @@ RCT_EXPORT_MODULE(RCTPushy);
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// Check for version changes first
NSString *curPackageVersion = [RCTPushy packageVersion];
NSString *curBuildTime = [RCTPushy buildTime];
NSString *storedPackageVersion = [defaults stringForKey:paramPackageVersion];
NSString *storedBuildTime = [defaults stringForKey:paramBuildTime];
// If stored versions don't exist, write current versions first
if (!storedPackageVersion || !storedBuildTime) {
[defaults setObject:curPackageVersion forKey:paramPackageVersion];
[defaults setObject:curBuildTime forKey:paramBuildTime];
storedPackageVersion = curPackageVersion;
storedBuildTime = curBuildTime;
}
BOOL packageVersionChanged = ![curPackageVersion isEqualToString:storedPackageVersion];
BOOL buildTimeChanged = ![curBuildTime isEqualToString:storedBuildTime];
if (packageVersionChanged || buildTimeChanged) {
// Clear all update data and store new versions
[defaults setObject:nil forKey:keyPushyInfo];
[defaults setObject:nil forKey:keyHashInfo];
[defaults setObject:@(YES) forKey:KeyPackageUpdatedMarked];
[defaults setObject:curPackageVersion forKey:paramPackageVersion];
[defaults setObject:curBuildTime forKey:paramBuildTime];
// ...need clear files later
}
NSDictionary *pushyInfo = [defaults dictionaryForKey:keyPushyInfo];
if (pushyInfo) {
NSString *curPackageVersion = [RCTPushy packageVersion];
NSString *packageVersion = [pushyInfo objectForKey:paramPackageVersion];
BOOL needClearPushyInfo = ![curPackageVersion isEqualToString:packageVersion];
if (needClearPushyInfo) {
[defaults setObject:nil forKey:keyPushyInfo];
[defaults setObject:nil forKey:keyHashInfo];
[defaults setObject:@(YES) forKey:KeyPackageUpdatedMarked];
// ...need clear files later
}
else {
NSString *curVersion = pushyInfo[paramCurrentVersion];
BOOL isFirstTime = [pushyInfo[paramIsFirstTime] boolValue];
@@ -93,8 +110,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;
@@ -116,7 +132,6 @@ RCT_EXPORT_MODULE(RCTPushy);
loadVersion = [self rollback];
}
}
}
}
return [RCTPushy binaryBundleURL];
@@ -128,13 +143,11 @@ RCT_EXPORT_MODULE(RCTPushy);
NSDictionary *pushyInfo = [defaults dictionaryForKey:keyPushyInfo];
NSString *lastVersion = pushyInfo[paramLastVersion];
NSString *curVersion = pushyInfo[paramCurrentVersion];
NSString *curPackageVersion = [RCTPushy packageVersion];
if (lastVersion.length) {
// roll back to last version
[defaults setObject:@{paramCurrentVersion:lastVersion,
paramIsFirstTime:@(NO),
paramIsFirstLoadOk:@(YES),
paramPackageVersion:curPackageVersion}
paramIsFirstLoadOk:@(YES)}
forKey:keyPushyInfo];
}
else {
@@ -163,7 +176,11 @@ RCT_EXPORT_MODULE(RCTPushy);
ret[@"isFirstTime"] = [defaults objectForKey:keyFirstLoadMarked];
ret[@"uuid"] = [defaults objectForKey:keyUuid];
NSDictionary *pushyInfo = [defaults dictionaryForKey:keyPushyInfo];
ret[@"currentVersion"] = [pushyInfo objectForKey:paramCurrentVersion];
NSString *currentVersion = [pushyInfo objectForKey:paramCurrentVersion];
ret[@"currentVersion"] = currentVersion;
if (currentVersion != nil) {
ret[@"currentVersionInfo"] = [defaults objectForKey:[keyHashInfo stringByAppendingString:currentVersion]];
}
// clear isFirstTimemarked
if (ret[@"isFirstTime"]) {
@@ -297,39 +314,38 @@ RCT_EXPORT_METHOD(setNeedUpdate:(NSDictionary *)options
newInfo[paramLastVersion] = lastVersion;
newInfo[paramIsFirstTime] = @(YES);
newInfo[paramIsFirstLoadOk] = @(NO);
newInfo[paramPackageVersion] = [RCTPushy packageVersion];
[defaults setObject:newInfo forKey:keyPushyInfo];
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 +359,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);
}
@@ -361,6 +378,9 @@ RCT_EXPORT_METHOD(restartApp:(RCTPromiseResolveBlock)resolve
RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
#if DEBUG
resolve(@true);
#else
@try {
// up package info
@@ -384,6 +404,7 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
@catch (NSException *exception) {
reject(@"执行报错", nil, nil);
}
#endif
}
@@ -542,7 +563,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

View File

@@ -1,29 +1,29 @@
{
"name": "react-native-update",
"version": "10.28.5",
"version": "10.34.9",
"description": "react-native hot update",
"main": "src/index",
"scripts": {
"postinstall": "node scripts/check-expo-version.js",
"prepack": "yarn submodule && yarn lint",
"prepack": "bun submodule && bun lint",
"lint": "eslint \"src/*.@(ts|tsx|js|jsx)\" && tsc --noEmit",
"submodule": "git submodule update --init --recursive",
"test": "echo \"Error: no test specified\" && exit 1",
"build-lib": "yarn submodule && $ANDROID_HOME/ndk/20.1.5948944/ndk-build NDK_PROJECT_PATH=android APP_BUILD_SCRIPT=android/jni/Android.mk NDK_APPLICATION_MK=android/jni/Application.mk NDK_LIBS_OUT=android/lib",
"build:ios-debug": "cd Example/testHotUpdate && yarn && detox build --configuration ios.sim.debug",
"build:ios-release": "cd Example/testHotUpdate && yarn && detox build --configuration ios.sim.release",
"build:so": "bun submodule && $ANDROID_HOME/ndk/28.2.13676358/ndk-build NDK_PROJECT_PATH=android APP_BUILD_SCRIPT=android/jni/Android.mk NDK_APPLICATION_MK=android/jni/Application.mk NDK_LIBS_OUT=android/lib",
"build:ios-debug": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.debug",
"build:ios-release": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.release",
"test:ios-debug": "cd Example/testHotUpdate && detox test --configuration ios.sim.debug",
"test:ios-release": "cd Example/testHotUpdate && yarn detox test --configuration ios.sim.release",
"build:android-debug": "cd Example/testHotUpdate && yarn && detox build --configuration android.emu.debug",
"build:android-release": "cd Example/testHotUpdate && yarn && detox build --configuration android.emu.release",
"test:android-release": "cd Example/testHotUpdate && yarn detox test --configuration android.emu.release --headless --record-logs all",
"test:ios-release": "cd Example/testHotUpdate && bun detox test --configuration ios.sim.release",
"build:android-debug": "cd Example/testHotUpdate && bun && detox build --configuration android.emu.debug",
"build:android-release": "cd Example/testHotUpdate && bun && detox build --configuration android.emu.release",
"test:android-release": "cd Example/testHotUpdate && bun detox test --configuration android.emu.release --headless --record-logs all",
"test:android-debug": "cd Example/testHotUpdate && detox test --configuration android.emu.debug --headless --record-logs all",
"e2e:ios": "npm run build:ios-release && npm run test:ios-release",
"e2e:android": "npm run build:android-release && npm run test:android-release",
"tests:emulator:prepare": "cd .github/workflows/scripts/functions && yarn && yarn build",
"tests:emulator:start-ci": "yarn tests:emulator:prepare && cd ./.github/workflows/scripts && ./start-firebase-emulator.sh",
"e2e:ios": "bun build:ios-release && bun test:ios-release",
"e2e:android": "bun build:android-release && bun test:android-release",
"tests:emulator:prepare": "cd .github/workflows/scripts/functions && bun && bun build",
"tests:emulator:start-ci": "bun tests:emulator:prepare && cd ./.github/workflows/scripts && ./start-firebase-emulator.sh",
"tests:packager:jet-ci": "cd Example/testHotUpdate && cross-env TMPDIR=$HOME/.metro REACT_DEBUGGER=\"echo nope\" node_modules/.bin/react-native start --no-interactive",
"tests:ios:pod:install": "cd Example/testHotUpdate && yarn && yarn pod-install"
"tests:ios:pod:install": "cd Example/testHotUpdate && bun && bun pod-install"
},
"repository": {
"type": "git",
@@ -57,24 +57,20 @@
"devDependencies": {
"@babel/core": "^7.25.8",
"@react-native/babel-preset": "^0.73.21",
"@react-native/eslint-config": "^0.73.2",
"@react-native/typescript-config": "^0.74.0",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.13",
"@types/node": "^22.7.6",
"@react-native/eslint-config": "0.79.1",
"@react-native/typescript-config": "0.79.1",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.2",
"@types/react": "^18.3.11",
"detox": "^20.27.3",
"detox": "^20.37.0",
"eslint": "^8.57.0",
"eslint-plugin-ft-flow": "^3.0.7",
"firebase-tools": "^13.22.1",
"fs-extra": "^11.2.0",
"jest": "^29.7.0",
"pod-install": "^0.2.2",
"pod-install": "^0.3.7",
"prettier": "^2",
"react": "18.2.0",
"react-native": "0.73",
"ts-jest": "^29.2.5",
"ts-jest": "^29.3.2",
"typescript": "^5.6.3"
},
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
}
}

View File

@@ -9,7 +9,6 @@ podspec_dir = File.dirname(__FILE__)
Pod::Spec.new do |s|
is_expo_in_podfile = false
is_version_sufficient = false
begin
# Check Podfile for use_expo_modules!
podfile_path = File.join(Pod::Config.instance.installation_root, 'Podfile')
@@ -17,24 +16,67 @@ Pod::Spec.new do |s|
podfile_content = File.read(podfile_path)
is_expo_in_podfile = podfile_content.include?('use_expo_modules!')
end
# Check root package.json for Expo version >= 50
root_package_json_path = File.join(podspec_dir, '..', '..', 'package.json')
if File.exist?(root_package_json_path)
pkg_json = JSON.parse(File.read(root_package_json_path))
expo_version_string = pkg_json['dependencies']&.[]('expo') || pkg_json['devDependencies']&.[]('expo')
if expo_version_string
match = expo_version_string.match(/\d+/)
rescue => e
# Silently ignore errors during check
end
# Determine final validity by checking Podfile presence AND Expo version
valid_expo_project = false # Default
if is_expo_in_podfile
# Only check expo version if use_expo_modules! is present
is_version_sufficient = false
begin
expo_version_str = `node --print \"require('expo/package.json').version\"`.strip
if expo_version_str && !expo_version_str.empty?
match = expo_version_str.match(/^\d+/)
if match
major_version = match[0].to_i
is_version_sufficient = major_version >= 50
end
end
rescue
# Node command failed, version remains insufficient
end
rescue => e
# Silently ignore errors during check
# Final check
valid_expo_project = is_version_sufficient
end
# Determine final validity
valid_expo_project = is_expo_in_podfile && is_version_sufficient
# Set platform based on whether it's a valid Expo project and if we can parse its target
final_ios_deployment_target = '11.0' # Default target
if valid_expo_project
# --- Try to find and parse ExpoModulesCore.podspec only if it's an Expo project ---
parsed_expo_ios_target = nil
expo_modules_core_podspec_path = begin
package_json_path = `node -p "require.resolve('expo-modules-core/package.json')"`.strip
File.join(File.dirname(package_json_path), 'ExpoModulesCore.podspec') if $?.success? && package_json_path && !package_json_path.empty?
rescue
nil
end
if expo_modules_core_podspec_path && File.exist?(expo_modules_core_podspec_path)
begin
content = File.read(expo_modules_core_podspec_path)
match = content.match(/s\.platforms\s*=\s*\{[\s\S]*?:ios\s*=>\s*'([^\']+)'/) # Match within s.platforms hash
if match && match[1]
parsed_expo_ios_target = match[1]
else
match = content.match(/s\.platform\s*=\s*:ios,\s*'([^\']+)'/) # Fallback to s.platform = :ios, 'version'
if match && match[1]
parsed_expo_ios_target = match[1]
end
end
rescue => e
# Pod::UI.warn "Failed to read or parse ExpoModulesCore.podspec content: #{e.message}"
end
end
if parsed_expo_ios_target
final_ios_deployment_target = parsed_expo_ios_target
end
end
s.platforms = { :ios => final_ios_deployment_target }
s.name = package['name']
s.version = package['version']
@@ -45,16 +87,8 @@ Pod::Spec.new do |s|
s.homepage = package['homepage']
s.cocoapods_version = '>= 1.6.0'
s.platform = :ios, "8.0"
s.platforms = { :ios => "11.0" }
s.source = { :git => 'https://github.com/reactnativecn/react-native-update.git', :tag => '#{s.version}' }
# Conditionally set source files
if valid_expo_project
s.source_files = Dir.glob("ios/**/*.{h,m,mm,swift}") # Include Expo files
else
s.source_files = Dir.glob("ios/**/*.{h,m,mm,swift}").reject { |f| f.start_with?("ios/Expo/") } # Exclude Expo files
end
s.source = { :git => 'https://github.com/reactnativecn/react-native-update.git', :tag => '#{s.version}' }
s.libraries = 'bz2', 'z'
s.vendored_libraries = 'RCTPushy/libRCTPushy.a'
@@ -75,37 +109,38 @@ Pod::Spec.new do |s|
end
s.subspec 'RCTPushy' do |ss|
ss.source_files = 'ios/RCTPushy/*.{h,m,mm,swift}'
ss.public_header_files = ['ios/RCTPushy/*.h']
end
s.subspec 'HDiffPatch' do |ss|
ss.source_files = ['ios/RCTPushy/HDiffPatch/**/*.{h,m,c}',
ss.source_files = ['ios/**/*.{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/**/*.h']
end
# Conditionally add Expo subspec and check ExpoModulesCore version
if valid_expo_project
supports_bundle_url_final = false # Default
begin
# Check installed ExpoModulesCore version for bundle URL support
expo_core_package_json_path = File.join(podspec_dir, '..', 'expo-modules-core', 'package.json')
if File.exist?(expo_core_package_json_path)
core_package_json = JSON.parse(File.read(expo_core_package_json_path))
installed_version_str = core_package_json['version']
if installed_version_str
installed_version = Gem::Version.new(installed_version_str)
# 1. Try executing node to get the version string
expo_modules_core_version_str = begin
# Use node to directly require expo-modules-core/package.json and get its version
`node --print \"require('expo-modules-core/package.json').version\"` # Execute, keep raw output
rescue
# Node command failed (e.g., node not found, package not found). Return empty string.
''
end
# 2. Process the obtained version string (if not empty)
if expo_modules_core_version_str && !expo_modules_core_version_str.empty?
begin
# Compare versions using Gem::Version (handles trailing newline)
installed_version = Gem::Version.new(expo_modules_core_version_str)
target_version = Gem::Version.new('1.12.0')
supports_bundle_url_final = installed_version >= target_version
end
end
rescue JSON::ParserError, Errno::ENOENT, ArgumentError, StandardError => e
# Pod::UI.warn "Could not check ExpoModulesCore version: #{e.message}"
rescue ArgumentError
# If Gem::Version fails parsing, supports_bundle_url_final remains false.
end
end
s.subspec 'Expo' do |ss|

Some files were not shown because too many files have changed in this diff Show More