@hot-updater/react-native 0.16.6 → 0.17.0
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/newarch/ReactIntegrationManager.kt +17 -3
- package/android/src/oldarch/ReactIntegrationManager.kt +0 -2
- package/app.plugin.js +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +16 -6
- package/plugin/build/withHotUpdater.js +146 -0
- package/src/native.ts +1 -1
|
@@ -3,7 +3,9 @@ package com.hotupdater
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.util.Log
|
|
5
5
|
import com.facebook.react.ReactApplication
|
|
6
|
+
import com.facebook.react.bridge.JSBundleLoader
|
|
6
7
|
import com.facebook.react.common.LifecycleState
|
|
8
|
+
import java.lang.reflect.Field
|
|
7
9
|
|
|
8
10
|
class ReactIntegrationManager(
|
|
9
11
|
context: Context,
|
|
@@ -25,8 +27,22 @@ class ReactIntegrationManager(
|
|
|
25
27
|
jsBundleLoaderField.isAccessible = true
|
|
26
28
|
jsBundleLoaderField.set(reactHostDelegate, getJSBundlerLoader(bundleURL))
|
|
27
29
|
} catch (e: Exception) {
|
|
30
|
+
try {
|
|
31
|
+
val instanceManager = application.reactNativeHost.reactInstanceManager
|
|
32
|
+
val bundleLoader: JSBundleLoader? = this.getJSBundlerLoader(bundleURL)
|
|
33
|
+
val bundleLoaderField: Field =
|
|
34
|
+
instanceManager::class.java.getDeclaredField("mBundleLoader")
|
|
35
|
+
bundleLoaderField.isAccessible = true
|
|
36
|
+
|
|
37
|
+
if (bundleLoader != null) {
|
|
38
|
+
bundleLoaderField.set(instanceManager, bundleLoader)
|
|
39
|
+
} else {
|
|
40
|
+
bundleLoaderField.set(instanceManager, null)
|
|
41
|
+
}
|
|
42
|
+
} catch (e: Exception) {
|
|
43
|
+
Log.d("HotUpdater", "Failed to setJSBundle (fallback): ${e.message}")
|
|
44
|
+
}
|
|
28
45
|
Log.d("HotUpdater", "Failed to setJSBundle: ${e.message}")
|
|
29
|
-
throw IllegalAccessException("Could not setJSBundle")
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
48
|
|
|
@@ -57,12 +73,10 @@ class ReactIntegrationManager(
|
|
|
57
73
|
}
|
|
58
74
|
} catch (e: Exception) {
|
|
59
75
|
Log.d("HotUpdater", "Failed to reload: ${e.message}")
|
|
60
|
-
throw e
|
|
61
76
|
}
|
|
62
77
|
}
|
|
63
78
|
} catch (e: Exception) {
|
|
64
79
|
Log.d("HotUpdater", "Failed to reload: ${e.message}")
|
|
65
|
-
throw e
|
|
66
80
|
}
|
|
67
81
|
}
|
|
68
82
|
}
|
|
@@ -27,7 +27,6 @@ class ReactIntegrationManager(
|
|
|
27
27
|
}
|
|
28
28
|
} catch (e: Exception) {
|
|
29
29
|
Log.d("HotUpdater", "Failed to setJSBundle: ${e.message}")
|
|
30
|
-
throw IllegalAccessException("Could not setJSBundle")
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
32
|
|
|
@@ -49,7 +48,6 @@ class ReactIntegrationManager(
|
|
|
49
48
|
}
|
|
50
49
|
} catch (e: Exception) {
|
|
51
50
|
Log.d("HotUpdater", "Failed to reload: ${e.message}")
|
|
52
|
-
throw e
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("./plugin/build/withHotUpdater");
|
package/dist/index.js
CHANGED
|
@@ -104,7 +104,7 @@ var __webpack_exports__ = {};
|
|
|
104
104
|
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
105
105
|
const HotUpdater = {
|
|
106
106
|
HOT_UPDATER_BUNDLE_ID: __HOT_UPDATER_BUNDLE_ID || NIL_UUID,
|
|
107
|
-
CHANNEL: __HOT_UPDATER_CHANNEL || "production"
|
|
107
|
+
CHANNEL: __HOT_UPDATER_CHANNEL || (__DEV__ ? null : "production")
|
|
108
108
|
};
|
|
109
109
|
const RCTNativeHotUpdater = __webpack_require__("./src/specs/NativeHotUpdater.ts").Z;
|
|
110
110
|
const LINKING_ERROR = `The package '@hot-updater/react-native' doesn't seem to be linked. Make sure: \n\n` + external_react_native_.Platform.select({
|
package/dist/index.mjs
CHANGED
|
@@ -79,7 +79,7 @@ const fetchUpdateInfo = async (source, { appVersion, bundleId, platform, minBund
|
|
|
79
79
|
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
80
80
|
const HotUpdater = {
|
|
81
81
|
HOT_UPDATER_BUNDLE_ID: __HOT_UPDATER_BUNDLE_ID || NIL_UUID,
|
|
82
|
-
CHANNEL: __HOT_UPDATER_CHANNEL || "production"
|
|
82
|
+
CHANNEL: __HOT_UPDATER_CHANNEL || (__DEV__ ? null : "production")
|
|
83
83
|
};
|
|
84
84
|
const RCTNativeHotUpdater = __webpack_require__("./src/specs/NativeHotUpdater.ts").Z;
|
|
85
85
|
const LINKING_ERROR = `The package '@hot-updater/react-native' doesn't seem to be linked. Make sure: \n\n` + external_react_native_.Platform.select({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"ios",
|
|
15
15
|
"cpp",
|
|
16
16
|
"*.podspec",
|
|
17
|
+
"app.plugin.js",
|
|
18
|
+
"plugin/build/withHotUpdater.js",
|
|
17
19
|
"react-native.config.js",
|
|
18
20
|
"!ios/build",
|
|
19
21
|
"!android/build",
|
|
@@ -66,9 +68,15 @@
|
|
|
66
68
|
},
|
|
67
69
|
"includesGeneratedCode": true
|
|
68
70
|
},
|
|
71
|
+
"peerDependenciesMeta": {
|
|
72
|
+
"expo": {
|
|
73
|
+
"optional": true
|
|
74
|
+
}
|
|
75
|
+
},
|
|
69
76
|
"peerDependencies": {
|
|
70
77
|
"react": "*",
|
|
71
|
-
"react-native": "*"
|
|
78
|
+
"react-native": "*",
|
|
79
|
+
"expo": ">=50.0.0"
|
|
72
80
|
},
|
|
73
81
|
"devDependencies": {
|
|
74
82
|
"@react-native-community/cli": "15.0.1",
|
|
@@ -77,15 +85,17 @@
|
|
|
77
85
|
"del-cli": "^6.0.0",
|
|
78
86
|
"react": "18.3.1",
|
|
79
87
|
"react-native": "0.76.2",
|
|
80
|
-
"react-native-builder-bob": "^0.33.1"
|
|
88
|
+
"react-native-builder-bob": "^0.33.1",
|
|
89
|
+
"expo": "^50.0.0"
|
|
81
90
|
},
|
|
82
91
|
"dependencies": {
|
|
83
92
|
"use-sync-external-store": "1.4.0",
|
|
84
|
-
"@hot-updater/js": "0.
|
|
85
|
-
"@hot-updater/core": "0.
|
|
93
|
+
"@hot-updater/js": "0.17.0",
|
|
94
|
+
"@hot-updater/core": "0.17.0"
|
|
86
95
|
},
|
|
87
96
|
"scripts": {
|
|
88
|
-
"build": "rslib build",
|
|
97
|
+
"build": "rslib build && npm run build:plugin",
|
|
98
|
+
"build:plugin": "tsc -p plugin/tsconfig.json",
|
|
89
99
|
"test:type": "tsc --noEmit",
|
|
90
100
|
"test": "vitest",
|
|
91
101
|
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib"
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
var config_plugins_1 = require("expo/config-plugins");
|
|
7
|
+
var package_json_1 = __importDefault(require("../../package.json"));
|
|
8
|
+
/**
|
|
9
|
+
* Helper to add lines if they don't exist, anchored by a specific string.
|
|
10
|
+
*/
|
|
11
|
+
function addLinesOnce(contents, anchor, linesToAdd) {
|
|
12
|
+
if (linesToAdd.every(function (line) { return contents.includes(line); })) {
|
|
13
|
+
// All lines already exist, do nothing
|
|
14
|
+
return contents;
|
|
15
|
+
}
|
|
16
|
+
// Check if the anchor exists
|
|
17
|
+
if (!contents.includes(anchor)) {
|
|
18
|
+
// Anchor not found, cannot add lines reliably.
|
|
19
|
+
// Consider logging a warning or throwing an error here if necessary.
|
|
20
|
+
return contents;
|
|
21
|
+
}
|
|
22
|
+
// Add lines after the anchor
|
|
23
|
+
// Ensure newline separation
|
|
24
|
+
return contents.replace(anchor, "".concat(anchor, "\n").concat(linesToAdd.join("\n")));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Helper to replace content only if the target content exists and hasn't been replaced yet.
|
|
28
|
+
*/
|
|
29
|
+
function replaceContentOnce(contents, searchRegex, replacement, checkIfAlreadyReplaced) {
|
|
30
|
+
// If the replacement content is already present, assume it's done.
|
|
31
|
+
if (contents.includes(checkIfAlreadyReplaced)) {
|
|
32
|
+
return contents;
|
|
33
|
+
}
|
|
34
|
+
// Otherwise, perform the replacement if the search target exists.
|
|
35
|
+
return contents.replace(searchRegex, replacement);
|
|
36
|
+
}
|
|
37
|
+
var withHotUpdater = function (config) {
|
|
38
|
+
var modifiedConfig = config;
|
|
39
|
+
// === iOS: Objective-C & Swift in AppDelegate ===
|
|
40
|
+
modifiedConfig = (0, config_plugins_1.withAppDelegate)(modifiedConfig, function (cfg) {
|
|
41
|
+
var contents = cfg.modResults.contents;
|
|
42
|
+
var iosImport = "#import <HotUpdater/HotUpdater.h>";
|
|
43
|
+
var iosBundleUrl = "[HotUpdater bundleURL]";
|
|
44
|
+
var iosOriginalBundleUrlRegex = /\[\[NSBundle mainBundle\] URLForResource:@"main" withExtension:@"jsbundle"\]/g;
|
|
45
|
+
var iosAppDelegateHeader = '#import "AppDelegate.h"'; // Anchor for import
|
|
46
|
+
var swiftImport = "import HotUpdater";
|
|
47
|
+
var swiftBundleUrl = "HotUpdater.bundleURL()";
|
|
48
|
+
var swiftOriginalBundleUrlRegex = /Bundle\.main\.url\(forResource: "?main"?, withExtension: "jsbundle"\)/g;
|
|
49
|
+
var swiftReactImport = "import React"; // Anchor for import
|
|
50
|
+
// --- Objective-C ---
|
|
51
|
+
if (contents.includes(iosAppDelegateHeader)) {
|
|
52
|
+
// Check if it's likely Obj-C
|
|
53
|
+
// 1. Add import if missing
|
|
54
|
+
contents = addLinesOnce(contents, iosAppDelegateHeader, [iosImport]);
|
|
55
|
+
// 2. Replace bundleURL provider if the original exists and hasn't been replaced
|
|
56
|
+
contents = replaceContentOnce(contents, iosOriginalBundleUrlRegex, iosBundleUrl, iosBundleUrl);
|
|
57
|
+
}
|
|
58
|
+
// --- Swift ---
|
|
59
|
+
if (contents.includes(swiftReactImport)) {
|
|
60
|
+
// Check if it's likely Swift
|
|
61
|
+
// 1. Add import if missing
|
|
62
|
+
contents = addLinesOnce(contents, swiftReactImport, [swiftImport]);
|
|
63
|
+
// 2. Replace bundleURL provider if the original exists and hasn't been replaced
|
|
64
|
+
contents = replaceContentOnce(contents, swiftOriginalBundleUrlRegex, swiftBundleUrl, swiftBundleUrl);
|
|
65
|
+
}
|
|
66
|
+
cfg.modResults.contents = contents;
|
|
67
|
+
return cfg;
|
|
68
|
+
});
|
|
69
|
+
// === Android: Kotlin & Java in MainApplication ===
|
|
70
|
+
modifiedConfig = (0, config_plugins_1.withMainApplication)(modifiedConfig, function (cfg) {
|
|
71
|
+
var contents = cfg.modResults.contents;
|
|
72
|
+
var kotlinImport = "import com.hotupdater.HotUpdater";
|
|
73
|
+
var kotlinImportAnchor = "import com.facebook.react.ReactApplication";
|
|
74
|
+
var kotlinReactNativeHostAnchor = "object : DefaultReactNativeHost(this) {"; // Start of block
|
|
75
|
+
var kotlinMethodCheck = "HotUpdater.getJSBundleFile(applicationContext)"; // Unique part of the method body
|
|
76
|
+
// Regex to find an existing getJSBundleFile override (non-greedy)
|
|
77
|
+
var kotlinExistingMethodRegex = /^\s*override fun getJSBundleFile\(\): String\?\s*\{[\s\S]*?^\s*\}/gm;
|
|
78
|
+
var kotlinHermesAnchor = "override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED";
|
|
79
|
+
var kotlinNewMethod = "\n override fun getJSBundleFile(): String? {\n return HotUpdater.getJSBundleFile(applicationContext)\n }";
|
|
80
|
+
var javaImport = "import com.hotupdater.HotUpdater;";
|
|
81
|
+
var javaImportAnchor = "import com.facebook.react.ReactApplication;";
|
|
82
|
+
var javaReactNativeHostAnchor = "new DefaultReactNativeHost"; // Part of the instantiation
|
|
83
|
+
var javaMethodCheck = "HotUpdater.Companion.getJSBundleFile"; // Unique part of the method body
|
|
84
|
+
var javaMethodSignature = "protected String getJSBundleFile()";
|
|
85
|
+
// Regex to find an existing getJSBundleFile override (non-greedy)
|
|
86
|
+
var javaExistingMethodRegex = /^\s*@Override\s+protected String getJSBundleFile\(\)\s*\{[\s\S]*?^\s*\}/gm;
|
|
87
|
+
var javaHermesBlockEndAnchor = "return BuildConfig.IS_HERMES_ENABLED;\n }"; // End of the isHermesEnabled method block
|
|
88
|
+
var javaNewMethod = "\n @Override\n protected String getJSBundleFile() {\n return HotUpdater.Companion.getJSBundleFile(this.getApplication().getApplicationContext());\n }";
|
|
89
|
+
// --- Kotlin ---
|
|
90
|
+
if (contents.includes(kotlinReactNativeHostAnchor)) {
|
|
91
|
+
// Check if likely Kotlin
|
|
92
|
+
// 1. Add import if missing
|
|
93
|
+
contents = addLinesOnce(contents, kotlinImportAnchor, [kotlinImport]);
|
|
94
|
+
// 2. Add/Replace getJSBundleFile method if needed
|
|
95
|
+
if (!contents.includes(kotlinMethodCheck)) {
|
|
96
|
+
// Desired method content not found
|
|
97
|
+
// Remove potentially existing (different) override first
|
|
98
|
+
contents = contents.replace(kotlinExistingMethodRegex, "");
|
|
99
|
+
// Add the new method after the isHermesEnabled property
|
|
100
|
+
if (contents.includes(kotlinHermesAnchor)) {
|
|
101
|
+
contents = contents.replace(kotlinHermesAnchor, "".concat(kotlinHermesAnchor, "\n").concat(kotlinNewMethod));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Fallback: Add before the closing brace of the object if anchor not found
|
|
105
|
+
var rnHostEndRegex = /(\s*object\s*:\s*DefaultReactNativeHost\s*\([\s\S]*?\n)(\s*\})\s*$/m;
|
|
106
|
+
if (rnHostEndRegex.test(contents)) {
|
|
107
|
+
contents = contents.replace(rnHostEndRegex, "$1".concat(kotlinNewMethod, "\n$2"));
|
|
108
|
+
throw new Error("[withHotUpdater] Kotlin: Could not find Hermes anchor. Added getJSBundleFile before closing brace.");
|
|
109
|
+
}
|
|
110
|
+
throw new Error("[withHotUpdater] Kotlin: Could not find Hermes anchor or closing brace to insert getJSBundleFile.");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// --- Java ---
|
|
115
|
+
if (contents.includes(javaReactNativeHostAnchor) &&
|
|
116
|
+
contents.includes("@Override")) {
|
|
117
|
+
// Check if likely Java
|
|
118
|
+
// 1. Add import if missing
|
|
119
|
+
contents = addLinesOnce(contents, javaImportAnchor, [javaImport]);
|
|
120
|
+
// 2. Add/Replace getJSBundleFile method if needed
|
|
121
|
+
if (!contents.includes(javaMethodCheck)) {
|
|
122
|
+
// Desired method content not found
|
|
123
|
+
// Remove potentially existing (different) override first
|
|
124
|
+
contents = contents.replace(javaExistingMethodRegex, "");
|
|
125
|
+
// Add the new method after the isHermesEnabled method block
|
|
126
|
+
if (contents.includes(javaHermesBlockEndAnchor)) {
|
|
127
|
+
contents = contents.replace(javaHermesBlockEndAnchor, "".concat(javaHermesBlockEndAnchor, "\n").concat(javaNewMethod));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Fallback: Add before the closing brace of the anonymous class
|
|
131
|
+
var rnHostEndRegex = /(\s*new\s*DefaultReactNativeHost\s*\([\s\S]*?\n)(\s*\});\s*$/m;
|
|
132
|
+
if (rnHostEndRegex.test(contents)) {
|
|
133
|
+
contents = contents.replace(rnHostEndRegex, "$1".concat(javaNewMethod, "\n$2"));
|
|
134
|
+
throw new Error("[withHotUpdater] Java: Could not find Hermes anchor. Added getJSBundleFile before closing brace.");
|
|
135
|
+
}
|
|
136
|
+
throw new Error("[withHotUpdater] Java: Could not find Hermes anchor or closing brace to insert getJSBundleFile.");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
cfg.modResults.contents = contents;
|
|
141
|
+
return cfg;
|
|
142
|
+
});
|
|
143
|
+
return modifiedConfig;
|
|
144
|
+
};
|
|
145
|
+
// Export the plugin using createRunOncePlugin for idempotency at the plugin level
|
|
146
|
+
exports.default = (0, config_plugins_1.createRunOncePlugin)(withHotUpdater, package_json_1.default.name, package_json_1.default.version);
|
package/src/native.ts
CHANGED
|
@@ -7,7 +7,7 @@ declare const __HOT_UPDATER_CHANNEL: string | undefined;
|
|
|
7
7
|
|
|
8
8
|
const HotUpdater = {
|
|
9
9
|
HOT_UPDATER_BUNDLE_ID: __HOT_UPDATER_BUNDLE_ID || NIL_UUID,
|
|
10
|
-
CHANNEL: __HOT_UPDATER_CHANNEL || "production",
|
|
10
|
+
CHANNEL: __HOT_UPDATER_CHANNEL || (!__DEV__ ? "production" : null),
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
const RCTNativeHotUpdater = require("./specs/NativeHotUpdater").default;
|