2021-10-09 13:12:09 +08:00
|
|
|
|
import {
|
|
|
|
|
tryBackupEndpoints,
|
|
|
|
|
getCheckUrl,
|
|
|
|
|
setCustomEndpoints,
|
2021-10-21 09:29:37 +08:00
|
|
|
|
getReportUrl,
|
2021-10-09 13:12:09 +08:00
|
|
|
|
} from './endpoint';
|
|
|
|
|
import {
|
|
|
|
|
NativeEventEmitter,
|
|
|
|
|
NativeModules,
|
|
|
|
|
Platform,
|
|
|
|
|
PermissionsAndroid,
|
|
|
|
|
} from 'react-native';
|
|
|
|
|
export { setCustomEndpoints };
|
|
|
|
|
const {
|
|
|
|
|
version: v,
|
|
|
|
|
} = require('react-native/Libraries/Core/ReactNativeVersion');
|
|
|
|
|
const RNVersion = `${v.major}.${v.minor}.${v.patch}`;
|
2023-02-19 18:20:21 +08:00
|
|
|
|
const isTurboModuleEnabled = global.__turboModuleProxy != null;
|
2021-10-09 13:12:09 +08:00
|
|
|
|
|
2023-02-20 13:03:12 +08:00
|
|
|
|
export const PushyModule = isTurboModuleEnabled
|
|
|
|
|
? require('./NativeUpdate').default
|
|
|
|
|
: NativeModules.Pushy;
|
|
|
|
|
|
|
|
|
|
if (!PushyModule) {
|
2021-10-09 13:12:09 +08:00
|
|
|
|
throw new Error('react-native-update模块无法加载,请对照安装文档检查配置。');
|
|
|
|
|
}
|
2023-02-20 13:03:12 +08:00
|
|
|
|
const PushyConstants = isTurboModuleEnabled
|
|
|
|
|
? PushyModule.getConstants()
|
|
|
|
|
: PushyModule;
|
2021-10-09 13:12:09 +08:00
|
|
|
|
|
2023-02-19 21:14:03 +08:00
|
|
|
|
export const downloadRootDir = PushyConstants.downloadRootDir;
|
|
|
|
|
export const packageVersion = PushyConstants.packageVersion;
|
|
|
|
|
export const currentVersion = PushyConstants.currentVersion;
|
|
|
|
|
export const isFirstTime = PushyConstants.isFirstTime;
|
|
|
|
|
const rolledBackVersion = PushyConstants.rolledBackVersion;
|
2021-10-21 09:29:37 +08:00
|
|
|
|
export const isRolledBack = typeof rolledBackVersion === 'string';
|
|
|
|
|
|
2023-02-19 21:14:03 +08:00
|
|
|
|
export const buildTime = PushyConstants.buildTime;
|
|
|
|
|
let blockUpdate = PushyConstants.blockUpdate;
|
|
|
|
|
let uuid = PushyConstants.uuid;
|
2021-10-09 13:12:09 +08:00
|
|
|
|
|
2023-02-19 21:14:03 +08:00
|
|
|
|
if (Platform.OS === 'android' && !PushyConstants.isUsingBundleUrl) {
|
2021-10-09 13:12:09 +08:00
|
|
|
|
throw new Error(
|
|
|
|
|
'react-native-update模块无法加载,请对照文档检查Bundle URL的配置',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setLocalHashInfo(hash, info) {
|
2023-02-20 13:03:12 +08:00
|
|
|
|
PushyModule.setLocalHashInfo(hash, JSON.stringify(info));
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getLocalHashInfo(hash) {
|
2023-02-20 13:03:12 +08:00
|
|
|
|
return JSON.parse(await PushyModule.getLocalHashInfo(hash));
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getCurrentVersionInfo() {
|
2021-10-21 09:29:37 +08:00
|
|
|
|
return currentVersion ? (await getLocalHashInfo(currentVersion)) || {} : {};
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-02-20 13:03:12 +08:00
|
|
|
|
const eventEmitter = new NativeEventEmitter(PushyModule);
|
2021-10-09 13:12:09 +08:00
|
|
|
|
|
|
|
|
|
if (!uuid) {
|
|
|
|
|
uuid = require('nanoid/non-secure').nanoid();
|
2023-02-20 13:03:12 +08:00
|
|
|
|
PushyModule.setUuid(uuid);
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function logger(text) {
|
|
|
|
|
console.log(`Pushy: ${text}`);
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-21 09:29:37 +08:00
|
|
|
|
function report(hash, type) {
|
|
|
|
|
logger(type);
|
|
|
|
|
fetch(getReportUrl(), {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
Accept: 'application/json',
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
hash,
|
|
|
|
|
type,
|
|
|
|
|
cInfo,
|
|
|
|
|
packageVersion,
|
|
|
|
|
buildTime,
|
|
|
|
|
}),
|
|
|
|
|
}).catch((_e) => {});
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-09 13:12:09 +08:00
|
|
|
|
logger('uuid: ' + uuid);
|
|
|
|
|
|
2021-10-21 09:29:37 +08:00
|
|
|
|
if (isRolledBack) {
|
|
|
|
|
report(rolledBackVersion, 'rollback');
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const cInfo = {
|
|
|
|
|
pushy: require('../package.json').version,
|
|
|
|
|
rn: RNVersion,
|
|
|
|
|
os: Platform.OS + ' ' + Platform.Version,
|
|
|
|
|
uuid,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function assertRelease() {
|
|
|
|
|
if (__DEV__) {
|
2022-04-27 21:52:38 +08:00
|
|
|
|
throw new Error('react-native-update 只能在 RELEASE 版本中运行.');
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let checkingThrottling = false;
|
|
|
|
|
export async function checkUpdate(APPKEY, isRetry) {
|
|
|
|
|
assertRelease();
|
|
|
|
|
if (checkingThrottling) {
|
|
|
|
|
logger('repeated checking, ignored');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
checkingThrottling = true;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
checkingThrottling = false;
|
|
|
|
|
}, 3000);
|
|
|
|
|
if (blockUpdate && blockUpdate.until > Date.now() / 1000) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`热更新已暂停,原因:${blockUpdate.reason}。请在"${new Date(
|
|
|
|
|
blockUpdate.until * 1000,
|
|
|
|
|
).toLocaleString()}"之后重试。`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (typeof APPKEY !== 'string') {
|
|
|
|
|
throw new Error('未检查到合法的APPKEY,请查看update.json文件是否正确生成');
|
|
|
|
|
}
|
|
|
|
|
logger('checking update');
|
|
|
|
|
let resp;
|
|
|
|
|
try {
|
|
|
|
|
resp = await fetch(getCheckUrl(APPKEY), {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
Accept: 'application/json',
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
packageVersion,
|
|
|
|
|
hash: currentVersion,
|
|
|
|
|
buildTime,
|
|
|
|
|
cInfo,
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (isRetry) {
|
2022-04-27 21:52:38 +08:00
|
|
|
|
throw new Error('无法连接更新服务器,请检查网络连接后重试');
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
2022-11-23 18:23:48 +08:00
|
|
|
|
await tryBackupEndpoints();
|
2021-10-09 13:12:09 +08:00
|
|
|
|
return checkUpdate(APPKEY, true);
|
|
|
|
|
}
|
|
|
|
|
const result = await resp.json();
|
|
|
|
|
checkOperation(result.op);
|
|
|
|
|
|
|
|
|
|
if (resp.status !== 200) {
|
|
|
|
|
throw new Error(result.message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function checkOperation(op) {
|
|
|
|
|
if (!Array.isArray(op)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
op.forEach((action) => {
|
|
|
|
|
if (action.type === 'block') {
|
|
|
|
|
blockUpdate = {
|
|
|
|
|
reason: action.reason,
|
|
|
|
|
until: Math.round((Date.now() + action.duration) / 1000),
|
|
|
|
|
};
|
2023-02-20 13:03:12 +08:00
|
|
|
|
PushyModule.setBlockUpdate(blockUpdate);
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let downloadingThrottling = false;
|
|
|
|
|
let downloadedHash;
|
|
|
|
|
export async function downloadUpdate(options, eventListeners) {
|
|
|
|
|
assertRelease();
|
|
|
|
|
if (!options.update) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-10-30 14:56:23 +08:00
|
|
|
|
if (rolledBackVersion === options.hash) {
|
|
|
|
|
logger(`rolledback hash ${rolledBackVersion}, ignored`);
|
2021-10-21 09:29:37 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2021-10-09 13:12:09 +08:00
|
|
|
|
if (downloadedHash === options.hash) {
|
|
|
|
|
logger(`duplicated downloaded hash ${downloadedHash}, ignored`);
|
2022-05-31 09:52:24 +08:00
|
|
|
|
return downloadedHash;
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
if (downloadingThrottling) {
|
|
|
|
|
logger('repeated downloading, ignored');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
downloadingThrottling = true;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
downloadingThrottling = false;
|
|
|
|
|
}, 3000);
|
|
|
|
|
let progressHandler;
|
|
|
|
|
if (eventListeners) {
|
|
|
|
|
if (eventListeners.onDownloadProgress) {
|
|
|
|
|
const downloadCallback = eventListeners.onDownloadProgress;
|
|
|
|
|
progressHandler = eventEmitter.addListener(
|
|
|
|
|
'RCTPushyDownloadProgress',
|
|
|
|
|
(progressData) => {
|
|
|
|
|
if (progressData.hash === options.hash) {
|
|
|
|
|
downloadCallback(progressData);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-27 21:52:38 +08:00
|
|
|
|
let succeeded = false;
|
2021-10-09 13:12:09 +08:00
|
|
|
|
if (options.diffUrl) {
|
|
|
|
|
logger('downloading diff');
|
|
|
|
|
try {
|
2023-02-20 13:03:12 +08:00
|
|
|
|
await PushyModule.downloadPatchFromPpk({
|
2021-10-09 13:12:09 +08:00
|
|
|
|
updateUrl: options.diffUrl,
|
|
|
|
|
hash: options.hash,
|
|
|
|
|
originHash: currentVersion,
|
|
|
|
|
});
|
2022-04-27 21:52:38 +08:00
|
|
|
|
succeeded = true;
|
2021-10-09 13:12:09 +08:00
|
|
|
|
} catch (e) {
|
2021-10-24 16:19:17 +08:00
|
|
|
|
logger(`diff error: ${e.message}, try pdiff`);
|
|
|
|
|
}
|
2022-04-27 21:52:38 +08:00
|
|
|
|
}
|
|
|
|
|
if (!succeeded && options.pdiffUrl) {
|
2021-10-24 16:19:17 +08:00
|
|
|
|
logger('downloading pdiff');
|
|
|
|
|
try {
|
2023-02-20 13:03:12 +08:00
|
|
|
|
await PushyModule.downloadPatchFromPackage({
|
2021-10-09 13:12:09 +08:00
|
|
|
|
updateUrl: options.pdiffUrl,
|
|
|
|
|
hash: options.hash,
|
|
|
|
|
});
|
2022-04-27 21:52:38 +08:00
|
|
|
|
succeeded = true;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
logger(`pdiff error: ${e.message}, try full patch`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!succeeded && options.updateUrl) {
|
|
|
|
|
logger('downloading full patch');
|
|
|
|
|
try {
|
2023-02-20 13:03:12 +08:00
|
|
|
|
await PushyModule.downloadFullUpdate({
|
2022-04-27 21:52:38 +08:00
|
|
|
|
updateUrl: options.updateUrl,
|
|
|
|
|
hash: options.hash,
|
|
|
|
|
});
|
|
|
|
|
succeeded = true;
|
2021-10-24 16:19:17 +08:00
|
|
|
|
} catch (e) {
|
2022-04-27 21:52:38 +08:00
|
|
|
|
logger(`full patch error: ${e.message}`);
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-27 21:52:38 +08:00
|
|
|
|
progressHandler && progressHandler.remove();
|
|
|
|
|
if (!succeeded) {
|
|
|
|
|
report(options.hash, 'error');
|
|
|
|
|
throw new Error('all update attempts failed');
|
|
|
|
|
}
|
2021-10-09 13:12:09 +08:00
|
|
|
|
setLocalHashInfo(options.hash, {
|
|
|
|
|
name: options.name,
|
|
|
|
|
description: options.description,
|
|
|
|
|
metaInfo: options.metaInfo,
|
|
|
|
|
});
|
|
|
|
|
downloadedHash = options.hash;
|
|
|
|
|
return options.hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function assertHash(hash) {
|
|
|
|
|
if (!downloadedHash) {
|
|
|
|
|
logger(`no downloaded hash`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (hash !== downloadedHash) {
|
|
|
|
|
logger(`use downloaded hash ${downloadedHash} first`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-10-29 13:02:49 +08:00
|
|
|
|
return true;
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function switchVersion(hash) {
|
|
|
|
|
assertRelease();
|
2021-10-29 13:02:49 +08:00
|
|
|
|
if (assertHash(hash)) {
|
|
|
|
|
logger('switchVersion: ' + hash);
|
2023-02-20 13:03:12 +08:00
|
|
|
|
PushyModule.reloadUpdate({ hash });
|
2021-10-29 13:02:49 +08:00
|
|
|
|
}
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function switchVersionLater(hash) {
|
|
|
|
|
assertRelease();
|
2021-10-29 13:02:49 +08:00
|
|
|
|
if (assertHash(hash)) {
|
|
|
|
|
logger('switchVersionLater: ' + hash);
|
2023-02-20 13:03:12 +08:00
|
|
|
|
PushyModule.setNeedUpdate({ hash });
|
2021-10-29 13:02:49 +08:00
|
|
|
|
}
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let marked = false;
|
|
|
|
|
export function markSuccess() {
|
|
|
|
|
assertRelease();
|
|
|
|
|
if (marked) {
|
|
|
|
|
logger('repeated markSuccess, ignored');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
marked = true;
|
2023-02-20 13:03:12 +08:00
|
|
|
|
PushyModule.markSuccess();
|
2021-10-21 09:29:37 +08:00
|
|
|
|
report(currentVersion, 'success');
|
2021-10-09 13:12:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function downloadAndInstallApk({ url, onDownloadProgress }) {
|
|
|
|
|
logger('downloadAndInstallApk');
|
|
|
|
|
if (Platform.OS === 'android' && Platform.Version <= 23) {
|
|
|
|
|
try {
|
|
|
|
|
const granted = await PermissionsAndroid.request(
|
|
|
|
|
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
|
|
|
|
|
);
|
|
|
|
|
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.warn(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let hash = Date.now().toString();
|
|
|
|
|
let progressHandler;
|
|
|
|
|
if (onDownloadProgress) {
|
|
|
|
|
progressHandler = eventEmitter.addListener(
|
|
|
|
|
'RCTPushyDownloadProgress',
|
|
|
|
|
(progressData) => {
|
|
|
|
|
if (progressData.hash === hash) {
|
|
|
|
|
onDownloadProgress(progressData);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-02-20 13:03:12 +08:00
|
|
|
|
await PushyModule.downloadAndInstallApk({
|
2021-10-09 13:12:09 +08:00
|
|
|
|
url,
|
|
|
|
|
target: 'update.apk',
|
|
|
|
|
hash,
|
|
|
|
|
});
|
|
|
|
|
progressHandler && progressHandler.remove();
|
|
|
|
|
}
|