UNPKG

react-native-flix-codepush

Version:

A modern CodePush implementation for React Native applications

537 lines (437 loc) โ€ข 12.9 kB
# React Native Flix CodePush A modern CodePush implementation for React Native applications that provides over-the-air updates for your React Native apps. This SDK is compatible with the latest React Native versions, including support for Hermes Engine and the New Architecture (Fabric). ## Features - ๐Ÿš€ Compatible with the latest React Native versions (0.71.0 - 0.81.1+) - ๐Ÿ”„ Backward compatibility with older React Native versions - โšก๏ธ Full support for Hermes Engine - ๐Ÿงฉ Support for New Architecture (Fabric) - ๐Ÿ”Œ Auto-linking package - ๐Ÿ› ๏ธ Modern hooks-based API with configurable methods - ๐Ÿ“ฑ Full native implementations for both iOS and Android - ๐Ÿงช Production-ready code with robust error handling - ๐Ÿ—๏ธ Modular architecture (ApiClient, UpdateManager, PackageManager) - ๐ŸŽฃ React hooks for a first-class developer experience - ๐Ÿงฉ Pre-built UI components for update experience - ๐ŸŒ“ Dark mode support - ๐Ÿ”„ Download progress tracking - ๐Ÿ”” Update notifications ## Installation ```bash npm install react-native-flix-codepush --save # or yarn add react-native-flix-codepush ``` ## Basic Setup ### iOS Add the following to your `Info.plist`: ```xml <key>CodePushDeploymentKey</key> <string>YOUR_DEPLOYMENT_KEY</string> <key>CodePushServerUrl</key> <string>YOUR_SERVER_URL</string> ``` ### Android Add the following to your `strings.xml`: ```xml <string name="codepush_deployment_key">YOUR_DEPLOYMENT_KEY</string> <string name="codepush_server_url">YOUR_SERVER_URL</string> ``` ## Usage ### Hooks-based API (Recommended) ```jsx import React, { useEffect } from 'react'; import { View, Text, Button, SafeAreaView } from 'react-native'; import { useCodePush } from 'react-native-flix-codepush'; const App = () => { const { checkForUpdate, sync, restartApp, syncStatus, currentPackage, isCheckingForUpdate, downloadProgress } = useCodePush({ deploymentKey: 'YOUR_DEPLOYMENT_KEY', updateDialog: true, installMode: 'ON_NEXT_RESTART' }); useEffect(() => { // Check for updates when the app starts checkForUpdate(); }, []); return ( <SafeAreaView> <Text>Current Version: {currentPackage?.label || 'No package installed'}</Text> <Text> Download Progress: {isDownloading ? `${Math.round((downloadProgress.receivedBytes / downloadProgress.totalBytes) * 100)}%` : 'Not downloading' } </Text> <Button title="Check for Updates" onPress={() => checkForUpdate()} disabled={isCheckingForUpdate} /> <Button title="Sync Updates" onPress={() => sync({ updateDialog: true })} /> <Button title="Restart App" onPress={() => restartApp()} /> </SafeAreaView> ); }; export default App; ``` ### Component Wrapper (Simple) ```jsx import React from 'react'; import { View, Text } from 'react-native'; import { CodePushHandler } from 'react-native-flix-codepush'; const App = () => { return ( <View> <Text>My App Content</Text> </View> ); }; // Wrap your app with CodePushHandler export default () => ( <CodePushHandler deploymentKey="YOUR_DEPLOYMENT_KEY" checkOnStart={true} checkOnResume={true} updateDialog={true} installMode="ON_NEXT_RESTART" mandatoryInstallMode="IMMEDIATE" > <App /> </CodePushHandler> ); ``` ### Configuration Method ```jsx import React from 'react'; import { configureCodePush } from 'react-native-flix-codepush'; // Configure CodePush const { CodePushHandler, useCodePush } = configureCodePush({ deploymentKey: 'YOUR_DEPLOYMENT_KEY', serverUrl: 'YOUR_SERVER_URL', checkFrequency: 1, // ON_APP_START installMode: 'ON_NEXT_RESTART', mandatoryInstallMode: 'IMMEDIATE' }); // Create a custom hook with your configuration const useMyCodePush = () => useCodePush(); // Wrap your app with CodePushHandler const App = () => { return ( <CodePushHandler> <YourApp /> </CodePushHandler> ); }; export default App; ``` ### Using HOC (Legacy Approach) ```jsx import React, { Component } from 'react'; import { View, Text, Button } from 'react-native'; import { codePush } from 'react-native-flix-codepush'; class App extends Component { checkForUpdates = () => { codePush.checkForUpdate() .then((update) => { if (update) { console.log('Update available'); } else { console.log('No update available'); } }); }; sync = () => { codePush.sync({ updateDialog: true, installMode: codePush.InstallMode.IMMEDIATE, }); }; render() { return ( <View> <Text>CodePush Example</Text> <Button title="Check for Updates" onPress={this.checkForUpdates} /> <Button title="Sync" onPress={this.sync} /> </View> ); } } export default codePush({ checkFrequency: codePush.CheckFrequency.ON_APP_START, installMode: codePush.InstallMode.ON_NEXT_RESTART, })(App); ``` ## API Reference ### `useCodePush(options)` React hook for using CodePush in functional components. ```typescript function useCodePush(options?: CodePushSyncOptions): UseCodePushResult; interface UseCodePushResult { checkForUpdate: (deploymentKey?: string) => Promise<CodePushRemotePackage | null>; sync: (options?: CodePushSyncOptions) => Promise<CodePushSyncStatus>; getCurrentPackage: () => Promise<CodePushLocalPackage | null>; restartApp: () => Promise<void>; clearUpdates: () => Promise<void>; getUpdateMetadata: () => Promise<CodePushLocalPackage | null>; downloadUpdate: (remotePackage: CodePushRemotePackage) => Promise<CodePushLocalPackage>; installUpdate: (packageHash: string, installMode?: CodePushInstallMode) => Promise<void>; cancelSync: () => void; syncStatus: CodePushSyncStatus; currentPackage: CodePushLocalPackage | null; pendingUpdate: CodePushRemotePackage | null; isCheckingForUpdate: boolean; isDownloading: boolean; isInstalling: boolean; downloadProgress: { receivedBytes: number; totalBytes: number }; } ``` ### `configureCodePush(config)` Configures the CodePush SDK with the provided options. ```typescript function configureCodePush(config: { deploymentKey: string; serverUrl: string; checkFrequency?: number; installMode?: string; mandatoryInstallMode?: string; minimumBackgroundDuration?: number; }): { useCodePush: UseCodePushHook; CodePushHandler: React.ComponentType<CodePushHandlerProps>; UpdateDialog: React.ComponentType<UpdateDialogProps>; DownloadProgressView: React.ComponentType<DownloadProgressViewProps>; codePush: CodePushHOC; // Legacy methods checkForUpdate: (deploymentKey?: string) => Promise<CodePushRemotePackage | null>; getCurrentPackage: () => Promise<CodePushLocalPackage | null>; sync: (options?: CodePushSyncOptions) => Promise<CodePushSyncStatus>; restartApp: (onlyIfUpdateIsPending?: boolean) => Promise<void>; }; ``` ### `createCodePushHook(options)` Creates a custom hook with pre-configured options. ```typescript function createCodePushHook(options: { deploymentKey: string; installMode?: string; mandatoryInstallMode?: string; minimumBackgroundDuration?: number; }): () => UseCodePushResult; ``` ### `CodePushHandler` Component for handling CodePush updates. ```typescript interface CodePushHandlerProps { children: React.ReactNode; checkOnStart?: boolean; checkOnResume?: boolean; deploymentKey?: string; serverUrl?: string; installMode?: CodePushInstallMode; mandatoryInstallMode?: CodePushInstallMode; minimumBackgroundDuration?: number; updateDialog?: UpdateDialogOptions | boolean; ignoreFailedUpdates?: boolean; onSyncStatusChanged?: (status: CodePushSyncStatus) => void; onDownloadProgress?: (progress: { receivedBytes: number; totalBytes: number }) => void; onBinaryVersionMismatch?: (update: CodePushRemotePackage) => boolean; } ``` ### `codePush(options)(Component)` Higher-order component for class components. ```typescript function codePush(options?: CodePushSyncOptions): (WrappedComponent: React.ComponentType<any>) => React.ComponentType<any>; ``` ## Components ### `UpdateDialog` A customizable dialog for prompting users to install updates. ```typescript interface UpdateDialogProps { update: CodePushRemotePackage; onIgnore: () => void; onAccept: () => Promise<void>; title?: string; descriptionPrefix?: string; mandatoryUpdateMessage?: string; optionalUpdateMessage?: string; mandatoryContinueButtonLabel?: string; optionalInstallButtonLabel?: string; optionalIgnoreButtonLabel?: string; appendReleaseDescription?: boolean; } ``` ### `DownloadProgressView` A component for displaying download progress. ```typescript interface DownloadProgressViewProps { progress: number; total: number; visible: boolean; title?: string; message?: string; } ``` ## Types ### `CodePushInstallMode` ```typescript enum CodePushInstallMode { IMMEDIATE = 'IMMEDIATE', ON_NEXT_RESTART = 'ON_NEXT_RESTART', ON_NEXT_RESUME = 'ON_NEXT_RESUME', ON_NEXT_SUSPEND = 'ON_NEXT_SUSPEND', } ``` ### `CodePushSyncStatus` ```typescript enum CodePushSyncStatus { UP_TO_DATE = 0, UPDATE_INSTALLED = 1, UPDATE_IGNORED = 2, UNKNOWN_ERROR = 3, SYNC_IN_PROGRESS = 4, CHECKING_FOR_UPDATE = 5, AWAITING_USER_ACTION = 6, DOWNLOADING_PACKAGE = 7, INSTALLING_UPDATE = 8, } ``` ### `CodePushCheckFrequency` ```typescript enum CodePushCheckFrequency { MANUAL = 0, ON_APP_START = 1, ON_APP_RESUME = 2, } ``` ## Server API Endpoints This SDK is designed to work with the following API endpoints: ### 1. Check for Update ``` GET /codepush/update/check ``` Query Parameters: - `deployment_key`: Your deployment key - `app_version`: The current app version - `package_hash`: The current package hash (optional) ### 2. Report Deployment Status ``` POST /codepush/report_status/{status} ``` Where `{status}` can be: - `download` - `install` - `rollback` Body: ```json { "deploymentKey": "YOUR_DEPLOYMENT_KEY", "label": "v2", "appVersion": "1.0.0", "clientUniqueId": "a-unique-device-identifier", "previousLabelOrAppVersion": "v1" } ``` ### 3. Download CodePush Bundle ``` GET /codepush/download/{packageHash} ``` ## Advanced Features ### Binary Version Mismatch Handling ```javascript const { useCodePush } = configureCodePush({ deploymentKey: 'YOUR_DEPLOYMENT_KEY', serverUrl: 'YOUR_SERVER_URL', }); const App = () => { const codePushOptions = { onBinaryVersionMismatch: (update) => { // Return true to install the update, false to ignore it return update.appVersion === '1.0.0'; } }; const { sync } = useCodePush(codePushOptions); // Rest of your component }; ``` ### Custom Update Dialog ```javascript const updateDialogOptions = { title: 'New Update Available', mandatoryUpdateMessage: 'An important update is available that you must install now.', optionalUpdateMessage: 'A new update is available. Would you like to install it?', mandatoryContinueButtonLabel: 'Install Now', optionalInstallButtonLabel: 'Install', optionalIgnoreButtonLabel: 'Later', appendReleaseDescription: true, descriptionPrefix: 'What\'s New:\n' }; const { sync } = useCodePush(); // Use the custom dialog options sync({ updateDialog: updateDialogOptions }); ``` ### Monitoring Download Progress ```javascript const { downloadUpdate } = useCodePush(); const handleDownload = async (remotePackage) => { try { const localPackage = await downloadUpdate( remotePackage, (progress) => { console.log(`Downloaded ${progress.receivedBytes} of ${progress.totalBytes} bytes`); const percent = Math.round((progress.receivedBytes / progress.totalBytes) * 100); setDownloadProgress(percent); } ); // Install the update await installUpdate(localPackage.packageHash); } catch (error) { console.error('Download failed:', error); } }; ``` ### Sync Status Monitoring ```javascript const { sync } = useCodePush(); const handleSync = async () => { try { await sync( { updateDialog: true }, (status) => { switch (status) { case CodePushSyncStatus.CHECKING_FOR_UPDATE: console.log('Checking for update...'); break; case CodePushSyncStatus.DOWNLOADING_PACKAGE: console.log('Downloading package...'); break; case CodePushSyncStatus.INSTALLING_UPDATE: console.log('Installing update...'); break; case CodePushSyncStatus.UPDATE_INSTALLED: console.log('Update installed!'); break; // Handle other statuses } } ); } catch (error) { console.error('Sync failed:', error); } }; ``` ## License MIT