npm package: @rishav9875/react-native-ota-update
Description: Over-the-Air (OTA) JavaScript bundle updates for React Native apps on Android and iOS
Author: Rishav Barnwal
Latest version: 1.0.1

# react-native-ota-update

Over-the-Air (OTA) JavaScript bundle updates for React Native apps on Android and iOS.

## Features

- ✅ Download and apply JavaScript bundles at runtime
- ✅ **ZIP support** - Download bundles with assets
- ✅ Works on both Android and iOS
- ✅ Simple API
- ✅ Automatic bundle switching on app restart
- ✅ Bundle verification and error handling
- ✅ Asset extraction and management both Android and iOS
- ✅ **Easy to Use**: Simple JavaScript API
- ✅ **TypeScript Support**: Full TypeScript definitions included
- ✅ **Secure**: Downloads to app's private storage
- ✅ **Lightweight**: Minimal dependencies
- ✅ **Production Ready**: Used in production apps

## Installation

```bash
npm install react-native-ota-update
# or
yarn add react-native-ota-update
```

### iOS Setup

1. Install pods:
```bash
cd ios && pod install && cd ..
```

2. Update your `AppDelegate.mm`:

```objc
#import "OTAUpdateManager.h"

// In the bundleURL method, add this before returning:
- (NSURL *)bundleURL
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  // Check for OTA update bundle first
  NSURL *customBundleURL = [[OTAUpdateManager sharedManager] getCustomBundleURL];
  if (customBundleURL) {
    return customBundleURL;
  }
  
  // Fall back to default bundle
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
```

### Android Setup

1. Update your `MainApplication.kt` or `MainApplication.java`:

**Kotlin:**
```kotlin
import com.otaupdate.OTAUpdatePackage
import com.otaupdate.OTAUpdateManager

// In getPackages():
override fun getPackages(): List<ReactPackage> {
    val packages = PackageList(this).packages.toMutableList()
    packages.add(OTAUpdatePackage())
    return packages
}

// In ReactNativeHost, add getJSBundleFile():
override fun getJSBundleFile(): String? {
    val otaManager = OTAUpdateManager(this@MainApplication)
    val bundleFile = OTAUpdateManager.getBundleFile(this@MainApplication)
    
    return if (otaManager.hasPendingUpdate() && bundleFile.exists()) {
        otaManager.setPendingUpdate(false)
        bundleFile.absolutePath
    } else {
        super.getJSBundleFile()
    }
}
```

**Java:**
```java
import com.otaupdate.OTAUpdatePackage;
import com.otaupdate.OTAUpdateManager;

// In getPackages():
@Override
protected List<ReactPackage> getPackages() {
    List<ReactPackage> packages = new PackageList(this).getPackages();
    packages.add(new OTAUpdatePackage());
    return packages;
}

// In ReactNativeHost:
@Override
protected String getJSBundleFile() {
    OTAUpdateManager otaManager = new OTAUpdateManager(getApplicationContext());
    File bundleFile = OTAUpdateManager.getBundleFile(getApplicationContext());
    
    if (otaManager.hasPendingUpdate() && bundleFile.exists()) {
        otaManager.setPendingUpdate(false);
        return bundleFile.getAbsolutePath();
    }
    return super.getJSBundleFile();
}
```

2. Add Kotlin coroutines dependency to `android/app/build.gradle`:

```gradle
dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
}
```

## Usage

```javascript
import OTAUpdateManager from 'react-native-ota-update';

// Download an update
async function downloadUpdate() {
  try {
    const result = await OTAUpdateManager.downloadUpdate(
      'https://your-server.com/bundle.js'
    );
    console.log(result); // "Bundle downloaded successfully. Restart app to apply."
    
    // Restart to apply update
    await OTAUpdateManager.restartApp();
  } catch (error) {
    console.error('Download failed:', error);
  }
}

// Get bundle information
async function checkUpdate() {
  const info = await OTAUpdateManager.getBundleInfo();
  console.log('Bundle exists:', info.exists);
  console.log('Bundle size:', info.size, 'bytes');
  console.log('Pending update:', info.pendingUpdate);
}

// Clear update
async function clearUpdate() {
  await OTAUpdateManager.clearUpdate();
  console.log('Update cleared');
}
```

## API Reference

### `downloadUpdate(url: string): Promise<string>`

Downloads a JavaScript bundle from the specified URL.

**Parameters:**
- `url` - The URL to download the bundle from

**Returns:** Promise that resolves with a success message

**Example:**
```javascript
await OTAUpdateManager.downloadUpdate('https://example.com/bundle.js');
```

### `getBundleInfo(): Promise<BundleInfo>`

Gets information about the downloaded bundle.

**Returns:** Promise that resolves with bundle information:
```typescript
{
  exists: boolean;        // Whether a bundle exists
  size: number;          // Size in bytes
  path: string;          // File system path
  pendingUpdate: boolean; // Whether update is pending
}
```

### `clearUpdate(): Promise<string>`

Clears the downloaded bundle and reverts to the original.

**Returns:** Promise that resolves with a success message

### `restartApp(): Promise<string>`

Restarts the app to apply the update.

**Note:** On iOS, this only shows a message. Users must manually restart. On Android, the app restarts programmatically.

**Returns:** Promise that resolves with a success message or instruction

### `getPlatform(): 'android' | 'ios'`

Returns the current platform.

### `canRestartProgrammatically(): boolean`

Returns `true` for Android, `false` for iOS.

## Creating Bundles

### For Android:

```bash
npx react-native bundle \
  --platform android \
  --dev false \
  --entry-file index.js \
  --bundle-output ./bundle/index.android.bundle \
  --assets-dest ./bundle/assets
```

### For iOS:

```bash
npx react-native bundle \
  --platform ios \
  --dev false \
  --entry-file index.js \
  --bundle-output ./bundle/main.jsbundle \
  --assets-dest ./bundle/assets
```

### For Expo:

**Android:**
```bash
npx expo export:embed \
  --platform android \
  --entry-file node_modules/expo/AppEntry.js \
  --bundle-output ./bundle/index.android.bundle \
  --assets-dest ./bundle/assets \
  --dev false
```

**iOS:**
```bash
npx expo export:embed \
  --platform ios \
  --entry-file node_modules/expo/AppEntry.js \
  --bundle-output ./bundle/main.jsbundle \
  --assets-dest ./bundle/assets \
  --dev false
```

## Server Setup

You need a server to host your bundles. Here's a simple Express.js example:

```javascript
const express = require('express');
const app = express();

app.get('/update/android', (req, res) => {
  res.sendFile('/path/to/index.android.bundle');
});

app.get('/update/ios', (req, res) => {
  res.sendFile('/path/to/main.jsbundle');
});

app.listen(3000);
```

Or use a CDN:
- AWS S3 + CloudFront
- Google Cloud Storage
- Firebase Storage
- Azure Blob Storage

## Platform Differences

| Feature | Android | iOS |
|---------|---------|-----|
| Bundle Name | `index.android.bundle` | `main.jsbundle` |
| Storage | Internal storage | Documents directory |
| Restart | Programmatic ✅ | Manual ⚠️ |
| Permissions | None needed | None needed |

## Security Considerations

### Production Checklist

- [ ] Use HTTPS only
- [ ] Add authentication (API keys, JWT)
- [ ] Implement bundle signature verification
- [ ] Add version control
- [ ] Monitor crash rates after updates
- [ ] Implement rollback mechanism
- [ ] Use gradual rollouts

### What Can Be Updated

✅ **Allowed:**
- JavaScript code
- React components
- Business logic
- UI changes
- Bug fixes

❌ **Not Allowed:**
- Native code (Kotlin/Swift/Objective-C)
- Native dependencies
- App permissions
- AndroidManifest.xml / Info.plist

## App Store Guidelines

### Google Play (Android)
- ✅ Fully allowed
- ✅ No restrictions

### Apple App Store (iOS)
- ✅ Allowed for JavaScript updates
- ⚠️ Don't change core app functionality
- ⚠️ Only update interpreted code (JavaScript)
- ⚠️ Document for App Review if asked

## Troubleshooting

### Android

**Module not found:**
```bash
cd android && ./gradlew clean && cd ..
npx react-native run-android
```

**Bundle not loading:**
```bash
adb shell run-as <your.package.name> ls -lh files/
```

### iOS

**Module not found:**
```bash
cd ios && pod install && cd ..
npx react-native run-ios
```

**Bundle not loading:**
Check Xcode console for `[OTAUpdateManager]` logs

## Example App

See the [example](./example) directory for a complete working example.

## Contributing

Contributions are welcome! Please open an issue or submit a PR.

## License

MIT

## Credits

Created by [Your Name]

## Support

- 🐛 [Report a bug](https://github.com/yourusername/react-native-ota-update/issues)
- 💡 [Request a feature](https://github.com/yourusername/react-native-ota-update/issues)
- 📖 [Documentation](https://github.com/yourusername/react-native-ota-update#readme)