import { AcquisitionManager as Sdk } from "code-push/script/acquisition-sdk";
import { Alert } from "./AlertAdapter";
import requestFetchAdapter from "./request-fetch-adapter";
import { AppState, Platform, DeviceEventEmitter } from "react-native";
import log from "./logging";
import hoistStatics from 'hoist-non-react-statics';
import NativeCodePush from './src/NativeCodePush';
const PackageMixins = require("./package-mixins")(NativeCodePush);
async function checkForUpdate(deploymentKey = null, handleBinaryVersionMismatchCallback = null) {
console.log('checkForUpdate-entry');
const nativeConfig = await getConfiguration();
const config = deploymentKey ? { ...nativeConfig, ...{ deploymentKey } } : nativeConfig;
console.log('checkForUpdate-config='+JSON.stringify(config));
const sdk = getPromisifiedSdk(requestFetchAdapter, config);
const localPackage = await module.exports.getCurrentPackage();
console.log('checkForUpdate-localPackage='+JSON.stringify(localPackage));
let queryPackage;
if (localPackage) {
queryPackage = localPackage;
} else {
queryPackage = { appVersion: config.appVersion };
if (Platform.OS === "ios" && config.packageHash) {
queryPackage.packageHash = config.packageHash;
}
}
console.log('checkForUpdate-queryPackage='+JSON.stringify(queryPackage));
const update = await sdk.queryUpdateWithCurrentPackage(queryPackage);
console.log('checkForUpdate-update='+JSON.stringify(update));
if (!update || update.updateAppVersion ||
localPackage && (update.packageHash === localPackage.packageHash) ||
(!localPackage || localPackage._isDebugOnly) && config.packageHash === update.packageHash) {
console.log('checkForUpdate-进!update判断');
if (update && update.updateAppVersion) {
log("An update is available but it is not targeting the binary version of your app.");
if (handleBinaryVersionMismatchCallback && typeof handleBinaryVersionMismatchCallback === "function") {
handleBinaryVersionMismatchCallback(update)
}
}
return null;
} else {
const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) };
remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash);
remotePackage.deploymentKey = deploymentKey || nativeConfig.deploymentKey;
return remotePackage;
}
}
const getConfiguration = (() => {
let config;
return async function getConfiguration() {
if (config) {
return config;
} else if (testConfig) {
return testConfig;
} else {
config = await NativeCodePush.getConfiguration();
return config;
}
}
})();
async function getCurrentPackage() {
return await getUpdateMetadata(CodePush.UpdateState.LATEST);
}
async function getUpdateMetadata(updateState) {
let updateMetadata = await NativeCodePush.getUpdateMetadata(updateState || CodePush.UpdateState.RUNNING);
if (updateMetadata) {
updateMetadata = { ...PackageMixins.local, ...updateMetadata };
updateMetadata.failedInstall = await NativeCodePush.isFailedUpdate(updateMetadata.packageHash);
updateMetadata.isFirstRun = await NativeCodePush.isFirstRun(updateMetadata.packageHash);
}
return updateMetadata;
}
function getPromisifiedSdk(requestFetchAdapter, config) {
const sdk = new module.exports.AcquisitionSdk(requestFetchAdapter, config);
sdk.queryUpdateWithCurrentPackage = (queryPackage) => {
return new Promise((resolve, reject) => {
module.exports.AcquisitionSdk.prototype.queryUpdateWithCurrentPackage.call(sdk, queryPackage, (err, update) => {
if (err) {
reject(err);
} else {
resolve(update);
}
});
});
};
sdk.reportStatusDeploy = (deployedPackage, status, previousLabelOrAppVersion, previousDeploymentKey) => {
return new Promise((resolve, reject) => {
module.exports.AcquisitionSdk.prototype.reportStatusDeploy.call(sdk, deployedPackage, status, previousLabelOrAppVersion, previousDeploymentKey, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
sdk.reportStatusDownload = (downloadedPackage) => {
return new Promise((resolve, reject) => {
module.exports.AcquisitionSdk.prototype.reportStatusDownload.call(sdk, downloadedPackage, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
return sdk;
}
const notifyApplicationReady = (() => {
let notifyApplicationReadyPromise;
return () => {
if (!notifyApplicationReadyPromise) {
notifyApplicationReadyPromise = notifyApplicationReadyInternal();
}
return notifyApplicationReadyPromise;
};
})();
async function notifyApplicationReadyInternal() {
await NativeCodePush.notifyApplicationReady();
const statusReport = await NativeCodePush.getNewStatusReport();
statusReport && tryReportStatus(statusReport);
return statusReport;
}
async function tryReportStatus(statusReport, retryOnAppResume) {
const config = await getConfiguration();
const previousLabelOrAppVersion = statusReport.previousLabelOrAppVersion;
const previousDeploymentKey = statusReport.previousDeploymentKey || config.deploymentKey;
try {
if (statusReport.appVersion) {
log(`Reporting binary update (${statusReport.appVersion})`);
if (!config.deploymentKey) {
throw new Error("Deployment key is missed");
}
const sdk = getPromisifiedSdk(requestFetchAdapter, config);
await sdk.reportStatusDeploy( null, null, previousLabelOrAppVersion, previousDeploymentKey);
} else {
const label = statusReport.package.label;
if (statusReport.status === "DeploymentSucceeded") {
log(`Reporting CodePush update success (${label})`);
} else {
log(`Reporting CodePush update rollback (${label})`);
await NativeCodePush.setLatestRollbackInfo(statusReport.package.packageHash);
}
config.deploymentKey = statusReport.package.deploymentKey;
const sdk = getPromisifiedSdk(requestFetchAdapter, config);
await sdk.reportStatusDeploy(statusReport.package, statusReport.status, previousLabelOrAppVersion, previousDeploymentKey);
}
NativeCodePush.recordStatusReported(statusReport);
retryOnAppResume && retryOnAppResume.remove();
} catch (e) {
log(`Report status failed: ${JSON.stringify(statusReport)}`);
NativeCodePush.saveStatusReportForRetry(statusReport);
if (!retryOnAppResume) {
const resumeListener = AppState.addEventListener("change", async (newState) => {
if (newState !== "active") return;
const refreshedStatusReport = await NativeCodePush.getNewStatusReport();
if (refreshedStatusReport) {
tryReportStatus(refreshedStatusReport, resumeListener);
} else {
resumeListener && resumeListener.remove();
}
});
}
}
}
async function shouldUpdateBeIgnored(remotePackage, syncOptions) {
let { rollbackRetryOptions } = syncOptions;
const isFailedPackage = remotePackage && remotePackage.failedInstall;
if (!isFailedPackage || !syncOptions.ignoreFailedUpdates) {
return false;
}
if (!rollbackRetryOptions) {
return true;
}
if (typeof rollbackRetryOptions !== "object") {
rollbackRetryOptions = CodePush.DEFAULT_ROLLBACK_RETRY_OPTIONS;
} else {
rollbackRetryOptions = { ...CodePush.DEFAULT_ROLLBACK_RETRY_OPTIONS, ...rollbackRetryOptions };
}
if (!validateRollbackRetryOptions(rollbackRetryOptions)) {
return true;
}
const latestRollbackInfo = await NativeCodePush.getLatestRollbackInfo();
if (!validateLatestRollbackInfo(latestRollbackInfo, remotePackage.packageHash)) {
log("The latest rollback info is not valid.");
return true;
}
const { delayInHours, maxRetryAttempts } = rollbackRetryOptions;
const hoursSinceLatestRollback = (Date.now() - latestRollbackInfo.time) / (1000 * 60 * 60);
if (hoursSinceLatestRollback >= delayInHours && maxRetryAttempts >= latestRollbackInfo.count) {
log("Previous rollback should be ignored due to rollback retry options.");
return false;
}
return true;
}
function validateLatestRollbackInfo(latestRollbackInfo, packageHash) {
return latestRollbackInfo &&
latestRollbackInfo.time &&
latestRollbackInfo.count &&
latestRollbackInfo.packageHash &&
latestRollbackInfo.packageHash === packageHash;
}
function validateRollbackRetryOptions(rollbackRetryOptions) {
if (typeof rollbackRetryOptions.delayInHours !== "number") {
log("The 'delayInHours' rollback retry parameter must be a number.");
return false;
}
if (typeof rollbackRetryOptions.maxRetryAttempts !== "number") {
log("The 'maxRetryAttempts' rollback retry parameter must be a number.");
return false;
}
if (rollbackRetryOptions.maxRetryAttempts < 1) {
log("The 'maxRetryAttempts' rollback retry parameter cannot be less then 1.");
return false;
}
return true;
}
var testConfig;
function setUpTestDependencies(testSdk, providedTestConfig, testNativeBridge) {
if (testSdk) module.exports.AcquisitionSdk = testSdk;
if (providedTestConfig) testConfig = providedTestConfig;
if (testNativeBridge) NativeCodePush = testNativeBridge;
}
async function restartApp(onlyIfUpdateIsPending = false) {
NativeCodePush.restartApp(onlyIfUpdateIsPending);
}
const sync = (() => {
let syncInProgress = false;
const setSyncCompleted = () => { syncInProgress = false; };
return (options = {}, syncStatusChangeCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback) => {
let syncStatusCallbackWithTryCatch, downloadProgressCallbackWithTryCatch;
if (typeof syncStatusChangeCallback === "function") {
syncStatusCallbackWithTryCatch = (...args) => {
try {
syncStatusChangeCallback(...args);
} catch (error) {
log(`An error has occurred : ${error.stack}`);
}
}
}
if (typeof downloadProgressCallback === "function") {
DeviceEventEmitter.addListener('CodePushDownloadProgress', (date) => {
downloadProgressCallback(date)
})
downloadProgressCallbackWithTryCatch = (...args) => {
DeviceEventEmitter.addListener('CodePushDownloadProgress', (date) => {
})
}
}
if (syncInProgress) {
typeof syncStatusCallbackWithTryCatch === "function"
? syncStatusCallbackWithTryCatch(CodePush.SyncStatus.SYNC_IN_PROGRESS)
: log("Sync already in progress.");
return Promise.resolve(CodePush.SyncStatus.SYNC_IN_PROGRESS);
}
syncInProgress = true;
const syncPromise = syncInternal(options, syncStatusCallbackWithTryCatch, downloadProgressCallbackWithTryCatch, handleBinaryVersionMismatchCallback);
syncPromise
.then(setSyncCompleted)
.catch(setSyncCompleted);
return syncPromise;
};
})();
async function syncInternal(options = {}, syncStatusChangeCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback) {
let resolvedInstallMode;
const syncOptions = {
deploymentKey: null,
ignoreFailedUpdates: true,
rollbackRetryOptions: null,
installMode: CodePush.InstallMode.ON_NEXT_RESTART,
mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE,
minimumBackgroundDuration: 0,
updateDialog: null,
...options
};
syncStatusChangeCallback = typeof syncStatusChangeCallback === "function"
? syncStatusChangeCallback
: (syncStatus) => {
switch (syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
log("Checking for update.");
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
log("Awaiting user action.");
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
log("Downloading package.");
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
log("Installing update.");
break;
case CodePush.SyncStatus.UP_TO_DATE:
log("App is up to date.");
break;
case CodePush.SyncStatus.UPDATE_IGNORED:
log("User cancelled the update.");
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
if (resolvedInstallMode == CodePush.InstallMode.ON_NEXT_RESTART) {
log("Update is installed and will be run on the next app restart.");
} else if (resolvedInstallMode == CodePush.InstallMode.ON_NEXT_RESUME) {
if (syncOptions.minimumBackgroundDuration > 0) {
log(`Update is installed and will be run after the app has been in the background for at least ${syncOptions.minimumBackgroundDuration} seconds.`);
} else {
log("Update is installed and will be run when the app next resumes.");
}
}
break;
case CodePush.SyncStatus.UNKNOWN_ERROR:
log("An unknown error occurred.");
break;
}
};
try {
syncStatusChangeCallback(CodePush.SyncStatus.CHECKING_FOR_UPDATE);
const remotePackage = await checkForUpdate(syncOptions.deploymentKey, handleBinaryVersionMismatchCallback);
console.log('checkForUpdate-remotePackage='+JSON.stringify(remotePackage) );
const doDownloadAndInstall = async () => {
syncStatusChangeCallback(CodePush.SyncStatus.DOWNLOADING_PACKAGE);
const localPackage = await remotePackage.download(downloadProgressCallback);
resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode;
syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE);
await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => {
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
});
return CodePush.SyncStatus.UPDATE_INSTALLED;
};
const updateShouldBeIgnored = await shouldUpdateBeIgnored(remotePackage, syncOptions);
if (!remotePackage || updateShouldBeIgnored) {
console.log('checkForUpdate-!remotePackage || updateShouldBeIgnored= 进最新版本的判断');
if (updateShouldBeIgnored) {
log("An update is available, but it is being ignored due to having been previously rolled back.");
}
const currentPackage = await CodePush.getCurrentPackage();
if (currentPackage && currentPackage.isPending) {
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
return CodePush.SyncStatus.UPDATE_INSTALLED;
} else {
syncStatusChangeCallback(CodePush.SyncStatus.UP_TO_DATE);
return CodePush.SyncStatus.UP_TO_DATE;
}
}else if (syncOptions.updateDialog) {
console.log('checkForUpdate-syncOptions.updateDialog= 去下载更新');
if (typeof syncOptions.updateDialog !== "object") {
syncOptions.updateDialog = CodePush.DEFAULT_UPDATE_DIALOG;
} else {
syncOptions.updateDialog = { ...CodePush.DEFAULT_UPDATE_DIALOG, ...syncOptions.updateDialog };
}
return await new Promise((resolve, reject) => {
let message = null;
let installButtonText = null;
const dialogButtons = [];
if (remotePackage.isMandatory) {
message = syncOptions.updateDialog.mandatoryUpdateMessage;
installButtonText = syncOptions.updateDialog.mandatoryContinueButtonLabel;
} else {
message = syncOptions.updateDialog.optionalUpdateMessage;
installButtonText = syncOptions.updateDialog.optionalInstallButtonLabel;
dialogButtons.push({
text: syncOptions.updateDialog.optionalIgnoreButtonLabel,
onPress: () => {
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_IGNORED);
resolve(CodePush.SyncStatus.UPDATE_IGNORED);
}
});
}
dialogButtons.push({
text: installButtonText,
onPress: () => {
doDownloadAndInstall()
.then(resolve, reject);
}
})
if (syncOptions.updateDialog.appendReleaseDescription && remotePackage.description) {
message += `${syncOptions.updateDialog.descriptionPrefix} ${remotePackage.description}`;
}
syncStatusChangeCallback(CodePush.SyncStatus.AWAITING_USER_ACTION);
Alert.alert(syncOptions.updateDialog.title, message, dialogButtons);
});
} else {
return await doDownloadAndInstall();
}
} catch (error) {
syncStatusChangeCallback(CodePush.SyncStatus.UNKNOWN_ERROR);
log(error.message);
throw error;
}
};
let CodePush;
function codePushify(options = {}) {
let React;
let ReactNative = require("react-native");
try { React = require("react"); } catch (e) { }
if (!React) {
try { React = ReactNative.React; } catch (e) { }
if (!React) {
throw new Error("Unable to find the 'React' module.");
}
}
if (!React.Component) {
throw new Error(
`Unable to find the "Component" class, please either:
1. Upgrade to a newer version of React Native that supports it, or
2. Call the codePush.sync API in your component instead of using the @codePush decorator`
);
}
const decorator = (RootComponent) => {
class CodePushComponent extends React.Component {
constructor(props) {
super(props);
this.rootComponentRef = React.createRef();
}
componentDidMount() {
if (options.checkFrequency === CodePush.CheckFrequency.MANUAL) {
CodePush.notifyAppReady();
} else {
const rootComponentInstance = this.rootComponentRef.current;
let syncStatusCallback;
if (rootComponentInstance && rootComponentInstance.codePushStatusDidChange) {
syncStatusCallback = rootComponentInstance.codePushStatusDidChange.bind(rootComponentInstance);
}
let downloadProgressCallback;
if (rootComponentInstance && rootComponentInstance.codePushDownloadDidProgress) {
downloadProgressCallback = rootComponentInstance.codePushDownloadDidProgress.bind(rootComponentInstance);
}
let handleBinaryVersionMismatchCallback;
if (rootComponentInstance && rootComponentInstance.codePushOnBinaryVersionMismatch) {
handleBinaryVersionMismatchCallback = rootComponentInstance.codePushOnBinaryVersionMismatch.bind(rootComponentInstance);
}
CodePush.sync(options, syncStatusCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback);
if (options.checkFrequency === CodePush.CheckFrequency.ON_APP_RESUME) {
ReactNative.AppState.addEventListener("change", (newState) => {
if (newState === "active") {
CodePush.sync(options, syncStatusCallback, downloadProgressCallback);
}
});
}
}
}
render() {
const props = {...this.props};
if (RootComponent.prototype && RootComponent.prototype.render) {
props.ref = this.rootComponentRef;
}
return <RootComponent {...props} />
}
}
return hoistStatics(CodePushComponent, RootComponent);
}
if (typeof options === "function") {
return decorator(options);
} else {
return decorator;
}
}
if (NativeCodePush) {
CodePush = codePushify;
Object.assign(CodePush, {
AcquisitionSdk: Sdk,
checkForUpdate,
getConfiguration,
getCurrentPackage,
getUpdateMetadata,
log,
notifyAppReady: notifyApplicationReady,
notifyApplicationReady,
restartApp,
setUpTestDependencies,
sync,
disallowRestart: NativeCodePush.disallow,
allowRestart: NativeCodePush.allow,
clearUpdates: NativeCodePush.clearUpdates,
InstallMode: {
IMMEDIATE: 0,
ON_NEXT_RESTART: 1,
ON_NEXT_RESUME: 2,
ON_NEXT_SUSPEND: 3
},
SyncStatus: {
UP_TO_DATE: 0,
UPDATE_INSTALLED: 1,
UPDATE_IGNORED: 2,
UNKNOWN_ERROR: 3,
SYNC_IN_PROGRESS: 4,
CHECKING_FOR_UPDATE: 5,
AWAITING_USER_ACTION: 6,
DOWNLOADING_PACKAGE: 7,
INSTALLING_UPDATE: 8
},
CheckFrequency: {
ON_APP_START: 0,
ON_APP_RESUME: 1,
MANUAL: 2
},
UpdateState: {
RUNNING: 0,
PENDING: 1,
LATEST: 2
},
DeploymentStatus: {
FAILED: "DeploymentFailed",
SUCCEEDED: "DeploymentSucceeded",
},
DEFAULT_UPDATE_DIALOG: {
appendReleaseDescription: false,
descriptionPrefix: " Description: ",
mandatoryContinueButtonLabel: "Continue",
mandatoryUpdateMessage: "An update is available that must be installed.",
optionalIgnoreButtonLabel: "Ignore",
optionalInstallButtonLabel: "Install",
optionalUpdateMessage: "An update is available. Would you like to install it?",
title: "Update available"
},
DEFAULT_ROLLBACK_RETRY_OPTIONS: {
delayInHours: 24,
maxRetryAttempts: 1
}
});
} else {
log("The CodePush module doesn't appear to be properly installed. Please double-check that everything is setup correctly.");
}
module.exports = CodePush;