{"_id":"@dangtrungthang123/react-native-ota","name":"@dangtrungthang123/react-native-ota","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@dangtrungthang123/react-native-ota","version":"1.0.0","description":"A comprehensive React Native library for Over-The-Air (OTA) updates with download, extraction, and automatic bundle loading capabilities for both Android and iOS platforms.","main":"./lib/module/index.js","types":"./lib/typescript/src/index.d.ts","exports":{".":{"source":"./src/index.tsx","types":"./lib/typescript/src/index.d.ts","default":"./lib/module/index.js"},"./package.json":"./package.json"},"scripts":{"example":"yarn workspace @speed/react-native-ota-example","test":"jest","typecheck":"tsc","lint":"eslint \"**/*.{js,ts,tsx}\"","clean":"del-cli android/build example/android/build example/android/app/build example/ios/build lib","prepare":"bob build","release":"release-it --only-version"},"keywords":["react-native","ios","android","ota","over-the-air","update","bundle","download","hot-update","turbo-module"],"repository":{"type":"git","url":"git+https://github.com/dangtrungthang/speed-react-native-ota.git"},"author":{"name":"dangtrungthang123","email":"dangtrungthang123@gmail.com","url":"https://github.com/dangtrungthang"},"license":"MIT","bugs":{"url":"https://github.com/dangtrungthang/speed-react-native-ota/issues"},"homepage":"https://github.com/dangtrungthang/speed-react-native-ota#readme","publishConfig":{"registry":"https://registry.npmjs.org/"},"devDependencies":{"@commitlint/config-conventional":"^19.6.0","@eslint/compat":"^1.2.7","@eslint/eslintrc":"^3.3.0","@eslint/js":"^9.22.0","@evilmartians/lefthook":"^1.5.0","@react-native-community/cli":"15.0.0-alpha.2","@react-native/babel-preset":"0.79.2","@react-native/eslint-config":"^0.78.0","@release-it/conventional-changelog":"^9.0.2","@types/jest":"^29.5.5","@types/react":"^19.0.0","commitlint":"^19.6.1","del-cli":"^5.1.0","eslint":"^9.22.0","eslint-config-prettier":"^10.1.1","eslint-plugin-prettier":"^5.2.3","jest":"^29.7.0","prettier":"^3.0.3","react":"19.0.0","react-native":"0.79.2","react-native-builder-bob":"^0.40.11","release-it":"^17.10.0","turbo":"^1.10.7","typescript":"^5.8.3"},"peerDependencies":{"react":"*","react-native":"*"},"workspaces":["example"],"packageManager":"yarn@3.6.1","jest":{"preset":"react-native","modulePathIgnorePatterns":["<rootDir>/example/node_modules","<rootDir>/lib/"]},"commitlint":{"extends":["@commitlint/config-conventional"]},"release-it":{"git":{"commitMessage":"chore: release ${version}","tagName":"v${version}"},"npm":{"publish":true},"github":{"release":true},"plugins":{"@release-it/conventional-changelog":{"preset":{"name":"angular"}}}},"prettier":{"quoteProps":"consistent","singleQuote":true,"tabWidth":2,"trailingComma":"es5","useTabs":false},"react-native-builder-bob":{"source":"src","output":"lib","targets":[["module",{"esm":true}],["typescript",{"project":"tsconfig.build.json"}]]},"codegenConfig":{"name":"ReactNativeOtaSpec","type":"modules","jsSrcsDir":"src","android":{"javaPackageName":"com.speed.reactnativeota"}},"create-react-native-library":{"languages":"kotlin-objc","type":"turbo-module","version":"0.50.3"},"_id":"@dangtrungthang123/react-native-ota@1.0.0","gitHead":"b795069a38dc83045e69b3d8cefdc53060613afb","_nodeVersion":"22.14.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-zHObAEa7dAqcNJlsATIE1mhqlr/XzKK7p/PfW74ye3A1ogf+GWH8TtzDNXQbhu7d6zc4cauVuq7rNjiD1Pl84g==","shasum":"11ccdee9bb1fe06378cae8814ff4e9adbb423ab9","tarball":"https://registry.npmjs.org/@dangtrungthang123/react-native-ota/-/react-native-ota-1.0.0.tgz","fileCount":29,"unpackedSize":45861,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDSds1Xl1w2OaQmDv72/KfvhScHCRu9vAcR9ASElZEWgwIgKzzqx/84gnxqRMVLeQdbDszHbFxn+u9w8RA2mej3rjU="}]},"_npmUser":{"name":"dangtrungthang123","email":"dangtrungthang123@gmail.com"},"directories":{},"maintainers":[{"name":"dangtrungthang123","email":"dangtrungthang123@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-native-ota_1.0.0_1748581680601_0.7443255287088058"},"_hasShrinkwrap":false}},"time":{"created":"2025-05-30T05:08:00.486Z","1.0.0":"2025-05-30T05:08:00.797Z","modified":"2025-05-30T05:08:01.096Z"},"maintainers":[{"name":"dangtrungthang123","email":"dangtrungthang123@gmail.com"}],"description":"A comprehensive React Native library for Over-The-Air (OTA) updates with download, extraction, and automatic bundle loading capabilities for both Android and iOS platforms.","homepage":"https://github.com/dangtrungthang/speed-react-native-ota#readme","keywords":["react-native","ios","android","ota","over-the-air","update","bundle","download","hot-update","turbo-module"],"repository":{"type":"git","url":"git+https://github.com/dangtrungthang/speed-react-native-ota.git"},"author":{"name":"dangtrungthang123","email":"dangtrungthang123@gmail.com","url":"https://github.com/dangtrungthang"},"bugs":{"url":"https://github.com/dangtrungthang/speed-react-native-ota/issues"},"license":"MIT","readme":"# @dangtrungthang123/react-native-ota\n\nA comprehensive React Native library for **Over-The-Air (OTA) updates** with download, extraction, and automatic bundle loading capabilities for both Android and iOS platforms.\n\n## 🚀 Features\n\n- ✅ **Cross-platform** - Works on both Android and iOS\n- ✅ **Download OTA updates** from remote URLs (ZIP files)\n- ✅ **Extract ZIP bundles** automatically\n- ✅ **Automatic bundle loading** - Apps automatically use OTA bundles when available\n- ✅ **Debug/Release mode detection** - Handles development and production scenarios\n- ✅ **TypeScript support** - Full type definitions included\n- ✅ **New Architecture ready** - Built with Turbo Modules\n- ✅ **Zero configuration** - Works out of the box\n\n## 📦 Installation\n\n```bash\nnpm install @dangtrungthang123/react-native-ota\n# or\nyarn add @dangtrungthang123/react-native-ota\n```\n\n### iOS Setup\n\nAdd to your `ios/Podfile`:\n\n```ruby\npod 'ReactNativeOta', :path => '../node_modules/@speed/react-native-ota'\n```\n\nThen run:\n\n```bash\ncd ios && pod install\n```\n\n### Android Setup\n\nNo additional setup required - auto-linking handles everything!\n\n## 🔧 Configuration\n\n### iOS Bundle Loading (AppDelegate.swift)\n\n```swift\nimport UIKit\nimport React\nimport React_RCTAppDelegate\nimport ReactAppDependencyProvider\n\n@main\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n  var window: UIWindow?\n  var reactNativeDelegate: ReactNativeDelegate?\n  var reactNativeFactory: RCTReactNativeFactory?\n\n  func application(\n    _ application: UIApplication,\n    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil\n  ) -> Bool {\n    let delegate = ReactNativeDelegate()\n    let factory = RCTReactNativeFactory(delegate: delegate)\n    delegate.dependencyProvider = RCTAppDependencyProvider()\n\n    reactNativeDelegate = delegate\n    reactNativeFactory = factory\n\n    window = UIWindow(frame: UIScreen.main.bounds)\n\n    factory.startReactNative(\n      withModuleName: \"YourAppName\",\n      in: window,\n      launchOptions: launchOptions\n    )\n\n    return true\n  }\n}\n\nclass ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {\n  override func sourceURL(for bridge: RCTBridge) -> URL? {\n    self.bundleURL()\n  }\n\n  override func bundleURL() -> URL? {\n    // Check for OTA bundle first\n    let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]\n    let otaDirectory = (documentsPath as NSString).appendingPathComponent(\"otaupdate\")\n    let otaBundlePath = (otaDirectory as NSString).appendingPathComponent(\"index.ios.bundle\")\n    \n    if FileManager.default.fileExists(atPath: otaBundlePath) {\n      print(\"OTA: Loading OTA bundle from \\(otaBundlePath)\")\n      return URL(fileURLWithPath: otaBundlePath)\n    }\n    \n    // Fallback to default behavior\n#if DEBUG\n    print(\"OTA: Debug mode - using Metro server\")\n    return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: \"index\")\n#else\n    print(\"OTA: Release mode - using main bundle\")\n    return Bundle.main.url(forResource: \"main\", withExtension: \"jsbundle\")\n#endif\n  }\n}\n```\n\n### Android Bundle Loading (MainApplication.kt)\n\n```kotlin\npackage your.package.name\n\nimport android.app.Application\nimport com.facebook.react.PackageList\nimport com.facebook.react.ReactApplication\nimport com.facebook.react.ReactHost\nimport com.facebook.react.ReactNativeHost\nimport com.facebook.react.ReactPackage\nimport com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load\nimport com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost\nimport com.facebook.react.defaults.DefaultReactNativeHost\nimport com.facebook.react.soloader.OpenSourceMergedSoMapping\nimport com.facebook.soloader.SoLoader\nimport java.io.File\n\nclass MainApplication : Application(), ReactApplication {\n\n  override val reactNativeHost: ReactNativeHost =\n      object : DefaultReactNativeHost(this) {\n        override fun getPackages(): List<ReactPackage> =\n            PackageList(this).packages\n\n        override fun getJSMainModuleName(): String = \"index\"\n\n        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG\n\n        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED\n        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED\n\n        // Override to use OTA bundle when available\n        override fun getJSBundleFile(): String? {\n          return try {\n            // Check if OTA bundle exists\n            val otaDir = File(applicationContext.filesDir, \"otaupdate\")\n            val otaBundleFile = File(otaDir, \"index.android.bundle\")\n            \n            if (otaBundleFile.exists()) {\n              // Use OTA bundle\n              android.util.Log.d(\"OTA\", \"Loading OTA bundle: ${otaBundleFile.absolutePath}\")\n              otaBundleFile.absolutePath\n            } else {\n              // Use default behavior (assets or Metro in debug)\n              if (BuildConfig.DEBUG) {\n                android.util.Log.d(\"OTA\", \"Debug mode: using Metro server\")\n                null // Let Metro handle it\n              } else {\n                android.util.Log.d(\"OTA\", \"Release mode: using assets bundle\")\n                super.getJSBundleFile()\n              }\n            }\n          } catch (e: Exception) {\n            android.util.Log.e(\"OTA\", \"Error checking OTA bundle: ${e.message}\")\n            super.getJSBundleFile()\n          }\n        }\n      }\n\n  override val reactHost: ReactHost\n    get() = getDefaultReactHost(applicationContext, reactNativeHost)\n\n  override fun onCreate() {\n    super.onCreate()\n    SoLoader.init(this, OpenSourceMergedSoMapping)\n    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {\n      load()\n    }\n  }\n}\n```\n\n## 📱 Usage\n\n```typescript\nimport {\n  downloadAndInstallOTAUpdate,\n  checkOTABundleExists,\n  isDebugMode,\n  getOTADirectory,\n  getJSBundleFile,\n  downloadOTAUpdate,\n  extractOTAUpdate,\n} from '@dangtrungthang123/react-native-ota';\n\n// Check current status\nconst hasOTABundle = checkOTABundleExists();\nconst debugMode = isDebugMode();\nconst otaDirectory = getOTADirectory();\nconst currentBundle = getJSBundleFile();\n\nconsole.log('OTA Bundle available:', hasOTABundle);\nconsole.log('Debug mode:', debugMode);\nconsole.log('OTA Directory:', otaDirectory);\nconsole.log('Current bundle:', currentBundle);\n\n// Download and install OTA update\nasync function updateApp() {\n  try {\n    const success = await downloadAndInstallOTAUpdate(\n      'https://your-server.com/updates/app-bundle.zip'\n    );\n    \n    if (success) {\n      console.log('Update downloaded successfully!');\n      // Restart the app to use new bundle\n    } else {\n      console.log('Update failed');\n    }\n  } catch (error) {\n    console.error('Update error:', error);\n  }\n}\n\n// Or use separate download and extract steps\nasync function manualUpdate() {\n  try {\n    // Download ZIP file\n    const zipPath = await downloadOTAUpdate(\n      'https://your-server.com/updates/app-bundle.zip'\n    );\n    \n    if (zipPath) {\n      // Extract ZIP file\n      const success = await extractOTAUpdate(zipPath);\n      console.log('Extract success:', success);\n    }\n  } catch (error) {\n    console.error('Manual update error:', error);\n  }\n}\n```\n\n## 🎯 API Reference\n\n### Core Functions\n\n#### `downloadAndInstallOTAUpdate(url: string): Promise<boolean>`\nDownloads a ZIP file from the given URL and extracts it to the OTA directory.\n\n**Parameters:**\n- `url` - The URL of the ZIP file containing the update bundle\n\n**Returns:** Promise that resolves to `true` if successful, `false` otherwise\n\n---\n\n#### `downloadOTAUpdate(url: string): Promise<string | null>`\nDownloads a ZIP file from the given URL.\n\n**Parameters:**\n- `url` - The URL of the ZIP file\n\n**Returns:** Promise that resolves to the local file path if successful, `null` otherwise\n\n---\n\n#### `extractOTAUpdate(zipPath: string): Promise<boolean>`\nExtracts a ZIP file to the OTA directory.\n\n**Parameters:**\n- `zipPath` - Local path to the ZIP file\n\n**Returns:** Promise that resolves to `true` if successful, `false` otherwise\n\n---\n\n### Status Functions\n\n#### `checkOTABundleExists(): boolean`\nChecks if an OTA bundle is currently available.\n\n**Returns:** `true` if OTA bundle exists, `false` otherwise\n\n---\n\n#### `isDebugMode(): boolean`\nChecks if the app is running in debug mode.\n\n**Returns:** `true` if in debug mode, `false` if in release mode\n\n---\n\n#### `getOTADirectory(): string`\nGets the path to the OTA updates directory.\n\n**Returns:** Absolute path to the OTA directory\n\n---\n\n#### `getJSBundleFile(): string`\nGets the path to the currently loaded JS bundle.\n\n**Returns:** Path to the current bundle file\n\n---\n\n#### `multiply(a: number, b: number): number`\nA simple utility function for testing the library integration.\n\n**Parameters:**\n- `a` - First number\n- `b` - Second number\n\n**Returns:** The product of `a` and `b`\n\n## 🏗️ Bundle Creation\n\nTo create OTA bundles, you can use the React Native CLI:\n\n### Android Bundle\n```bash\nnpx react-native bundle \\\n  --platform android \\\n  --dev false \\\n  --entry-file index.js \\\n  --bundle-output index.android.bundle \\\n  --assets-dest ./assets \\\n  --sourcemap-output index.android.bundle.map\n```\n\n### iOS Bundle\n```bash\nnpx react-native bundle \\\n  --platform ios \\\n  --dev false \\\n  --entry-file index.js \\\n  --bundle-output index.ios.bundle \\\n  --assets-dest ./assets \\\n  --sourcemap-output index.ios.bundle.map\n```\n\n### Create ZIP Package\n```bash\n# Create a ZIP file containing both bundles\nzip -r app-update.zip index.android.bundle index.ios.bundle assets/\n```\n\n## 🔄 How It Works\n\n1. **Bundle Detection**: The app checks for OTA bundles in the designated directory on startup\n2. **Automatic Loading**: If an OTA bundle exists, it's loaded instead of the default bundle\n3. **Fallback**: If no OTA bundle is found, the app uses the default bundle (Metro in debug, assets in release)\n4. **Download & Extract**: The library downloads ZIP files and extracts them to the correct location\n5. **Cross-Platform**: Works seamlessly on both Android and iOS with platform-specific optimizations\n\n## 🛡️ Platform Differences\n\n### Android\n- OTA bundles stored in: `/data/data/[package]/files/otaupdate/`\n- Uses `index.android.bundle`\n- ZIP extraction via native ZIP APIs\n- HTTP downloads with OkHttp\n\n### iOS\n- OTA bundles stored in: `Documents/otaupdate/`\n- Uses `index.ios.bundle`\n- ZIP extraction via command line `unzip`\n- HTTP downloads with NSData\n\n## 🐛 Troubleshooting\n\n### Bundle Not Loading\n1. Check if OTA bundle exists: `checkOTABundleExists()`\n2. Verify bundle file names match platform expectations\n3. Ensure app has proper file permissions\n4. Check logs for bundle loading messages\n\n### Download Issues\n1. Verify URL is accessible\n2. Check network permissions\n3. Ensure sufficient storage space\n4. Validate ZIP file format\n\n### Debug Mode\n- In debug mode, Metro server takes precedence over OTA bundles\n- Build a release version to test OTA functionality\n- Use `isDebugMode()` to detect current mode\n\n## 📄 License\n\nMIT License - see the [LICENSE](LICENSE) file for details.\n\n## 🤝 Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## 📞 Support\n\nIf you have any questions or issues, please create an issue on [GitHub](https://github.com/dangtrungthang/speed-react-native-ota/issues).\n","readmeFilename":"README.md","_rev":"1-2cd7e3be2befdcaf5ea85b9efe414dab"}