react-native-flix-codepush
Version:
A modern CodePush implementation for React Native applications
537 lines (437 loc) โข 12.9 kB
Markdown
# 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