react-native-uat-update
Version:
包含热更新codepush以及appstore更新检查
405 lines (359 loc) • 11.8 kB
JavaScript
/**
* 备注:更新检测:包含热更新、appstore更新检测
* create by Mark at 2019-August
*/
import React, { Component } from 'react';
import {
View,
Text,
Image,
TouchableOpacity,
StyleSheet,
ViewStyle,
Modal,
ScrollView,
TouchableWithoutFeedback,
Linking,
AppState,
AppStateStatus,
} from 'react-native';
import { UpdateProp,AppInfo,AppStoreResponse, UpdateStatus, } from './index.model';
import CodePush, {
DownloadProgress,
DownloadProgressCallback,
RemotePackage,
LocalPackage,
} from "react-native-code-push";
import DeviceInfo from "react-native-device-info";
import { FindNewUpdate } from 'react-native-uat-update/view/findNewUpdate';
import { HeaderDetault } from "react-native-uat-update/view/headerDefault";
import { defaultContainerStyle, defaultModelStyle, defaultHeaderContainer } from 'react-native-uat-update/index.default';
import { DownloadView } from 'react-native-uat-update/view/downloadView';
type Props = {
style : ViewStyle,
appleid : String,//appleID,必须
supportCodePush : Boolean,//是否支持热更新
codepushKey : String,//热更新检测地址
codepushDebugKey: String,//热更新测试版本key
forceUpdate : Boolean,//是否强制更新
wrapper : (state:State)=>View,//容器
themeColor : String,//主题颜色
onCheckingAppStore : ()=>{},//检查appstore
onCheckedAppStore : (update:UpdateProp)=>{},//检查到appstore版本有更新
onAppStoreNoUpdate : ()=>{},//appStore没有更新
onAppStoreException: (e:String)=>{},//检查appstore版本异常
onCheckingCodepush : ()=>{},//开始检查codepush
onCodepushHasUpdate: (update:UpdateProp)=>{},//codepush有更新
onCodepushNoUpdate : ()=>{},//codepush暂无更新
onCPDownloading : (progress:DownloadProgress)=>{},//正在下载bundle
onCPInstalling : ()=>{},//正在安装
onCPInstallComplete: ()=>{},//安装完成
onRestart : ()=>{},//重启
onClose : ()=>{},
};
export type State = {
data : UpdateProp,
show : Boolean,
status : UpdateStatus,
downloadProgress:DownloadProgress,
remtePackage:?RemotePackage,
};
const Header = (state:State)=>{
return (
<View style={{backgroundColor:'red',width:300,height:100}}>
<Text>{state.data.title}</Text>
</View>
)
}
export class Version extends Component <Props,State>{
static defaultProps :Props = {
style:defaultContainerStyle,
supportCodePush : true,
codepushKey : '',
codepushDebugKey:'',
appleid : '1448092987',
forceUpdate:true,
themeColor : 'rgb(230,180,120)',
wrapper : (state:State)=><HeaderDetault data={state}/>,
onClose : ()=>{},//当强制更新forceUpdate为true时,不再回调
onCheckingAppStore : ()=>{},//检查appstore
onCheckedAppStore : (update:UpdateProp)=>{},//检查到appstore版本有更新
onAppStoreNoUpdate : ()=>{},//appStore没有更新
onAppStoreException: (e:String)=>{},//检查appstore版本异常
onCheckingCodepush : ()=>{},//开始检查codepush
onCodepushHasUpdate: (update:UpdateProp)=>{},//codepush有更新
onCodepushNoUpdate : ()=>{},//codepush暂无更新
onCPDownloading : (progress:DownloadProgress)=>{},//正在下载bundle
onCPInstalling : ()=>{},//正在安装
onCPInstallComplete: ()=>{},//安装完成
onRestart : ()=>{},//重启
}
state:State={
data : {
title:'检查更新',
},
show : false,
status: UpdateStatus.NoCheck,
remotePackage:null
}
onChange = (state:AppStateStatus)=>{
switch(state){
case 'active':this.setup();break;
default :
}
}
addListener = ()=>{
AppState.addEventListener('change',this.onChange);
}
removeListener = ()=>{
AppState.removeEventListener('change',this.onChange);
}
//修改当前状态
onChangeStatus = (status:UpdateStatus)=>{
console.log(`当前状态:${status}`);
this.setState({status : status,
data:{
...this.state.data,
title:status
}},
()=>{
if(status == UpdateStatus.AppStoreHasUpdate || status == UpdateStatus.CodepushHasUpdate){
this.onChangeModal(true)
}
});
}
//显示模态窗
onChangeModal = (show:?Boolean)=>{
if(this.state.show == show){
return ;
}
this.setState({show:show==undefined||show==null?!this.state.show:show});
}
componentDidMount(){
this.setup();
this.addListener()
}
componentWillUnmount(){
this.removeListener();
}
/**检查appstore版本 */
checkAppStore= async():?UpdateProp=>{
let appleid:String = this.props.appleid;
let url:String = `http://itunes.apple.com/lookup?id=${appleid}`;
this.onChangeStatus(UpdateStatus.CheckingAppStore);
let appInfo : AppStoreResponse = await fetch(url).then(res=>res.json());
console.log(`appstore请求结果:${JSON.stringify(appInfo)}`);
try {
if(appInfo && appInfo.resultCount==1){
let update : UpdateProp = new UpdateProp(appInfo.results[0]);
return update
}else{
return null;
}
} catch (error) {
console.log(`请求appstore版本异常:${JSON.stringify(error)}`)
return null;
}
}
/**检查codepush版本 */
checkCodepush= async():Promise<?RemotePackage>=>{
console.log(`__dev__:${__DEV__}`)
const key : String = __DEV__ === true ? this.props.codepushDebugKey : this.props.codepushKey
let remotePackage : ?RemotePackage = await CodePush.checkForUpdate(key);
return remotePackage ;
}
onDownloadProgress = (p:DownloadProgress)=>{
this.setState({downloadProgress:p})
}
onError= (err)=>{
console.log(err);
}
/**下载codepush */
getRemoteUpdate =async(remotePackage:RemotePackage)=>{
// let localPackage : LocalPackage = await remotePackage.download(this.onDownloadProgress).catch(this.onError);
this.onChangeStatus(UpdateStatus.CodepushIsDownloading);
let localPackage : LocalPackage=await remotePackage.download(this.onDownloadProgress).catch(this.onError);
if(localPackage){
this.onChangeStatus(UpdateStatus.CodepushInInstalling)
this.installCodepush(localPackage);
return ;
// this.installCodepush(localPackage);
// if(this.installCodepush){
// this.onChangeStatus(UpdateStatus.CodepushInstalled)
// this.onChangeStatus(UpdateStatus.CodepushRestart);
// this.restart();
// }
}
// return localPackage;
}
/**安装codepush */
installCodepush = (localPackage:LocalPackage)=>{
localPackage.install(CodePush.InstallMode.IMMEDIATE).then(res=>{
}).catch(res=>{
console.error(res);
}).finally(()=>{
this.restart()
});
}
/**重启 */
restart = ()=>{
this.onChangeModal(false)
CodePush.restartApp();
}
/**启动更新 */
setup = async () =>{
CodePush.notifyAppReady();
const {appleid} = this.props;
if(!appleid || appleid==''){
console.warn(`appleid属性不能为空`);
return ;
}
this.onChangeStatus(UpdateStatus.CheckingAppStore);
let appstoreUpdate : UpdateProp = await this.checkAppStore();
let localVersion:String = DeviceInfo.getVersion();
// localVersion = '3.0.0';
if(appstoreUpdate && Version.hasNew(localVersion,appstoreUpdate.version)){
this.setState({data : appstoreUpdate})
this.onChangeStatus(UpdateStatus.AppStoreHasUpdate);
}else{
this.onChangeStatus(UpdateStatus.AppStoreNoUpdate);
this.onChangeStatus(UpdateStatus.CheckingCodepush);
//没有appstore版本
let remotePackage : RemotePackage =await this.checkCodepush();
if(remotePackage && remotePackage.downloadUrl!=''){
const codePushInfo : AppInfo = {
fileSizeBytes : remotePackage.packageSize,
releaseNotes:remotePackage.description,
version : `${remotePackage.appVersion}.${remotePackage.label.replace('v','')}`
}
this.setState({remtePackage:remotePackage,data:new UpdateProp(codePushInfo)},()=>{
this.onChangeStatus(UpdateStatus.CodepushHasUpdate);
});
}else{
this.onChangeStatus(UpdateStatus.CodepushNoUpdate);
}
}
}
/**新旧版本号比较
* @param {本地版本} localVersion
* @param {远程版本} remoteVersion
* @returns {true:远程有新版本 false:没有新版本}
*/
static hasNew(localVersion:String,remoteVersion:String):Boolean{
try {
let local : Number = localVersion.split('.').map(item=>parseInt(item));
let remote: Number = remoteVersion.split('.').map(item=>parseInt(item));
if(local && remote && remote.length==3 && local.length==3){
let local_major : Number = local[0];
let local_minor : Number = local[1];
let local_patch : Number = local[2];
let remote_major: Number = remote[0];
let remote_minor: Number = remote[1];
let remote_patch: Number = remote[2];
if(remote_major>local_major){return true}
if(remote_major==local_major && remote_minor>local_minor){return true}
if(remote_major==local_major && remote_minor==local_minor && remote_patch>local_patch){return true}
return false;
}else{
return true
}
} catch (error) {
return true;
}
}
/**点击去更新
* 1.去appstore更新
* 2.直接开始下载热更新
*/
onClickUpdate = ()=>{
let currentStatus : UpdateStatus = this.state.status;
if(currentStatus == UpdateStatus.AppStoreHasUpdate){
Linking.openURL(`itms-apps://itunes.apple.com/app/${this.props.appleid}`)
}else if(currentStatus == UpdateStatus.CodepushHasUpdate){
this.getRemoteUpdate(this.state.remtePackage);
}else{
alert('nono')
}
}
bodyRender = (state:State,props:Props)=>{
switch(state.status){
case UpdateStatus.CodepushHasUpdate:
case UpdateStatus.AppStoreHasUpdate:return <FindNewUpdate data={state.data} themeColor={props.themeColor} onClick={this.onClickUpdate} />
case UpdateStatus.CodepushIsDownloading:return <DownloadView progress={state.downloadProgress} prgressColor="blue"/>
default:return this.renderNormal(this.state);
}
}
/**检查更新 */
renderChecking = (state:State)=>{
return this.renderNormal(state);
}
/**正在下载 */
/**正在安装 */
renderInstalling = (state:State)=>{
return this.renderNormal(state)
}
/**渲染一般视图:标题+子标题 */
renderNormal = (state:State)=>{
return (
<View>
<Text>{state.data.title}</Text>
</View>
)
}
onMaskClick = ()=>{
// alert('alert');
if(this.state.status != UpdateStatus.AppStoreHasUpdate && this.state.status!=UpdateStatus.CodepushHasUpdate){
if(!this.props.forceUpdate){
this.onChangeModal(false);
this.props.onClose && this.props.onClose();
}
}else{
this.onChangeModal(false);
}
}
render(){
const {style,wrapper} = this.props;
const {show} = this.state;
return (
<Modal
visible={show}
transparent={true}
onRequestClose={this.onChangeModal}
animated={true}
animationType="fade"
presentationStyle="overFullScreen"
>
<TouchableOpacity
activeOpacity={1}
style={[styles.container,defaultModelStyle]}
onPress={this.onMaskClick}>
<View
onStartShouldSetResponder={()=>true}
// onMoveShouldSetResponder={()=>false}
// onMoveShouldSetResponderCapture={()=>false}
style={[defaultContainerStyle]}
>
<View style={defaultHeaderContainer}>
{wrapper(this.state)}
</View>
<View
style={styles.content}>
{this.bodyRender(this.state,this.props)}
</View>
</View>
</TouchableOpacity>
</Modal>
)
}
}
const styles = StyleSheet.create({
container:{
flex:1,
alignItems:'center',
justifyContent:'center',
backgroundColor: 'rgba(0,0,0,0.5)',
},
content:{
flex:1,
}
})