UNPKG

react-native-flix-codepush

Version:

A modern CodePush implementation for React Native applications

257 lines (222 loc) 7.51 kB
import { useCallback, useEffect, useRef, useState } from 'react'; import { AppState, type AppStateStatus } from 'react-native'; import { CodePush } from '../core/CodePush'; import { CodePushSyncStatus, type CodePushInstallMode, type CodePushLocalPackage, type CodePushRemotePackage, type CodePushSyncOptions, type UseCodePushResult, } from '../types'; export function useCodePush(options: CodePushSyncOptions = {}): UseCodePushResult { const codePush = CodePush.getInstance(); const isMountedRef = useRef(true); const [syncStatus, setSyncStatus] = useState<CodePushSyncStatus>(CodePushSyncStatus.UP_TO_DATE); const [currentPackage, setCurrentPackage] = useState<CodePushLocalPackage | null>(null); const [pendingUpdate, setPendingUpdate] = useState<CodePushRemotePackage | null>(null); const [isCheckingForUpdate, setIsCheckingForUpdate] = useState<boolean>(false); const [isDownloading, setIsDownloading] = useState<boolean>(false); const [isInstalling, setIsInstalling] = useState<boolean>(false); const [downloadProgress, setDownloadProgress] = useState<{ receivedBytes: number; totalBytes: number }>({ receivedBytes: 0, totalBytes: 0, }); const handleStatusChange = useCallback((status: CodePushSyncStatus) => { if (isMountedRef.current) { setSyncStatus(status); } }, []); const handleDownloadProgress = useCallback((progress: { receivedBytes: number; totalBytes: number }) => { if (isMountedRef.current) { setDownloadProgress(progress); } }, []); useEffect(() => { const loadCurrentPackage = async () => { try { const pkg = await codePush.getCurrentPackage(); if (pkg && isMountedRef.current) { setCurrentPackage(pkg); } } catch (error) { console.error('Failed to load current package:', error); } }; loadCurrentPackage(); return () => { isMountedRef.current = false; }; }, [codePush]); const checkForUpdate = useCallback(async (deploymentKey?: string): Promise<CodePushRemotePackage | null> => { if (isCheckingForUpdate) { return null; } try { setIsCheckingForUpdate(true); setSyncStatus(CodePushSyncStatus.CHECKING_FOR_UPDATE); const update = await codePush.checkForUpdate(deploymentKey); if (isMountedRef.current) { if (update) { setPendingUpdate(update); } else { setSyncStatus(CodePushSyncStatus.UP_TO_DATE); } } return update; } catch (error) { console.error('Failed to check for update:', error); if (isMountedRef.current) { setSyncStatus(CodePushSyncStatus.UNKNOWN_ERROR); } return null; } finally { if (isMountedRef.current) { setIsCheckingForUpdate(false); } } }, [isCheckingForUpdate, codePush]); useEffect(() => { const handleAppStateChange = async (nextAppState: AppStateStatus) => { if (nextAppState === 'active') { const config = await codePush.getConfiguration(); if (config.checkFrequency === 2) { // ON_APP_RESUME checkForUpdate(); } } }; const subscription = AppState.addEventListener('change', handleAppStateChange); const checkOnStart = async () => { const config = await codePush.getConfiguration(); if (config.checkFrequency === 1) { // ON_APP_START checkForUpdate(); } }; checkOnStart(); return () => { subscription.remove(); }; }, [codePush, checkForUpdate]); const downloadUpdate = useCallback(async (remotePackage: CodePushRemotePackage): Promise<CodePushLocalPackage> => { try { setIsDownloading(true); setSyncStatus(CodePushSyncStatus.DOWNLOADING_PACKAGE); const localPackage = await codePush.downloadUpdate(remotePackage, handleDownloadProgress); return localPackage; } catch (error) { console.error('Failed to download update:', error); setSyncStatus(CodePushSyncStatus.UNKNOWN_ERROR); throw error; } finally { if (isMountedRef.current) { setIsDownloading(false); } } }, [codePush, handleDownloadProgress]); const installUpdate = useCallback(async (packageHash: string, installMode?: CodePushInstallMode): Promise<void> => { try { setIsInstalling(true); setSyncStatus(CodePushSyncStatus.INSTALLING_UPDATE); await codePush.installUpdate(packageHash, installMode); if (isMountedRef.current) { setSyncStatus(CodePushSyncStatus.UPDATE_INSTALLED); } } catch (error) { console.error('Failed to install update:', error); if (isMountedRef.current) { setSyncStatus(CodePushSyncStatus.UNKNOWN_ERROR); } throw error; } finally { if (isMountedRef.current) { setIsInstalling(false); } } }, [codePush]); const sync = useCallback(async (syncOptions?: CodePushSyncOptions): Promise<CodePushSyncStatus> => { try { if (isMountedRef.current) { setSyncStatus(CodePushSyncStatus.SYNC_IN_PROGRESS); } const mergedOptions = { ...options, ...syncOptions }; const status = await codePush.sync( mergedOptions, handleStatusChange, handleDownloadProgress ); if (isMountedRef.current) { setSyncStatus(status); } if (status === CodePushSyncStatus.UPDATE_INSTALLED && isMountedRef.current) { const pkg = await codePush.getCurrentPackage(); if (pkg && isMountedRef.current) { setCurrentPackage(pkg); } } return status; } catch (error) { console.error('Sync failed:', error); if (isMountedRef.current) { setSyncStatus(CodePushSyncStatus.UNKNOWN_ERROR); } throw error; } }, [options, codePush, handleStatusChange, handleDownloadProgress]); const getCurrentPackage = useCallback(async (): Promise<CodePushLocalPackage | null> => { try { const pkg = await codePush.getCurrentPackage(); if (pkg && isMountedRef.current) { setCurrentPackage(pkg); } return pkg; } catch (error) { console.error('Failed to get current package:', error); return null; } }, [codePush]); const restartApp = useCallback(async (): Promise<void> => { return codePush.restartApplication(true); }, [codePush]); const clearUpdates = useCallback(async (): Promise<void> => { try { await codePush.clearUpdates(); if (isMountedRef.current) { setCurrentPackage(null); setPendingUpdate(null); setSyncStatus(CodePushSyncStatus.UP_TO_DATE); } } catch (error) { console.error('Failed to clear updates:', error); throw error; } }, [codePush]); const getUpdateMetadata = useCallback(async (): Promise<CodePushLocalPackage | null> => { return codePush.getCurrentPackage(); }, [codePush]); const cancelSync = useCallback((): void => { codePush.cancelSync(); if (isMountedRef.current) { setIsDownloading(false); setIsInstalling(false); setSyncStatus(CodePushSyncStatus.UP_TO_DATE); } }, [codePush]); return { checkForUpdate, sync, getCurrentPackage, restartApp, clearUpdates, getUpdateMetadata, downloadUpdate, installUpdate, syncStatus, currentPackage, pendingUpdate, isCheckingForUpdate, isDownloading, isInstalling, cancelSync, downloadProgress, }; }