react-native-code-sync
Version:
React-native plugin for the code-sync functionality on live via OTA (Over-the-air)
189 lines (174 loc) • 7.45 kB
JavaScript
import { Platform } from 'react-native';
import ReactNativeBlobUtil from 'react-native-blob-util';
import { unzip } from 'react-native-zip-archive';
import RNFS from 'react-native-fs';
import OtaApiConstant from './OtaApiConstant.json';
import { Utility } from '../utils';
const OTA_ROOT_PATH = RNFS.DocumentDirectoryPath;
export default class OtaUpdateClass {
constructor() {
this.bundleFileName = Platform.select({
android: 'index.android.bundle',
ios: 'main.jsbundle',
});
}
installFail = (message) => {
Utility.log("Update failed:", message);
};
downloadBundleFile = async (uri, downloadProgress) => {
if (!uri) return '';
const filename = uri.split('/').pop();
const downloadPath = `${OTA_ROOT_PATH}/${filename}`;
try {
const downloadResult = await RNFS.downloadFile({
fromUrl: uri,
toFile: downloadPath,
begin: () => { },
progress: (res) => {
const progressPercent = (res.bytesWritten / res.contentLength) * 100;
downloadProgress?.(progressPercent);
}
}).promise;
return downloadResult.statusCode === 200 ? downloadPath : '';
} catch (error) {
Utility.log("Download error:", error);
return '';
}
};
deleteOldBundleIfNeeded = async () => {
const filePath = `${OTA_ROOT_PATH}/${this.bundleFileName}`;
const fileExists = await Utility.checkFileExists(filePath);
if (fileExists) {
try {
await ReactNativeBlobUtil.fs.unlink(filePath);
return true;
} catch (error) {
Utility.log("Error deleting old bundle:", error);
}
}
return false;
};
extractZipFile = async (zipFilePath = '') => {
if (!zipFilePath) return false;
try {
const extractedPath = `${OTA_ROOT_PATH}/${Date.now()}`;
await unzip(zipFilePath, extractedPath);
const files = await ReactNativeBlobUtil.fs.ls(extractedPath);
if (files?.length) {
const androidDirectoryName = 'output';
if (!files.includes(androidDirectoryName)) return false;
const outputfiles = `${extractedPath}/${androidDirectoryName}`;
const outputDirectoryFiles = await ReactNativeBlobUtil.fs.ls(outputfiles);
if (outputDirectoryFiles.includes(this.bundleFileName)) {
await this.deleteOldBundleIfNeeded();
return await this.moveBundleFile(outputfiles, [extractedPath, zipFilePath], outputDirectoryFiles);
}
}
} catch (error) {
Utility.log("Unzip error:", error);
}
return false;
};
moveBundleFile = async (extractedPath, filesToDelete = [], bundleFiles = []) => {
try {
for (const element of bundleFiles) {
const completeTargetPath = `${OTA_ROOT_PATH}/${element}`;
const completeExtractedPath = `${extractedPath}/${element}`;
if (await Utility.checkFileExists(completeTargetPath)) {
await ReactNativeBlobUtil.fs.unlink(completeTargetPath);
}
if (await Utility.checkFileExists(completeExtractedPath)) {
await ReactNativeBlobUtil.fs.mv(completeExtractedPath, completeTargetPath);
}
}
await Promise.all(filesToDelete.map(path => ReactNativeBlobUtil.fs.unlink(path)));
return true;
} catch (error) {
Utility.log("Move file error:", error);
}
return false;
};
setupBundlePath = async (path, curentbundleVersion, onUpdateComplete, onUpdateFailed) => {
try {
if (!path || !(await this.extractZipFile(path))) {
onUpdateFailed?.();
return;
}
const otaVersionInfo = {
ota_version_code: curentbundleVersion?.toString(),
ota_app_version: Utility.getAppVersion()?.toString()
};
Utility.setAsyncStorage({ key: OtaApiConstant.LOCAL_KEYS.OTA_INFO, value: JSON.stringify(otaVersionInfo) });
onUpdateComplete?.();
} catch (err) {
onUpdateFailed?.();
}
};
startUpdate = async (url, curentbundleVersion, downloadProgress, onUpdateComplete, onUpdateFailed) => {
try {
if (!url || !curentbundleVersion) return this.installFail("Invalid URL or version");
const downloadedPath = await this.downloadBundleFile(url, downloadProgress);
if (downloadedPath) {
await this.setupBundlePath(downloadedPath, curentbundleVersion, onUpdateComplete, onUpdateFailed);
} else {
onUpdateFailed?.();
}
} catch (error) {
Utility.log("Update error:", error);
onUpdateFailed?.();
}
};
checkForUpgradedAppVersionAvailable = async () => {
if (Utility.checkForDevMode()) return;
const otaInfo = await Utility.getAsyncStorage({ key: OtaApiConstant.LOCAL_KEYS.OTA_INFO });
let previousAppVersion = null;
if (otaInfo) {
const parsedOTAInfo = JSON.parse(otaInfo);
previousAppVersion = parsedOTAInfo.ota_app_version;
}
const installedAppVersion = Utility.getAppVersion();
const convertedInstalled = Number(installedAppVersion?.toString().replace(/\./g, ''));
const convertedPrevious = Number(previousAppVersion?.toString().replace(/\./g, ''));
if (convertedInstalled > convertedPrevious) {
await this.deleteOldBundleIfNeeded();
const otaVersionInfo = {
ota_version_code: '0',
ota_app_version: installedAppVersion?.toString()
};
Utility.setAsyncStorage({ key: OtaApiConstant.LOCAL_KEYS.OTA_INFO, value: JSON.stringify(otaVersionInfo) });
Utility.reloadApp();
}
};
onCheckVersion = async ({
bundleUrl = '',
curentbundleVersion = '',
serverAppVersion = '',
onStartUpdate = () => { },
downloadProgress = () => { },
onUpdateComplete = () => { },
onUpdateFailed = () => { },
onUpdateNotAvailable = () => { }
}) => {
if (Utility.checkForDevMode()) return onUpdateNotAvailable?.();
const otaInfo = await Utility.getAsyncStorage({ key: OtaApiConstant.LOCAL_KEYS.OTA_INFO });
let previousVersion = null, previousAppVersion = null;
const installedAppVersion = Utility.getAppVersion();
if (otaInfo) {
const parsedOTAInfo = JSON.parse(otaInfo);
previousVersion = parsedOTAInfo.ota_version_code;
previousAppVersion = parsedOTAInfo.ota_app_version;
}
if (curentbundleVersion > previousVersion && serverAppVersion === installedAppVersion) {
onStartUpdate?.(true);
this.startUpdate(
bundleUrl,
curentbundleVersion,
downloadProgress,
onUpdateComplete,
onUpdateFailed
);
} else {
onUpdateNotAvailable?.();
}
}
}