react-native-flix-codepush
Version:
A modern CodePush implementation for React Native applications
148 lines (124 loc) • 4.25 kB
text/typescript
import { NativeModules } from 'react-native';
import {
CodePushDeploymentStatus,
CodePushInstallMode,
type CodePushConfig,
type CodePushLocalPackage,
type CodePushRemotePackage,
type DownloadProgressCallback,
type FlixCodePushNativeModule,
type SyncStatusChangedCallback
} from '../types';
import { CodePushError } from '../utils/CodePushError';
import { handleNativeError, validateDeploymentKey } from '../utils/common';
import { ApiClient } from './ApiClient';
import { PackageManager } from './PackageManager';
let NativeFlixCodePush: FlixCodePushNativeModule;
try {
const NativeModule = require('../NativeFlixCodePush').default;
NativeFlixCodePush = NativeModule;
} catch (error) {
NativeFlixCodePush = NativeModules.FlixCodePush as FlixCodePushNativeModule;
}
export class UpdateManager {
private apiClient: ApiClient;
private packageManager: PackageManager;
private config: CodePushConfig | null = null;
private statusChangeCallbacks: Set<SyncStatusChangedCallback> = new Set();
constructor() {
this.apiClient = new ApiClient('');
this.packageManager = new PackageManager('');
}
async initialize(): Promise<CodePushConfig> {
if (this.config) {
return this.config;
}
try {
this.config = await handleNativeError(
NativeFlixCodePush.getConfiguration()
);
this.apiClient = new ApiClient(this.config.serverUrl);
this.packageManager = new PackageManager(this.config.serverUrl);
return this.config;
} catch (error) {
if (error instanceof CodePushError) {
throw error;
}
throw new CodePushError(
`Failed to initialize update manager: ${(error as Error).message}`,
'INITIALIZATION_ERROR',
'Configuration'
);
}
}
async getConfiguration(): Promise<CodePushConfig> {
return this.config || this.initialize();
}
async checkForUpdate(deploymentKey?: string): Promise<CodePushRemotePackage | null> {
const config = await this.getConfiguration();
const key = deploymentKey || config.deploymentKey;
if (!validateDeploymentKey(key)) {
throw CodePushError.invalidDeploymentKey(key);
}
const currentPackage = await this.packageManager.getCurrentPackage();
const appVersion = await handleNativeError(NativeFlixCodePush.getAppVersion());
return this.apiClient.checkForUpdate(
key,
appVersion,
currentPackage?.packageHash
);
}
async downloadUpdate(
remotePackage: CodePushRemotePackage,
progressCallback?: DownloadProgressCallback
): Promise<CodePushLocalPackage> {
return this.packageManager.downloadAndPreparePackage(remotePackage, progressCallback);
}
async installUpdate(
packageHash: string,
installMode: CodePushInstallMode = CodePushInstallMode.ON_NEXT_RESTART
): Promise<void> {
return this.packageManager.installPackage(packageHash, installMode);
}
async reportStatus(
status: CodePushDeploymentStatus,
localPackage: CodePushLocalPackage
): Promise<void> {
const deviceId = await handleNativeError(NativeFlixCodePush.getDeviceId());
return this.apiClient.reportStatus(
status,
localPackage.deploymentKey,
localPackage.label,
localPackage.appVersion,
deviceId,
localPackage.previousLabelOrAppVersion
);
}
async getCurrentPackage(): Promise<CodePushLocalPackage | null> {
return this.packageManager.getCurrentPackage();
}
async restartApplication(onlyIfUpdateIsPending: boolean = false): Promise<void> {
return this.packageManager.restartApplication(onlyIfUpdateIsPending);
}
async clearUpdates(): Promise<void> {
return this.packageManager.clearUpdates();
}
cancelDownload(): void {
this.packageManager.cancelDownload();
}
addStatusChangeListener(callback: SyncStatusChangedCallback): () => void {
this.statusChangeCallbacks.add(callback);
return () => {
this.statusChangeCallbacks.delete(callback);
};
}
notifyStatusChange(status: number): void {
this.statusChangeCallbacks.forEach(callback => {
try {
callback(status);
} catch (error) {
console.error('Error in status change callback:', error);
}
});
}
}