npm package: @swiftpatch/react-native
Description: React Native SDK for SwiftPatch OTA updates with differential patching, automatic rollback, and bundle signing
Author: SwiftPatch
Latest version: 3.0.0

# SwiftPatch React Native SDK

[![npm version](https://img.shields.io/npm/v/@swiftpatch/react-native.svg)](https://www.npmjs.com/package/@swiftpatch/react-native)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![React Native](https://img.shields.io/badge/React%20Native-0.76+-blue.svg)](https://reactnative.dev/)
[![New Architecture](https://img.shields.io/badge/New%20Architecture-Supported-green.svg)](https://reactnative.dev/architecture/landing-page)
[![Docs](https://img.shields.io/badge/Docs-docs.swiftpatch.io-blue.svg)](https://docs.swiftpatch.io)

Over-the-Air (OTA) update SDK for React Native with **differential patching**, automatic rollback, and cryptographic verification. Optimized for the React Native New Architecture.

## ✨ Features

- 🚀 **Differential Patching** - Download only what changed (bsdiff/bspatch)
- 🔄 **Automatic Rollback** - Crash detection with instant recovery
- 🔒 **Bundle Signing** - RSA signature verification for security
- ⚡ **New Architecture** - TurboModules for 40% faster performance
- 🎯 **Dual-Slot System** - Safe production/staging environment switching
- 📦 **Small Updates** - Patches typically 10-50x smaller than full bundles
- 🛡️ **Type-Safe** - Full TypeScript support
- 🎨 **React Hooks** - Modern API with Provider pattern
- 📱 **Cross-Platform** - iOS and Android support

---

## 📦 Installation

```bash
npm install @swiftpatch/react-native
# or
yarn add @swiftpatch/react-native
```

### iOS Setup

#### Step 1: Install CocoaPods Dependencies

```bash
cd ios && pod install && cd ..
```

#### Step 2: Update AppDelegate

In `ios/AppDelegate.mm` (or `.swift`), update the bundle URL method:

**Objective-C:**
```objc
// ...other imports
#import "SwiftPatchModule.h"

@implementation AppDelegate
// ...other implementations
- (NSURL *)bundleURL
{
  #if DEBUG
    return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
  #else
    return [SwiftPatchModule getBundleURL];
  #endif
}
```

**Swift (RN 0.76+):**
```swift
import react_native_swiftpatch

override func bundleURL() -> URL? {
  #if DEBUG
    RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
  #else
    SwiftPatchModule.getBundleURL()
  #endif
}
```

#### Step 3: Add Credentials to Info.plist

Add your SwiftPatch credentials to `ios/YourApp/Info.plist`:

```xml
<plist version="1.0">
  <dict>
    <!-- ...other configs... -->
    <key>SwiftPatchProjectId</key>
    <string>YOUR_PROJECT_ID</string>
    <!-- App token starts with spb_... and is 46 characters long -->
    <key>SwiftPatchAppToken</key>
    <string>spb_YOUR_APP_TOKEN_HERE</string>
    <!-- Optional: Public key for bundle signing -->
    <key>SwiftPatchPublicKey</key>
    <string>YOUR_PUBLIC_KEY</string>
    <!-- ...other configs... -->
  </dict>
</plist>
```

### Android Setup

#### Step 1: Update MainApplication

In `android/app/src/main/java/.../MainApplication.kt`:

**Kotlin (RN 0.76+):**
```kotlin
// ...other imports
import com.swiftpatch.SwiftPatchModule

override val reactNativeHost: ReactNativeHost =
  object : DefaultReactNativeHost(this) {
    // ...other methods
    override fun getJSBundleFile(): String? {
      return SwiftPatchModule.getJSBundleFile(applicationContext)
    }
  }
```

**For React Native 0.82+:**
```kotlin
import com.swiftpatch.SwiftPatchModule

class MainApplication : Application(), ReactApplication {
  override val reactHost: ReactHost by lazy {
    getDefaultReactHost(
      context = applicationContext,
      packageList = PackageList(this).packages,
      jsBundleFilePath = SwiftPatchModule.getJSBundleFile(applicationContext)
    )
  }
}
```

#### Step 2: Add Credentials to strings.xml

Add your credentials to `android/app/src/main/res/values/strings.xml`:

```xml
<resources>
  <string name="app_name">YourApp</string>
  <!-- App token starts with spb_... and is 46 characters long -->
  <string name="SwiftPatchProjectId">YOUR_PROJECT_ID</string>
  <string name="SwiftPatchAppToken">spb_YOUR_APP_TOKEN_HERE</string>
  <!-- Optional: Public key for bundle signing -->
  <string name="SwiftPatchPublicKey">YOUR_PUBLIC_KEY</string>
</resources>
```

---

## 🚀 Quick Start

### 1. Wrap your app with SwiftPatchProvider

**Using Native Config (Recommended):**

If you've added credentials to `Info.plist` and `strings.xml`, you don't need to pass them in JS:

```tsx
import { withSwiftPatch } from '@swiftpatch/react-native';

function App() {
  return <YourApp />;
}

// Credentials are read from native config files
export default withSwiftPatch(App);
```

**Or with Provider pattern:**

```tsx
import { SwiftPatchProvider } from '@swiftpatch/react-native';

export default function App() {
  return (
    <SwiftPatchProvider config={{ debug: __DEV__ }}>
      <YourApp />
    </SwiftPatchProvider>
  );
}
```

**Using JS Config (Alternative):**

```tsx
import { SwiftPatchProvider } from '@swiftpatch/react-native';

export default function App() {
  return (
    <SwiftPatchProvider
      config={{
        deploymentKey: 'YOUR_DEPLOYMENT_KEY',
        serverUrl: 'https://your-server.com/api/v1', // optional
        debug: __DEV__,
      }}
    >
      <YourApp />
    </SwiftPatchProvider>
  );
}
```

### 2. Use the hook in your components

```tsx
import { useSwiftPatch, UpdateStatus } from '@swiftpatch/react-native';

function UpdateButton() {
  const {
    status,
    availableUpdate,
    downloadProgress,
    checkForUpdate,
    downloadUpdate,
    installUpdate,
    restart,
  } = useSwiftPatch();

  const handleUpdate = async () => {
    // Check for updates
    const update = await checkForUpdate();

    if (update) {
      // Download the update
      await downloadUpdate();

      // Install and restart
      await installUpdate();
      restart();
    }
  };

  if (status === UpdateStatus.DOWNLOADING) {
    return <Text>Downloading: {downloadProgress?.percentage}%</Text>;
  }

  return (
    <Button
      title={availableUpdate ? 'Update Available' : 'Check for Updates'}
      onPress={handleUpdate}
    />
  );
}
```

### 3. Or use the built-in modal

```tsx
import { useSwiftPatchModal, SwiftPatchModal } from '@swiftpatch/react-native';

function App() {
  const { showModal } = useSwiftPatchModal();

  return (
    <>
      <YourApp />
      <SwiftPatchModal />
      <Button title="Check for Updates" onPress={showModal} />
    </>
  );
}
```

---

## 📚 API Reference

### SwiftPatchProvider

```tsx
interface SwiftPatchConfig {
  /**
   * Deployment key from SwiftPatch dashboard.
   * Optional if configured in native files (Info.plist / strings.xml)
   */
  deploymentKey?: string;
  serverUrl?: string;
  checkOnResume?: boolean;
  checkInterval?: number;
  installMode?: InstallMode;
  mandatoryInstallMode?: InstallMode;
  debug?: boolean;
  customHeaders?: Record<string, string>;
  publicKey?: string;
  autoStabilizeAfterLaunches?: number;
  crashDetectionWindowMs?: number;       // Default: 10000
  maxCrashesBeforeRollback?: number;     // Default: 2
  autoStagingInDev?: boolean;            // Default: false
}
```

### Native Config Keys

| Key | Platform | Description |
|-----|----------|-------------|
| `SwiftPatchProjectId` | iOS/Android | Your project ID |
| `SwiftPatchAppToken` | iOS/Android | App token (starts with `spb_...`) |
| `SwiftPatchDeploymentKey` | iOS/Android | Alternative to AppToken |
| `SwiftPatchServerUrl` | iOS/Android | Custom server URL (optional) |
| `SwiftPatchPublicKey` | iOS/Android | Public key for signing (optional) |

### Verify Native Config

```tsx
import { getNativeConfig } from '@swiftpatch/react-native';

const config = await getNativeConfig();
console.log('Project ID:', config.projectId);
console.log('App Token:', config.appToken);
```

### useSwiftPatch Hook

```tsx
const {
  // State
  status,                    // Current update status
  downloadProgress,          // Download progress (0-100%)
  currentBundle,             // Currently installed bundle info
  availableUpdate,           // Available update info
  isRestartRequired,         // Whether restart is needed
  error,                     // Last error
  lastCheckedAt,             // Last check timestamp
  slotMetadata,             // Dual-slot system metadata
  environment,              // Current environment (PROD/STAGE)

  // Actions
  checkForUpdate,           // Check for available updates
  downloadUpdate,           // Download available update
  installUpdate,            // Install downloaded update
  restart,                  // Restart app to apply update
  rollback,                 // Rollback to previous version
  clearPendingUpdate,       // Clear pending update
  getCurrentBundle,         // Get current bundle info
  stabilize,                // Stabilize current bundle
  switchEnvironment,        // Switch PROD/STAGE environment
  getSlotMetadata,          // Get slot metadata
  markMounted,              // Mark app as mounted (crash detection)
  downloadStageBundle,      // Download staging bundle
} = useSwiftPatch();
```

### Imperative API (Non-React)

For use outside React components:

```tsx
import { SwiftPatch } from '@swiftpatch/react-native';

const swiftPatch = new SwiftPatch({
  deploymentKey: 'YOUR_KEY',
});

await swiftPatch.init();

const update = await swiftPatch.checkForUpdate();
if (update) {
  await swiftPatch.downloadAndInstall(update);
  swiftPatch.restart();
}
```

---

## 🎯 Install Modes

```tsx
enum InstallMode {
  IMMEDIATE = 'immediate',           // Install and restart immediately
  ON_NEXT_RESTART = 'onNextRestart', // Install on next app restart
  ON_NEXT_RESUME = 'onNextResume',   // Install when app resumes
}
```

---

## 🔧 Advanced Features

### Differential Patching

SwiftPatch automatically uses differential patching when available:

```tsx
// Server determines if patch is available
// SDK handles full bundle vs patch automatically
await downloadUpdate();

// Patch files are typically 10-50x smaller
// e.g., 50MB bundle → 2MB patch
```

### Automatic Rollback

The crash detection window and crash threshold are configurable:

```tsx
<SwiftPatchProvider
  config={{
    crashDetectionWindowMs: 15_000,     // 15 second window (default: 10s)
    maxCrashesBeforeRollback: 3,        // Allow 3 crashes before rollback (default: 2)
  }}
>
  <App />
</SwiftPatchProvider>
```

```tsx
const { rollback } = useSwiftPatch();

// Manual rollback
await rollback();
```

### Bundle Signing (Optional)

```tsx
<SwiftPatchProvider
  config={{
    deploymentKey: 'YOUR_KEY',
    publicKey: 'YOUR_RSA_PUBLIC_KEY',
  }}
>
  <App />
</SwiftPatchProvider>

// SDK automatically verifies signatures
```

### Environment Switching (PROD/STAGE)

```tsx
const { switchEnvironment, environment } = useSwiftPatch();

// Switch to staging
await switchEnvironment(EnvironmentMode.STAGING);

// Download and test staging bundle
await downloadStageBundle(url, hash);

// Switch back to production
await switchEnvironment(EnvironmentMode.PRODUCTION);
```

---

## 📋 Common Patterns

### Silent Background Update

Download and install updates silently. The update applies on the next app restart — no UI code needed.

```tsx
import { SwiftPatchProvider, InstallMode } from '@swiftpatch/react-native';

export default function App() {
  return (
    <SwiftPatchProvider
      config={{
        installMode: InstallMode.ON_NEXT_RESTART,
        checkOnResume: true,
        checkInterval: 300_000, // Check every 5 minutes
        debug: __DEV__,
      }}
    >
      <YourApp />
    </SwiftPatchProvider>
  );
}
```

### Mandatory Update with Blocking UI

Force users to update before using the app. Shows a full-screen blocking overlay during download.

```tsx
import React, { useEffect, useState } from 'react';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';
import {
  SwiftPatchProvider,
  useSwiftPatch,
  UpdateStatus,
  InstallMode,
} from '@swiftpatch/react-native';

function MandatoryUpdateGate({ children }: { children: React.ReactNode }) {
  const {
    status,
    availableUpdate,
    downloadProgress,
    checkForUpdate,
    downloadUpdate,
    installUpdate,
    restart,
  } = useSwiftPatch();
  const [checking, setChecking] = useState(true);

  useEffect(() => {
    (async () => {
      const update = await checkForUpdate();
      if (update?.isMandatory) {
        await downloadUpdate();
        await installUpdate();
        restart();
      }
      setChecking(false);
    })();
  }, []);

  if (
    checking ||
    (availableUpdate?.isMandatory && status !== UpdateStatus.UP_TO_DATE)
  ) {
    return (
      <View style={styles.overlay}>
        <ActivityIndicator size="large" color="#007AFF" />
        <Text style={styles.text}>
          {status === UpdateStatus.DOWNLOADING
            ? `Updating... ${downloadProgress?.percentage ?? 0}%`
            : 'Checking for updates...'}
        </Text>
      </View>
    );
  }

  return <>{children}</>;
}

const styles = StyleSheet.create({
  overlay: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  text: { marginTop: 16, fontSize: 16, color: '#333' },
});

export default function App() {
  return (
    <SwiftPatchProvider
      config={{ mandatoryInstallMode: InstallMode.IMMEDIATE }}
    >
      <MandatoryUpdateGate>
        <YourApp />
      </MandatoryUpdateGate>
    </SwiftPatchProvider>
  );
}
```

### Custom Update Banner

Show a dismissible banner when an update is available. Let users choose when to update.

```tsx
import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useSwiftPatch, UpdateStatus } from '@swiftpatch/react-native';

export function UpdateBanner() {
  const {
    status,
    availableUpdate,
    downloadProgress,
    checkForUpdate,
    downloadUpdate,
    installUpdate,
    restart,
  } = useSwiftPatch();
  const [dismissed, setDismissed] = useState(false);

  useEffect(() => {
    checkForUpdate();
  }, []);

  if (dismissed || !availableUpdate || status === UpdateStatus.UP_TO_DATE) {
    return null;
  }

  const handleUpdate = async () => {
    await downloadUpdate();
    await installUpdate();
    restart();
  };

  return (
    <View style={styles.banner}>
      <Text style={styles.title}>
        Update Available v{availableUpdate.version}
      </Text>
      {status === UpdateStatus.DOWNLOADING ? (
        <Text style={styles.progress}>
          Downloading... {downloadProgress?.percentage ?? 0}%
        </Text>
      ) : (
        <View style={styles.actions}>
          <TouchableOpacity onPress={handleUpdate} style={styles.updateBtn}>
            <Text style={styles.updateText}>Update Now</Text>
          </TouchableOpacity>
          <TouchableOpacity
            onPress={() => setDismissed(true)}
            style={styles.laterBtn}
          >
            <Text style={styles.laterText}>Later</Text>
          </TouchableOpacity>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  banner: {
    backgroundColor: '#007AFF',
    padding: 12,
    marginHorizontal: 16,
    borderRadius: 8,
    marginTop: 8,
    alignItems: 'center',
  },
  title: { color: '#fff', fontWeight: '600', fontSize: 14 },
  progress: { color: '#fff', fontSize: 12, marginTop: 4 },
  actions: { flexDirection: 'row', marginTop: 8, gap: 12 },
  updateBtn: {
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    paddingVertical: 6,
    borderRadius: 6,
  },
  updateText: { color: '#007AFF', fontWeight: '600' },
  laterBtn: { paddingHorizontal: 16, paddingVertical: 6 },
  laterText: { color: 'rgba(255,255,255,0.8)' },
});
```

### Error Handling with Descriptions

Use `getErrorDescription()` to show helpful error messages to developers:

```tsx
import {
  useSwiftPatch,
  getErrorDescription,
} from '@swiftpatch/react-native';

function MyComponent() {
  const { error } = useSwiftPatch();

  if (error) {
    const info = getErrorDescription(error.code);
    console.warn(info.description);
    console.warn('Fix:', info.troubleshooting);
  }

  return null;
}
```

---

## 🆕 New Architecture Support

SwiftPatch v2.0+ fully supports the React Native New Architecture:

### Enable New Architecture

**iOS** (`ios/Podfile`):
```ruby
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
```

**Android** (`android/gradle.properties`):
```properties
newArchEnabled=true
```

### Performance Benefits

| Operation | Legacy | TurboModule | Improvement |
|-----------|--------|-------------|-------------|
| Check Update | 15ms | 2ms | 87% faster |
| Get Bundle Info | 8ms | 1ms | 87% faster |
| Native Calls | 3-5ms | 0.5-1ms | 80% faster |

### Detection

```tsx
import { IS_TURBO_MODULE_ENABLED } from '@swiftpatch/react-native';

console.log('Using TurboModules:', IS_TURBO_MODULE_ENABLED);
```

---

## 🧪 Testing

```bash
npm test
```

All core functionality is tested:
- Update checking
- Download & installation
- Rollback mechanisms
- Cryptographic verification
- Event handling

---

## 📱 Platform Requirements

| Platform | Minimum | Recommended |
|----------|---------|-------------|
| iOS | 13.4+ | 15.0+ |
| Android | API 24+ (7.0) | API 31+ (12) |
| React Native | 0.76.0+ | 0.76.5+ |
| React | 18.2.0+ | 18.3.0+ |

---

## 🔒 Security

- **Bundle Signing**: Optional RSA signature verification
- **HTTPS Only**: All downloads over secure connections
- **Hash Verification**: SHA-256 hash checking for all bundles
- **Integrity Checks**: Automatic corruption detection

---

## 📊 Bundle Size

| File | Size | Compressed |
|------|------|------------|
| Core JS | ~45KB | ~12KB |
| Native (iOS) | ~150KB | - |
| Native (Android) | ~200KB | - |

---

## 🛠️ Development

```bash
# Clone the repository
git clone https://github.com/codewprincee/react-native-swiftpatch.git

# Install dependencies
npm install

# Run tests
npm test

# Build
npm run prepare

# Run example app
npm run example start
```

---

## 🐛 Troubleshooting

### iOS Build Errors

```bash
cd ios
rm -rf Pods Podfile.lock
pod install
cd ..
```

### Android Build Errors

```bash
cd android
./gradlew clean
cd ..
```

### Type Errors

```bash
npm run typescript
```

---

## 📄 License

MIT License - see [LICENSE](LICENSE) for details

---

## 🤝 Contributing

Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) first.

---

## 📞 Support

- 📧 Email: sdk@swiftpatch.io
- 🐛 Issues: [GitHub Issues](https://github.com/codewprincee/react-native-swiftpatch/issues)
- 📖 Docs: [docs.swiftpatch.io](https://docs.swiftpatch.io)

---

## 🎉 Acknowledgments

Built with:
- [React Native](https://reactnative.dev/)
- [bsdiff/bspatch](http://www.daemonology.net/bsdiff/) for differential patching
- [TypeScript](https://www.typescriptlang.org/)

---

**Made with ❤️ by the SwiftPatch Team**