import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'; import { AppState, AppStateStatus } from 'react-native'; import CustomCodePush, { CodePushConfiguration, UpdatePackage, SyncStatus } from './CustomCodePush'; interface CodePushContextType { codePush: CustomCodePush | null; currentUpdate: UpdatePackage | null; availableUpdate: UpdatePackage | null; syncStatus: SyncStatus | null; isChecking: boolean; isDownloading: boolean; isInstalling: boolean; checkForUpdate: () => Promise; syncUpdate: () => Promise; rollback: () => Promise; clearUpdates: () => Promise; getBundleUrl: () => string | null; } const CodePushContext = createContext(undefined); interface CodePushProviderProps { children: ReactNode; config: CodePushConfiguration; autoCheck?: boolean; checkOnResume?: boolean; } export const CodePushProvider: React.FC = ({ children, config, autoCheck = true, checkOnResume = true, }) => { const [codePush] = useState(() => new CustomCodePush(config)); const [currentUpdate, setCurrentUpdate] = useState(null); const [availableUpdate, setAvailableUpdate] = useState(null); const [syncStatus, setSyncStatus] = useState(null); const [isChecking, setIsChecking] = useState(false); const [isDownloading, setIsDownloading] = useState(false); const [isInstalling, setIsInstalling] = useState(false); useEffect(() => { // Wait for SDK to initialize (load stored package) before updating state codePush.initialize() .then(async () => { await loadCurrentUpdate(); if (autoCheck) { await checkForUpdate(); } }) .catch(error => { console.error('Error initializing CodePush SDK:', error); }); // Set up app state listener for resume checks if (checkOnResume) { const handleAppStateChange = (nextAppState: AppStateStatus) => { if (nextAppState === 'active') { checkForUpdate(); } }; const subscription = AppState.addEventListener('change', handleAppStateChange); return () => subscription?.remove(); } }, []); const loadCurrentUpdate = async () => { try { const current = await codePush.getCurrentPackage(); setCurrentUpdate(current); } catch (error) { console.error('Failed to load current update:', error); } }; const checkForUpdate = async () => { if (isChecking) return; setIsChecking(true); try { const update = await codePush.checkForUpdate(); setAvailableUpdate(update); } catch (error) { console.error('Failed to check for update:', error); } finally { setIsChecking(false); } }; const syncUpdate = async () => { if (!availableUpdate || isDownloading || isInstalling) return; try { const success = await codePush.sync( { installMode: 'ON_NEXT_RESTART', mandatoryInstallMode: 'ON_NEXT_RESTART', }, (status) => { setSyncStatus(status); switch (status.status) { case 'DOWNLOADING_PACKAGE': setIsDownloading(true); setIsInstalling(false); break; case 'INSTALLING_UPDATE': setIsDownloading(false); setIsInstalling(true); break; case 'UPDATE_INSTALLED': case 'UP_TO_DATE': case 'UNKNOWN_ERROR': setIsDownloading(false); setIsInstalling(false); break; } }, (progress) => { // Handle download progress if needed } ); if (success) { setAvailableUpdate(null); await loadCurrentUpdate(); } } catch (error) { console.error('Failed to sync update:', error); setIsDownloading(false); setIsInstalling(false); } }; const rollback = async () => { try { await codePush.rollback(); setCurrentUpdate(null); } catch (error) { console.error('Failed to rollback:', error); } }; const clearUpdates = async () => { try { await codePush.clearUpdates(); setCurrentUpdate(null); setAvailableUpdate(null); } catch (error) { console.error('Failed to clear updates:', error); } }; const contextValue: CodePushContextType = { codePush, currentUpdate, availableUpdate, syncStatus, isChecking, isDownloading, isInstalling, checkForUpdate, syncUpdate, rollback, clearUpdates, getBundleUrl: () => codePush.getBundleUrl(), }; return ( {children} ); }; export const useCodePush = (): CodePushContextType => { const context = useContext(CodePushContext); if (context === undefined) { throw new Error('useCodePush must be used within a CodePushProvider'); } return context; };