@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.
- package/android/src/main/java/com/otaupdate/OTAUpdateHelper.kt +172 -0
- package/app.plugin.js +135 -165
- 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
|
@@ -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
|
-
*
|
|
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,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('
|
|
20
|
-
console.log('[OTAUpdate] Android:
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
//
|
|
130
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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.
|
|
156
|
-
console.log('[OTAUpdate] Android:
|
|
157
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
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