react-native-flix-codepush
Version:
A modern CodePush implementation for React Native applications
250 lines (216 loc) • 7.57 kB
text/typescript
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);
}
}