@vanikya/ota-react-native 0.2.3 → 0.2.5

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.
@@ -0,0 +1,172 @@
1
+ package com.otaupdate
2
+
3
+ import android.content.Context
4
+ import android.content.SharedPreferences
5
+ import android.content.pm.PackageInfo
6
+ import android.os.Build
7
+ import android.util.Log
8
+ import java.io.File
9
+
10
+ /**
11
+ * Helper object for OTA Update bundle loading.
12
+ * This is called from MainApplication.getJSBundleFile() to get the OTA bundle path.
13
+ *
14
+ * Based on patterns from react-native-ota-hot-update and hot-updater libraries.
15
+ */
16
+ object OTAUpdateHelper {
17
+ private const val TAG = "OTAUpdate"
18
+ private const val PREFS_NAME = "OTAUpdate"
19
+ private const val KEY_BUNDLE_PATH = "BundlePath"
20
+ private const val KEY_APP_VERSION = "AppVersion"
21
+
22
+ /**
23
+ * Get the JS bundle file path for React Native to load.
24
+ * Returns the OTA bundle path if available and valid, otherwise null (loads default bundle).
25
+ *
26
+ * @param context Application context
27
+ * @return Bundle file path or null
28
+ */
29
+ @JvmStatic
30
+ fun getJSBundleFile(context: Context): String? {
31
+ return try {
32
+ Log.d(TAG, "=== OTAUpdateHelper.getJSBundleFile called ===")
33
+
34
+ val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
35
+ val bundlePath = prefs.getString(KEY_BUNDLE_PATH, null)
36
+ val savedAppVersion = prefs.getString(KEY_APP_VERSION, null)
37
+ val currentAppVersion = getAppVersionCode(context)
38
+
39
+ Log.d(TAG, "Stored bundle path: $bundlePath")
40
+ Log.d(TAG, "Saved app version: $savedAppVersion, Current app version: $currentAppVersion")
41
+
42
+ // Clear bundle if app version changed (user updated the app)
43
+ if (savedAppVersion != null && savedAppVersion != currentAppVersion.toString()) {
44
+ Log.w(TAG, "App version changed from $savedAppVersion to $currentAppVersion, clearing OTA bundle")
45
+ prefs.edit()
46
+ .remove(KEY_BUNDLE_PATH)
47
+ .remove(KEY_APP_VERSION)
48
+ .commit()
49
+ return null
50
+ }
51
+
52
+ if (bundlePath.isNullOrEmpty()) {
53
+ Log.d(TAG, "No OTA bundle path stored, loading default bundle")
54
+ return null
55
+ }
56
+
57
+ val file = File(bundlePath)
58
+ Log.d(TAG, "Checking file: ${file.absolutePath}")
59
+ Log.d(TAG, "File exists: ${file.exists()}, canRead: ${file.canRead()}")
60
+
61
+ if (!file.exists()) {
62
+ Log.w(TAG, "OTA bundle file does not exist: $bundlePath")
63
+ // Clear invalid path
64
+ prefs.edit().remove(KEY_BUNDLE_PATH).commit()
65
+ return null
66
+ }
67
+
68
+ if (!file.canRead()) {
69
+ Log.w(TAG, "OTA bundle file is not readable: $bundlePath")
70
+ prefs.edit().remove(KEY_BUNDLE_PATH).commit()
71
+ return null
72
+ }
73
+
74
+ val fileSize = file.length()
75
+ if (fileSize < 100) {
76
+ Log.w(TAG, "OTA bundle file is too small (${fileSize} bytes), likely corrupted")
77
+ prefs.edit().remove(KEY_BUNDLE_PATH).commit()
78
+ return null
79
+ }
80
+
81
+ // Verify it looks like a JS file by checking first bytes
82
+ try {
83
+ val firstBytes = file.inputStream().use { it.readNBytes(50) }
84
+ val preview = String(firstBytes, Charsets.UTF_8)
85
+ Log.d(TAG, "Bundle preview: ${preview.take(30)}...")
86
+
87
+ // Check for HTML (error pages)
88
+ if (preview.contains("<!DOCTYPE") || preview.contains("<html") || preview.contains("<HTML")) {
89
+ Log.e(TAG, "OTA bundle appears to be HTML, not JavaScript - clearing")
90
+ prefs.edit().remove(KEY_BUNDLE_PATH).commit()
91
+ return null
92
+ }
93
+ } catch (e: Exception) {
94
+ Log.w(TAG, "Could not verify bundle content: ${e.message}")
95
+ }
96
+
97
+ Log.d(TAG, "✓ Loading OTA bundle: $bundlePath ($fileSize bytes)")
98
+ bundlePath
99
+ } catch (e: Exception) {
100
+ Log.e(TAG, "Error getting JS bundle file: ${e.message}", e)
101
+ null
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Get app version code for cache invalidation
107
+ */
108
+ @JvmStatic
109
+ fun getAppVersionCode(context: Context): Long {
110
+ return try {
111
+ val packageInfo: PackageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
112
+ context.packageManager.getPackageInfo(context.packageName, android.content.pm.PackageManager.PackageInfoFlags.of(0))
113
+ } else {
114
+ @Suppress("DEPRECATION")
115
+ context.packageManager.getPackageInfo(context.packageName, 0)
116
+ }
117
+
118
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
119
+ packageInfo.longVersionCode
120
+ } else {
121
+ @Suppress("DEPRECATION")
122
+ packageInfo.versionCode.toLong()
123
+ }
124
+ } catch (e: Exception) {
125
+ Log.e(TAG, "Error getting app version: ${e.message}")
126
+ 0L
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Check if an OTA bundle is pending to be loaded.
132
+ *
133
+ * @param context Application context
134
+ * @return true if OTA bundle is available
135
+ */
136
+ @JvmStatic
137
+ fun hasPendingBundle(context: Context): Boolean {
138
+ val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
139
+ val bundlePath = prefs.getString(KEY_BUNDLE_PATH, null)
140
+ if (bundlePath.isNullOrEmpty()) return false
141
+ return File(bundlePath).exists()
142
+ }
143
+
144
+ /**
145
+ * Clear the pending OTA bundle.
146
+ *
147
+ * @param context Application context
148
+ */
149
+ @JvmStatic
150
+ fun clearPendingBundle(context: Context) {
151
+ val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
152
+ prefs.edit()
153
+ .remove(KEY_BUNDLE_PATH)
154
+ .remove(KEY_APP_VERSION)
155
+ .commit()
156
+ Log.d(TAG, "Pending bundle cleared")
157
+ }
158
+
159
+ /**
160
+ * Save the bundle path with app version for cache invalidation
161
+ */
162
+ @JvmStatic
163
+ fun saveBundlePath(context: Context, bundlePath: String) {
164
+ val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
165
+ val appVersion = getAppVersionCode(context)
166
+ prefs.edit()
167
+ .putString(KEY_BUNDLE_PATH, bundlePath)
168
+ .putString(KEY_APP_VERSION, appVersion.toString())
169
+ .commit()
170
+ Log.d(TAG, "Bundle path saved: $bundlePath (app version: $appVersion)")
171
+ }
172
+ }
package/app.plugin.js CHANGED
@@ -4,11 +4,11 @@ const { withMainApplication, withAppDelegate } = require('@expo/config-plugins')
4
4
  * OTA Update Expo Config Plugin
5
5
  *
6
6
  * This plugin modifies the native code to enable OTA bundle loading:
7
- * - Android: Overrides getJSBundleFile() in MainApplication.kt
7
+ * - Android: Overrides getJSBundleFile() or sets jsBundleFilePath in MainApplication.kt
8
8
  * - iOS: Modifies bundleURL() in AppDelegate.swift
9
9
  *
10
- * The modifications check SharedPreferences (Android) / UserDefaults (iOS)
11
- * for a pending OTA bundle path and load it instead of the default bundle.
10
+ * Supports both old architecture (getJSBundleFile override) and
11
+ * new architecture (jsBundleFilePath parameter in getDefaultReactHost).
12
12
  */
13
13
 
14
14
  function withOTAUpdateAndroid(config) {
@@ -16,152 +16,152 @@ function withOTAUpdateAndroid(config) {
16
16
  let contents = config.modResults.contents;
17
17
 
18
18
  // Check if already modified
19
- if (contents.includes('getJSBundleFile')) {
20
- console.log('[OTAUpdate] Android: getJSBundleFile already present, skipping');
19
+ if (contents.includes('OTAUpdateHelper') || contents.includes('com.otaupdate')) {
20
+ console.log('[OTAUpdate] Android: OTAUpdateHelper already present, skipping');
21
21
  return config;
22
22
  }
23
23
 
24
- // Add imports if not present
25
- if (!contents.includes('import android.content.SharedPreferences')) {
26
- // Find the package declaration and add imports after it
27
- const packageMatch = contents.match(/^package\s+[\w.]+\s*\n/m);
28
- if (packageMatch) {
29
- const insertPos = packageMatch.index + packageMatch[0].length;
30
- const imports = `\nimport android.content.SharedPreferences\nimport java.io.File\n`;
31
- contents = contents.slice(0, insertPos) + imports + contents.slice(insertPos);
32
- }
24
+ console.log('[OTAUpdate] Android: Analyzing MainApplication.kt structure...');
25
+
26
+ // Add import for OTAUpdateHelper at the top
27
+ const packageMatch = contents.match(/^package\s+[\w.]+\s*\n/m);
28
+ if (packageMatch) {
29
+ const insertPos = packageMatch.index + packageMatch[0].length;
30
+ const importStatement = `\nimport com.otaupdate.OTAUpdateHelper\n`;
31
+ contents = contents.slice(0, insertPos) + importStatement + contents.slice(insertPos);
32
+ console.log('[OTAUpdate] Android: Added import for OTAUpdateHelper');
33
33
  }
34
34
 
35
- // Strategy 1: Look for "override val reactNativeHost" pattern (Expo SDK 50+)
36
- // This is more reliable than matching getUseDeveloperSupport
37
- const reactNativeHostPattern = /(override\s+val\s+reactNativeHost\s*:\s*ReactNativeHost\s*=\s*object\s*:\s*DefaultReactNativeHost\s*\(\s*this\s*\)\s*\{)/;
38
-
39
- if (reactNativeHostPattern.test(contents)) {
40
- const getJSBundleFileOverride = `
41
- override fun getJSBundleFile(): String? {
42
- val prefs: SharedPreferences = applicationContext.getSharedPreferences("OTAUpdate", android.content.Context.MODE_PRIVATE)
43
- val bundlePath = prefs.getString("BundlePath", null)
44
- android.util.Log.d("OTAUpdate", "getJSBundleFile called, stored path: $bundlePath")
45
- if (bundlePath != null) {
46
- val file = File(bundlePath)
47
- if (file.exists() && file.canRead()) {
48
- android.util.Log.d("OTAUpdate", "Loading OTA bundle: $bundlePath (${file.length()} bytes)")
49
- return bundlePath
50
- } else {
51
- android.util.Log.w("OTAUpdate", "OTA bundle not found or not readable: $bundlePath, exists=${file.exists()}")
52
- }
53
- }
54
- android.util.Log.d("OTAUpdate", "Loading default bundle")
55
- return null
35
+ let injected = false;
36
+
37
+ // ============================================================
38
+ // Strategy 1: React Native 0.82+ NEW ARCHITECTURE
39
+ // Look for: getDefaultReactHost(applicationContext, ...) and add jsBundleFilePath parameter
40
+ // ============================================================
41
+ const newArchPattern = /getDefaultReactHost\s*\(\s*\n?\s*applicationContext\s*,/;
42
+ if (newArchPattern.test(contents)) {
43
+ console.log('[OTAUpdate] Android: Detected React Native 0.82+ (new architecture)');
44
+
45
+ // Find the getDefaultReactHost call and add jsBundleFilePath parameter
46
+ // Pattern: getDefaultReactHost(applicationContext, packageList, ...)
47
+ const reactHostRegex = /(getDefaultReactHost\s*\(\s*\n?\s*)(applicationContext)(\s*,)/g;
48
+
49
+ if (reactHostRegex.test(contents)) {
50
+ contents = contents.replace(
51
+ /(getDefaultReactHost\s*\(\s*\n?\s*)(applicationContext)(\s*,)/,
52
+ '$1$2$3\n jsBundleFilePath = OTAUpdateHelper.getJSBundleFile(applicationContext),'
53
+ );
54
+ console.log('[OTAUpdate] Android: Injected jsBundleFilePath parameter (new architecture)');
55
+ injected = true;
56
56
  }
57
- `;
58
- contents = contents.replace(
59
- reactNativeHostPattern,
60
- `$1${getJSBundleFileOverride}`
61
- );
62
- console.log('[OTAUpdate] Android: Successfully injected getJSBundleFile (pattern 1)');
63
- config.modResults.contents = contents;
64
- return config;
65
57
  }
66
58
 
67
- // Strategy 2: Look for DefaultReactNativeHost with different formatting
68
- const altPattern = /(object\s*:\s*DefaultReactNativeHost\s*\(\s*this\s*\)\s*\{)/;
69
-
70
- if (altPattern.test(contents)) {
71
- const getJSBundleFileOverride = `
72
- override fun getJSBundleFile(): String? {
73
- val prefs: SharedPreferences = applicationContext.getSharedPreferences("OTAUpdate", android.content.Context.MODE_PRIVATE)
74
- val bundlePath = prefs.getString("BundlePath", null)
75
- android.util.Log.d("OTAUpdate", "getJSBundleFile called, stored path: $bundlePath")
76
- if (bundlePath != null) {
77
- val file = File(bundlePath)
78
- if (file.exists() && file.canRead()) {
79
- android.util.Log.d("OTAUpdate", "Loading OTA bundle: $bundlePath (${file.length()} bytes)")
80
- return bundlePath
81
- } else {
82
- android.util.Log.w("OTAUpdate", "OTA bundle not found or not readable: $bundlePath, exists=${file.exists()}")
59
+ // ============================================================
60
+ // Strategy 2: Look for reactHost with getDefaultReactHost
61
+ // override val reactHost: ReactHost get() = getDefaultReactHost(...)
62
+ // ============================================================
63
+ if (!injected) {
64
+ const reactHostGetPattern = /(override\s+val\s+reactHost\s*:\s*ReactHost\s+get\s*\(\s*\)\s*=\s*getDefaultReactHost\s*\()([^)]*)\)/;
65
+ if (reactHostGetPattern.test(contents)) {
66
+ console.log('[OTAUpdate] Android: Detected reactHost with getDefaultReactHost');
67
+
68
+ contents = contents.replace(
69
+ reactHostGetPattern,
70
+ (match, prefix, params) => {
71
+ if (params.includes('jsBundleFilePath')) {
72
+ return match; // Already has it
73
+ }
74
+ // Add jsBundleFilePath to the parameters
75
+ const newParams = params.trim() + ',\n jsBundleFilePath = OTAUpdateHelper.getJSBundleFile(applicationContext)';
76
+ return `${prefix}${newParams})`;
83
77
  }
84
- }
85
- android.util.Log.d("OTAUpdate", "Loading default bundle")
86
- return null
78
+ );
79
+ console.log('[OTAUpdate] Android: Injected jsBundleFilePath in reactHost getter');
80
+ injected = true;
87
81
  }
88
- `;
89
- contents = contents.replace(
90
- altPattern,
91
- `$1${getJSBundleFileOverride}`
92
- );
93
- console.log('[OTAUpdate] Android: Successfully injected getJSBundleFile (pattern 2)');
94
- config.modResults.contents = contents;
95
- return config;
96
82
  }
97
83
 
98
- // Strategy 3: Look for getUseDeveloperSupport with flexible whitespace
99
- const devSupportPattern = /(override\s+fun\s+getUseDeveloperSupport\s*\(\s*\)\s*[:\s]*Boolean\s*[=\{])/;
100
-
101
- if (devSupportPattern.test(contents)) {
102
- const getJSBundleFileOverride = `override fun getJSBundleFile(): String? {
103
- val prefs: SharedPreferences = applicationContext.getSharedPreferences("OTAUpdate", android.content.Context.MODE_PRIVATE)
104
- val bundlePath = prefs.getString("BundlePath", null)
105
- android.util.Log.d("OTAUpdate", "getJSBundleFile called, stored path: $bundlePath")
106
- if (bundlePath != null) {
107
- val file = File(bundlePath)
108
- if (file.exists() && file.canRead()) {
109
- android.util.Log.d("OTAUpdate", "Loading OTA bundle: $bundlePath (${file.length()} bytes)")
110
- return bundlePath
111
- } else {
112
- android.util.Log.w("OTAUpdate", "OTA bundle not found or not readable: $bundlePath, exists=${file.exists()}")
113
- }
84
+ // ============================================================
85
+ // Strategy 3: Old architecture - DefaultReactNativeHost with object block
86
+ // Look for: object : DefaultReactNativeHost(this) { ... }
87
+ // ============================================================
88
+ if (!injected) {
89
+ const defaultHostPattern = /(object\s*:\s*DefaultReactNativeHost\s*\([^)]*\)\s*\{)/;
90
+ if (defaultHostPattern.test(contents)) {
91
+ console.log('[OTAUpdate] Android: Detected DefaultReactNativeHost (old architecture)');
92
+
93
+ const getJSBundleFileOverride = `
94
+ override fun getJSBundleFile(): String? {
95
+ return OTAUpdateHelper.getJSBundleFile(applicationContext)
114
96
  }
115
- android.util.Log.d("OTAUpdate", "Loading default bundle")
116
- return null
97
+ `;
98
+ contents = contents.replace(defaultHostPattern, `$1${getJSBundleFileOverride}`);
99
+ console.log('[OTAUpdate] Android: Injected getJSBundleFile override');
100
+ injected = true;
117
101
  }
102
+ }
118
103
 
119
- `;
120
- contents = contents.replace(
121
- devSupportPattern,
122
- `${getJSBundleFileOverride}$1`
123
- );
124
- console.log('[OTAUpdate] Android: Successfully injected getJSBundleFile (pattern 3)');
125
- config.modResults.contents = contents;
126
- return config;
104
+ // ============================================================
105
+ // Strategy 4: Look for override fun getUseDeveloperSupport and insert before it
106
+ // ============================================================
107
+ if (!injected) {
108
+ const devSupportPattern = /([ \t]*)(override\s+fun\s+getUseDeveloperSupport\s*\(\s*\))/;
109
+ if (devSupportPattern.test(contents)) {
110
+ console.log('[OTAUpdate] Android: Found getUseDeveloperSupport, inserting before it');
111
+
112
+ const match = contents.match(devSupportPattern);
113
+ const indent = match ? match[1] : ' ';
114
+
115
+ const getJSBundleFileOverride = `${indent}override fun getJSBundleFile(): String? {
116
+ ${indent} return OTAUpdateHelper.getJSBundleFile(applicationContext)
117
+ ${indent}}
118
+
119
+ ${indent}`;
120
+
121
+ contents = contents.replace(devSupportPattern, `${getJSBundleFileOverride}$2`);
122
+ console.log('[OTAUpdate] Android: Injected getJSBundleFile before getUseDeveloperSupport');
123
+ injected = true;
124
+ }
127
125
  }
128
126
 
129
- // Strategy 4: Look for ReactNativeHost in any form
130
- const genericPattern = /(ReactNativeHost\s*[\(\{])/;
127
+ // ============================================================
128
+ // Strategy 5: Look for ReactNativeHost in any form
129
+ // ============================================================
130
+ if (!injected) {
131
+ const anyHostPattern = /(:\s*ReactNativeHost\s*\{)/;
132
+ if (anyHostPattern.test(contents)) {
133
+ console.log('[OTAUpdate] Android: Found ReactNativeHost block');
131
134
 
132
- if (genericPattern.test(contents)) {
133
- // Find the opening brace after ReactNativeHost and insert after it
134
- const match = contents.match(/ReactNativeHost[^{]*\{/);
135
- if (match) {
136
- const insertPos = match.index + match[0].length;
137
135
  const getJSBundleFileOverride = `
138
- override fun getJSBundleFile(): String? {
139
- val prefs: SharedPreferences = applicationContext.getSharedPreferences("OTAUpdate", android.content.Context.MODE_PRIVATE)
140
- val bundlePath = prefs.getString("BundlePath", null)
141
- android.util.Log.d("OTAUpdate", "getJSBundleFile called, stored path: $bundlePath")
142
- if (bundlePath != null) {
143
- val file = File(bundlePath)
144
- if (file.exists() && file.canRead()) {
145
- android.util.Log.d("OTAUpdate", "Loading OTA bundle: $bundlePath (${file.length()} bytes)")
146
- return bundlePath
147
- } else {
148
- android.util.Log.w("OTAUpdate", "OTA bundle not found or not readable: $bundlePath, exists=${file.exists()}")
149
- }
136
+ override fun getJSBundleFile(): String? {
137
+ return OTAUpdateHelper.getJSBundleFile(applicationContext)
150
138
  }
151
- android.util.Log.d("OTAUpdate", "Loading default bundle")
152
- return null
153
- }
154
139
  `;
155
- contents = contents.slice(0, insertPos) + getJSBundleFileOverride + contents.slice(insertPos);
156
- console.log('[OTAUpdate] Android: Successfully injected getJSBundleFile (pattern 4)');
157
- config.modResults.contents = contents;
158
- return config;
140
+ contents = contents.replace(anyHostPattern, `$1${getJSBundleFileOverride}`);
141
+ console.log('[OTAUpdate] Android: Injected getJSBundleFile in ReactNativeHost');
142
+ injected = true;
159
143
  }
160
144
  }
161
145
 
162
- console.warn('[OTAUpdate] Android: Could not find suitable injection point for getJSBundleFile');
163
- console.warn('[OTAUpdate] Android: Please manually add getJSBundleFile override to MainApplication.kt');
164
- console.warn('[OTAUpdate] Android: See https://vanikya.github.io/ota-update/ for manual setup instructions');
146
+ if (!injected) {
147
+ console.warn('[OTAUpdate] Android: ⚠️ Could not find injection point!');
148
+ console.warn('[OTAUpdate] Android: Please manually add getJSBundleFile override');
149
+ console.warn('[OTAUpdate] Android: See documentation for manual setup');
150
+
151
+ // Log relevant lines for debugging
152
+ const lines = contents.split('\n');
153
+ console.log('[OTAUpdate] Android: Relevant lines in MainApplication.kt:');
154
+ lines.forEach((line, i) => {
155
+ if (line.includes('ReactNativeHost') ||
156
+ line.includes('DefaultReactNativeHost') ||
157
+ line.includes('getDefaultReactHost') ||
158
+ line.includes('reactHost') ||
159
+ line.includes('getJSBundleFile') ||
160
+ line.includes('jsBundleFilePath')) {
161
+ console.log(` ${i + 1}: ${line.trim()}`);
162
+ }
163
+ });
164
+ }
165
165
 
166
166
  config.modResults.contents = contents;
167
167
  return config;
@@ -195,7 +195,8 @@ function withOTAUpdateIOS(config) {
195
195
  }
196
196
  return URL(fileURLWithPath: path)
197
197
  } else {
198
- NSLog("[OTAUpdate] OTA bundle not found at path: %@", path)
198
+ NSLog("[OTAUpdate] OTA bundle not found at path: %@, clearing", path)
199
+ UserDefaults.standard.removeObject(forKey: "OTAUpdateBundlePath")
199
200
  }
200
201
  }
201
202
  NSLog("[OTAUpdate] Loading default bundle")
@@ -204,14 +205,12 @@ function withOTAUpdateIOS(config) {
204
205
  `;
205
206
 
206
207
  // Strategy 1: Look for bundleURL() with flexible pattern
207
- // Match: func bundleURL() -> URL? { ... } with any content inside
208
208
  const bundleURLPattern1 = /(func\s+bundleURL\s*\(\s*\)\s*->\s*URL\?\s*\{)([\s\S]*?)(\n\s*\})/;
209
209
 
210
210
  if (bundleURLPattern1.test(contents)) {
211
211
  contents = contents.replace(
212
212
  bundleURLPattern1,
213
213
  (match, funcStart, funcBody, funcEnd) => {
214
- // Check if it already has OTA check
215
214
  if (funcBody.includes('getOTABundleURL')) {
216
215
  return match;
217
216
  }
@@ -223,12 +222,12 @@ function withOTAUpdateIOS(config) {
223
222
  ${funcBody}${funcEnd}${helperFunction}`;
224
223
  }
225
224
  );
226
- console.log('[OTAUpdate] iOS: Successfully modified bundleURL (pattern 1)');
225
+ console.log('[OTAUpdate] iOS: Successfully modified bundleURL');
227
226
  config.modResults.contents = contents;
228
227
  return config;
229
228
  }
230
229
 
231
- // Strategy 2: Look for sourceURL(for bridge:) pattern (older Expo versions)
230
+ // Strategy 2: Look for sourceURL(for bridge:) pattern
232
231
  const sourceURLPattern = /(func\s+sourceURL\s*\(\s*for\s+bridge\s*:\s*RCTBridge\s*\)\s*->\s*URL\?\s*\{)([\s\S]*?)(\n\s*\})/;
233
232
 
234
233
  if (sourceURLPattern.test(contents)) {
@@ -246,53 +245,25 @@ ${funcBody}${funcEnd}${helperFunction}`;
246
245
  ${funcBody}${funcEnd}${helperFunction}`;
247
246
  }
248
247
  );
249
- console.log('[OTAUpdate] iOS: Successfully modified sourceURL (pattern 2)');
250
- config.modResults.contents = contents;
251
- return config;
252
- }
253
-
254
- // Strategy 3: Add bundleURL method if it doesn't exist but class exists
255
- const appDelegateClassPattern = /(class\s+AppDelegate\s*[^{]*\{)/;
256
-
257
- if (appDelegateClassPattern.test(contents) && !contents.includes('func bundleURL')) {
258
- const newBundleURLMethod = `
259
- // OTA Update: Bundle URL with OTA support
260
- func bundleURL() -> URL? {
261
- // Check for downloaded OTA bundle first
262
- if let otaBundle = getOTABundleURL() {
263
- return otaBundle
264
- }
265
- #if DEBUG
266
- return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
267
- #else
268
- return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
269
- #endif
270
- }
271
- ${helperFunction}
272
- `;
273
- contents = contents.replace(
274
- appDelegateClassPattern,
275
- `$1${newBundleURLMethod}`
276
- );
277
- console.log('[OTAUpdate] iOS: Successfully added bundleURL method (pattern 3)');
248
+ console.log('[OTAUpdate] iOS: Successfully modified sourceURL');
278
249
  config.modResults.contents = contents;
279
250
  return config;
280
251
  }
281
252
 
282
- console.warn('[OTAUpdate] iOS: Could not find suitable injection point');
283
- console.warn('[OTAUpdate] iOS: Please manually modify AppDelegate.swift');
284
- console.warn('[OTAUpdate] iOS: See https://vanikya.github.io/ota-update/ for manual setup instructions');
253
+ console.warn('[OTAUpdate] iOS: Could not find bundleURL or sourceURL method');
285
254
  } else if (config.modResults.language === 'objc' || config.modResults.language === 'objcpp') {
286
- // Objective-C AppDelegate (older Expo versions)
255
+ // Objective-C AppDelegate
287
256
  const sourceURLPattern = /(- \(NSURL \*\)sourceURLForBridge:\(RCTBridge \*\)bridge\s*\{)([\s\S]*?)(\n\})/;
288
257
 
289
258
  if (sourceURLPattern.test(contents)) {
290
259
  const helperCode = `
291
- // Check for OTA bundle
260
+ // OTA Update: Check for OTA bundle
292
261
  NSString *bundlePath = [[NSUserDefaults standardUserDefaults] stringForKey:@"OTAUpdateBundlePath"];
293
262
  if (bundlePath && [[NSFileManager defaultManager] fileExistsAtPath:bundlePath]) {
263
+ NSLog(@"[OTAUpdate] Loading OTA bundle: %@", bundlePath);
294
264
  return [NSURL fileURLWithPath:bundlePath];
295
265
  }
266
+ NSLog(@"[OTAUpdate] Loading default bundle");
296
267
  `;
297
268
  contents = contents.replace(
298
269
  sourceURLPattern,
@@ -320,6 +291,5 @@ module.exports = function withOTAUpdate(config) {
320
291
  return config;
321
292
  };
322
293
 
323
- // Export individual functions for testing
324
294
  module.exports.withOTAUpdateAndroid = withOTAUpdateAndroid;
325
295
  module.exports.withOTAUpdateIOS = withOTAUpdateIOS;
@@ -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.3';
106
+ const VERSION = exports.VERSION = '0.2.5';
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.3';
13
+ export const VERSION = '0.2.5';
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.3";
12
+ export declare const VERSION = "0.2.5";
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.3",
3
+ "version": "0.2.5",
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",
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.3';
39
+ export const VERSION = '0.2.5';