/ services / updateService.ts
updateService.ts
  1  import * as Updates from 'expo-updates';
  2  import { Platform } from 'react-native';
  3  
  4  export interface UpdateStatus {
  5    isChecking: boolean;
  6    isUpdateAvailable: boolean;
  7    isDownloading: boolean;
  8    isReady: boolean;
  9    error: string | null;
 10  }
 11  
 12  export interface UpdateResult {
 13    success: boolean;
 14    message: string;
 15  }
 16  
 17  export interface UpdateOptions {
 18    silent?: boolean;
 19    forceReload?: boolean;
 20  }
 21  
 22  /**
 23   * Checks if an update is available from Expo's update server
 24   */
 25  export const checkForUpdate = async (): Promise<UpdateResult> => {
 26    try {
 27      if (__DEV__) {
 28        return {
 29          success: false,
 30          message: 'Updates are not available in development mode',
 31        };
 32      }
 33  
 34      console.log('Checking for updates...');
 35      const update = await Updates.checkForUpdateAsync();
 36  
 37      if (update.isAvailable) {
 38        return {
 39          success: true,
 40          message: 'Update available',
 41        };
 42      } else {
 43        return {
 44          success: false,
 45          message: 'App is up to date',
 46        };
 47      }
 48    } catch (error) {
 49      console.error('Error checking for update:', error);
 50      return {
 51        success: false,
 52        message: `Error checking for updates: ${error instanceof Error ? error.message : 'Unknown error'}`,
 53      };
 54    }
 55  };
 56  
 57  /**
 58   * Downloads the latest update from Expo's update server
 59   */
 60  export const downloadUpdate = async (): Promise<UpdateResult> => {
 61    try {
 62      if (__DEV__) {
 63        return {
 64          success: false,
 65          message: 'Cannot download updates in development mode',
 66        };
 67      }
 68  
 69      console.log('Downloading update...');
 70      await Updates.fetchUpdateAsync();
 71      return {
 72        success: true,
 73        message: 'Update downloaded successfully',
 74      };
 75    } catch (error) {
 76      console.error('Error downloading update:', error);
 77      return {
 78        success: false,
 79        message: `Error downloading update: ${error instanceof Error ? error.message : 'Unknown error'}`,
 80      };
 81    }
 82  };
 83  
 84  /**
 85   * Applies the update and restarts the app
 86   */
 87  export const applyUpdate = async (): Promise<UpdateResult> => {
 88    try {
 89      if (__DEV__ || Platform.OS === 'web') {
 90        return {
 91          success: false,
 92          message: 'Cannot apply updates in development or web mode',
 93        };
 94      }
 95  
 96      console.log('Reloading app with update...');
 97      await Updates.reloadAsync();
 98  
 99      return {
100        success: true,
101        message: 'Update applied successfully',
102      };
103    } catch (error) {
104      console.error('Error applying update:', error);
105      return {
106        success: false,
107        message: `Error applying update: ${error instanceof Error ? error.message : 'Unknown error'}`,
108      };
109    }
110  };
111  
112  /**
113   * Performs the complete update flow: check, download, and apply
114   * Can run silently in the background with optional immediate reload
115   */
116  export const performFullUpdateFlow = async (
117    options: UpdateOptions = {},
118    onStatusChange?: (status: UpdateStatus) => void
119  ): Promise<UpdateResult> => {
120    const { silent = false, forceReload = false } = options;
121  
122    const updateStatus: UpdateStatus = {
123      isChecking: false,
124      isUpdateAvailable: false,
125      isDownloading: false,
126      isReady: false,
127      error: null,
128    };
129  
130    // Helper to update status
131    const updateState = (updates: Partial<UpdateStatus>) => {
132      Object.assign(updateStatus, updates);
133      if (onStatusChange) {
134        onStatusChange({ ...updateStatus });
135      }
136    };
137  
138    try {
139      // Check for update
140      updateState({ isChecking: true });
141      const checkResult = await checkForUpdate();
142  
143      if (!checkResult.success) {
144        updateState({
145          isChecking: false,
146          error: silent ? null : checkResult.message,
147        });
148        return checkResult;
149      }
150  
151      updateState({
152        isChecking: false,
153        isUpdateAvailable: true,
154      });
155  
156      // Download update
157      updateState({ isDownloading: true });
158      const downloadResult = await downloadUpdate();
159  
160      if (!downloadResult.success) {
161        updateState({
162          isDownloading: false,
163          error: silent ? null : downloadResult.message,
164        });
165        return downloadResult;
166      }
167  
168      updateState({
169        isDownloading: false,
170        isReady: true,
171      });
172  
173      // Apply update immediately if forceReload is true
174      if (forceReload) {
175        return await applyUpdate();
176      }
177  
178      return {
179        success: true,
180        message: 'Update is ready to be applied',
181      };
182    } catch (error) {
183      const errorMessage =
184        error instanceof Error ? error.message : 'Unknown error';
185      updateState({
186        isChecking: false,
187        isDownloading: false,
188        isReady: false,
189        error: silent ? null : errorMessage,
190      });
191  
192      return {
193        success: false,
194        message: `Update failed: ${errorMessage}`,
195      };
196    }
197  };