rn-update-apk
Version:
Check for new APK versions and update app from React Native
253 lines (231 loc) • 7.84 kB
JavaScript
"use strict";
import { NativeModules, Platform } from "react-native";
import semverLt from 'semver/functions/lt'
const RNUpdateAPK = NativeModules.RNUpdateAPK;
let jobId = -1;
export class UpdateAPK {
constructor(options) {
this.options = options;
}
get = (url, success, error, options = {}) => {
fetch(url, options)
.then(response => {
if (!response.ok) {
let message;
if (response.statusText) {
message = `${response.url} ${response.statusText}`
}
else {
message = `${response.url} Status Code:${response.status}`
}
throw Error(message);
}
return response;
})
.then(response => response.json())
.then(json => {
success && success(json);
})
.catch(err => {
error && error(err);
});
};
getApkVersion = () => {
if (jobId !== -1) {
return;
}
if (!this.options.apkVersionUrl) {
console.log("RNUpdateAPK::getApkVersion - apkVersionUrl doesn't exist.");
return;
}
this.get(
this.options.apkVersionUrl,
this.getApkVersionSuccess.bind(this),
this.getVersionError.bind(this),
this.options.apkVersionOptions
);
};
getApkVersionSuccess = remote => {
console.log("getApkVersionSuccess", remote);
// TODO switch this to versionCode
let outdated = false;
if (remote.versionCode && (remote.versionCode > RNUpdateAPK.versionCode)) {
console.log('RNUpdateAPK::getApkVersionSuccess - outdated based on code, local/remote: ' + RNUpdateAPK.versionCode + "/" + remote.versionCode);
outdated = true;
}
if (!remote.versionCode && semverLt(RNUpdateAPK.versionName, remote.versionName)) {
console.log('RNUpdateAPK::getApkVersionSuccess - APK outdated based on version name, local/remote: ' + RNUpdateAPK.versionName + "/" + remote.versionName);
outdated = true
}
if (outdated) {
if (remote.forceUpdate) {
if (this.options.forceUpdateApp) {
this.options.forceUpdateApp();
}
this.downloadApk(remote);
} else if (this.options.needUpdateApp) {
this.options.needUpdateApp(isUpdate => {
if (isUpdate) {
this.downloadApk(remote);
}
}, remote.whatsNew);
}
} else if (this.options.notNeedUpdateApp) {
this.options.notNeedUpdateApp();
}
};
downloadApk = remote => {
const RNFS = require("react-native-fs");
const progress = data => {
const percentage = ((100 * data.bytesWritten) / data.contentLength) | 0;
this.options.downloadApkProgress &&
this.options.downloadApkProgress(percentage, data.contentLength, data.bytesWritten);
};
const begin = res => {
console.log("RNUpdateAPK::downloadApk - downloadApkStart");
this.options.downloadApkStart && this.options.downloadApkStart();
};
const progressDivider = 1;
// You must be sure filepaths.xml exposes this path or you will have a FileProvider error API24+
// You might check {totalSpace, freeSpace} = await RNFS.getFSInfo() to make sure there is room
const downloadDestPath = `${RNFS.CachesDirectoryPath}/NewApp.apk`;
let options = this.options.apkOptions ? this.options.apkOptions : {};
const ret = RNFS.downloadFile(
Object.assign(
{
fromUrl: remote.apkUrl,
toFile: downloadDestPath,
begin,
progress,
background: true,
progressDivider
},
options
)
);
jobId = ret.jobId;
ret.promise
.then(res => {
if (res['statusCode'] >= 400 && res['statusCode'] <= 599) {
throw "Failed to Download APK. Server returned with " + res['statusCode'] + " statusCode";
}
console.log("RNUpdateAPK::downloadApk - downloadApkEnd");
this.options.downloadApkEnd && this.options.downloadApkEnd();
RNUpdateAPK.getApkInfo(downloadDestPath)
.then(res => {
console.log(
"RNUpdateAPK::downloadApk - Old Cert SHA-256: " + RNUpdateAPK.signatures[0].thumbprint
);
console.log("RNUpdateAPK::downloadApk - New Cert SHA-256: " + res.signatures[0].thumbprint);
if (
res.signatures[0].thumbprint !==
RNUpdateAPK.signatures[0].thumbprint
) {
// FIXME should add extra callback for this
console.log(
"The signature thumbprints seem unequal. Install will fail"
);
}
})
.catch(rej => {
console.log("RNUpdateAPK::downloadApk - apk info error: ", rej);
this.options.onError && this.options.onError("Failed to get Downloaded APK Info");
// re-throw so we don't attempt to install the APK, this will call the downloadApkError handler
throw rej;
});
RNUpdateAPK.installApk(
downloadDestPath,
this.options.fileProviderAuthority
);
jobId = -1;
})
.catch(err => {
this.downloadApkError(err);
jobId = -1;
});
};
getAppStoreVersion = () => {
if (!this.options.iosAppId) {
console.log("RNUpdateAPK::getAppStoreVersion - iosAppId doesn't exist.");
return;
}
const URL = "https://itunes.apple.com/lookup?id=" + this.options.iosAppId;
console.log("RNUpdateAPK::getAppStoreVersion - attempting to fetch " + URL);
this.get(
URL,
this.getAppStoreVersionSuccess.bind(this),
this.getVersionError.bind(this)
);
};
getAppStoreVersionSuccess = data => {
if (data.resultCount < 1) {
console.log("RNUpdateAPK::getAppStoreVersionSuccess - iosAppId is wrong.");
return;
}
const result = data.results[0];
const version = result.version;
const trackViewUrl = result.trackViewUrl;
if (semverLt(RNUpdateAPK.versionName, version)) {
console.log('RNUpdateAPK::getAppStoreVersionSuccess - outdated based on version name, local/remote: ' + RNUpdateAPK.versionName + "/" + version);
if (this.options.needUpdateApp) {
this.options.needUpdateApp(isUpdate => {
if (isUpdate) {
RNUpdateAPK.installFromAppStore(trackViewUrl);
}
});
}
} else {
this.options.notNeedUpdateApp && this.options.notNeedUpdateApp();
}
};
getVersionError = err => {
console.log("RNUpdateAPK::getVersionError - getVersionError", err);
this.options.onError && this.options.onError(err);
};
downloadApkError = err => {
console.log("RNUpdateAPK::downloadApkError - downloadApkError", err);
this.options.onError && this.options.onError(err);
};
checkUpdate = () => {
if (Platform.OS === "android") {
this.getApkVersion();
} else {
this.getAppStoreVersion();
}
};
}
export function getInstalledVersionName() {
return RNUpdateAPK.versionName;
}
export function getInstalledVersionCode() {
return RNUpdateAPK.versionCode;
}
export function getInstalledPackageName() {
return RNUpdateAPK.packageName;
}
export function getInstalledFirstInstallTime() {
return RNUpdateAPK.firstInstallTime;
}
export function getInstalledLastUpdateTime() {
return RNUpdateAPK.lastUpdateTime;
}
export function getInstalledPackageInstaller() {
return RNUpdateAPK.packageInstaller;
}
export function getInstalledSigningInfo() {
return RNUpdateAPK.signatures;
}
export async function getApps() {
if (Platform.OS === "android") {
return RNUpdateAPK.getApps();
} else {
return Promise.resolve([]);
}
}
export async function getNonSystemApps() {
if (Platform.OS === "android") {
return RNUpdateAPK.getNonSystemApps();
} else {
return Promise.resolve([]);
}
}