catalyst-core 0.1.0-beta.2 → 0.1.0-beta.4
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/README.md +1 -1
- package/bin/catalyst.js +8 -1
- package/changelog.md +6 -0
- package/dist/native/androidProject/app/build.gradle.kts +9 -0
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +2 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +1 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +17 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +31 -3
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +148 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeCameraManager.kt +337 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/TransitionManager.kt +202 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +3 -3
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +123 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +244 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/HoldController.kt +70 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/TorchController.kt +57 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamState.kt +41 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamStateMachine.kt +40 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ViewfinderMapper.kt +75 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ZoomController.kt +156 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/CatalystPlugin.kt +7 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/GeneratedPluginIndex.kt +6 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/PluginBridge.kt +253 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/FridaDetector.kt +16 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +42 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +7 -3
- package/dist/native/androidProject/app/src/main/res/drawable/debug_viewfinder_border.xml +8 -0
- package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +37 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +139 -0
- package/dist/native/androidSetup.js +1 -1
- package/dist/native/bridge/WebBridge.js +5 -5
- package/dist/native/bridge/constants/NativeInterfaces.js +9 -5
- package/dist/native/bridge/hooks/index.js +1 -0
- package/dist/native/bridge/hooks/useCamera.js +9 -0
- package/dist/native/bridge/hooks/useDataProtection.js +2 -0
- package/dist/native/bridge/hooks/useDeviceInfo.js +2 -0
- package/dist/native/bridge/hooks/useFilePicker.js +2 -0
- package/dist/native/bridge/hooks/useGoogleSignIn.js +1 -0
- package/dist/native/bridge/hooks/useHapticFeedback.js +1 -0
- package/dist/native/bridge/hooks/useIntent.js +1 -0
- package/dist/native/bridge/hooks/useNativeTransition.js +43 -0
- package/dist/native/bridge/hooks/useNetworkStatus.js +3 -0
- package/dist/native/bridge/hooks/useNotification.js +2 -0
- package/dist/native/bridge/hooks/useSafeArea.js +2 -0
- package/dist/native/bridge/hooks/useVideoStream.js +6 -0
- package/dist/native/bridge/hooks/utils.js +1 -0
- package/dist/native/bridge/hooks.js +1 -137
- package/dist/native/bridge/useBaseHook.js +12 -7
- package/dist/native/bridge/utils/NativeBridge.js +41 -6
- package/dist/native/buildAppAndroid.js +7 -9
- package/dist/native/buildAppIos.js +14 -22
- package/dist/native/internalPluginUtils.js +1 -0
- package/dist/native/iosnativeWebView/Package.resolved +36 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/BarcodeDetector.swift +116 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraPreviewView.swift +58 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +308 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +63 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +194 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/TorchController.swift +60 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamState.swift +44 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamStateMachine.swift +60 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +130 -0
- package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +11 -1
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/CatalystPlugin.swift +5 -0
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/GeneratedPluginIndex.swift +6 -0
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +364 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +4 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +227 -4
- package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +169 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WeakScriptMessageHandler.swift +14 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +38 -7
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +6 -2
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +15 -4
- package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +11 -17
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +8 -2
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/PluginBridgeTests.swift +160 -0
- package/dist/native/plugin-bridge/PluginBridge.js +1 -0
- package/dist/native/pluginComposerAndroid.js +9 -0
- package/dist/native/pluginComposerIos.js +7 -0
- package/dist/native/renameAndroidProject.js +3 -3
- package/dist/native/setupServer.js +2 -3
- package/dist/native/utils.js +4 -1
- package/dist/otel.js +70 -2
- package/dist/scripts/devServe.js +1 -1
- package/dist/scripts/plugins.js +1 -0
- package/dist/scripts/registerAliases.js +6 -2
- package/dist/scripts/scriptUtils.js +2 -1
- package/dist/scripts/serve.js +2 -1
- package/dist/scripts/start.js +4 -1
- package/dist/server/renderer/extract.js +5 -5
- package/dist/server/renderer/handler.js +15 -14
- package/dist/server/utils/validator.js +1 -1
- package/dist/webpack/base.babel.js +2 -1
- package/dist/webpack/development.client.babel.js +6 -2
- package/dist/webpack/production.ssr.babel.js +5 -1
- package/mcp_v2/knowledge-base.json +1564 -1067
- package/mcp_v2/lib/helpers.js +19 -7
- package/mcp_v2/tools/build.js +1 -1
- package/mcp_v2/tools/config.js +16 -4
- package/mcp_v2/tools/conversion.js +11 -11
- package/mcp_v2/tools/knowledge.js +16 -3
- package/mcp_v2/tools/tasks.js +13 -4
- package/package.json +2 -2
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
package io.yourname.androidproject.plugins
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import android.webkit.JavascriptInterface
|
|
7
|
+
import android.webkit.WebView
|
|
8
|
+
import io.yourname.androidproject.CatalystConstants
|
|
9
|
+
import org.json.JSONArray
|
|
10
|
+
import org.json.JSONException
|
|
11
|
+
import org.json.JSONObject
|
|
12
|
+
import java.util.Properties
|
|
13
|
+
|
|
14
|
+
internal data class PluginRequest(
|
|
15
|
+
val pluginId: String,
|
|
16
|
+
val command: String,
|
|
17
|
+
val data: JSONObject?,
|
|
18
|
+
val requestId: String?
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
private class PluginBridgeRuntimeError(
|
|
22
|
+
val publicMessage: String,
|
|
23
|
+
val code: String,
|
|
24
|
+
cause: Throwable? = null
|
|
25
|
+
) : Exception(publicMessage, cause)
|
|
26
|
+
|
|
27
|
+
class PluginBridge(
|
|
28
|
+
private val activity: Activity,
|
|
29
|
+
private val webView: WebView,
|
|
30
|
+
private val properties: Properties
|
|
31
|
+
) {
|
|
32
|
+
companion object {
|
|
33
|
+
private const val TAG = "PluginBridge"
|
|
34
|
+
private const val ERROR_EVENT = "PLUGIN_BRIDGE_ERROR"
|
|
35
|
+
private const val SYSTEM_PLUGIN_ID = "__bridge__"
|
|
36
|
+
private const val ERROR_CODE_INVALID_PAYLOAD = "INVALID_PAYLOAD"
|
|
37
|
+
private const val ERROR_CODE_PLUGIN_NOT_FOUND = "PLUGIN_NOT_FOUND"
|
|
38
|
+
private const val ERROR_CODE_COMMAND_NOT_SUPPORTED = "COMMAND_NOT_SUPPORTED"
|
|
39
|
+
private const val ERROR_CODE_PLUGIN_NOT_REGISTERED = "PLUGIN_NOT_REGISTERED"
|
|
40
|
+
private const val ERROR_CODE_PLUGIN_INSTANTIATION_FAILED = "PLUGIN_INSTANTIATION_FAILED"
|
|
41
|
+
private const val ERROR_CODE_PLUGIN_EXECUTION_FAILED = "PLUGIN_EXECUTION_FAILED"
|
|
42
|
+
|
|
43
|
+
private fun readRequiredString(body: JSONObject, key: String): String {
|
|
44
|
+
if (!body.has(key) || body.isNull(key)) {
|
|
45
|
+
return ""
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
val rawValue = body.get(key)
|
|
49
|
+
if (rawValue !is String) {
|
|
50
|
+
throw IllegalArgumentException("$key must be a string")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return rawValue.trim()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private fun readOptionalString(body: JSONObject, key: String): String? {
|
|
57
|
+
if (!body.has(key) || body.isNull(key)) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
val rawValue = body.get(key)
|
|
62
|
+
if (rawValue !is String) {
|
|
63
|
+
throw IllegalArgumentException("$key must be a string when provided")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return rawValue.trim().ifEmpty { null }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private fun readOptionalObject(body: JSONObject, key: String): JSONObject? {
|
|
70
|
+
if (!body.has(key) || body.isNull(key)) {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
val rawValue = body.get(key)
|
|
75
|
+
if (rawValue !is JSONObject) {
|
|
76
|
+
throw IllegalArgumentException("$key must be an object when provided")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return rawValue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
internal fun parseRequest(payload: String?): PluginRequest {
|
|
83
|
+
if (payload.isNullOrBlank()) {
|
|
84
|
+
throw IllegalArgumentException("Payload is required")
|
|
85
|
+
}
|
|
86
|
+
val messageSize = payload.toByteArray(Charsets.UTF_8).size
|
|
87
|
+
if (messageSize > CatalystConstants.Bridge.MAX_MESSAGE_SIZE) {
|
|
88
|
+
throw IllegalArgumentException("Payload exceeds maximum size")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
val body = JSONObject(payload)
|
|
92
|
+
return PluginRequest(
|
|
93
|
+
pluginId = readRequiredString(body, "pluginId"),
|
|
94
|
+
command = readRequiredString(body, "command"),
|
|
95
|
+
data = readOptionalObject(body, "data"),
|
|
96
|
+
requestId = readOptionalString(body, "requestId")
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private val pluginIdToClassName = GeneratedPluginIndex.pluginIdToClassName
|
|
102
|
+
private val pluginToCommands = GeneratedPluginIndex.pluginToCommands
|
|
103
|
+
|
|
104
|
+
@JavascriptInterface
|
|
105
|
+
fun emit(payload: String?) {
|
|
106
|
+
var request: PluginRequest? = null
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
request = parseRequest(payload)
|
|
110
|
+
|
|
111
|
+
if (request.pluginId.isEmpty()) {
|
|
112
|
+
sendBridgeError("pluginId is required", ERROR_CODE_INVALID_PAYLOAD, request)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
if (request.command.isEmpty()) {
|
|
116
|
+
sendBridgeError("command is required", ERROR_CODE_INVALID_PAYLOAD, request)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!hasPlugin(request.pluginId)) {
|
|
121
|
+
sendBridgeError("Unsupported plugin: ${request.pluginId}", ERROR_CODE_PLUGIN_NOT_FOUND, request)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!hasCommand(request.pluginId, request.command)) {
|
|
126
|
+
sendBridgeError(
|
|
127
|
+
"Unsupported command '${request.command}' for plugin '${request.pluginId}'",
|
|
128
|
+
ERROR_CODE_COMMAND_NOT_SUPPORTED,
|
|
129
|
+
request
|
|
130
|
+
)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
val plugin = try {
|
|
135
|
+
getPluginForId(request.pluginId)
|
|
136
|
+
} catch (error: PluginBridgeRuntimeError) {
|
|
137
|
+
sendBridgeError(error.publicMessage, error.code, request)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
val callbackContext = PluginBridgeContext(
|
|
142
|
+
activity = activity,
|
|
143
|
+
webView = webView,
|
|
144
|
+
properties = properties,
|
|
145
|
+
pluginId = request.pluginId,
|
|
146
|
+
command = request.command,
|
|
147
|
+
requestId = request.requestId
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
plugin.handle(request.command, request.data, callbackContext)
|
|
151
|
+
} catch (error: IllegalArgumentException) {
|
|
152
|
+
sendBridgeError(error.message ?: "Invalid payload", ERROR_CODE_INVALID_PAYLOAD, request)
|
|
153
|
+
} catch (error: JSONException) {
|
|
154
|
+
sendBridgeError("Invalid JSON payload", ERROR_CODE_INVALID_PAYLOAD, request)
|
|
155
|
+
} catch (error: Exception) {
|
|
156
|
+
Log.e(TAG, "Plugin command failed for ${request?.pluginId ?: "<unknown>"}.${request?.command ?: "<unknown>"}", error)
|
|
157
|
+
sendBridgeError("Plugin execution failed", ERROR_CODE_PLUGIN_EXECUTION_FAILED, request)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun sendBridgeError(message: String, code: String, request: PluginRequest?) {
|
|
162
|
+
PluginBridgeContext(
|
|
163
|
+
activity = activity,
|
|
164
|
+
webView = webView,
|
|
165
|
+
properties = properties,
|
|
166
|
+
pluginId = SYSTEM_PLUGIN_ID,
|
|
167
|
+
command = request?.command,
|
|
168
|
+
requestId = request?.requestId
|
|
169
|
+
).callback(
|
|
170
|
+
ERROR_EVENT,
|
|
171
|
+
JSONObject().apply {
|
|
172
|
+
put("message", message)
|
|
173
|
+
put("code", code)
|
|
174
|
+
put("pluginId", request?.pluginId ?: SYSTEM_PLUGIN_ID)
|
|
175
|
+
request?.command?.takeIf { it.isNotEmpty() }?.let { put("command", it) }
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private fun hasPlugin(pluginId: String): Boolean {
|
|
181
|
+
return pluginIdToClassName.containsKey(pluginId)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private fun hasCommand(pluginId: String, command: String): Boolean {
|
|
185
|
+
return pluginToCommands[pluginId]?.contains(command) ?: false
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private fun getPluginForId(pluginId: String): CatalystPlugin {
|
|
189
|
+
val className = pluginIdToClassName[pluginId]
|
|
190
|
+
?: throw PluginBridgeRuntimeError(
|
|
191
|
+
"Plugin is not registered",
|
|
192
|
+
ERROR_CODE_PLUGIN_NOT_REGISTERED
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return try {
|
|
196
|
+
val clazz = Class.forName(className)
|
|
197
|
+
val instance = clazz.getDeclaredConstructor().newInstance()
|
|
198
|
+
instance as? CatalystPlugin
|
|
199
|
+
?: throw IllegalStateException("Plugin class '$className' must implement CatalystPlugin")
|
|
200
|
+
} catch (error: Exception) {
|
|
201
|
+
Log.e(TAG, "Failed to instantiate plugin class $className for plugin $pluginId", error)
|
|
202
|
+
throw PluginBridgeRuntimeError(
|
|
203
|
+
"Plugin could not be initialized",
|
|
204
|
+
ERROR_CODE_PLUGIN_INSTANTIATION_FAILED,
|
|
205
|
+
error
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
class PluginBridgeContext(
|
|
212
|
+
val activity: Activity,
|
|
213
|
+
val webView: WebView,
|
|
214
|
+
val properties: Properties,
|
|
215
|
+
val pluginId: String,
|
|
216
|
+
val command: String?,
|
|
217
|
+
val requestId: String?
|
|
218
|
+
) {
|
|
219
|
+
val context: Context
|
|
220
|
+
get() = activity
|
|
221
|
+
|
|
222
|
+
fun callback(
|
|
223
|
+
eventName: String,
|
|
224
|
+
data: Any?,
|
|
225
|
+
command: String? = this.command
|
|
226
|
+
) {
|
|
227
|
+
require(eventName.isNotBlank()) { "Callback eventName is required" }
|
|
228
|
+
|
|
229
|
+
val pluginLiteral = JSONObject.quote(pluginId)
|
|
230
|
+
val eventLiteral = JSONObject.quote(eventName)
|
|
231
|
+
val dataLiteral = toJavaScriptLiteral(data)
|
|
232
|
+
val requestLiteral = requestId?.let(JSONObject::quote) ?: "null"
|
|
233
|
+
val commandLiteral = command?.takeIf { it.isNotBlank() }?.let(JSONObject::quote) ?: "null"
|
|
234
|
+
|
|
235
|
+
webView.post {
|
|
236
|
+
webView.evaluateJavascript(
|
|
237
|
+
"window.PluginBridgeWeb && window.PluginBridgeWeb.callback($pluginLiteral, $eventLiteral, $dataLiteral, $requestLiteral, $commandLiteral);",
|
|
238
|
+
null
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private fun toJavaScriptLiteral(value: Any?): String {
|
|
244
|
+
return when (value) {
|
|
245
|
+
null -> "null"
|
|
246
|
+
is JSONObject -> value.toString()
|
|
247
|
+
is JSONArray -> value.toString()
|
|
248
|
+
is Number, is Boolean -> value.toString()
|
|
249
|
+
is String -> JSONObject.quote(value)
|
|
250
|
+
else -> JSONObject.wrap(value)?.toString() ?: "null"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -5,7 +5,8 @@ import io.yourname.androidproject.utils.BridgeUtils
|
|
|
5
5
|
import java.io.BufferedReader
|
|
6
6
|
import java.io.File
|
|
7
7
|
import java.io.InputStreamReader
|
|
8
|
-
import java.net.
|
|
8
|
+
import java.net.InetSocketAddress
|
|
9
|
+
import java.nio.channels.SocketChannel
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Local Frida and hooking framework detection.
|
|
@@ -68,10 +69,20 @@ object FridaDetector {
|
|
|
68
69
|
private fun checkFridaPorts(): Boolean {
|
|
69
70
|
return FRIDA_PORTS.any { port ->
|
|
70
71
|
try {
|
|
71
|
-
|
|
72
|
-
val address =
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
SocketChannel.open().use { channel ->
|
|
73
|
+
val address = InetSocketAddress("127.0.0.1", port)
|
|
74
|
+
val deadline = System.currentTimeMillis() + 2000
|
|
75
|
+
channel.configureBlocking(false)
|
|
76
|
+
channel.connect(address)
|
|
77
|
+
|
|
78
|
+
while (!channel.finishConnect()) {
|
|
79
|
+
if (System.currentTimeMillis() >= deadline) {
|
|
80
|
+
return@use false
|
|
81
|
+
}
|
|
82
|
+
Thread.sleep(25)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
true
|
|
75
86
|
}
|
|
76
87
|
} catch (e: Exception) {
|
|
77
88
|
// Connection failed or timed out - no Frida on this port
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt
CHANGED
|
@@ -76,12 +76,25 @@ object BridgeUtils {
|
|
|
76
76
|
// Safe area events
|
|
77
77
|
ON_SAFE_AREA_INSETS_UPDATED("ON_SAFE_AREA_INSETS_UPDATED"),
|
|
78
78
|
|
|
79
|
+
// Video stream events
|
|
80
|
+
ON_VIDEO_STREAM_READY("ON_VIDEO_STREAM_READY"),
|
|
81
|
+
ON_VIDEO_STREAM_STOPPED("ON_VIDEO_STREAM_STOPPED"),
|
|
82
|
+
ON_QR_DETECTED("ON_QR_DETECTED"),
|
|
83
|
+
ON_TORCH_CHANGED("ON_TORCH_CHANGED"),
|
|
84
|
+
ON_ZOOM_CHANGED("ON_ZOOM_CHANGED"),
|
|
85
|
+
|
|
79
86
|
// Security events
|
|
80
87
|
ON_SCREEN_SECURE_SET("ON_SCREEN_SECURE_SET"),
|
|
81
88
|
ON_SCREEN_SECURE_STATUS("ON_SCREEN_SECURE_STATUS"),
|
|
82
89
|
ON_SCREEN_SECURE_ERROR("ON_SCREEN_SECURE_ERROR"),
|
|
83
90
|
ON_WEB_DATA_CLEARED("ON_WEB_DATA_CLEARED"),
|
|
84
91
|
ON_WEB_DATA_CLEAR_ERROR("ON_WEB_DATA_CLEAR_ERROR"),
|
|
92
|
+
|
|
93
|
+
// Transition events
|
|
94
|
+
ON_TRANSITION_COMMITTED("ON_TRANSITION_COMMITTED"),
|
|
95
|
+
ON_TRANSITION_CANCELLED("ON_TRANSITION_CANCELLED"),
|
|
96
|
+
ON_TRANSITION_TIMEOUT("ON_TRANSITION_TIMEOUT"),
|
|
97
|
+
|
|
85
98
|
}
|
|
86
99
|
|
|
87
100
|
/**
|
|
@@ -99,13 +112,25 @@ object BridgeUtils {
|
|
|
99
112
|
}
|
|
100
113
|
|
|
101
114
|
try {
|
|
102
|
-
val safeData = data?.replace("'", "\\'") ?: ""
|
|
103
115
|
val jsCode = if (data != null) {
|
|
104
|
-
|
|
116
|
+
// If data looks like a JSON object or array, inject it raw (with U+2028/U+2029
|
|
117
|
+
// escaping) so the JS side receives a live value rather than a double-encoded string.
|
|
118
|
+
// Otherwise wrap with JSONObject.quote() to produce a safe JS string literal.
|
|
119
|
+
val trimmed = data.trimStart()
|
|
120
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
121
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
122
|
+
val safeData = data
|
|
123
|
+
.replace("
", "\\u2028")
|
|
124
|
+
.replace("
", "\\u2029")
|
|
125
|
+
"window.WebBridge.callback('${event.eventName}', $safeData)"
|
|
126
|
+
} else {
|
|
127
|
+
val quotedData = org.json.JSONObject.quote(data)
|
|
128
|
+
"window.WebBridge.callback('${event.eventName}', $quotedData)"
|
|
129
|
+
}
|
|
105
130
|
} else {
|
|
106
131
|
"window.WebBridge.callback('${event.eventName}', null)"
|
|
107
132
|
}
|
|
108
|
-
|
|
133
|
+
|
|
109
134
|
webView.evaluateJavascript(jsCode, null)
|
|
110
135
|
Log.d(TAG, "✅ Web notification sent: ${event.eventName}")
|
|
111
136
|
} catch (e: Exception) {
|
|
@@ -146,9 +171,21 @@ object BridgeUtils {
|
|
|
146
171
|
* @param jsonData The JSON object to send
|
|
147
172
|
*/
|
|
148
173
|
fun notifyWebJson(webView: WebView, event: WebEvents, jsonData: JSONObject) {
|
|
174
|
+
// Ensure WebView interactions happen on the main thread
|
|
175
|
+
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
176
|
+
webView.post { notifyWebJson(webView, event, jsonData) }
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
149
180
|
try {
|
|
150
|
-
|
|
151
|
-
|
|
181
|
+
// Inject JSON directly into the JS call. U+2028/U+2029 are valid JSON whitespace but
|
|
182
|
+
// are treated as line terminators by V8, causing a SyntaxError — escape them first.
|
|
183
|
+
val safeJson = jsonData.toString()
|
|
184
|
+
.replace("
", "\\u2028")
|
|
185
|
+
.replace("
", "\\u2029")
|
|
186
|
+
val jsCode = "window.WebBridge.callback('${event.eventName}', $safeJson)"
|
|
187
|
+
webView.evaluateJavascript(jsCode, null)
|
|
188
|
+
Log.d(TAG, "✅ Web JSON notification sent: ${event.eventName}")
|
|
152
189
|
} catch (e: Exception) {
|
|
153
190
|
Log.e(TAG, "❌ Failed to send JSON to web: ${e.message}")
|
|
154
191
|
notifyWebError(webView, event, "Error processing data: ${e.message}")
|
|
@@ -12,7 +12,8 @@ import io.ktor.server.routing.*
|
|
|
12
12
|
import kotlinx.coroutines.*
|
|
13
13
|
import java.io.File
|
|
14
14
|
import java.io.IOException
|
|
15
|
-
import java.net.
|
|
15
|
+
import java.net.InetSocketAddress
|
|
16
|
+
import java.nio.channels.ServerSocketChannel
|
|
16
17
|
import java.security.SecureRandom
|
|
17
18
|
import java.util.*
|
|
18
19
|
import java.util.concurrent.ConcurrentHashMap
|
|
@@ -279,7 +280,10 @@ object FrameworkServerUtils {
|
|
|
279
280
|
|
|
280
281
|
private fun isPortAvailable(port: Int): Boolean {
|
|
281
282
|
return try {
|
|
282
|
-
|
|
283
|
+
ServerSocketChannel.open().use { channel ->
|
|
284
|
+
channel.bind(InetSocketAddress("127.0.0.1", port))
|
|
285
|
+
true
|
|
286
|
+
}
|
|
283
287
|
} catch (e: IOException) {
|
|
284
288
|
false
|
|
285
289
|
}
|
|
@@ -408,4 +412,4 @@ object FrameworkServerUtils {
|
|
|
408
412
|
}
|
|
409
413
|
}
|
|
410
414
|
}
|
|
411
|
-
}
|
|
415
|
+
}
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
app:layout_constraintStart_toStartOf="parent"
|
|
14
14
|
app:layout_constraintEnd_toEndOf="parent">
|
|
15
15
|
|
|
16
|
+
<androidx.camera.view.PreviewView
|
|
17
|
+
android:id="@+id/camera_preview"
|
|
18
|
+
android:layout_width="match_parent"
|
|
19
|
+
android:layout_height="match_parent"
|
|
20
|
+
android:visibility="invisible" />
|
|
21
|
+
|
|
16
22
|
<WebView
|
|
17
23
|
android:id="@+id/webview"
|
|
18
24
|
android:layout_width="match_parent"
|
|
@@ -24,6 +30,37 @@
|
|
|
24
30
|
android:layout_width="wrap_content"
|
|
25
31
|
android:layout_height="wrap_content"
|
|
26
32
|
android:visibility="gone" />
|
|
33
|
+
|
|
34
|
+
<RelativeLayout
|
|
35
|
+
android:id="@+id/debug_viewfinder_overlay"
|
|
36
|
+
android:layout_width="0dp"
|
|
37
|
+
android:layout_height="0dp"
|
|
38
|
+
android:background="@null"
|
|
39
|
+
android:visibility="gone">
|
|
40
|
+
|
|
41
|
+
<TextView
|
|
42
|
+
android:id="@+id/debug_qr_status"
|
|
43
|
+
android:layout_width="wrap_content"
|
|
44
|
+
android:layout_height="wrap_content"
|
|
45
|
+
android:layout_alignParentBottom="true"
|
|
46
|
+
android:layout_centerHorizontal="true"
|
|
47
|
+
android:layout_marginBottom="8dp"
|
|
48
|
+
android:paddingHorizontal="10dp"
|
|
49
|
+
android:paddingVertical="4dp"
|
|
50
|
+
android:background="#CC000000"
|
|
51
|
+
android:textColor="#FFFFFF"
|
|
52
|
+
android:textSize="12sp"
|
|
53
|
+
android:text="⬤ –" />
|
|
54
|
+
|
|
55
|
+
</RelativeLayout>
|
|
56
|
+
|
|
57
|
+
<!-- Debug: shows where native thinks the barcode box is in screen space -->
|
|
58
|
+
<View
|
|
59
|
+
android:id="@+id/debug_barcode_overlay"
|
|
60
|
+
android:layout_width="0dp"
|
|
61
|
+
android:layout_height="0dp"
|
|
62
|
+
android:background="#5500FF00"
|
|
63
|
+
android:visibility="gone" />
|
|
27
64
|
</RelativeLayout>
|
|
28
65
|
|
|
29
66
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
package io.yourname.androidproject.plugins
|
|
2
|
+
|
|
3
|
+
import io.yourname.androidproject.CatalystConstants
|
|
4
|
+
import org.json.JSONException
|
|
5
|
+
import org.json.JSONObject
|
|
6
|
+
import org.junit.Assert.assertEquals
|
|
7
|
+
import org.junit.Assert.assertNull
|
|
8
|
+
import org.junit.Assert.assertTrue
|
|
9
|
+
import org.junit.Assert.fail
|
|
10
|
+
import org.junit.Test
|
|
11
|
+
|
|
12
|
+
class PluginBridgeTest {
|
|
13
|
+
|
|
14
|
+
@Test
|
|
15
|
+
fun `parseRequest accepts valid payload and trims string fields`() {
|
|
16
|
+
val request = PluginBridge.parseRequest(
|
|
17
|
+
"""
|
|
18
|
+
{
|
|
19
|
+
"pluginId": " device-info-plugin ",
|
|
20
|
+
"command": " getDeviceInfo ",
|
|
21
|
+
"data": { "includeSecurity": true },
|
|
22
|
+
"requestId": " req-123 "
|
|
23
|
+
}
|
|
24
|
+
""".trimIndent()
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
assertEquals("device-info-plugin", request.pluginId)
|
|
28
|
+
assertEquals("getDeviceInfo", request.command)
|
|
29
|
+
assertEquals("req-123", request.requestId)
|
|
30
|
+
assertEquals(true, (request.data as JSONObject).getBoolean("includeSecurity"))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@Test
|
|
34
|
+
fun `parseRequest treats blank requestId as null`() {
|
|
35
|
+
val request = PluginBridge.parseRequest(
|
|
36
|
+
"""
|
|
37
|
+
{
|
|
38
|
+
"pluginId": "device-info-plugin",
|
|
39
|
+
"command": "getDeviceInfo",
|
|
40
|
+
"requestId": " "
|
|
41
|
+
}
|
|
42
|
+
""".trimIndent()
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
assertNull(request.requestId)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Test
|
|
49
|
+
fun `parseRequest rejects blank payload`() {
|
|
50
|
+
try {
|
|
51
|
+
PluginBridge.parseRequest(" ")
|
|
52
|
+
fail("Expected invalid blank payload to throw")
|
|
53
|
+
} catch (error: IllegalArgumentException) {
|
|
54
|
+
assertEquals("Payload is required", error.message)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Test
|
|
59
|
+
fun `parseRequest rejects oversized payload`() {
|
|
60
|
+
val oversizedData = "x".repeat(CatalystConstants.Bridge.MAX_MESSAGE_SIZE + 256)
|
|
61
|
+
val payload = """
|
|
62
|
+
{
|
|
63
|
+
"pluginId": "device-info-plugin",
|
|
64
|
+
"command": "getDeviceInfo",
|
|
65
|
+
"data": "$oversizedData"
|
|
66
|
+
}
|
|
67
|
+
""".trimIndent()
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
PluginBridge.parseRequest(payload)
|
|
71
|
+
fail("Expected oversized payload to throw")
|
|
72
|
+
} catch (error: IllegalArgumentException) {
|
|
73
|
+
assertEquals("Payload exceeds maximum size", error.message)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Test
|
|
78
|
+
fun `parseRequest rejects non string pluginId`() {
|
|
79
|
+
try {
|
|
80
|
+
PluginBridge.parseRequest(
|
|
81
|
+
"""
|
|
82
|
+
{
|
|
83
|
+
"pluginId": 42,
|
|
84
|
+
"command": "getDeviceInfo"
|
|
85
|
+
}
|
|
86
|
+
""".trimIndent()
|
|
87
|
+
)
|
|
88
|
+
fail("Expected non-string pluginId to throw")
|
|
89
|
+
} catch (error: IllegalArgumentException) {
|
|
90
|
+
assertEquals("pluginId must be a string", error.message)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@Test
|
|
95
|
+
fun `parseRequest rejects non string requestId`() {
|
|
96
|
+
try {
|
|
97
|
+
PluginBridge.parseRequest(
|
|
98
|
+
"""
|
|
99
|
+
{
|
|
100
|
+
"pluginId": "device-info-plugin",
|
|
101
|
+
"command": "getDeviceInfo",
|
|
102
|
+
"requestId": 42
|
|
103
|
+
}
|
|
104
|
+
""".trimIndent()
|
|
105
|
+
)
|
|
106
|
+
fail("Expected non-string requestId to throw")
|
|
107
|
+
} catch (error: IllegalArgumentException) {
|
|
108
|
+
assertEquals("requestId must be a string when provided", error.message)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@Test
|
|
113
|
+
fun `parseRequest rejects invalid JSON`() {
|
|
114
|
+
try {
|
|
115
|
+
PluginBridge.parseRequest("{")
|
|
116
|
+
fail("Expected invalid JSON to throw")
|
|
117
|
+
} catch (error: JSONException) {
|
|
118
|
+
assertTrue(error.message?.isNotBlank() == true)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@Test
|
|
123
|
+
fun `parseRequest rejects non object data`() {
|
|
124
|
+
try {
|
|
125
|
+
PluginBridge.parseRequest(
|
|
126
|
+
"""
|
|
127
|
+
{
|
|
128
|
+
"pluginId": "device-info-plugin",
|
|
129
|
+
"command": "getDeviceInfo",
|
|
130
|
+
"data": "unsafe"
|
|
131
|
+
}
|
|
132
|
+
""".trimIndent()
|
|
133
|
+
)
|
|
134
|
+
fail("Expected non-object data to throw")
|
|
135
|
+
} catch (error: IllegalArgumentException) {
|
|
136
|
+
assertEquals("data must be an object when provided", error.message)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -3,6 +3,6 @@ try{await(0,_utils.runCommand)("brew install --cask zulu@17");}catch(error){if(e
|
|
|
3
3
|
if(!_fs.default.existsSync(javaHome)){throw new Error(`Java installation not found at ${javaHome}. Please verify the installation manually.`);}// Set up environment variables
|
|
4
4
|
const homeDir=process.env.HOME;const shellProfile=`${homeDir}/.zshrc`;const exportCommand=`\n# Java Configuration\nexport JAVA_HOME=${javaHome}\nexport PATH=$JAVA_HOME/bin:$PATH\n`;try{_fs.default.appendFileSync(shellProfile,exportCommand);}catch(error){throw new Error(`Failed to update ${shellProfile}: ${error.message}. Please add the following lines manually:\n${exportCommand}`);}// Set for current process
|
|
5
5
|
process.env.JAVA_HOME=javaHome;process.env.PATH=`${javaHome}/bin:${process.env.PATH}`;// Verify Java configuration
|
|
6
|
-
try{const javaVersion=(0,_utils.runCommand)("java -version");progress.log("Java installed and configured successfully","success");progress.log(`Java Version: ${javaVersion}`,"info");progress.log(`IMPORTANT: Please run the following commands to complete setup:`,"warning");progress.log(`1. source ${shellProfile}`,"info");progress.log(`2. echo $JAVA_HOME`,"info");return true;}catch(error){throw new Error(`Java installation verified but environment not configured. Please run:\n1. source ${shellProfile}\n2. echo $JAVA_HOME`);}}catch(error){progress.log(error.message,"error");progress.log("Manual Setup Instructions:","info");progress.log("1. Run: brew reinstall --cask zulu@17","info");progress.log("2. Add to ~/.zshrc:","info");progress.log(" export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home","info");progress.log(" export PATH=$JAVA_HOME/bin:$PATH","info");progress.log("3. Run: source ~/.zshrc","info");throw error;}}async function validateJavaEnvironment(){progress.start("java");const hasJava=await checkJavaInstallation();if(!hasJava){progress.log("Java installation required. Starting installation process...");const javaInstalled=await installJava();if(!javaInstalled){progress.fail("java","Failed to set up Java environment");throw new Error("Failed to set up Java environment");}}progress.complete("java");}async function initializeConfig(){const configFile=_fs.default.readFileSync(configPath,"utf8");const config=JSON.parse(configFile);const{WEBVIEW_CONFIG}=config;if(!WEBVIEW_CONFIG||Object.keys(WEBVIEW_CONFIG).length===0){progress.log("WebView Config missing in "+configPath,"error");process.exit(1);}if(!WEBVIEW_CONFIG.android){WEBVIEW_CONFIG.android={};}return{WEBVIEW_CONFIG};}async function saveConfig(newConfig){try{const existingConfigFile=_fs.default.readFileSync(configPath,"utf8");const existingConfig=JSON.parse(existingConfigFile);const updatedConfig={...existingConfig,WEBVIEW_CONFIG:{...existingConfig.WEBVIEW_CONFIG,android:newConfig.WEBVIEW_CONFIG.android}};_fs.default.writeFileSync(configPath,JSON.stringify(updatedConfig,null,2));progress.log("Configuration saved successfully","success");}catch(error){progress.log("Failed to save configuration: "+error.message,"error");process.exit(1);}}function validateAndroidTools(androidConfig){const ANDROID_SDK=androidConfig.sdkPath;const EMULATOR_PATH=`${ANDROID_SDK}/emulator/emulator`;const ADB_PATH=`${ANDROID_SDK}/platform-tools/adb`;if(!ANDROID_SDK){throw new Error("Android SDK path is not configured");}if(!_fs.default.existsSync(ANDROID_SDK)){throw new Error(`Android SDK path does not exist: ${ANDROID_SDK}`);}if(!_fs.default.existsSync(ADB_PATH)){throw new Error(`ADB not found at: ${ADB_PATH}`);}try{(0,_utils.runCommand)(`${ADB_PATH} version`);progress.log("ADB is valid","success");}catch(error){throw new Error(`ADB is not working properly: ${error.message}`);}if(!_fs.default.existsSync(EMULATOR_PATH)){throw new Error(`Emulator not found at: ${EMULATOR_PATH}`);}try{(0,_utils.runCommand)(`${EMULATOR_PATH} -version`);progress.log("Emulator is valid","success");}catch(error){throw new Error(`Emulator is not working properly: ${error.message}`);}try{const avdOutput=(0,_utils.runCommand)(`${EMULATOR_PATH} -list-avds`);if(!avdOutput.includes(androidConfig.emulatorName)){throw new Error(`Specified emulator "${androidConfig.emulatorName}" not found in available AVDs`);}progress.log(`Emulator "${androidConfig.emulatorName}" exists`,"success");}catch(error){throw new Error(`Error checking emulator AVD: ${error.message}`);}progress.log("Android tools validation completed successfully!","success");return{EMULATOR_PATH,ADB_PATH};}async function checkEmulator(ADB_PATH){try{const devices=(0,_utils.runCommand)(`${ADB_PATH} devices`);return devices.includes("emulator");}catch(error){progress.log(`Error checking emulator: ${error}`,"error");return false;}}async function startEmulator(EMULATOR_PATH,androidConfig){progress.log(`Starting emulator: ${androidConfig.emulatorName}...`);//
|
|
6
|
+
try{const javaVersion=(0,_utils.runCommand)("java -version");progress.log("Java installed and configured successfully","success");progress.log(`Java Version: ${javaVersion}`,"info");progress.log(`IMPORTANT: Please run the following commands to complete setup:`,"warning");progress.log(`1. source ${shellProfile}`,"info");progress.log(`2. echo $JAVA_HOME`,"info");return true;}catch(error){throw new Error(`Java installation verified but environment not configured. Please run:\n1. source ${shellProfile}\n2. echo $JAVA_HOME`);}}catch(error){progress.log(error.message,"error");progress.log("Manual Setup Instructions:","info");progress.log("1. Run: brew reinstall --cask zulu@17","info");progress.log("2. Add to ~/.zshrc:","info");progress.log(" export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home","info");progress.log(" export PATH=$JAVA_HOME/bin:$PATH","info");progress.log("3. Run: source ~/.zshrc","info");throw error;}}async function validateJavaEnvironment(){progress.start("java");const hasJava=await checkJavaInstallation();if(!hasJava){progress.log("Java installation required. Starting installation process...");const javaInstalled=await installJava();if(!javaInstalled){progress.fail("java","Failed to set up Java environment");throw new Error("Failed to set up Java environment");}}progress.complete("java");}async function initializeConfig(){const configFile=_fs.default.readFileSync(configPath,"utf8");const config=JSON.parse(configFile);const{WEBVIEW_CONFIG}=config;if(!WEBVIEW_CONFIG||Object.keys(WEBVIEW_CONFIG).length===0){progress.log("WebView Config missing in "+configPath,"error");process.exit(1);}if(!WEBVIEW_CONFIG.android){WEBVIEW_CONFIG.android={};}return{WEBVIEW_CONFIG};}async function saveConfig(newConfig){try{const existingConfigFile=_fs.default.readFileSync(configPath,"utf8");const existingConfig=JSON.parse(existingConfigFile);const updatedConfig={...existingConfig,WEBVIEW_CONFIG:{...existingConfig.WEBVIEW_CONFIG,android:newConfig.WEBVIEW_CONFIG.android}};_fs.default.writeFileSync(configPath,JSON.stringify(updatedConfig,null,2));progress.log("Configuration saved successfully","success");}catch(error){progress.log("Failed to save configuration: "+error.message,"error");process.exit(1);}}function validateAndroidTools(androidConfig){const ANDROID_SDK=androidConfig.sdkPath;const EMULATOR_PATH=`${ANDROID_SDK}/emulator/emulator`;const ADB_PATH=`${ANDROID_SDK}/platform-tools/adb`;if(!ANDROID_SDK){throw new Error("Android SDK path is not configured");}if(!_fs.default.existsSync(ANDROID_SDK)){throw new Error(`Android SDK path does not exist: ${ANDROID_SDK}`);}if(!_fs.default.existsSync(ADB_PATH)){throw new Error(`ADB not found at: ${ADB_PATH}`);}try{(0,_utils.runCommand)(`${ADB_PATH} version`);progress.log("ADB is valid","success");}catch(error){throw new Error(`ADB is not working properly: ${error.message}`);}if(!_fs.default.existsSync(EMULATOR_PATH)){throw new Error(`Emulator not found at: ${EMULATOR_PATH}`);}try{(0,_utils.runCommand)(`${EMULATOR_PATH} -version`);progress.log("Emulator is valid","success");}catch(error){throw new Error(`Emulator is not working properly: ${error.message}`);}try{const avdOutput=(0,_utils.runCommand)(`${EMULATOR_PATH} -list-avds`);if(!avdOutput.includes(androidConfig.emulatorName)){throw new Error(`Specified emulator "${androidConfig.emulatorName}" not found in available AVDs`);}progress.log(`Emulator "${androidConfig.emulatorName}" exists`,"success");}catch(error){throw new Error(`Error checking emulator AVD: ${error.message}`);}progress.log("Android tools validation completed successfully!","success");return{EMULATOR_PATH,ADB_PATH};}async function checkEmulator(ADB_PATH){try{const devices=(0,_utils.runCommand)(`${ADB_PATH} devices`);return devices.includes("emulator");}catch(error){progress.log(`Error checking emulator: ${error}`,"error");return false;}}async function startEmulator(EMULATOR_PATH,androidConfig){progress.log(`Starting emulator: ${androidConfig.emulatorName}...`);// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - EMULATOR_PATH is validated and emulatorName comes from local Android config.
|
|
7
7
|
(0,_child_process.exec)(`${EMULATOR_PATH} -avd ${androidConfig.emulatorName} -read-only > /dev/null &`,error=>{if(error){progress.log(`Error starting emulator: ${error}`,"error");}});}async function updateLocalProperties(sdkPath){progress.start("properties");try{(0,_utils.runCommand)(`cd ${pwd}/androidProject && ./gradlew updateSdkPath -PsdkPath="${sdkPath}"`);progress.log("Updated local.properties successfully","success");progress.complete("properties");}catch(error){progress.fail("properties",`Failed to update local.properties: ${error.message}`);throw error;}}async function setupAndroidEnvironment(){try{await validateJavaEnvironment();progress.start("config");await initializeConfig();const config=await(0,_utils.validateAndCompleteConfig)("android",configPath);progress.complete("config");progress.start("androidTools");const{EMULATOR_PATH,ADB_PATH}=validateAndroidTools(config.android);progress.complete("androidTools");progress.start("emulator");const emulatorRunning=await checkEmulator(ADB_PATH);if(!emulatorRunning){progress.log("No emulator running, attempting to start one...");await startEmulator(EMULATOR_PATH,config.android);}else{progress.log("Emulator already running, proceeding with installation...","success");}progress.complete("emulator");if(config.android?.sdkPath){await updateLocalProperties(config.android.sdkPath);}progress.start("saveConfig");await saveConfig({WEBVIEW_CONFIG:config});progress.complete("saveConfig");progress.start("setupServer");await(0,_setupServer.setupServer)(configPath);progress.complete("setupServer");progress.printTreeContent("Configuration Explanation",["WEBVIEW_CONFIG: Main configuration object for the WebView setup",{text:"port: Port number for the WebView server",indent:1,prefix:"├─ ",color:"gray"},{text:"android: Android-specific configuration",indent:1,prefix:"└─ ",color:"gray"},{text:"buildType: Build type (debug/release)",indent:2,prefix:"├─ ",color:"gray"},{text:"sdkPath: Android SDK path",indent:2,prefix:"├─ ",color:"gray"},{text:"emulatorName: Selected Android emulator name",indent:2,prefix:"└─ ",color:"gray"}]);progress.printTreeContent("Final Configuration",[JSON.stringify(config,null,2)]);process.exit(0);}catch(error){if(progress.currentStep){progress.fail(progress.currentStep.id,error.message);}process.exit(1);}}// Execute the main setup
|
|
8
8
|
setupAndroidEnvironment();
|