@vanikya/ota-react-native 0.2.7 → 0.2.9
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/OTAUpdatePackage.kt +82 -1
- package/app.plugin.js +12 -160
- package/expo-module.config.json +8 -0
- 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 +2 -1
- package/src/index.ts +1 -1
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
package com.otaupdate
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
3
5
|
import com.facebook.react.ReactPackage
|
|
4
6
|
import com.facebook.react.bridge.NativeModule
|
|
5
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
|
+
import com.facebook.react.bridge.ReactContext
|
|
6
9
|
import com.facebook.react.uimanager.ViewManager
|
|
10
|
+
import expo.modules.core.interfaces.Package
|
|
11
|
+
import expo.modules.core.interfaces.ReactNativeHostHandler
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
/**
|
|
14
|
+
* OTAUpdatePackage implements both React Native's ReactPackage and Expo's Package interfaces.
|
|
15
|
+
*
|
|
16
|
+
* The Expo Package interface is critical for bundle loading with Expo's new architecture.
|
|
17
|
+
* It provides ReactNativeHostHandler which allows us to override:
|
|
18
|
+
* - getJSBundleFile: Returns path to downloaded OTA bundle
|
|
19
|
+
* - getBundleAssetName: Returns null when OTA bundle exists to force using getJSBundleFile
|
|
20
|
+
*/
|
|
21
|
+
class OTAUpdatePackage : ReactPackage, Package {
|
|
22
|
+
|
|
23
|
+
companion object {
|
|
24
|
+
private const val TAG = "OTAUpdatePackage"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ReactPackage implementation
|
|
9
28
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
29
|
return listOf(OTAUpdateModule(reactContext))
|
|
11
30
|
}
|
|
@@ -13,4 +32,66 @@ class OTAUpdatePackage : ReactPackage {
|
|
|
13
32
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
33
|
return emptyList()
|
|
15
34
|
}
|
|
35
|
+
|
|
36
|
+
// Expo Package implementation - this is the key for Expo new architecture support
|
|
37
|
+
override fun createReactNativeHostHandlers(context: Context): List<ReactNativeHostHandler> {
|
|
38
|
+
Log.d(TAG, "=== createReactNativeHostHandlers called ===")
|
|
39
|
+
|
|
40
|
+
val handler = object : ReactNativeHostHandler {
|
|
41
|
+
|
|
42
|
+
override fun getJSBundleFile(useDeveloperSupport: Boolean): String? {
|
|
43
|
+
Log.d(TAG, "=== getJSBundleFile called (useDeveloperSupport=$useDeveloperSupport) ===")
|
|
44
|
+
|
|
45
|
+
// In development mode, let Metro bundler handle it
|
|
46
|
+
if (useDeveloperSupport) {
|
|
47
|
+
Log.d(TAG, "Developer support enabled, returning null")
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get the bundle path from OTAUpdateHelper
|
|
52
|
+
val bundlePath = OTAUpdateHelper.getJSBundleFile(context)
|
|
53
|
+
Log.d(TAG, "OTAUpdateHelper returned bundle path: $bundlePath")
|
|
54
|
+
return bundlePath
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override fun getBundleAssetName(useDeveloperSupport: Boolean): String? {
|
|
58
|
+
Log.d(TAG, "=== getBundleAssetName called (useDeveloperSupport=$useDeveloperSupport) ===")
|
|
59
|
+
|
|
60
|
+
// In development mode, use default behavior
|
|
61
|
+
if (useDeveloperSupport) {
|
|
62
|
+
Log.d(TAG, "Developer support enabled, returning null (use default)")
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if we have an OTA bundle
|
|
67
|
+
val bundlePath = OTAUpdateHelper.getJSBundleFile(context)
|
|
68
|
+
if (bundlePath != null) {
|
|
69
|
+
// Return null to force React Native to use getJSBundleFile instead of bundled assets
|
|
70
|
+
Log.d(TAG, "OTA bundle exists at $bundlePath, returning null to force getJSBundleFile")
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// No OTA bundle, use default bundled assets
|
|
75
|
+
Log.d(TAG, "No OTA bundle, returning null (use default bundled assets)")
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun onWillCreateReactInstance(useDeveloperSupport: Boolean) {
|
|
80
|
+
Log.d(TAG, "=== onWillCreateReactInstance called (useDeveloperSupport=$useDeveloperSupport) ===")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun onDidCreateReactInstance(useDeveloperSupport: Boolean, reactContext: ReactContext) {
|
|
84
|
+
Log.d(TAG, "=== onDidCreateReactInstance called ===")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override fun onReactInstanceException(useDeveloperSupport: Boolean, exception: Exception) {
|
|
88
|
+
Log.e(TAG, "=== onReactInstanceException called ===", exception)
|
|
89
|
+
// If there's an exception with OTA bundle, we could clear it here
|
|
90
|
+
// For now, just log the exception
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Log.d(TAG, "Returning ReactNativeHostHandler")
|
|
95
|
+
return listOf(handler)
|
|
96
|
+
}
|
|
16
97
|
}
|
package/app.plugin.js
CHANGED
|
@@ -1,171 +1,23 @@
|
|
|
1
|
-
const {
|
|
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:
|
|
7
|
+
* - Android: Uses Expo's Package interface (ReactNativeHostHandler) - no modification needed
|
|
8
8
|
* - iOS: Modifies bundleURL() in AppDelegate.swift
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* For Android, the OTAUpdatePackage implements expo.modules.core.interfaces.Package
|
|
11
|
+
* which provides ReactNativeHostHandler to hook into bundle loading.
|
|
12
|
+
* This is registered via expo-module.config.json.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
function withOTAUpdateAndroid(config) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
console.log('[OTAUpdate] Android: OTAUpdateHelper already present, skipping');
|
|
21
|
-
return config;
|
|
22
|
-
}
|
|
23
|
-
|
|
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
|
-
}
|
|
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;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
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;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
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
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
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
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
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
|
-
}
|
|
139
|
-
`;
|
|
140
|
-
contents = contents.replace(anyHostPattern, `$1${getJSBundleFileOverride}`);
|
|
141
|
-
console.log('[OTAUpdate] Android: Injected getJSBundleFile in ReactNativeHost');
|
|
142
|
-
injected = true;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
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
|
-
|
|
166
|
-
config.modResults.contents = contents;
|
|
167
|
-
return config;
|
|
168
|
-
});
|
|
16
|
+
// Android bundle loading is handled by OTAUpdatePackage implementing
|
|
17
|
+
// expo.modules.core.interfaces.Package with ReactNativeHostHandler.
|
|
18
|
+
// No MainApplication.kt modification is needed for Expo apps.
|
|
19
|
+
console.log('[OTAUpdate] Android: Using Expo Package interface for bundle loading');
|
|
20
|
+
return config;
|
|
169
21
|
}
|
|
170
22
|
|
|
171
23
|
function withOTAUpdateIOS(config) {
|
|
@@ -204,7 +56,7 @@ function withOTAUpdateIOS(config) {
|
|
|
204
56
|
}
|
|
205
57
|
`;
|
|
206
58
|
|
|
207
|
-
// Strategy 1: Look for bundleURL()
|
|
59
|
+
// Strategy 1: Look for bundleURL()
|
|
208
60
|
const bundleURLPattern1 = /(func\s+bundleURL\s*\(\s*\)\s*->\s*URL\?\s*\{)([\s\S]*?)(\n\s*\})/;
|
|
209
61
|
|
|
210
62
|
if (bundleURLPattern1.test(contents)) {
|
|
@@ -227,7 +79,7 @@ ${funcBody}${funcEnd}${helperFunction}`;
|
|
|
227
79
|
return config;
|
|
228
80
|
}
|
|
229
81
|
|
|
230
|
-
// Strategy 2: Look for sourceURL(for bridge:)
|
|
82
|
+
// Strategy 2: Look for sourceURL(for bridge:)
|
|
231
83
|
const sourceURLPattern = /(func\s+sourceURL\s*\(\s*for\s+bridge\s*:\s*RCTBridge\s*\)\s*->\s*URL\?\s*\{)([\s\S]*?)(\n\s*\})/;
|
|
232
84
|
|
|
233
85
|
if (sourceURLPattern.test(contents)) {
|
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.9';
|
|
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.9";
|
|
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
|
+
"version": "0.2.9",
|
|
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