@vanikya/ota-react-native 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,12 @@ def safeExtGet(prop, fallback) {
5
5
  apply plugin: 'com.android.library'
6
6
  apply plugin: 'kotlin-android'
7
7
 
8
+ // Get expo-modules-core if available
9
+ def expoModulesCore = null
10
+ try {
11
+ expoModulesCore = project(':expo-modules-core')
12
+ } catch (Exception ignored) {}
13
+
8
14
  android {
9
15
  namespace "com.otaupdate"
10
16
 
@@ -51,4 +57,10 @@ dependencies {
51
57
  implementation "com.facebook.react:react-android:+"
52
58
  // ProcessPhoenix for graceful app restart
53
59
  implementation "com.jakewharton:process-phoenix:3.0.0"
60
+
61
+ // Expo modules core - needed for ReactNativeHostHandler interface
62
+ // compileOnly so we don't bundle it (it's provided by the app)
63
+ if (expoModulesCore != null) {
64
+ compileOnly expoModulesCore
65
+ }
54
66
  }
@@ -0,0 +1,86 @@
1
+ package com.otaupdate
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import com.facebook.react.bridge.ReactContext
6
+ import expo.modules.core.interfaces.Package
7
+ import expo.modules.core.interfaces.ReactNativeHostHandler
8
+
9
+ /**
10
+ * Expo Package implementation that provides ReactNativeHostHandler.
11
+ * This is the key integration point for Expo's new architecture (bridgeless mode).
12
+ *
13
+ * In Expo's new architecture, bundle loading doesn't go through DefaultReactNativeHost.getJSBundleFile().
14
+ * Instead, Expo's core scans for packages implementing this interface and calls the handlers.
15
+ *
16
+ * This is exactly how expo-updates works.
17
+ */
18
+ class OTAUpdateExpoPackage : Package {
19
+
20
+ companion object {
21
+ private const val TAG = "OTAUpdateExpoPackage"
22
+ }
23
+
24
+ override fun createReactNativeHostHandlers(context: Context): List<ReactNativeHostHandler> {
25
+ Log.d(TAG, "=== createReactNativeHostHandlers called ===")
26
+
27
+ val handler = object : ReactNativeHostHandler {
28
+
29
+ override fun getJSBundleFile(useDeveloperSupport: Boolean): String? {
30
+ Log.d(TAG, "=== getJSBundleFile called (useDeveloperSupport=$useDeveloperSupport) ===")
31
+
32
+ // In development mode, let Metro bundler handle it
33
+ if (useDeveloperSupport) {
34
+ Log.d(TAG, "Developer support enabled, using Metro bundler")
35
+ return null
36
+ }
37
+
38
+ // Get the bundle path from OTAUpdateHelper
39
+ val bundlePath = OTAUpdateHelper.getJSBundleFile(context)
40
+ if (bundlePath != null) {
41
+ Log.d(TAG, "Returning OTA bundle: $bundlePath")
42
+ } else {
43
+ Log.d(TAG, "No OTA bundle, using default")
44
+ }
45
+ return bundlePath
46
+ }
47
+
48
+ override fun getBundleAssetName(useDeveloperSupport: Boolean): String? {
49
+ Log.d(TAG, "=== getBundleAssetName called (useDeveloperSupport=$useDeveloperSupport) ===")
50
+
51
+ // In development mode, use default behavior
52
+ if (useDeveloperSupport) {
53
+ return null
54
+ }
55
+
56
+ // Check if we have an OTA bundle
57
+ val bundlePath = OTAUpdateHelper.getJSBundleFile(context)
58
+ if (bundlePath != null) {
59
+ // Return null to force React Native to use getJSBundleFile() instead of bundled assets
60
+ Log.d(TAG, "OTA bundle exists, returning null to force getJSBundleFile")
61
+ return null
62
+ }
63
+
64
+ // No OTA bundle, use default bundled assets
65
+ return null
66
+ }
67
+
68
+ override fun onWillCreateReactInstance(useDeveloperSupport: Boolean) {
69
+ Log.d(TAG, "=== onWillCreateReactInstance (useDeveloperSupport=$useDeveloperSupport) ===")
70
+ }
71
+
72
+ override fun onDidCreateReactInstance(useDeveloperSupport: Boolean, reactContext: ReactContext) {
73
+ Log.d(TAG, "=== onDidCreateReactInstance ===")
74
+ }
75
+
76
+ override fun onReactInstanceException(useDeveloperSupport: Boolean, exception: Exception) {
77
+ Log.e(TAG, "=== onReactInstanceException ===", exception)
78
+ // If there's an exception with OTA bundle, we could clear it here for recovery
79
+ // For now, just log it
80
+ }
81
+ }
82
+
83
+ Log.d(TAG, "Returning ReactNativeHostHandler")
84
+ return listOf(handler)
85
+ }
86
+ }
package/app.plugin.js CHANGED
@@ -1,116 +1,20 @@
1
- const { withMainApplication, withAppDelegate } = require('@expo/config-plugins');
1
+ const { withAppDelegate } = require('@expo/config-plugins');
2
2
 
3
3
  /**
4
4
  * OTA Update Expo Config Plugin
5
5
  *
6
6
  * This plugin modifies the native code to enable OTA bundle loading:
7
- * - Android: Modifies MainApplication.kt to override getJSBundleFile and getBundleAssetName
7
+ * - Android: Uses Expo Package interface (ReactNativeHostHandler) via expo-module.config.json
8
+ * No MainApplication.kt modification needed!
8
9
  * - iOS: Modifies bundleURL() in AppDelegate.swift
9
10
  */
10
11
 
11
12
  function withOTAUpdateAndroid(config) {
12
- return withMainApplication(config, (config) => {
13
- let contents = config.modResults.contents;
14
-
15
- // Check if already modified
16
- if (contents.includes('OTAUpdateHelper')) {
17
- console.log('[OTAUpdate] Android: OTAUpdateHelper already present, skipping');
18
- return config;
19
- }
20
-
21
- console.log('[OTAUpdate] Android: Analyzing MainApplication.kt structure...');
22
-
23
- // Add import for OTAUpdateHelper at the top
24
- const packageMatch = contents.match(/^package\s+[\w.]+\s*\n/m);
25
- if (packageMatch) {
26
- const insertPos = packageMatch.index + packageMatch[0].length;
27
- const importStatement = `\nimport com.otaupdate.OTAUpdateHelper\n`;
28
- contents = contents.slice(0, insertPos) + importStatement + contents.slice(insertPos);
29
- console.log('[OTAUpdate] Android: Added import for OTAUpdateHelper');
30
- }
31
-
32
- let injected = false;
33
-
34
- // ============================================================
35
- // Strategy 1: Look for DefaultReactNativeHost and add overrides
36
- // This works for both old and new architecture Expo apps
37
- // ============================================================
38
- const defaultHostPattern = /(object\s*:\s*DefaultReactNativeHost\s*\([^)]*\)\s*\{)/;
39
- if (defaultHostPattern.test(contents)) {
40
- console.log('[OTAUpdate] Android: Detected DefaultReactNativeHost');
41
-
42
- const bundleOverride = `
43
- override fun getJSBundleFile(): String? {
44
- return OTAUpdateHelper.getJSBundleFile(applicationContext)
45
- }
46
-
47
- override fun getBundleAssetName(): String? {
48
- // Return null if OTA bundle exists to force using getJSBundleFile
49
- val otaBundle = OTAUpdateHelper.getJSBundleFile(applicationContext)
50
- if (otaBundle != null) {
51
- return null
52
- }
53
- return super.getBundleAssetName()
54
- }
55
- `;
56
- contents = contents.replace(defaultHostPattern, `$1${bundleOverride}`);
57
- console.log('[OTAUpdate] Android: Injected getJSBundleFile and getBundleAssetName overrides');
58
- injected = true;
59
- }
60
-
61
- // ============================================================
62
- // Strategy 2: Look for getUseDeveloperSupport and insert before it
63
- // ============================================================
64
- if (!injected) {
65
- const devSupportPattern = /([ \t]*)(override\s+fun\s+getUseDeveloperSupport\s*\(\s*\))/;
66
- if (devSupportPattern.test(contents)) {
67
- console.log('[OTAUpdate] Android: Found getUseDeveloperSupport, inserting before it');
68
-
69
- const match = contents.match(devSupportPattern);
70
- const indent = match ? match[1] : ' ';
71
-
72
- const bundleOverride = `${indent}override fun getJSBundleFile(): String? {
73
- ${indent} return OTAUpdateHelper.getJSBundleFile(applicationContext)
74
- ${indent}}
75
-
76
- ${indent}override fun getBundleAssetName(): String? {
77
- ${indent} val otaBundle = OTAUpdateHelper.getJSBundleFile(applicationContext)
78
- ${indent} if (otaBundle != null) {
79
- ${indent} return null
80
- ${indent} }
81
- ${indent} return super.getBundleAssetName()
82
- ${indent}}
83
-
84
- ${indent}`;
85
-
86
- contents = contents.replace(devSupportPattern, `${bundleOverride}$2`);
87
- console.log('[OTAUpdate] Android: Injected bundle overrides before getUseDeveloperSupport');
88
- injected = true;
89
- }
90
- }
91
-
92
- if (!injected) {
93
- console.warn('[OTAUpdate] Android: ⚠️ Could not find injection point!');
94
- console.warn('[OTAUpdate] Android: Please manually add getJSBundleFile override');
95
-
96
- // Log relevant lines for debugging
97
- const lines = contents.split('\n');
98
- console.log('[OTAUpdate] Android: Relevant lines in MainApplication.kt:');
99
- lines.forEach((line, i) => {
100
- if (line.includes('ReactNativeHost') ||
101
- line.includes('DefaultReactNativeHost') ||
102
- line.includes('getDefaultReactHost') ||
103
- line.includes('reactHost') ||
104
- line.includes('getJSBundleFile') ||
105
- line.includes('jsBundleFilePath')) {
106
- console.log(` ${i + 1}: ${line.trim()}`);
107
- }
108
- });
109
- }
110
-
111
- config.modResults.contents = contents;
112
- return config;
113
- });
13
+ // Android bundle loading is handled by OTAUpdateExpoPackage implementing
14
+ // expo.modules.core.interfaces.Package with ReactNativeHostHandler.
15
+ // This is registered via expo-module.config.json and discovered by Expo automatically.
16
+ console.log('[OTAUpdate] Android: Using Expo Package interface (ReactNativeHostHandler)');
17
+ return config;
114
18
  }
115
19
 
116
20
  function withOTAUpdateIOS(config) {
@@ -0,0 +1,7 @@
1
+ {
2
+ "platforms": ["android", "ios"],
3
+ "android": {
4
+ "package": "com.otaupdate.OTAUpdateExpoPackage"
5
+ },
6
+ "ios": {}
7
+ }
@@ -103,5 +103,5 @@ var _verification = require("./utils/verification");
103
103
  // Utilities
104
104
 
105
105
  // Version info
106
- const VERSION = exports.VERSION = '0.2.10';
106
+ const VERSION = exports.VERSION = '0.2.11';
107
107
  //# sourceMappingURL=index.js.map
@@ -10,5 +10,5 @@ export { OTAApiClient, getDeviceInfo } from './utils/api';
10
10
  export { UpdateStorage, getStorageAdapter } from './utils/storage';
11
11
  export { calculateHash, verifyBundleHash, verifySignature, verifyBundle } from './utils/verification';
12
12
  // Version info
13
- export const VERSION = '0.2.10';
13
+ export const VERSION = '0.2.11';
14
14
  //# sourceMappingURL=index.js.map
@@ -9,5 +9,5 @@ export { UpdateStorage, getStorageAdapter } from './utils/storage';
9
9
  export type { StoredUpdate, StorageAdapter } from './utils/storage';
10
10
  export { calculateHash, verifyBundleHash, verifySignature, verifyBundle, } from './utils/verification';
11
11
  export type { VerificationResult } from './utils/verification';
12
- export declare const VERSION = "0.2.10";
12
+ export declare const VERSION = "0.2.11";
13
13
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanikya/ota-react-native",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "OTA Update SDK for React Native apps - self-hosted CodePush/EAS Updates alternative",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -15,6 +15,7 @@
15
15
  "*.podspec",
16
16
  "react-native.config.js",
17
17
  "app.plugin.js",
18
+ "expo-module.config.json",
18
19
  "README.md"
19
20
  ],
20
21
  "scripts": {
package/src/index.ts CHANGED
@@ -36,4 +36,4 @@ export {
36
36
  export type { VerificationResult } from './utils/verification';
37
37
 
38
38
  // Version info
39
- export const VERSION = '0.2.10';
39
+ export const VERSION = '0.2.11';