UNPKG

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
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?.(); } } }