@bazumax/capacitor-codepush
Version:
CodePush Plugin for Capacitor
494 lines • 25.4 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Directory, Filesystem } from "@capacitor/filesystem";
import { AcquisitionStatus } from "code-push/script/acquisition-sdk";
import { CodePushUtil } from "./codePushUtil";
import { FileUtil } from "./fileUtil";
import { InstallMode } from "./installMode";
import { NativeAppInfo } from "./nativeAppInfo";
import { CodePush as NativeCodePush } from "./nativeCodePushPlugin";
import { Package } from "./package";
import { Sdk } from "./sdk";
/**
* Defines a local package.
*
* !! THIS TYPE IS READ FROM NATIVE CODE AS WELL. ANY CHANGES TO THIS INTERFACE NEEDS TO BE UPDATED IN NATIVE CODE !!
*/
export class LocalPackage extends Package {
/**
* Applies this package to the application. The application will be reloaded with this package and on every application launch this package will be loaded.
* On the first run after the update, the application will wait for a codePush.notifyApplicationReady() call. Once this call is made, the install operation is considered a success.
* Otherwise, the install operation will be marked as failed, and the application is reverted to its previous version on the next run.
*
* @param installOptions Optional parameter used for customizing the installation behavior.
*/
install(installOptions) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
CodePushUtil.logMessage("Installing update");
if (!installOptions) {
installOptions = LocalPackage.getDefaultInstallOptions();
}
else {
CodePushUtil.copyUnassignedMembers(LocalPackage.getDefaultInstallOptions(), installOptions);
}
var installError = (error) => {
CodePushUtil.invokeErrorCallback(error, reject);
Sdk.reportStatusDeploy(this, AcquisitionStatus.DeploymentFailed, this.deploymentKey);
};
let unzipDir;
try {
unzipDir = yield FileUtil.cleanDataDirectory(LocalPackage.DownloadUnzipDir);
}
catch (error) {
installError(error);
return;
}
try {
yield NativeCodePush.unzip({ zipFile: this.localPath, targetDirectory: unzipDir });
}
catch (unzipError) {
installError(new Error("Could not unzip package" + CodePushUtil.getErrorMessage(unzipError)));
return;
}
try {
const newPackageLocation = LocalPackage.VersionsDir + "/" + this.packageHash;
const deploymentResult = yield LocalPackage.handleDeployment(newPackageLocation);
yield this.verifyPackage(deploymentResult);
this.localPath = deploymentResult.deployDir;
this.finishInstall(deploymentResult.deployDir, installOptions, resolve, installError);
}
catch (error) {
installError(error);
}
}
catch (e) {
installError && installError(new Error("An error occured while installing the package. " + CodePushUtil.getErrorMessage(e)));
}
}));
});
}
verifyPackage(deploymentResult) {
return new Promise((resolve, reject) => {
var deployDir = deploymentResult.deployDir;
var verificationFail = (error) => {
reject(error);
};
var verify = (isSignatureVerificationEnabled, isSignatureAppearedInBundle, publicKey, signature) => {
if (isSignatureVerificationEnabled) {
if (isSignatureAppearedInBundle) {
this.verifyHash(deployDir, this.packageHash, verificationFail, () => {
this.verifySignature(deployDir, this.packageHash, publicKey, signature, verificationFail, resolve);
});
}
else {
var errorMessage = "Error! Public key was provided but there is no JWT signature within app bundle to verify. " +
"Possible reasons, why that might happen: \n" +
"1. You've been released CodePush bundle update using version of CodePush CLI that is not support code signing.\n" +
"2. You've been released CodePush bundle update without providing --privateKeyPath option.";
reject(new Error(errorMessage));
}
}
else {
if (isSignatureAppearedInBundle) {
CodePushUtil.logMessage("Warning! JWT signature exists in codepush update but code integrity check couldn't be performed because there is no public key configured. " +
"Please ensure that public key is properly configured within your application.");
// verifyHash
this.verifyHash(deployDir, this.packageHash, verificationFail, resolve);
}
else {
if (deploymentResult.isDiffUpdate) {
// verifyHash
this.verifyHash(deployDir, this.packageHash, verificationFail, resolve);
}
else {
resolve();
}
}
}
};
if (deploymentResult.isDiffUpdate) {
CodePushUtil.logMessage("Applying diff update");
}
else {
CodePushUtil.logMessage("Applying full update");
}
var isSignatureVerificationEnabled, isSignatureAppearedInBundle;
var publicKey;
this.getPublicKey((error, publicKeyResult) => {
if (error) {
reject(new Error("Error reading public key. " + error));
return;
}
publicKey = publicKeyResult;
isSignatureVerificationEnabled = !!publicKey;
this.getSignatureFromUpdate(deploymentResult.deployDir, (error, signature) => {
if (error) {
reject(new Error("Error reading signature from update. " + error));
return;
}
isSignatureAppearedInBundle = !!signature;
verify(isSignatureVerificationEnabled, isSignatureAppearedInBundle, publicKey, signature);
});
});
});
}
getPublicKey(callback) {
var success = (publicKey) => {
callback(null, publicKey);
};
var fail = (error) => {
callback(error, null);
};
NativeCodePush.getPublicKey().then(result => success(result.value || null), fail);
}
getSignatureFromUpdate(deployDir, callback) {
return __awaiter(this, void 0, void 0, function* () {
const filePath = deployDir + "/public/.codepushrelease";
if (!(yield FileUtil.fileExists(Directory.Data, filePath))) {
// signature absents in the bundle
callback(null, null);
return;
}
try {
const signature = yield FileUtil.readFile(Directory.Data, filePath);
callback(null, signature);
}
catch (error) {
// error reading signature file from bundle
callback(error, null);
}
});
}
verifyHash(deployDir, newUpdateHash, errorCallback, successCallback) {
var packageHashSuccess = (computedHash) => {
if (computedHash !== newUpdateHash) {
errorCallback(new Error("The update contents failed the data integrity check."));
return;
}
CodePushUtil.logMessage("The update contents succeeded the data integrity check.");
successCallback();
};
var packageHashFail = (error) => {
errorCallback(new Error("Unable to compute hash for package: " + error));
};
CodePushUtil.logMessage("Verifying hash for folder path: " + deployDir);
NativeCodePush.getPackageHash({ path: deployDir }).then(result => packageHashSuccess(result.value), packageHashFail);
}
verifySignature(deployDir, newUpdateHash, publicKey, signature, errorCallback, successCallback) {
var decodeSignatureSuccess = (contentHash) => {
if (contentHash !== newUpdateHash) {
errorCallback(new Error("The update contents failed the code signing check."));
return;
}
CodePushUtil.logMessage("The update contents succeeded the code signing check.");
successCallback();
};
var decodeSignatureFail = (error) => {
errorCallback(new Error("Unable to verify signature for package: " + error));
};
CodePushUtil.logMessage("Verifying signature for folder path: " + deployDir);
NativeCodePush.decodeSignature({ publicKey, signature }).then(result => decodeSignatureSuccess(result.value), decodeSignatureFail);
}
finishInstall(deployDir, installOptions, installSuccess, installError) {
function backupPackageInformationFileIfNeeded(backupIfNeededDone) {
return __awaiter(this, void 0, void 0, function* () {
const pendingUpdate = yield NativeAppInfo.isPendingUpdate();
if (pendingUpdate) {
// Don't back up the currently installed update since it hasn't been "confirmed"
backupIfNeededDone(null, null);
}
else {
try {
yield LocalPackage.backupPackageInformationFile();
backupIfNeededDone(null, null);
}
catch (err) {
backupIfNeededDone(err, null);
}
}
});
}
LocalPackage.getCurrentOrDefaultPackage().then((oldPackage) => {
backupPackageInformationFileIfNeeded((backupError) => {
/* continue on error, current package information is missing if this is the first update */
this.writeNewPackageMetadata().then(() => {
var invokeSuccessAndInstall = () => {
CodePushUtil.logMessage("Install succeeded.");
var installModeToUse = this.isMandatory ? installOptions.mandatoryInstallMode : installOptions.installMode;
if (installModeToUse === InstallMode.IMMEDIATE) {
/* invoke success before navigating */
installSuccess && installSuccess(installModeToUse);
/* no need for callbacks, the javascript context will reload */
NativeCodePush.install({
startLocation: deployDir,
installMode: installModeToUse,
minimumBackgroundDuration: installOptions.minimumBackgroundDuration
});
}
else {
NativeCodePush.install({
startLocation: deployDir,
installMode: installModeToUse,
minimumBackgroundDuration: installOptions.minimumBackgroundDuration
}).then(() => { installSuccess && installSuccess(installModeToUse); }, () => { installError && installError(); });
}
};
var preInstallSuccess = () => {
/* package will be cleaned up after success, on the native side */
invokeSuccessAndInstall();
};
var preInstallFailure = (preInstallError) => {
CodePushUtil.logError("Preinstall failure.", preInstallError);
var error = new Error("An error has occured while installing the package. " + CodePushUtil.getErrorMessage(preInstallError));
installError && installError(error);
};
NativeCodePush.preInstall({ startLocation: deployDir }).then(preInstallSuccess, preInstallFailure);
}, (writeMetadataError) => {
installError && installError(writeMetadataError);
});
});
}, installError);
}
static handleDeployment(newPackageLocation) {
return __awaiter(this, void 0, void 0, function* () {
const manifestFile = {
directory: Directory.Data,
path: LocalPackage.DownloadUnzipDir + "/" + LocalPackage.DiffManifestFile
};
const isDiffUpdate = yield FileUtil.fileExists(manifestFile.directory, manifestFile.path);
if (!(yield FileUtil.directoryExists(Directory.Data, LocalPackage.VersionsDir))) {
// If directory not exists, create recursive folder
yield Filesystem.mkdir({
path: LocalPackage.VersionsDir,
directory: Directory.Data,
recursive: true
});
}
if (isDiffUpdate) {
yield LocalPackage.handleDiffDeployment(newPackageLocation, manifestFile);
}
else {
yield LocalPackage.handleCleanDeployment(newPackageLocation);
}
return { deployDir: newPackageLocation, isDiffUpdate };
});
}
writeNewPackageMetadata() {
return __awaiter(this, void 0, void 0, function* () {
const timestamp = yield NativeAppInfo.getApplicationBuildTime().catch(buildTimeError => {
CodePushUtil.logError("Could not get application build time. " + buildTimeError);
});
const appVersion = yield NativeAppInfo.getApplicationVersion().catch(appVersionError => {
CodePushUtil.logError("Could not get application version." + appVersionError);
});
const currentPackageMetadata = {
nativeBuildTime: timestamp,
localPath: this.localPath,
appVersion: appVersion,
deploymentKey: this.deploymentKey,
description: this.description,
isMandatory: this.isMandatory,
packageSize: this.packageSize,
label: this.label,
packageHash: this.packageHash,
isFirstRun: false,
failedInstall: false,
install: undefined
};
return new Promise((resolve, reject) => {
LocalPackage.writeCurrentPackageInformation(currentPackageMetadata, error => error ? reject(error) : resolve());
});
});
}
static handleCleanDeployment(newPackageLocation) {
return __awaiter(this, void 0, void 0, function* () {
// no diff manifest
const source = { directory: Directory.Data, path: LocalPackage.DownloadUnzipDir };
const target = { directory: Directory.Data, path: newPackageLocation };
// TODO: create destination directory if it doesn't exist
return FileUtil.copyDirectoryEntriesTo(source, target);
});
}
static copyCurrentPackage(newPackageLocation, ignoreList) {
return __awaiter(this, void 0, void 0, function* () {
const currentPackagePath = yield new Promise(resolve => {
LocalPackage.getPackage(LocalPackage.PackageInfoFile, (currentPackage) => resolve(currentPackage.localPath), () => resolve());
});
newPackageLocation = currentPackagePath ? newPackageLocation : newPackageLocation + "/public";
// https://github.com/ionic-team/capacitor/pull/2514 Directory.Application variable was removed. (TODO - for check)
const source = currentPackagePath ? { directory: Directory.Data, path: currentPackagePath } : { directory: Directory.Data, path: "public" };
const target = { directory: Directory.Data, path: newPackageLocation };
return FileUtil.copyDirectoryEntriesTo(source, target, ignoreList);
});
}
static handleDiffDeployment(newPackageLocation, diffManifest) {
return __awaiter(this, void 0, void 0, function* () {
let manifest;
try {
yield LocalPackage.copyCurrentPackage(newPackageLocation, [".codepushrelease"]);
yield LocalPackage.handleCleanDeployment(newPackageLocation);
/* delete files mentioned in the manifest */
const content = yield FileUtil.readFile(diffManifest.directory, diffManifest.path);
manifest = JSON.parse(content);
yield FileUtil.deleteEntriesFromDataDirectory(newPackageLocation, manifest.deletedFiles);
}
catch (error) {
throw new Error("Cannot perform diff-update.");
}
});
}
/**
* Writes the given local package information to the current package information file.
* @param packageInfoMetadata The object to serialize.
* @param callback In case of an error, this function will be called with the error as the fist parameter.
*/
static writeCurrentPackageInformation(packageInfoMetadata, callback) {
var content = JSON.stringify(packageInfoMetadata);
FileUtil.writeStringToDataFile(content, LocalPackage.RootDir + "/" + LocalPackage.PackageInfoFile, true, callback);
}
/**
* Backs up the current package information to the old package information file.
* This file is used for recovery in case of an update going wrong.
* @param callback In case of an error, this function will be called with the error as the fist parameter.
*/
static backupPackageInformationFile() {
return __awaiter(this, void 0, void 0, function* () {
const source = {
directory: Directory.Data,
path: LocalPackage.RootDir + "/" + LocalPackage.PackageInfoFile
};
const destination = {
directory: Directory.Data,
path: LocalPackage.RootDir + "/" + LocalPackage.OldPackageInfoFile
};
return FileUtil.copy(source, destination);
});
}
/**
* Get the previous package information.
*
* @param packageSuccess Callback invoked with the old package information.
* @param packageError Optional callback invoked in case of an error.
*/
static getOldPackage(packageSuccess, packageError) {
LocalPackage.getPackage(LocalPackage.OldPackageInfoFile, packageSuccess, packageError);
}
/**
* Reads package information from a given file.
*
* @param packageFile The package file name.
* @param packageSuccess Callback invoked with the package information.
* @param packageError Optional callback invoked in case of an error.
*/
static getPackage(packageFile, packageSuccess, packageError) {
return __awaiter(this, void 0, void 0, function* () {
var handleError = (e) => {
packageError && packageError(new Error("Cannot read package information. " + CodePushUtil.getErrorMessage(e)));
};
try {
const content = yield FileUtil.readDataFile(LocalPackage.RootDir + "/" + packageFile);
const packageInfo = JSON.parse(content);
LocalPackage.getLocalPackageFromMetadata(packageInfo).then(packageSuccess, packageError);
}
catch (e) {
handleError(e);
}
});
}
static getLocalPackageFromMetadata(metadata) {
return __awaiter(this, void 0, void 0, function* () {
if (!metadata) {
throw new Error("Invalid package metadata.");
}
const installFailed = yield NativeAppInfo.isFailedUpdate(metadata.packageHash);
const isFirstRun = yield NativeAppInfo.isFirstRun(metadata.packageHash);
const localPackage = new LocalPackage();
localPackage.appVersion = metadata.appVersion;
localPackage.deploymentKey = metadata.deploymentKey;
localPackage.description = metadata.description;
localPackage.isMandatory = metadata.isMandatory;
localPackage.failedInstall = installFailed;
localPackage.isFirstRun = isFirstRun;
localPackage.label = metadata.label;
localPackage.localPath = metadata.localPath;
localPackage.packageHash = metadata.packageHash;
localPackage.packageSize = metadata.packageSize;
return localPackage;
});
}
static getCurrentOrDefaultPackage() {
return LocalPackage.getPackageInfoOrDefault(LocalPackage.PackageInfoFile);
}
static getOldOrDefaultPackage() {
return __awaiter(this, void 0, void 0, function* () {
return LocalPackage.getPackageInfoOrDefault(LocalPackage.OldPackageInfoFile);
});
}
static getPackageInfoOrDefault(packageFile) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const packageFailure = () => __awaiter(this, void 0, void 0, function* () {
/**
* For the default package we need the app version,
* and ideally the hash of the binary contents.
*/
let appVersion;
try {
appVersion = yield NativeAppInfo.getApplicationVersion();
}
catch (appVersionError) {
CodePushUtil.logError("Could not get application version." + appVersionError);
reject(appVersionError);
return;
}
const defaultPackage = new LocalPackage();
defaultPackage.appVersion = appVersion;
try {
defaultPackage.packageHash = yield NativeAppInfo.getBinaryHash();
}
catch (binaryHashError) {
CodePushUtil.logError("Could not get binary hash." + binaryHashError);
}
resolve(defaultPackage);
});
LocalPackage.getPackage(packageFile, resolve, packageFailure);
});
});
}
static getPackageInfoOrNull(packageFile, packageSuccess, packageError) {
LocalPackage.getPackage(packageFile, packageSuccess, packageSuccess.bind(null, null));
}
/**
* Returns the default options for the CodePush install operation.
* If the options are not defined yet, the static DefaultInstallOptions member will be instantiated.
*/
static getDefaultInstallOptions() {
if (!LocalPackage.DefaultInstallOptions) {
LocalPackage.DefaultInstallOptions = {
installMode: InstallMode.ON_NEXT_RESTART,
minimumBackgroundDuration: 0,
mandatoryInstallMode: InstallMode.IMMEDIATE
};
}
return LocalPackage.DefaultInstallOptions;
}
}
LocalPackage.RootDir = "codepush";
LocalPackage.DownloadDir = LocalPackage.RootDir + "/download";
LocalPackage.DownloadUnzipDir = LocalPackage.DownloadDir + "/unzipped";
LocalPackage.DeployDir = LocalPackage.RootDir + "/deploy";
LocalPackage.VersionsDir = LocalPackage.DeployDir + "/versions";
LocalPackage.PackageUpdateFileName = "update.zip";
LocalPackage.PackageInfoFile = "currentPackage.json";
LocalPackage.OldPackageInfoFile = "oldPackage.json";
LocalPackage.DiffManifestFile = "hotcodepush.json";
//# sourceMappingURL=localPackage.js.map