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

Compare commits

...

31 Commits

Author SHA1 Message Date
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
83 changed files with 5104 additions and 1071 deletions

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,29 @@
{
"expo": {
"name": "expoUsePushy",
"slug": "expoUsePushy",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.anonymous.expoUsePushy"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}

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,8 @@
import { registerRootComponent } from 'expo';
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

View File

@@ -0,0 +1,24 @@
{
"name": "expousepushy",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "expo start",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web"
},
"dependencies": {
"expo": "~52.0.46",
"expo-status-bar": "~2.0.1",
"react": "18.3.1",
"react-native": "0.76.9",
"react-native-update": "^10.28.7"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~18.3.12",
"typescript": "^5.3.3"
},
"private": true
}

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

@@ -36,10 +36,6 @@
]
}
]
},
{
name: 'pushy',
srcPath: '../node_modules/react-native-update/harmony',
}
]
}

View File

@@ -7,7 +7,7 @@
"license": "",
"dependencies": {
"@rnoh/react-native-openharmony": "0.72.38",
"pushy": "file:../../node_modules/react-native-update/harmony"
"pushy": "file:../../node_modules/react-native-update/harmony/pushy.har",
}
}

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 +1,4 @@
{
"pushy_build_time": "2025-04-12T11:12:43.423Z",
"pushy_build_time": "2025-04-30T02:46:33.340Z",
"versionName": "1.0.0"
}

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,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

View File

@@ -14,36 +14,36 @@
"dev:harmony": "react-native bundle-harmony --dev"
},
"dependencies": {
"form-data": "^4.0.2",
"form-data": "^4.0.3",
"patch-package": "^8.0.0",
"react": "19.0.0",
"react-native": "0.78.0",
"react-native-camera-kit": "^14.2.0",
"react-native-paper": "^5.13.1",
"react-native-safe-area-context": "^5.3.0",
"react-native-svg": "^15.11.2",
"react-native-update": "^10.26.4",
"react-native": "0.79.2",
"react-native-camera-kit": "^15.1.0",
"react-native-paper": "^5.14.5",
"react-native-safe-area-context": "^5.5.0",
"react-native-svg": "^15.12.0",
"react-native-update": "^10.29.4",
"react-native-vector-icons": "^10.2.0"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@babel/runtime": "^7.26.0",
"@react-native-community/cli": "15.0.1",
"@react-native-community/cli-platform-android": "15.0.1",
"@react-native-community/cli-platform-ios": "15.0.1",
"@react-native/babel-preset": "0.78.0",
"@react-native/eslint-config": "0.78.0",
"@react-native/metro-config": "0.78.0",
"@react-native/typescript-config": "0.78.0",
"@babel/core": "^7.27.3",
"@babel/preset-env": "^7.27.2",
"@babel/runtime": "^7.27.3",
"@react-native-community/cli": "18.0.0",
"@react-native-community/cli-platform-android": "18.0.0",
"@react-native-community/cli-platform-ios": "18.0.0",
"@react-native/babel-preset": "0.79.2",
"@react-native/eslint-config": "0.79.2",
"@react-native/metro-config": "0.79.2",
"@react-native/typescript-config": "0.79.2",
"@types/react": "^19.0.0",
"@types/react-test-renderer": "^19.0.0",
"detox": "^20.32.0",
"detox": "^20.39.0",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"prettier": "2.8.8",
"react-test-renderer": "19.0.0",
"typescript": "5.8.2"
"typescript": "5.8.3"
},
"engines": {
"node": ">=18"
@@ -52,4 +52,4 @@
"detox",
"dtrace-provider"
]
}
}

View File

@@ -52,7 +52,7 @@ function App() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>使Pushy热更新服务</Text>
<Text style={styles.welcome}>xxx使用Pushy热更新服务</Text>
<View style={{flexDirection: 'row'}}>
<Text>
{useDefaultAlert ? '当前使用' : '当前不使用'}alert更新提示

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'
@@ -148,7 +148,6 @@ repositories {
dependencies {
implementation 'com.facebook.react:react-native:+'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
implementation 'com.jakewharton:process-phoenix:3.0.0'
}
if (isNewArchitectureEnabled()) {
react {

36
android/proguard.pro vendored
View File

@@ -1,3 +1,37 @@
# Keep our update module classes
-keepnames class cn.reactnative.modules.update.DownloadTask { *; }
-keepnames class cn.reactnative.modules.update.UpdateModuleImpl { *; }
-keepnames class com.facebook.react.ReactInstanceManager { *; }
-keepnames class cn.reactnative.modules.update.** { *; }
# Keep React Native classes
-keepnames class com.facebook.react.ReactInstanceManager { *; }
-keepnames class com.facebook.react.** { *; }
-keepnames class com.facebook.react.bridge.** { *; }
-keepnames class com.facebook.react.devsupport.** { *; }
# Keep fields used in reflection
-keepclassmembers class com.facebook.react.ReactInstanceManager {
private JSBundleLoader mBundleLoader;
private String mJSBundleFile;
}
-keepclassmembers class com.facebook.react.ReactDelegate {
private ReactHost mReactHost;
}
-keepclassmembers class com.facebook.react.ReactHost {
private boolean mUseDevSupport;
private ReactHostDelegate mReactHostDelegate;
}
# Keep Expo related classes
-keepnames class expo.modules.ExpoReactHostFactory$ExpoReactHostDelegate { *; }
# Keep methods used in reflection
-keepclassmembers class com.facebook.react.ReactActivity {
public ReactDelegate getReactDelegate();
}
-keepclassmembers class com.facebook.react.ReactHost {
public void reload(java.lang.String);
}

View File

@@ -169,17 +169,19 @@ public class UpdateContext {
}
public void markSuccess() {
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("firstTimeOk", true);
String lastVersion = sp.getString("lastVersion", null);
String curVersion = sp.getString("currentVersion", null);
if (lastVersion != null && !lastVersion.equals(curVersion)) {
editor.remove("lastVersion");
editor.remove("hash_" + lastVersion);
}
editor.apply();
if (!BuildConfig.DEBUG) {
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("firstTimeOk", true);
String lastVersion = sp.getString("lastVersion", null);
String curVersion = sp.getString("currentVersion", null);
if (lastVersion != null && !lastVersion.equals(curVersion)) {
editor.remove("lastVersion");
editor.remove("hash_" + lastVersion);
}
editor.apply();
this.cleanUp();
this.cleanUp();
}
}
public void clearFirstTime() {

View File

@@ -147,13 +147,22 @@ public class UpdateModuleImpl {
reactHostField.setAccessible(true);
Object reactHost = reactHostField.get(reactDelegate);
Field devSupport = reactHost.getClass().getDeclaredField("mUseDevSupport");
devSupport.setAccessible(true);
devSupport.set(reactHost, false);
// Access the mReactHostDelegate field
Field reactHostDelegateField = reactHost.getClass().getDeclaredField("mReactHostDelegate");
reactHostDelegateField.setAccessible(true);
Object reactHostDelegate = reactHostDelegateField.get(reactHost);
String bundleFieldName = "jsBundleLoader";
if (reactHostDelegate.getClass().getCanonicalName().equals("expo.modules.ExpoReactHostFactory.ExpoReactHostDelegate")) {
bundleFieldName = "_jsBundleLoader";
}
// Modify the jsBundleLoader field
Field jsBundleLoaderField = reactHostDelegate.getClass().getDeclaredField("jsBundleLoader");
Field jsBundleLoaderField = reactHostDelegate.getClass().getDeclaredField(bundleFieldName);
jsBundleLoaderField.setAccessible(true);
jsBundleLoaderField.set(reactHostDelegate, loader);

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;
@@ -38,12 +41,22 @@ public class UpdateModule extends NativePushySpec {
boolean isFirstTime = updateContext.isFirstTime();
constants.put("isFirstTime", isFirstTime);
if (isFirstTime) {
updateContext.clearFirstTime();
handler.postDelayed(new Runnable() {
@Override
public void run() {
updateContext.clearFirstTime();
}
}, 2000);
}
String rolledBackVersion = updateContext.rolledBackVersion();
constants.put("rolledBackVersion", rolledBackVersion);
if (rolledBackVersion != null) {
updateContext.clearRollbackMark();
handler.postDelayed(new Runnable() {
@Override
public void run() {
updateContext.clearRollbackMark();
}
}, 2000);
}
constants.put("uuid", updateContext.getKv("uuid"));
return constants;

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;

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

BIN
harmony/pushy.har Normal file

Binary file not shown.

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

@@ -3,7 +3,7 @@ import fileIo from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
import { UpdateContext } from './UpdateContext';
export class FileJSBundleProvider extends JSBundleProvider {
export class PushyFileJSBundleProvider extends JSBundleProvider {
private updateContext: UpdateContext;
private filePath: string = ''
@@ -12,7 +12,7 @@ export class FileJSBundleProvider extends JSBundleProvider {
this.updateContext = new UpdateContext(context);
}
getURL(): string {
return this.updateContext.getBundleUrl();
return this.updateContext.getBundleUrl().substring(1);
}
async getBundle(): Promise<ArrayBuffer> {

2
ios/ImportReact.h Normal file
View File

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

View File

@@ -93,8 +93,7 @@ RCT_EXPORT_MODULE(RCTPushy);
BOOL needRollback = (!ignoreRollback && isFirstTime == NO && isFirstLoadOK == NO) || loadVersion.length<=0;
if (needRollback) {
loadVersion = [self rollback];
}
else if (isFirstTime && !ignoreRollback){
} else if (isFirstTime && !ignoreRollback){
// bundleURL may be called many times, ignore rollbacks before process restarted again.
ignoreRollback = true;
@@ -302,34 +301,34 @@ RCT_EXPORT_METHOD(setNeedUpdate:(NSDictionary *)options
resolve(@true);
}else{
} else {
reject(@"执行报错", nil, nil);
}
}
RCT_EXPORT_METHOD(reloadUpdate:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
rejecter:(RCTPromiseRejectBlock)reject)
{
@try {
NSString *hash = options[@"hash"];
if (hash.length) {
[self setNeedUpdate:options resolver:resolve rejecter:reject];
// reload in earlier version
dispatch_async(dispatch_get_main_queue(), ^{
[self.bridge setValue:[[self class] bundleURL] forKey:@"bundleURL"];
[self.bridge reload];
});
#if __has_include("RCTReloadCommand.h")
// reload 0.62+
RCTReloadCommandSetBundleURL([[self class] bundleURL]);
RCTTriggerReloadCommandListeners(@"pushy reload");
#endif
resolve(@true);
}else{
// 只在 setNeedUpdate 成功后 resolve
[self setNeedUpdate:options resolver:^(id result) {
dispatch_async(dispatch_get_main_queue(), ^{
#if __has_include("RCTReloadCommand.h")
// reload 0.62+
RCTReloadCommandSetBundleURL([[self class] bundleURL]);
RCTTriggerReloadCommandListeners(@"pushy reloadUpdate");
#else
[self.bridge reload];
#endif
});
resolve(@true);
} rejecter:^(NSString *code, NSString *message, NSError *error) {
reject(code, message, error);
}];
} else {
reject(@"执行报错", nil, nil);
}
}
@@ -343,13 +342,14 @@ RCT_EXPORT_METHOD(restartApp:(RCTPromiseResolveBlock)resolve
{
@try {
dispatch_async(dispatch_get_main_queue(), ^{
[self.bridge reload];
#if __has_include("RCTReloadCommand.h")
// reload 0.62+
RCTReloadCommandSetBundleURL([[self class] bundleURL]);
RCTTriggerReloadCommandListeners(@"pushy restartApp");
#else
[self.bridge reload];
#endif
});
#if __has_include("RCTReloadCommand.h")
// reload 0.62+
RCTReloadCommandSetBundleURL([[self class] bundleURL]);
RCTTriggerReloadCommandListeners(@"pushy restartApp");
#endif
resolve(@true);
}
@@ -542,7 +542,15 @@ RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
callback([self errorWithMessage:ERROR_HDIFFPATCH]);
}
};
[_fileManager hdiffFileAtPath:bundlePatch fromOrigin:bundleOrigin toDestination:destination completionHandler:completionHandler];
@try {
[_fileManager hdiffFileAtPath:bundlePatch fromOrigin:bundleOrigin toDestination:destination completionHandler:completionHandler];
}
@catch (NSException *exception) {
NSLog(@"Pushy _dopatch error: exception occurred during hdiffFileAtPath: %@, reason: %@",
exception.name, exception.reason);
callback([self errorWithMessage:ERROR_HDIFFPATCH]);
}
}
- (void)patch:(NSString *)hash fromBundle:(NSString *)bundleOrigin source:(NSString *)sourceOrigin callback:(void (^)(NSError *error))callback

View File

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

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'
@@ -71,41 +105,43 @@ Pod::Spec.new do |s|
# Conditionally add Expo dependency
if valid_expo_project
s.public_header_files = ['ios/ImportReact.h']
s.dependency 'ExpoModulesCore'
end
s.subspec 'RCTPushy' do |ss|
ss.source_files = 'ios/RCTPushy/*.{h,m,mm,swift}'
ss.public_header_files = ['ios/RCTPushy/*.h']
end
s.subspec 'HDiffPatch' do |ss|
ss.source_files = ['ios/RCTPushy/HDiffPatch/**/*.{h,m,c}',
ss.source_files = ['ios/RCTPushy/**/*.{h,m,mm,c}',
'android/jni/hpatch.{h,c}',
'android/jni/HDiffPatch/libHDiffPatch/HPatch/*.{h,c}',
'android/jni/HDiffPatch/file_for_patch.{h,c}',
'android/jni/lzma/C/LzmaDec.{h,c}',
'android/jni/lzma/C/Lzma2Dec.{h,c}']
ss.public_header_files = 'ios/RCTPushy/HDiffPatch/**/*.h'
ss.public_header_files = ['ios/RCTPushy/**/*.h']
end
# Conditionally add Expo subspec and check ExpoModulesCore version
if valid_expo_project
supports_bundle_url_final = false # Default
begin
# Check installed ExpoModulesCore version for bundle URL support
expo_core_package_json_path = File.join(podspec_dir, '..', 'expo-modules-core', 'package.json')
if File.exist?(expo_core_package_json_path)
core_package_json = JSON.parse(File.read(expo_core_package_json_path))
installed_version_str = core_package_json['version']
if installed_version_str
installed_version = Gem::Version.new(installed_version_str)
# 1. Try executing node to get the version string
expo_modules_core_version_str = begin
# Use node to directly require expo-modules-core/package.json and get its version
`node --print \"require('expo-modules-core/package.json').version\"` # Execute, keep raw output
rescue
# Node command failed (e.g., node not found, package not found). Return empty string.
''
end
# 2. Process the obtained version string (if not empty)
if expo_modules_core_version_str && !expo_modules_core_version_str.empty?
begin
# Compare versions using Gem::Version (handles trailing newline)
installed_version = Gem::Version.new(expo_modules_core_version_str)
target_version = Gem::Version.new('1.12.0')
supports_bundle_url_final = installed_version >= target_version
end
end
rescue JSON::ParserError, Errno::ENOENT, ArgumentError, StandardError => e
# Pod::UI.warn "Could not check ExpoModulesCore version: #{e.message}"
rescue ArgumentError
# If Gem::Version fails parsing, supports_bundle_url_final remains false.
end
end
s.subspec 'Expo' do |ss|

View File

@@ -1,3 +1,10 @@
const ownPackageJson = require('../package.json');
if (process.env.npm_package_name === ownPackageJson.name) {
console.log('Skipping postinstall during local development.');
process.exit(0);
}
const fs = require('fs');
const path = require('path');

View File

@@ -1,33 +1,33 @@
import { CheckResult, ClientOptions, ProgressData, EventType } from './type';
import {
assertDev,
DeviceEventEmitter,
EmitterSubscription,
Platform,
} from 'react-native';
import {
PushyModule,
buildTime,
cInfo,
currentVersion,
getCurrentVersionInfo,
isFirstTime,
isRolledBack,
packageVersion,
pushyNativeEventEmitter,
rolledBackVersion,
setLocalHashInfo,
} from './core';
import { PermissionsAndroid } from './permissions';
import { CheckResult, ClientOptions, EventType, ProgressData } from './type';
import {
assertWeb,
emptyObj,
enhancedFetch,
joinUrls,
log,
noop,
promiseAny,
testUrls,
} from './utils';
import {
EmitterSubscription,
Platform,
DeviceEventEmitter,
} from 'react-native';
import { PermissionsAndroid } from './permissions';
import {
PushyModule,
buildTime,
cInfo,
pushyNativeEventEmitter,
currentVersion,
packageVersion,
rolledBackVersion,
setLocalHashInfo,
isFirstTime,
isRolledBack,
getCurrentVersionInfo,
} from './core';
const SERVER_PRESETS = {
// cn
@@ -148,6 +148,7 @@ export class Pushy {
await this.loggerPromise.promise;
const { logger = noop, appKey } = this.options;
const info = await getCurrentVersionInfo();
const overridePackageVersion = this.options.overridePackageVersion;
logger({
type,
data: {
@@ -155,6 +156,7 @@ export class Pushy {
currentVersion,
cInfo,
packageVersion,
overridePackageVersion,
buildTime,
message,
...info,
@@ -170,10 +172,12 @@ export class Pushy {
getCheckUrl = (endpoint: string = this.options.server!.main) => {
return `${endpoint}/checkUpdate/${this.options.appKey}`;
};
assertDebug = () => {
assertDebug = (matter: string) => {
if (__DEV__ && !this.options.debug) {
console.info(
'You are currently in the development environment and have not enabled debug mode. The hot update check will not be performed. If you need to debug hot updates in the development environment, please set debug to true in the client.',
`You are currently in the development environment and have not enabled debug mode.
${matter} will not be performed.
If you need to debug ${matter} in the development environment, please set debug to true in the client.`,
);
return false;
}
@@ -188,7 +192,7 @@ export class Pushy {
this.report({ type: 'markSuccess' });
};
switchVersion = async (hash: string) => {
if (!assertDev('switchVersion()')) {
if (!this.assertDebug('switchVersion()')) {
return;
}
if (assertHash(hash) && !sharedState.applyingUpdate) {
@@ -199,7 +203,7 @@ export class Pushy {
};
switchVersionLater = async (hash: string) => {
if (!assertDev('switchVersionLater()')) {
if (!this.assertDebug('switchVersionLater()')) {
return;
}
if (assertHash(hash)) {
@@ -208,7 +212,7 @@ export class Pushy {
}
};
checkUpdate = async (extra?: Record<string, any>) => {
if (!this.assertDebug()) {
if (!this.assertDebug('checkUpdate()')) {
return;
}
if (!assertWeb()) {
@@ -231,7 +235,7 @@ export class Pushy {
}
this.lastChecking = now;
const fetchBody = {
packageVersion,
packageVersion: this.options.overridePackageVersion || packageVersion,
hash: currentVersion,
buildTime,
cInfo,
@@ -261,7 +265,7 @@ export class Pushy {
type: 'checking',
message: this.options.appKey + ': ' + stringifyBody,
});
resp = await fetch(this.getCheckUrl(), fetchPayload);
resp = await enhancedFetch(this.getCheckUrl(), fetchPayload);
} catch (e: any) {
this.report({
type: 'errorChecking',
@@ -272,7 +276,7 @@ export class Pushy {
try {
resp = await promiseAny(
backupEndpoints.map(endpoint =>
fetch(this.getCheckUrl(endpoint), fetchPayload),
enhancedFetch(this.getCheckUrl(endpoint), fetchPayload),
),
);
} catch (err: any) {
@@ -389,11 +393,16 @@ export class Pushy {
}
}
let succeeded = '';
this.report({ type: 'downloading' });
this.report({
type: 'downloading',
data: {
newVersion: hash,
},
});
let lastError: any;
let errorMessages: string[] = [];
const diffUrl = await testUrls(joinUrls(paths, diff));
if (diffUrl) {
if (diffUrl && !__DEV__) {
log('downloading diff');
try {
await PushyModule.downloadPatchFromPpk({
@@ -406,60 +415,56 @@ export class Pushy {
const errorMessage = `diff error: ${e.message}`;
errorMessages.push(errorMessage);
lastError = new Error(errorMessage);
if (__DEV__) {
succeeded = 'diff';
} else {
log(errorMessage);
}
log(errorMessage);
}
}
const pdiffUrl = await testUrls(joinUrls(paths, pdiff));
if (!succeeded && pdiffUrl) {
log('downloading pdiff');
try {
await PushyModule.downloadPatchFromPackage({
updateUrl: pdiffUrl,
hash,
});
succeeded = 'pdiff';
} catch (e: any) {
const errorMessage = `pdiff error: ${e.message}`;
errorMessages.push(errorMessage);
lastError = new Error(errorMessage);
if (__DEV__) {
if (!succeeded) {
const pdiffUrl = await testUrls(joinUrls(paths, pdiff));
if (pdiffUrl && !__DEV__) {
log('downloading pdiff');
try {
await PushyModule.downloadPatchFromPackage({
updateUrl: pdiffUrl,
hash,
});
succeeded = 'pdiff';
} else {
} catch (e: any) {
const errorMessage = `pdiff error: ${e.message}`;
errorMessages.push(errorMessage);
lastError = new Error(errorMessage);
log(errorMessage);
}
}
}
const fullUrl = await testUrls(joinUrls(paths, full));
if (!succeeded && fullUrl) {
log('downloading full patch');
try {
await PushyModule.downloadFullUpdate({
updateUrl: fullUrl,
hash,
});
succeeded = 'full';
} catch (e: any) {
const errorMessage = `full patch error: ${e.message}`;
errorMessages.push(errorMessage);
lastError = new Error(errorMessage);
if (__DEV__) {
if (!succeeded) {
const fullUrl = await testUrls(joinUrls(paths, full));
if (fullUrl) {
log('downloading full patch');
try {
await PushyModule.downloadFullUpdate({
updateUrl: fullUrl,
hash,
});
succeeded = 'full';
} else {
} catch (e: any) {
const errorMessage = `full patch error: ${e.message}`;
errorMessages.push(errorMessage);
lastError = new Error(errorMessage);
log(errorMessage);
}
} else if (__DEV__) {
log(
`当前是开发环境,无法执行增量式热更新,重启不会生效。
如果需要在开发环境中测试可生效的全量热更新(但也会在再次重启后重新连接 metro
请打开“忽略时间戳”开关再重试。`,
);
succeeded = 'full';
}
}
if (sharedState.progressHandlers[hash]) {
sharedState.progressHandlers[hash].remove();
delete sharedState.progressHandlers[hash];
}
if (__DEV__) {
return hash;
}
if (!succeeded) {
this.report({
type: 'errorUpdate',

View File

@@ -251,7 +251,7 @@ export const UpdateProvider = ({
const markSuccess = client.markSuccess;
useEffect(() => {
if (!client.assertDebug()) {
if (!client.assertDebug('checkUpdate()')) {
return;
}
const { checkStrategy, dismissErrorAfter, autoMarkSuccess } = options;

View File

@@ -93,6 +93,7 @@ export interface ClientOptions {
beforeDownloadUpdate?: (info: CheckResult) => Promise<boolean>;
afterDownloadUpdate?: (info: CheckResult) => Promise<boolean>;
onPackageExpired?: (info: CheckResult) => Promise<boolean>;
overridePackageVersion?: string;
}
export interface UpdateTestPayload {

View File

@@ -40,13 +40,13 @@ const ping =
: async (url: string) => {
let pingFinished = false;
return Promise.race([
fetch(url, {
enhancedFetch(url, {
method: 'HEAD',
})
.then(({ status, statusText }) => {
.then(({ status, statusText, url: finalUrl }) => {
pingFinished = true;
if (status === 200) {
return url;
return finalUrl;
}
log('ping failed', url, status, statusText);
throw new Error('Ping failed');
@@ -69,7 +69,7 @@ const ping =
export function joinUrls(paths: string[], fileName?: string) {
if (fileName) {
return paths.map(path => 'https://' + path + '/' + fileName);
return paths.map(path => `https://${path}/${fileName}`);
}
}
@@ -99,12 +99,19 @@ export const assertWeb = () => {
return true;
};
export const assertDev = (matter: string) => {
if (__DEV__) {
console.warn(
`${matter} is not supported in development environment; no action taken.`,
);
return false;
}
return true;
// export const isAndroid70AndBelow = () => {
// // android 7.0 and below devices do not support letsencrypt cert
// // https://letsencrypt.org/2023/07/10/cross-sign-expiration/
// return Platform.OS === 'android' && Platform.Version <= 24;
// };
export const enhancedFetch = async (
url: string,
params: Parameters<typeof fetch>[1],
) => {
return fetch(url, params).catch(e => {
log('fetch error', url, e);
log('trying fallback to http');
return fetch(url.replace('https', 'http'), params);
});
};