UNPKG

react-native-flix-codepush

Version:

A modern CodePush implementation for React Native applications

250 lines (216 loc) 7.57 kB
import { NativeModules } from 'react-native'; import { CodePushDeploymentStatus, CodePushInstallMode, CodePushSyncStatus, type BinaryVersionMismatchCallback, type CodePushConfig, type CodePushLocalPackage, type CodePushRemotePackage, type CodePushSyncOptions, type DownloadProgressCallback, type FlixCodePushNativeModule, type SyncStatusChangedCallback } from '../types'; import { CodePushError } from '../utils/CodePushError'; import { validateRemotePackage } from '../utils/common'; import { UpdateManager } from './UpdateManager'; let NativeFlixCodePush: FlixCodePushNativeModule; try { const NativeModule = require('../NativeFlixCodePush').default; NativeFlixCodePush = NativeModule; } catch (error) { NativeFlixCodePush = NativeModules.FlixCodePush as FlixCodePushNativeModule; } export class CodePush { private static instance: CodePush; private updateManager: UpdateManager; private syncInProgress: boolean = false; private binaryVersionMismatchCallback?: BinaryVersionMismatchCallback; private constructor() { this.updateManager = new UpdateManager(); } public static getInstance(): CodePush { if (!CodePush.instance) { CodePush.instance = new CodePush(); } return CodePush.instance; } async getConfiguration(): Promise<CodePushConfig> { return this.updateManager.getConfiguration(); } async checkForUpdate(deploymentKey?: string): Promise<CodePushRemotePackage | null> { try { const remotePackage = await this.updateManager.checkForUpdate(deploymentKey); if (remotePackage && this.binaryVersionMismatchCallback) { const shouldInstall = this.binaryVersionMismatchCallback(remotePackage); if (!shouldInstall) { return null; } } return remotePackage; } catch (error) { if (error instanceof CodePushError) { throw error; } throw new CodePushError( `Failed to check for update: ${(error as Error).message}`, 'CHECK_UPDATE_ERROR', 'Update' ); } } async downloadUpdate( remotePackage: CodePushRemotePackage, progressCallback?: DownloadProgressCallback ): Promise<CodePushLocalPackage> { if (!validateRemotePackage(remotePackage)) { throw CodePushError.packageError('Invalid remote package'); } try { return await this.updateManager.downloadUpdate(remotePackage, progressCallback); } catch (error) { if (error instanceof CodePushError) { throw error; } throw new CodePushError( `Failed to download update: ${(error as Error).message}`, 'DOWNLOAD_ERROR', 'Update' ); } } async installUpdate( packageHash: string, installMode: CodePushInstallMode = CodePushInstallMode.ON_NEXT_RESTART ): Promise<void> { try { return await this.updateManager.installUpdate(packageHash, installMode); } catch (error) { if (error instanceof CodePushError) { throw error; } throw new CodePushError( `Failed to install update: ${(error as Error).message}`, 'INSTALL_ERROR', 'Update' ); } } async getCurrentPackage(): Promise<CodePushLocalPackage | null> { try { return await this.updateManager.getCurrentPackage(); } catch (error) { if (error instanceof CodePushError) { throw error; } throw new CodePushError( `Failed to get current package: ${(error as Error).message}`, 'GET_PACKAGE_ERROR', 'Package' ); } } async restartApplication(onlyIfUpdateIsPending: boolean = false): Promise<void> { try { return await this.updateManager.restartApplication(onlyIfUpdateIsPending); } catch (error) { if (error instanceof CodePushError) { throw error; } throw new CodePushError( `Failed to restart application: ${(error as Error).message}`, 'RESTART_ERROR', 'Application' ); } } async clearUpdates(): Promise<void> { try { return await this.updateManager.clearUpdates(); } catch (error) { if (error instanceof CodePushError) { throw error; } throw new CodePushError( `Failed to clear updates: ${(error as Error).message}`, 'CLEAR_ERROR', 'Package' ); } } async sync( options: CodePushSyncOptions = {}, syncStatusChangeCallback?: SyncStatusChangedCallback, downloadProgressCallback?: DownloadProgressCallback ): Promise<CodePushSyncStatus> { if (this.syncInProgress) { return CodePushSyncStatus.SYNC_IN_PROGRESS; } this.syncInProgress = true; try { let removeListener: (() => void) | undefined; if (syncStatusChangeCallback) { removeListener = this.updateManager.addStatusChangeListener(syncStatusChangeCallback); } const config = await this.getConfiguration(); const deploymentKey = options.deploymentKey || config.deploymentKey; const installMode = options.installMode || config.installMode || CodePushInstallMode.ON_NEXT_RESTART; const mandatoryInstallMode = options.mandatoryInstallMode || config.mandatoryInstallMode || CodePushInstallMode.IMMEDIATE; this.notifyStatusChange(CodePushSyncStatus.CHECKING_FOR_UPDATE); const remotePackage = await this.checkForUpdate(deploymentKey); if (!remotePackage) { this.notifyStatusChange(CodePushSyncStatus.UP_TO_DATE); this.syncInProgress = false; if (removeListener) removeListener(); return CodePushSyncStatus.UP_TO_DATE; } if (options.updateDialog && !remotePackage.isMandatory) { this.notifyStatusChange(CodePushSyncStatus.AWAITING_USER_ACTION); } this.notifyStatusChange(CodePushSyncStatus.DOWNLOADING_PACKAGE); const localPackage = await this.downloadUpdate(remotePackage, downloadProgressCallback); this.notifyStatusChange(CodePushSyncStatus.INSTALLING_UPDATE); const effectiveInstallMode = remotePackage.isMandatory ? mandatoryInstallMode : installMode; await this.installUpdate(localPackage.packageHash, effectiveInstallMode); await this.updateManager.reportStatus( CodePushDeploymentStatus.SUCCEEDED, localPackage ); if (effectiveInstallMode === CodePushInstallMode.IMMEDIATE) { await this.restartApplication(true); } this.notifyStatusChange(CodePushSyncStatus.UPDATE_INSTALLED); this.syncInProgress = false; if (removeListener) removeListener(); return CodePushSyncStatus.UPDATE_INSTALLED; } catch (error) { this.syncInProgress = false; this.notifyStatusChange(CodePushSyncStatus.UNKNOWN_ERROR); if (error instanceof CodePushError) { throw error; } throw new CodePushError( `Sync failed: ${(error as Error).message}`, 'SYNC_ERROR', 'Update' ); } } cancelSync(): void { if (this.syncInProgress) { this.updateManager.cancelDownload(); this.syncInProgress = false; this.notifyStatusChange(CodePushSyncStatus.UP_TO_DATE); } } setBinaryVersionMismatchCallback(callback: BinaryVersionMismatchCallback): void { this.binaryVersionMismatchCallback = callback; } getInfo(): { isNewArchitectureEnabled: boolean; isHermesEnabled: boolean } { return NativeFlixCodePush.getConstants(); } private notifyStatusChange(status: CodePushSyncStatus): void { this.updateManager.notifyStatusChange(status); } }