@vanikya/ota-react-native 0.2.4 → 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.
- package/android/src/main/java/com/otaupdate/OTAUpdateHelper.kt +85 -3
- package/app.plugin.js +132 -134
- package/lib/commonjs/index.js +1 -1
- package/lib/module/index.js +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
|
@@ -2,17 +2,22 @@ package com.otaupdate
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.content.SharedPreferences
|
|
5
|
+
import android.content.pm.PackageInfo
|
|
6
|
+
import android.os.Build
|
|
5
7
|
import android.util.Log
|
|
6
8
|
import java.io.File
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Helper object for OTA Update bundle loading.
|
|
10
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.
|
|
11
15
|
*/
|
|
12
16
|
object OTAUpdateHelper {
|
|
13
17
|
private const val TAG = "OTAUpdate"
|
|
14
18
|
private const val PREFS_NAME = "OTAUpdate"
|
|
15
19
|
private const val KEY_BUNDLE_PATH = "BundlePath"
|
|
20
|
+
private const val KEY_APP_VERSION = "AppVersion"
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* Get the JS bundle file path for React Native to load.
|
|
@@ -24,10 +29,25 @@ object OTAUpdateHelper {
|
|
|
24
29
|
@JvmStatic
|
|
25
30
|
fun getJSBundleFile(context: Context): String? {
|
|
26
31
|
return try {
|
|
32
|
+
Log.d(TAG, "=== OTAUpdateHelper.getJSBundleFile called ===")
|
|
33
|
+
|
|
27
34
|
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
28
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")
|
|
29
41
|
|
|
30
|
-
|
|
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
|
+
}
|
|
31
51
|
|
|
32
52
|
if (bundlePath.isNullOrEmpty()) {
|
|
33
53
|
Log.d(TAG, "No OTA bundle path stored, loading default bundle")
|
|
@@ -35,6 +55,9 @@ object OTAUpdateHelper {
|
|
|
35
55
|
}
|
|
36
56
|
|
|
37
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
|
+
|
|
38
61
|
if (!file.exists()) {
|
|
39
62
|
Log.w(TAG, "OTA bundle file does not exist: $bundlePath")
|
|
40
63
|
// Clear invalid path
|
|
@@ -44,6 +67,7 @@ object OTAUpdateHelper {
|
|
|
44
67
|
|
|
45
68
|
if (!file.canRead()) {
|
|
46
69
|
Log.w(TAG, "OTA bundle file is not readable: $bundlePath")
|
|
70
|
+
prefs.edit().remove(KEY_BUNDLE_PATH).commit()
|
|
47
71
|
return null
|
|
48
72
|
}
|
|
49
73
|
|
|
@@ -54,7 +78,23 @@ object OTAUpdateHelper {
|
|
|
54
78
|
return null
|
|
55
79
|
}
|
|
56
80
|
|
|
57
|
-
|
|
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)")
|
|
58
98
|
bundlePath
|
|
59
99
|
} catch (e: Exception) {
|
|
60
100
|
Log.e(TAG, "Error getting JS bundle file: ${e.message}", e)
|
|
@@ -62,6 +102,31 @@ object OTAUpdateHelper {
|
|
|
62
102
|
}
|
|
63
103
|
}
|
|
64
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
|
+
|
|
65
130
|
/**
|
|
66
131
|
* Check if an OTA bundle is pending to be loaded.
|
|
67
132
|
*
|
|
@@ -84,7 +149,24 @@ object OTAUpdateHelper {
|
|
|
84
149
|
@JvmStatic
|
|
85
150
|
fun clearPendingBundle(context: Context) {
|
|
86
151
|
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
87
|
-
prefs.edit()
|
|
152
|
+
prefs.edit()
|
|
153
|
+
.remove(KEY_BUNDLE_PATH)
|
|
154
|
+
.remove(KEY_APP_VERSION)
|
|
155
|
+
.commit()
|
|
88
156
|
Log.d(TAG, "Pending bundle cleared")
|
|
89
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
|
+
}
|
|
90
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
|
-
*
|
|
11
|
-
*
|
|
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,122 +16,152 @@ function withOTAUpdateAndroid(config) {
|
|
|
16
16
|
let contents = config.modResults.contents;
|
|
17
17
|
|
|
18
18
|
// Check if already modified
|
|
19
|
-
if (contents.includes('OTAUpdateHelper')) {
|
|
19
|
+
if (contents.includes('OTAUpdateHelper') || contents.includes('com.otaupdate')) {
|
|
20
20
|
console.log('[OTAUpdate] Android: OTAUpdateHelper already present, skipping');
|
|
21
21
|
return config;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
console.log('[OTAUpdate] Android: Analyzing MainApplication.kt structure...');
|
|
25
|
+
|
|
26
|
+
// Add import for OTAUpdateHelper at the top
|
|
25
27
|
const packageMatch = contents.match(/^package\s+[\w.]+\s*\n/m);
|
|
26
28
|
if (packageMatch) {
|
|
27
29
|
const insertPos = packageMatch.index + packageMatch[0].length;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
}
|
|
34
|
+
|
|
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;
|
|
32
56
|
}
|
|
33
57
|
}
|
|
34
58
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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})`;
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
console.log('[OTAUpdate] Android: Injected jsBundleFilePath in reactHost getter');
|
|
80
|
+
injected = true;
|
|
39
81
|
}
|
|
40
|
-
|
|
82
|
+
}
|
|
41
83
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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)
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
contents = contents.replace(defaultHostPattern, `$1${getJSBundleFileOverride}`);
|
|
99
|
+
console.log('[OTAUpdate] Android: Injected getJSBundleFile override');
|
|
100
|
+
injected = true;
|
|
101
|
+
}
|
|
54
102
|
}
|
|
55
103
|
|
|
56
|
-
//
|
|
57
|
-
|
|
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');
|
|
58
111
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
reactNativeHostPattern,
|
|
62
|
-
`$1${getJSBundleFileOverride}`
|
|
63
|
-
);
|
|
64
|
-
console.log('[OTAUpdate] Android: Successfully injected getJSBundleFile (reactNativeHost pattern)');
|
|
65
|
-
config.modResults.contents = contents;
|
|
66
|
-
return config;
|
|
67
|
-
}
|
|
112
|
+
const match = contents.match(devSupportPattern);
|
|
113
|
+
const indent = match ? match[1] : ' ';
|
|
68
114
|
|
|
69
|
-
|
|
70
|
-
|
|
115
|
+
const getJSBundleFileOverride = `${indent}override fun getJSBundleFile(): String? {
|
|
116
|
+
${indent} return OTAUpdateHelper.getJSBundleFile(applicationContext)
|
|
117
|
+
${indent}}
|
|
71
118
|
|
|
72
|
-
|
|
73
|
-
contents = contents.replace(
|
|
74
|
-
anyHostPattern,
|
|
75
|
-
`$1${getJSBundleFileOverride}`
|
|
76
|
-
);
|
|
77
|
-
console.log('[OTAUpdate] Android: Successfully injected getJSBundleFile (generic ReactNativeHost pattern)');
|
|
78
|
-
config.modResults.contents = contents;
|
|
79
|
-
return config;
|
|
80
|
-
}
|
|
119
|
+
${indent}`;
|
|
81
120
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (devSupportPattern.test(contents)) {
|
|
87
|
-
const indentMatch = contents.match(devSupportPattern);
|
|
88
|
-
const indent = indentMatch ? indentMatch[1] : ' ';
|
|
89
|
-
const overrideCode = `${indent}override fun getJSBundleFile(): String? {\n${indent} return OTAUpdateHelper.getJSBundleFile(applicationContext)\n${indent}}\n\n${indent}`;
|
|
90
|
-
contents = contents.replace(
|
|
91
|
-
devSupportPattern,
|
|
92
|
-
`${overrideCode}$2`
|
|
93
|
-
);
|
|
94
|
-
console.log('[OTAUpdate] Android: Successfully injected getJSBundleFile (before getUseDeveloperSupport)');
|
|
95
|
-
config.modResults.contents = contents;
|
|
96
|
-
return config;
|
|
121
|
+
contents = contents.replace(devSupportPattern, `${getJSBundleFileOverride}$2`);
|
|
122
|
+
console.log('[OTAUpdate] Android: Injected getJSBundleFile before getUseDeveloperSupport');
|
|
123
|
+
injected = true;
|
|
124
|
+
}
|
|
97
125
|
}
|
|
98
126
|
|
|
99
|
-
//
|
|
100
|
-
// Look for
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return OTAUpdateHelper.getJSBundleFile(applicationContext)
|
|
112
|
-
}
|
|
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');
|
|
134
|
+
|
|
135
|
+
const getJSBundleFileOverride = `
|
|
136
|
+
override fun getJSBundleFile(): String? {
|
|
137
|
+
return OTAUpdateHelper.getJSBundleFile(applicationContext)
|
|
138
|
+
}
|
|
113
139
|
`;
|
|
114
|
-
contents = contents.
|
|
115
|
-
console.log('[OTAUpdate] Android:
|
|
116
|
-
|
|
140
|
+
contents = contents.replace(anyHostPattern, `$1${getJSBundleFileOverride}`);
|
|
141
|
+
console.log('[OTAUpdate] Android: Injected getJSBundleFile in ReactNativeHost');
|
|
142
|
+
injected = true;
|
|
117
143
|
}
|
|
118
144
|
}
|
|
119
145
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
}
|
|
135
165
|
|
|
136
166
|
config.modResults.contents = contents;
|
|
137
167
|
return config;
|
|
@@ -165,8 +195,7 @@ function withOTAUpdateIOS(config) {
|
|
|
165
195
|
}
|
|
166
196
|
return URL(fileURLWithPath: path)
|
|
167
197
|
} else {
|
|
168
|
-
NSLog("[OTAUpdate] OTA bundle not found at path:
|
|
169
|
-
// Clear invalid path
|
|
198
|
+
NSLog("[OTAUpdate] OTA bundle not found at path: %@, clearing", path)
|
|
170
199
|
UserDefaults.standard.removeObject(forKey: "OTAUpdateBundlePath")
|
|
171
200
|
}
|
|
172
201
|
}
|
|
@@ -193,12 +222,12 @@ function withOTAUpdateIOS(config) {
|
|
|
193
222
|
${funcBody}${funcEnd}${helperFunction}`;
|
|
194
223
|
}
|
|
195
224
|
);
|
|
196
|
-
console.log('[OTAUpdate] iOS: Successfully modified bundleURL
|
|
225
|
+
console.log('[OTAUpdate] iOS: Successfully modified bundleURL');
|
|
197
226
|
config.modResults.contents = contents;
|
|
198
227
|
return config;
|
|
199
228
|
}
|
|
200
229
|
|
|
201
|
-
// Strategy 2: Look for sourceURL(for bridge:) pattern
|
|
230
|
+
// Strategy 2: Look for sourceURL(for bridge:) pattern
|
|
202
231
|
const sourceURLPattern = /(func\s+sourceURL\s*\(\s*for\s+bridge\s*:\s*RCTBridge\s*\)\s*->\s*URL\?\s*\{)([\s\S]*?)(\n\s*\})/;
|
|
203
232
|
|
|
204
233
|
if (sourceURLPattern.test(contents)) {
|
|
@@ -216,49 +245,19 @@ ${funcBody}${funcEnd}${helperFunction}`;
|
|
|
216
245
|
${funcBody}${funcEnd}${helperFunction}`;
|
|
217
246
|
}
|
|
218
247
|
);
|
|
219
|
-
console.log('[OTAUpdate] iOS: Successfully modified sourceURL
|
|
220
|
-
config.modResults.contents = contents;
|
|
221
|
-
return config;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Strategy 3: Add bundleURL method if it doesn't exist but class exists
|
|
225
|
-
const appDelegateClassPattern = /(class\s+AppDelegate\s*[^{]*\{)/;
|
|
226
|
-
|
|
227
|
-
if (appDelegateClassPattern.test(contents) && !contents.includes('func bundleURL')) {
|
|
228
|
-
const newBundleURLMethod = `
|
|
229
|
-
// OTA Update: Bundle URL with OTA support
|
|
230
|
-
func bundleURL() -> URL? {
|
|
231
|
-
// Check for downloaded OTA bundle first
|
|
232
|
-
if let otaBundle = getOTABundleURL() {
|
|
233
|
-
return otaBundle
|
|
234
|
-
}
|
|
235
|
-
#if DEBUG
|
|
236
|
-
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
|
|
237
|
-
#else
|
|
238
|
-
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
239
|
-
#endif
|
|
240
|
-
}
|
|
241
|
-
${helperFunction}
|
|
242
|
-
`;
|
|
243
|
-
contents = contents.replace(
|
|
244
|
-
appDelegateClassPattern,
|
|
245
|
-
`$1${newBundleURLMethod}`
|
|
246
|
-
);
|
|
247
|
-
console.log('[OTAUpdate] iOS: Successfully added bundleURL method (pattern 3)');
|
|
248
|
+
console.log('[OTAUpdate] iOS: Successfully modified sourceURL');
|
|
248
249
|
config.modResults.contents = contents;
|
|
249
250
|
return config;
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
console.warn('[OTAUpdate] iOS: Could not find
|
|
253
|
-
console.warn('[OTAUpdate] iOS: Please manually modify AppDelegate.swift');
|
|
254
|
-
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');
|
|
255
254
|
} else if (config.modResults.language === 'objc' || config.modResults.language === 'objcpp') {
|
|
256
|
-
// Objective-C AppDelegate
|
|
255
|
+
// Objective-C AppDelegate
|
|
257
256
|
const sourceURLPattern = /(- \(NSURL \*\)sourceURLForBridge:\(RCTBridge \*\)bridge\s*\{)([\s\S]*?)(\n\})/;
|
|
258
257
|
|
|
259
258
|
if (sourceURLPattern.test(contents)) {
|
|
260
259
|
const helperCode = `
|
|
261
|
-
// Check for OTA bundle
|
|
260
|
+
// OTA Update: Check for OTA bundle
|
|
262
261
|
NSString *bundlePath = [[NSUserDefaults standardUserDefaults] stringForKey:@"OTAUpdateBundlePath"];
|
|
263
262
|
if (bundlePath && [[NSFileManager defaultManager] fileExistsAtPath:bundlePath]) {
|
|
264
263
|
NSLog(@"[OTAUpdate] Loading OTA bundle: %@", bundlePath);
|
|
@@ -292,6 +291,5 @@ module.exports = function withOTAUpdate(config) {
|
|
|
292
291
|
return config;
|
|
293
292
|
};
|
|
294
293
|
|
|
295
|
-
// Export individual functions for testing
|
|
296
294
|
module.exports.withOTAUpdateAndroid = withOTAUpdateAndroid;
|
|
297
295
|
module.exports.withOTAUpdateIOS = withOTAUpdateIOS;
|
package/lib/commonjs/index.js
CHANGED
package/lib/module/index.js
CHANGED
|
@@ -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.
|
|
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.
|
|
12
|
+
export declare const VERSION = "0.2.5";
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
package/src/index.ts
CHANGED