debugger:complete android part

This commit is contained in:
pengfeizhou 2021-02-21 01:51:59 +08:00 committed by osborn
parent 4c0237aec6
commit af31014e2e
16 changed files with 526 additions and 561 deletions

View File

@ -2,6 +2,8 @@ package pub.doric.devkit;
import android.widget.Toast; import android.widget.Toast;
import com.github.pengfeizhou.jscore.JSONBuilder;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
@ -15,13 +17,12 @@ import pub.doric.devkit.event.ConnectExceptionEvent;
import pub.doric.devkit.event.EOFExceptionEvent; import pub.doric.devkit.event.EOFExceptionEvent;
import pub.doric.devkit.event.OpenEvent; import pub.doric.devkit.event.OpenEvent;
import pub.doric.devkit.event.StopDebugEvent; import pub.doric.devkit.event.StopDebugEvent;
import pub.doric.utils.DoricLog;
public class DevKit implements IDevKit { public class DevKit implements IDevKit {
public static boolean isRunningInEmulator = false; public static boolean isRunningInEmulator = false;
public static String ip = ""; public static String ip = "";
private static class Inner { private static class Inner {
private static final DevKit sInstance = new DevKit(); private static final DevKit sInstance = new DevKit();
} }
@ -38,6 +39,10 @@ public class DevKit implements IDevKit {
private WSClient wsClient; private WSClient wsClient;
boolean devKitConnected = false;
private DoricContextDebuggable debuggable;
@Override @Override
public void connectDevKit(String url) { public void connectDevKit(String url) {
wsClient = new WSClient(url); wsClient = new WSClient(url);
@ -54,9 +59,39 @@ public class DevKit implements IDevKit {
wsClient = null; wsClient = null;
} }
boolean devKitConnected = false; @Override
public void startDebugging(String source) {
if (debuggable != null) {
debuggable.stopDebug(true);
}
DoricContext context = matchContext(source);
if (context == null) {
DoricLog.d("Cannot find context source %s for debugging", source);
wsClient.sendToDebugger("DEBUG_STOP", new JSONBuilder()
.put("msg", "Cannot find suitable alive context for debugging")
.toJSONObject());
} else {
wsClient.sendToDebugger(
"DEBUG_RES",
new JSONBuilder()
.put("contextId", context.getContextId())
.toJSONObject());
debuggable = new DoricContextDebuggable(wsClient, context);
debuggable.startDebug();
}
}
@Override
public void stopDebugging(boolean resume) {
wsClient.sendToDebugger("DEBUG_STOP", new JSONBuilder()
.put("msg", "Stop debugging")
.toJSONObject());
if (debuggable != null) {
debuggable.stopDebug(resume);
debuggable = null;
}
}
private DoricContextDebuggable doricContextDebuggable;
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
public void onOpenEvent(OpenEvent openEvent) { public void onOpenEvent(OpenEvent openEvent) {
@ -78,10 +113,10 @@ public class DevKit implements IDevKit {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
public void onQuitDebugEvent(StopDebugEvent quitDebugEvent) { public void onQuitDebugEvent(StopDebugEvent quitDebugEvent) {
doricContextDebuggable.stopDebug(); stopDebugging(true);
} }
public DoricContext requestDebugContext(String source) { public DoricContext matchContext(String source) {
for (DoricContext context : DoricContextManager.aliveContexts()) { for (DoricContext context : DoricContextManager.aliveContexts()) {
if (source.contains(context.getSource()) || context.getSource().equals("__dev__")) { if (source.contains(context.getSource()) || context.getSource().equals("__dev__")) {
return context; return context;
@ -91,16 +126,14 @@ public class DevKit implements IDevKit {
} }
public void reload(String source, String script) { public void reload(String source, String script) {
for (DoricContext context : DoricContextManager.aliveContexts()) { DoricContext context = matchContext(source);
if (doricContextDebuggable != null && if (context == null) {
doricContextDebuggable.isDebugging && DoricLog.d("Cannot find context source %s for reload", source);
doricContextDebuggable.getContext().getContextId().equals(context.getContextId())) { } else if (context.getDriver() instanceof DoricDebugDriver) {
System.out.println("is debugging context id: " + context.getContextId()); DoricLog.d("Context source %s in debugging,skip reload", source);
} else { } else {
if (source.contains(context.getSource()) || context.getSource().equals("__dev__")) { DoricLog.d("Context reload :id %s,source %s ", context.getContextId(), source);
context.reload(script); context.reload(script);
}
}
} }
} }
} }

View File

@ -5,34 +5,28 @@ import pub.doric.IDoricDriver;
public class DoricContextDebuggable { public class DoricContextDebuggable {
private final DoricContext doricContext; private final DoricContext doricContext;
private DoricDebugDriver debugDriver;
private final IDoricDriver nativeDriver; private final IDoricDriver nativeDriver;
public boolean isDebugging = false; private final WSClient wsClient;
public DoricContextDebuggable(DoricContext doricContext) { public DoricContextDebuggable(WSClient wsClient, DoricContext doricContext) {
this.wsClient = wsClient;
this.doricContext = doricContext; this.doricContext = doricContext;
this.isDebugging = true;
this.nativeDriver = this.doricContext.getDriver(); this.nativeDriver = this.doricContext.getDriver();
} }
public void startDebug() { public void startDebug() {
debugDriver = new DoricDebugDriver(new IStatusCallback() { doricContext.setDriver(new DoricDebugDriver(this.wsClient));
@Override
public void start() {
doricContext.setDriver(debugDriver);
doricContext.reload(doricContext.getScript());
}
});
}
public void stopDebug() {
isDebugging = false;
doricContext.setDriver(nativeDriver);
debugDriver.destroy();
doricContext.reload(doricContext.getScript()); doricContext.reload(doricContext.getScript());
} }
public DoricContext getContext() { public void stopDebug(boolean resume) {
return doricContext; IDoricDriver doricDriver = doricContext.getDriver();
if (doricDriver instanceof DoricDebugDriver) {
((DoricDebugDriver) doricDriver).destroy();
}
if (resume) {
doricContext.setDriver(nativeDriver);
doricContext.reload(doricContext.getScript());
}
} }
} }

View File

@ -28,7 +28,6 @@ import pub.doric.DoricRegistry;
import pub.doric.IDoricDriver; import pub.doric.IDoricDriver;
import pub.doric.async.AsyncCall; import pub.doric.async.AsyncCall;
import pub.doric.async.AsyncResult; import pub.doric.async.AsyncResult;
import pub.doric.engine.DoricJSEngine;
import pub.doric.utils.DoricConstant; import pub.doric.utils.DoricConstant;
import pub.doric.utils.DoricLog; import pub.doric.utils.DoricLog;
import pub.doric.utils.ThreadMode; import pub.doric.utils.ThreadMode;
@ -39,17 +38,17 @@ import pub.doric.utils.ThreadMode;
* @CreateDate: 2019-07-18 * @CreateDate: 2019-07-18
*/ */
public class DoricDebugDriver implements IDoricDriver { public class DoricDebugDriver implements IDoricDriver {
private final DoricJSEngine doricJSEngine; private final DoricDebugJSEngine doricDebugJSEngine;
private final ExecutorService mBridgeExecutor; private final ExecutorService mBridgeExecutor;
private final Handler mUIHandler; private final Handler mUIHandler;
private final Handler mJSHandler; private final Handler mJSHandler;
private String theContextId = null;
public DoricDebugDriver(WSClient wsClient) {
public DoricDebugDriver(IStatusCallback statusCallback) { doricDebugJSEngine = new DoricDebugJSEngine(wsClient);
doricJSEngine = new DoricDebugJSEngine(statusCallback);
mBridgeExecutor = Executors.newCachedThreadPool(); mBridgeExecutor = Executors.newCachedThreadPool();
mUIHandler = new Handler(Looper.getMainLooper()); mUIHandler = new Handler(Looper.getMainLooper());
mJSHandler = doricJSEngine.getJSHandler(); mJSHandler = doricDebugJSEngine.getJSHandler();
} }
@Override @Override
@ -69,7 +68,7 @@ public class DoricDebugDriver implements IDoricDriver {
@Override @Override
public JSDecoder call() { public JSDecoder call() {
try { try {
return doricJSEngine.invokeDoricMethod(method, args); return doricDebugJSEngine.invokeDoricMethod(method, args);
} catch (Exception e) { } catch (Exception e) {
DoricLog.e("invokeDoricMethod(%s,...),error is %s", method, e.getLocalizedMessage()); DoricLog.e("invokeDoricMethod(%s,...),error is %s", method, e.getLocalizedMessage());
return new JSDecoder(null); return new JSDecoder(null);
@ -97,7 +96,7 @@ public class DoricDebugDriver implements IDoricDriver {
@Override @Override
public Boolean call() { public Boolean call() {
try { try {
doricJSEngine.prepareContext(contextId, script, source); theContextId = contextId;
return true; return true;
} catch (Exception e) { } catch (Exception e) {
DoricLog.e("createContext %s error is %s", source, e.getLocalizedMessage()); DoricLog.e("createContext %s error is %s", source, e.getLocalizedMessage());
@ -113,7 +112,9 @@ public class DoricDebugDriver implements IDoricDriver {
@Override @Override
public Boolean call() { public Boolean call() {
try { try {
doricJSEngine.destroyContext(contextId); if (contextId.equals(theContextId)) {
DevKit.getInstance().stopDebugging(false);
}
return true; return true;
} catch (Exception e) { } catch (Exception e) {
DoricLog.e("destroyContext %s error is %s", contextId, e.getLocalizedMessage()); DoricLog.e("destroyContext %s error is %s", contextId, e.getLocalizedMessage());
@ -125,11 +126,11 @@ public class DoricDebugDriver implements IDoricDriver {
@Override @Override
public DoricRegistry getRegistry() { public DoricRegistry getRegistry() {
return doricJSEngine.getRegistry(); return doricDebugJSEngine.getRegistry();
} }
public void destroy() { public void destroy() {
doricJSEngine.teardown(); doricDebugJSEngine.teardown();
mBridgeExecutor.shutdown(); mBridgeExecutor.shutdown();
} }
} }

View File

@ -4,16 +4,15 @@ import pub.doric.devkit.remote.DoricRemoteJSExecutor;
import pub.doric.engine.DoricJSEngine; import pub.doric.engine.DoricJSEngine;
public class DoricDebugJSEngine extends DoricJSEngine { public class DoricDebugJSEngine extends DoricJSEngine {
private final WSClient wsClient;
private IStatusCallback statusCallback; public DoricDebugJSEngine(WSClient wsClient) {
public DoricDebugJSEngine(IStatusCallback statusCallback) {
super(); super();
this.statusCallback = statusCallback; this.wsClient = wsClient;
} }
@Override @Override
protected void initJSEngine() { protected void initJSEngine() {
mDoricJSE = new DoricRemoteJSExecutor(statusCallback); mDoricJSE = new DoricRemoteJSExecutor(this.wsClient);
} }
} }

View File

@ -14,4 +14,8 @@ public interface IDevKit {
void sendDevCommand(IDevKit.Command command, JSONObject jsonObject); void sendDevCommand(IDevKit.Command command, JSONObject jsonObject);
void disconnectDevKit(); void disconnectDevKit();
void startDebugging(String source);
void stopDebugging(boolean resume);
} }

View File

@ -1,5 +0,0 @@
package pub.doric.devkit;
public interface IStatusCallback {
void start();
}

View File

@ -23,6 +23,8 @@ import org.json.JSONObject;
import java.io.EOFException; import java.io.EOFException;
import java.net.ConnectException; import java.net.ConnectException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -43,6 +45,12 @@ import pub.doric.devkit.event.OpenEvent;
public class WSClient extends WebSocketListener { public class WSClient extends WebSocketListener {
private final WebSocket webSocket; private final WebSocket webSocket;
public interface Interceptor {
boolean intercept(String type, String command, JSONObject payload) throws JSONException;
}
private Set<Interceptor> interceptors = new HashSet<>();
public WSClient(String url) { public WSClient(String url) {
OkHttpClient okHttpClient = new OkHttpClient OkHttpClient okHttpClient = new OkHttpClient
.Builder() .Builder()
@ -53,6 +61,15 @@ public class WSClient extends WebSocketListener {
webSocket = okHttpClient.newWebSocket(request, this); webSocket = okHttpClient.newWebSocket(request, this);
} }
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public void removeInterceptor(Interceptor interceptor) {
interceptors.remove(interceptor);
}
public void close() { public void close() {
webSocket.close(1000, "Close"); webSocket.close(1000, "Close");
} }
@ -72,23 +89,21 @@ public class WSClient extends WebSocketListener {
String type = jsonObject.optString("type"); String type = jsonObject.optString("type");
String cmd = jsonObject.optString("cmd"); String cmd = jsonObject.optString("cmd");
JSONObject payload = jsonObject.optJSONObject("payload"); JSONObject payload = jsonObject.optJSONObject("payload");
if ("D2C".equals(type)) { for (Interceptor interceptor : interceptors) {
if ("DEBUG_REQ".equals(cmd)) { if (interceptor.intercept(type, cmd, payload)) {
String source = payload.optString("source"); return;
DoricContext context = DevKit.getInstance().requestDebugContext(source);
sendToDebugger("DEBUG_RES", new JSONBuilder()
.put("contextId", context == null ? "" : context.getContextId())
.toJSONObject());
}
} else if ("S2C".equals(type)) {
if ("RELOAD".equals(cmd)) {
String source = payload.optString("source");
String script = payload.optString("script");
DevKit.getInstance().reload(source, script);
} }
} }
if ("DEBUG_REQ".equals(cmd)) {
String source = payload.optString("source");
DevKit.getInstance().startDebugging(source);
} else if ("DEBUG_STOP".equals(cmd)) {
DevKit.getInstance().stopDebugging(true);
} else if ("RELOAD".equals(cmd)) {
String source = payload.optString("source");
String script = payload.optString("script");
DevKit.getInstance().reload(source, script);
}
} catch (JSONException e) { } catch (JSONException e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@ -20,15 +20,15 @@ import com.github.pengfeizhou.jscore.JSRuntimeException;
import com.github.pengfeizhou.jscore.JavaFunction; import com.github.pengfeizhou.jscore.JavaFunction;
import com.github.pengfeizhou.jscore.JavaValue; import com.github.pengfeizhou.jscore.JavaValue;
import pub.doric.devkit.IStatusCallback; import pub.doric.devkit.WSClient;
import pub.doric.engine.IDoricJSE; import pub.doric.engine.IDoricJSE;
public class DoricRemoteJSExecutor implements IDoricJSE { public class DoricRemoteJSExecutor implements IDoricJSE {
private final RemoteJSExecutor mRemoteJSExecutor; private final RemoteJSExecutor mRemoteJSExecutor;
public DoricRemoteJSExecutor(IStatusCallback statusCallback) { public DoricRemoteJSExecutor(WSClient wsClient) {
this.mRemoteJSExecutor = new RemoteJSExecutor(statusCallback); this.mRemoteJSExecutor = new RemoteJSExecutor(wsClient);
} }
@Override @Override

View File

@ -5,102 +5,26 @@ import com.github.pengfeizhou.jscore.JSONBuilder;
import com.github.pengfeizhou.jscore.JavaFunction; import com.github.pengfeizhou.jscore.JavaFunction;
import com.github.pengfeizhou.jscore.JavaValue; import com.github.pengfeizhou.jscore.JavaValue;
import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.EOFException;
import java.net.ConnectException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import okhttp3.OkHttpClient; import pub.doric.devkit.WSClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import pub.doric.devkit.DevKit;
import pub.doric.devkit.IStatusCallback;
import pub.doric.devkit.event.StopDebugEvent;
public class RemoteJSExecutor { public class RemoteJSExecutor implements WSClient.Interceptor {
private final WebSocket webSocket;
private final Map<String, JavaFunction> globalFunctions = new HashMap<>(); private final Map<String, JavaFunction> globalFunctions = new HashMap<>();
private JSDecoder temp; private JSDecoder temp;
private final WSClient wsClient;
private final Thread currentThread;
public RemoteJSExecutor(final IStatusCallback statusCallback) { public RemoteJSExecutor(WSClient wsClient) {
OkHttpClient okHttpClient = new OkHttpClient this.wsClient = wsClient;
.Builder() this.wsClient.addInterceptor(this);
.readTimeout(10, TimeUnit.SECONDS) currentThread = Thread.currentThread();
.writeTimeout(10, TimeUnit.SECONDS)
.build();
final Request request = new Request.Builder().url("ws://" + DevKit.ip + ":2080").build();
final Thread current = Thread.currentThread();
webSocket = okHttpClient.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
LockSupport.unpark(current);
statusCallback.start();
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
if (t instanceof ConnectException) {
// 连接remote异常
LockSupport.unpark(current);
throw new RuntimeException("remote js executor cannot connect");
} else if (t instanceof EOFException) {
// 被远端强制断开
System.out.println("remote js executor eof");
LockSupport.unpark(current);
EventBus.getDefault().post(new StopDebugEvent());
}
}
@Override
public void onMessage(WebSocket webSocket, String text) {
try {
JSONObject jsonObject = new JSONObject(text);
String cmd = jsonObject.optString("cmd");
switch (cmd) {
case "injectGlobalJSFunction": {
String name = jsonObject.optString("name");
JSONArray arguments = jsonObject.optJSONArray("arguments");
assert arguments != null;
JSDecoder[] decoders = new JSDecoder[arguments.length()];
for (int i = 0; i < arguments.length(); i++) {
Object o = arguments.get(i);
decoders[i] = new JSDecoder(new ValueBuilder(o).build());
}
globalFunctions.get(name).exec(decoders);
}
break;
case "invokeMethod": {
try {
Object result = jsonObject.opt("result");
ValueBuilder vb = new ValueBuilder(result);
temp = new JSDecoder(vb.build());
System.out.println(result);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
LockSupport.unpark(current);
}
}
break;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
LockSupport.park(current);
} }
public String loadJS(String script, String source) { public String loadJS(String script, String source) {
@ -113,17 +37,22 @@ public class RemoteJSExecutor {
public void injectGlobalJSFunction(String name, JavaFunction javaFunction) { public void injectGlobalJSFunction(String name, JavaFunction javaFunction) {
globalFunctions.put(name, javaFunction); globalFunctions.put(name, javaFunction);
webSocket.send(new JSONBuilder().put("cmd", "injectGlobalJSFunction") wsClient.sendToDebugger(
.put("name", name).toString() "injectGlobalJSFunction",
); new JSONBuilder()
.put("cmd", "injectGlobalJSFunction")
.put("name", name)
.toJSONObject());
} }
public void injectGlobalJSObject(String name, JavaValue javaValue) { public void injectGlobalJSObject(String name, JavaValue javaValue) {
webSocket.send(new JSONBuilder().put("cmd", "injectGlobalJSObject") wsClient.sendToDebugger(
.put("name", name) "injectGlobalJSObject",
.put("type", javaValue.getType()) new JSONBuilder()
.put("value", javaValue.getValue()).toString() .put("name", name)
); .put("type", javaValue.getType())
.put("value", javaValue.getValue())
.toJSONObject());
} }
public JSDecoder invokeMethod(String objectName, String functionName, JavaValue[] javaValues, boolean hashKey) { public JSDecoder invokeMethod(String objectName, String functionName, JavaValue[] javaValues, boolean hashKey) {
@ -134,19 +63,60 @@ public class RemoteJSExecutor {
.put("value", javaValue.getValue()) .put("value", javaValue.getValue())
.toJSONObject()); .toJSONObject());
} }
webSocket.send(new JSONBuilder() wsClient.sendToDebugger(
.put("cmd", "invokeMethod") "invokeMethod",
.put("objectName", objectName) new JSONBuilder()
.put("functionName", functionName) .put("cmd", "invokeMethod")
.put("values", jsonArray) .put("objectName", objectName)
.put("hashKey", hashKey) .put("functionName", functionName)
.toString()); .put("values", jsonArray)
.put("hashKey", hashKey)
.toJSONObject());
LockSupport.park(Thread.currentThread()); LockSupport.park(Thread.currentThread());
return temp; return temp;
} }
public void destroy() { public void destroy() {
webSocket.close(1000, "destroy"); wsClient.sendToDebugger("DEBUG_STOP", null);
wsClient.removeInterceptor(this);
}
@Override
public boolean intercept(String type, String cmd, JSONObject payload) throws JSONException {
if ("D2C".equals(type)) {
switch (cmd) {
case "injectGlobalJSFunction": {
String name = payload.optString("name");
JSONArray arguments = payload.optJSONArray("arguments");
assert arguments != null;
JSDecoder[] decoders = new JSDecoder[arguments.length()];
for (int i = 0; i < arguments.length(); i++) {
Object o = arguments.get(i);
decoders[i] = new JSDecoder(new ValueBuilder(o).build());
}
globalFunctions.get(name).exec(decoders);
}
break;
case "invokeMethod": {
try {
Object result = payload.opt("result");
ValueBuilder vb = new ValueBuilder(result);
temp = new JSDecoder(vb.build());
System.out.println(result);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
LockSupport.unpark(currentThread);
}
}
break;
default:
break;
}
return true;
}
return false;
} }
} }

View File

@ -19,6 +19,8 @@ import android.animation.AnimatorSet;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -209,6 +211,7 @@ public class DoricContext {
mPluginMap.clear(); mPluginMap.clear();
this.script = script; this.script = script;
this.mRootNode.setId(""); this.mRootNode.setId("");
this.mRootNode.clearSubModel();
getDriver().createContext(mContextId, script, source); getDriver().createContext(mContextId, script, source);
init(this.extra); init(this.extra);
callEntity(DoricConstant.DORIC_ENTITY_CREATE); callEntity(DoricConstant.DORIC_ENTITY_CREATE);

View File

@ -6,13 +6,7 @@
"request": "launch", "request": "launch",
"name": "Doric Debugger", "name": "Doric Debugger",
"program": "${workspaceFolder}/${relativeFile}", "program": "${workspaceFolder}/${relativeFile}",
"preLaunchTask": "Doric Build",
"sourceMaps": true, "sourceMaps": true,
"serverReadyAction": {
"pattern": "listening on port ([0-9]+)",
"uriFormat": "http://localhost:%s",
"action": "openExternally"
},
"outFiles": [ "outFiles": [
"${workspaceFolder}/bundle/**/*.js" "${workspaceFolder}/bundle/**/*.js"
] ]

View File

@ -1,10 +1,5 @@
import fs from "fs";
import WebSocket from "ws" import WebSocket from "ws"
import "colors"; import "colors";
import path from "path";
import { delay, glob } from "./util";
import { Shell } from "./shell";
export type MSG = { export type MSG = {
type: "D2C" | "C2D" | "C2S" | "D2S" | "S2C" | "S2D", type: "D2C" | "C2D" | "C2S" | "D2S" | "S2C" | "S2D",
@ -13,59 +8,43 @@ export type MSG = {
} }
export async function createServer() { export async function createServer() {
let contextId: string = "0" let client: WebSocket | undefined = undefined;
let clientConnection: WebSocket | undefined = undefined let debug: WebSocket | undefined = undefined;
let debuggerConnection: WebSocket | undefined = undefined
let deviceId = 0 let deviceId = 0
return new WebSocket.Server({ port: 7777 }) const wss = new WebSocket.Server({ port: 7777 })
.on("connection", (ws, request) => { .on("connection", (ws, request) => {
let thisDeviceId: string let thisDeviceId: string
console.log('Connected', request.headers.host) console.log('Connected', request.headers.host)
if (request.headers.host?.startsWith("localhost")) { if (request.headers.host?.startsWith("localhost")) {
thisDeviceId = `Debugger#${deviceId++}` thisDeviceId = `Debugger#${deviceId++}`
console.log(`Debugger ${thisDeviceId} attached to dev kit`.green) console.log(`${thisDeviceId} attached to dev kit`.green)
debuggerConnection = ws debug = ws;
clientConnection?.send(JSON.stringify({
cmd: 'SWITCH_TO_DEBUG',
contextId: contextId
}))
} else { } else {
thisDeviceId = `Client#${deviceId++}` thisDeviceId = `Client#${deviceId++}`
console.log(`${thisDeviceId} attached to dev kit`.green) console.log(`${thisDeviceId} attached to dev kit`.green)
} }
ws.on('message', async function (result: string) { ws.on('message', async function (result: string) {
const resultObject = JSON.parse(result) as MSG const resultObject = JSON.parse(result) as MSG
if (resultObject.type === "D2C" || resultObject.type === "C2D") { if (resultObject.type === "D2C") {
ws.send(result); if (client === undefined) {
wss?.clients.forEach(e => {
e.send(result);
})
} else {
client.send(result);
}
} else if (resultObject.type === "C2D") {
if (resultObject.cmd === "DEBUG_STOP") {
client = undefined;
}
if (client === undefined) {
client = ws;
} else if (client !== ws) {
console.log("Can only debugging one client at the same time.".red);
}
debug?.send(result);
} else if (resultObject.type === "C2S") { } else if (resultObject.type === "C2S") {
switch (resultObject.cmd) { switch (resultObject.cmd) {
case 'DEBUG':
clientConnection = ws;
(ws as any).debugging = true;
console.log("Enter debugging");
contextId = resultObject.payload.contextId;
const projectHome = '.';
await fs.promises.writeFile(path.resolve(projectHome, "build", "context"), contextId, "utf-8");
let source = resultObject.payload.source as string;
if (source.startsWith(".js")) {
source = source.replace(".js", ".ts");
} else if (!source.startsWith(".ts")) {
source = source + ".ts"
}
let sourceFile = path.resolve(projectHome, "src", source);
if (!fs.existsSync(sourceFile)) {
const tsFiles = await glob(source, {
cwd: path.resolve(projectHome, "src")
})
if (!!!tsFiles || tsFiles.length === 0) {
console.error(`Cannot find ${source} in ${path.resolve(projectHome)}`);
}
sourceFile = tsFiles[0];
}
console.log(thisDeviceId + " request debug, project home: " + projectHome);
await Shell.exec("code", [projectHome, sourceFile]);
await delay(1500);
break;
case 'EXCEPTION': case 'EXCEPTION':
console.log(resultObject.payload.source.red); console.log(resultObject.payload.source.red);
console.log(resultObject.payload.exception.red); console.log(resultObject.payload.exception.red);
@ -87,19 +66,38 @@ export async function createServer() {
break break
} }
} }
}) });
ws.on('connect', function (code: number) {
console.log('connect', code)
})
ws.on('close', function (code: number) { ws.on('close', function (code: number) {
console.log('close: code = ' + code, thisDeviceId) console.log('close: code = ' + code, thisDeviceId);
console.log("quit debugging"); (ws as any).debugging = false;
(ws as any).debugging = false if (ws === debug) {
}) console.log("quit debugging");
client?.send(JSON.stringify({
type: "S2C",
cmd: "DEBUG_STOP"
} as MSG));
}
if (ws === client) {
console.log("quit debugging");
client = undefined
}
});
ws.on('error', function (code: number) { ws.on('error', function (code: number) {
console.log('error', code) console.log('error', code)
}) if (ws === debug) {
}) console.log("quit debugging");
client?.send(JSON.stringify({
type: "S2C",
cmd: "DEBUG_STOP"
} as MSG));
}
if (ws === client) {
console.log("quit debugging");
client = undefined
}
});
});
return wss;
} }

View File

@ -9,13 +9,7 @@
"request": "launch", "request": "launch",
"name": "Doric Debugger", "name": "Doric Debugger",
"program": "${workspaceFolder}/${relativeFile}", "program": "${workspaceFolder}/${relativeFile}",
"preLaunchTask": "Doric Build",
"sourceMaps": true, "sourceMaps": true,
"serverReadyAction": {
"pattern": "listening on port ([0-9]+)",
"uriFormat": "http://localhost:%s",
"action": "openExternally"
},
"outFiles": [ "outFiles": [
"${workspaceFolder}/bundle/**/*.js" "${workspaceFolder}/bundle/**/*.js"
] ]

View File

@ -4236,33 +4236,111 @@ function initNativeEnvironment(source) {
return __awaiter$1(this, void 0, void 0, function* () { return __awaiter$1(this, void 0, void 0, function* () {
// dev kit client // dev kit client
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const devClient = new WebSocket__default['default']('ws://localhost:7777'); const ws = new WebSocket__default['default']('ws://localhost:7777')
devClient.on('open', () => { .on('open', () => {
console.log('Connectted Devkit on port', '7777'); console.log('Connectted Devkit on port', '7777');
devClient.send(JSON.stringify({ ws.send(JSON.stringify({
type: "D2C", type: "D2C",
cmd: "DEBUG_REQ",
payload: { payload: {
cmd: "DEBUG_REQ",
source, source,
}, },
})); }));
}); })
devClient.on('message', (data) => { .on('message', (data) => {
console.log(data); var _a;
const msg = JSON.parse(data); const msg = JSON.parse(data);
switch (msg.cwd) { const payload = msg.payload;
switch (msg.cmd) {
case "DEBUG_RES": case "DEBUG_RES":
const contextId = msg.contextId; const contextId = msg.payload.contextId;
if ((contextId === null || contextId === void 0 ? void 0 : contextId.length) > 0) { resolve(contextId);
resolve(contextId); break;
case "injectGlobalJSObject":
console.log("injectGlobalJSObject", payload);
const type = payload.type;
const value = payload.value;
let arg;
if (type === 0) {
arg = null;
} }
else { else if (type === 1) {
reject(`Cannot find applicable context in client for source ${source}`); arg = parseFloat(value);
} }
else if (type === 2) {
arg = (value == 'true');
}
else if (type === 3) {
arg = value.toString();
}
else if (type === 4) {
arg = JSON.parse(value);
}
else if (type === 5) {
arg = JSON.parse(value);
}
Reflect.set(global$2, payload.name, arg);
break;
case "injectGlobalJSFunction":
console.log("injectGlobalJSFunction", payload);
Reflect.set(global$2, payload.name, function () {
let args = [].slice.call(arguments);
console.log(args);
console.log("injected", payload.name, args);
ws.send(JSON.stringify({
type: "D2C",
cmd: 'injectGlobalJSFunction',
payload: {
name: payload.name,
arguments: args
}
}));
});
break;
case "invokeMethod":
console.log("invokeMethod", payload);
const values = payload.values;
let args = [];
for (let i = 0; i < values.length; i++) {
let value = values[i];
if (value.type === 0) {
args.push(null);
}
else if (value.type === 1) {
args.push(parseFloat(value.value));
}
else if (value.type === 2) {
args.push((value.value == 'true'));
}
else if (value.type === 3) {
args.push(value.value.toString());
}
else if (value.type === 4) {
args.push(JSON.parse(value.value));
}
else if (value.type === 5) {
args.push(JSON.parse(value.value));
}
}
const object = Reflect.get(global$2, payload.objectName);
const method = Reflect.get(object, payload.functionName);
const result = Reflect.apply(method, undefined, args);
console.log(result);
ws.send(JSON.stringify({
type: "D2C",
cmd: 'invokeMethod',
payload: {
result
}
}));
break;
case "DEBUG_STOP":
console.log(((_a = msg.payload) === null || _a === void 0 ? void 0 : _a.msg) || "Stop debugging");
process.exit(0);
break; break;
} }
}); })
devClient.on('error', (error) => { .on('error', (error) => {
console.log(error); console.log(error);
reject(error); reject(error);
}); });
@ -4287,98 +4365,10 @@ global$2.Entry = function () {
console.log("debugging context id: " + contextId); console.log("debugging context id: " + contextId);
global$2.context = jsObtainContext(contextId); global$2.context = jsObtainContext(contextId);
Reflect.apply(jsObtainEntry(contextId), doric, args); Reflect.apply(jsObtainEntry(contextId), doric, args);
}).catch(error => console.error(error)); });
return arguments[0]; return arguments[0];
} }
}; };
// debug server
const debugServer = new WebSocket__default['default'].Server({ port: 2080 });
debugServer.on('connection', (ws) => {
console.log('connected');
ws.on('message', (message) => {
let messageObject = JSON.parse(message);
switch (messageObject.cmd) {
case "injectGlobalJSObject":
console.log(messageObject.name);
let type = messageObject.type;
let value = messageObject.value;
let arg;
if (type.type === 0) {
arg = null;
}
else if (type === 1) {
arg = parseFloat(value);
}
else if (type === 2) {
arg = (value == 'true');
}
else if (type === 3) {
arg = value.toString();
}
else if (type === 4) {
arg = JSON.parse(value);
}
else if (type === 5) {
arg = JSON.parse(value);
}
Reflect.set(global$2, messageObject.name, arg);
break;
case "injectGlobalJSFunction":
console.log(messageObject.name);
Reflect.set(global$2, messageObject.name, function () {
let args = [].slice.call(arguments);
console.log("===============================");
console.log(args);
console.log("===============================");
ws.send(JSON.stringify({
cmd: 'injectGlobalJSFunction',
name: messageObject.name,
arguments: args
}));
});
break;
case "invokeMethod":
console.log(messageObject.objectName);
console.log(messageObject.functionName);
let args = [];
for (let i = 0; i < messageObject.values.length; i++) {
let value = messageObject.values[i];
if (value.type === 0) {
args.push(null);
}
else if (value.type === 1) {
args.push(parseFloat(value.value));
}
else if (value.type === 2) {
args.push((value.value == 'true'));
}
else if (value.type === 3) {
args.push(value.value.toString());
}
else if (value.type === 4) {
args.push(JSON.parse(value.value));
}
else if (value.type === 5) {
args.push(JSON.parse(value.value));
}
}
console.log(args);
console.log(messageObject.hashKey);
let object = Reflect.get(global$2, messageObject.objectName);
let method = Reflect.get(object, messageObject.functionName);
let result = Reflect.apply(method, undefined, args);
console.log(result);
ws.send(JSON.stringify({
cmd: 'invokeMethod',
result: result
}));
break;
}
});
});
debugServer.on('listening', function connection(ws) {
console.log('debugger server started on 2080');
});
global$2.injectGlobal = (objName, obj) => { global$2.injectGlobal = (objName, obj) => {
Reflect.set(global$2, objName, JSON.parse(obj)); Reflect.set(global$2, objName, JSON.parse(obj));
}; };

View File

@ -20,7 +20,7 @@ import path from 'path'
type MSG = { type MSG = {
type: "D2C" | "C2D" | "C2S" | "D2S" | "S2C" | "S2D", type: "D2C" | "C2D" | "C2S" | "D2S" | "S2C" | "S2D",
cmd: string, cmd: string,
payload: { [index: string]: string } payload: { [index: string]: string | number | { type: number, value: string }[] }
} }
let contextId: string | undefined = undefined; let contextId: string | undefined = undefined;
@ -36,35 +36,103 @@ global.doric = doric
async function initNativeEnvironment(source: string) { async function initNativeEnvironment(source: string) {
// dev kit client // dev kit client
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
const devClient = new WebSocket('ws://localhost:7777') const ws = new WebSocket('ws://localhost:7777')
devClient.on('open', () => { .on('open', () => {
console.log('Connectted Devkit on port', '7777') console.log('Connectted Devkit on port', '7777')
devClient.send(JSON.stringify({ ws.send(JSON.stringify({
type: "D2C", type: "D2C",
payload: {
cmd: "DEBUG_REQ", cmd: "DEBUG_REQ",
source, payload: {
}, source,
})) },
}) } as MSG))
devClient.on('message', (data) => { })
console.log(data) .on('message', (data) => {
const msg = JSON.parse(data as string) as { type: string, cwd: string, [idx: string]: string } const msg = JSON.parse(data as string) as MSG
switch (msg.cwd) { const payload = msg.payload
case "DEBUG_RES": switch (msg.cmd) {
const contextId = msg.contextId; case "DEBUG_RES":
if (contextId?.length > 0) { const contextId = msg.payload.contextId as string;
resolve(contextId) resolve(contextId);
} else { break;
reject(`Cannot find applicable context in client for source ${source}`) case "injectGlobalJSObject":
} console.log("injectGlobalJSObject", payload);
break; const type = payload.type as number
} const value = payload.value as string
}) let arg
devClient.on('error', (error) => { if (type === 0) {
console.log(error) arg = null
reject(error) } else if (type === 1) {
}) arg = parseFloat(value)
} else if (type === 2) {
arg = (value == 'true')
} else if (type === 3) {
arg = value.toString()
} else if (type === 4) {
arg = JSON.parse(value)
} else if (type === 5) {
arg = JSON.parse(value)
}
Reflect.set(global, payload.name as string, arg)
break
case "injectGlobalJSFunction":
console.log("injectGlobalJSFunction", payload);
Reflect.set(global, payload.name as string, function () {
let args = [].slice.call(arguments)
console.log(args)
console.log("injected", payload.name, args)
ws.send(JSON.stringify({
type: "D2C",
cmd: 'injectGlobalJSFunction',
payload: {
name: payload.name,
arguments: args
}
} as MSG))
})
break
case "invokeMethod":
console.log("invokeMethod", payload)
const values = payload.values as { type: number, value: string }[]
let args = []
for (let i = 0; i < values.length; i++) {
let value = values[i]
if (value.type === 0) {
args.push(null)
} else if (value.type === 1) {
args.push(parseFloat(value.value))
} else if (value.type === 2) {
args.push((value.value == 'true'))
} else if (value.type === 3) {
args.push(value.value.toString())
} else if (value.type === 4) {
args.push(JSON.parse(value.value))
} else if (value.type === 5) {
args.push(JSON.parse(value.value))
}
}
const object = Reflect.get(global, payload.objectName as string)
const method = Reflect.get(object, payload.functionName as string)
const result = Reflect.apply(method, undefined, args)
console.log(result)
ws.send(JSON.stringify({
type: "D2C",
cmd: 'invokeMethod',
payload: {
result
}
} as MSG))
break;
case "DEBUG_STOP":
console.log(msg.payload?.msg || "Stop debugging");
process.exit(0);
break;
}
})
.on('error', (error) => {
console.log(error)
reject(error)
})
}) })
} }
@ -87,94 +155,11 @@ global.Entry = function () {
console.log("debugging context id: " + contextId); console.log("debugging context id: " + contextId);
global.context = doric.jsObtainContext(contextId); global.context = doric.jsObtainContext(contextId);
Reflect.apply(doric.jsObtainEntry(contextId), doric, args); Reflect.apply(doric.jsObtainEntry(contextId), doric, args);
}).catch(error => console.error(error)); });
return arguments[0]; return arguments[0];
} }
} }
// debug server
const debugServer = new WebSocket.Server({ port: 2080 })
debugServer.on('connection', (ws) => {
console.log('connected')
ws.on('message', (message: string) => {
let messageObject = JSON.parse(message)
switch (messageObject.cmd) {
case "injectGlobalJSObject":
console.log(messageObject.name)
let type = messageObject.type
let value = messageObject.value
let arg
if (type.type === 0) {
arg = null
} else if (type === 1) {
arg = parseFloat(value)
} else if (type === 2) {
arg = (value == 'true')
} else if (type === 3) {
arg = value.toString()
} else if (type === 4) {
arg = JSON.parse(value)
} else if (type === 5) {
arg = JSON.parse(value)
}
Reflect.set(global, messageObject.name, arg)
break
case "injectGlobalJSFunction":
console.log(messageObject.name)
Reflect.set(global, messageObject.name, function () {
let args = [].slice.call(arguments)
console.log("===============================")
console.log(args)
console.log("===============================")
ws.send(JSON.stringify({
cmd: 'injectGlobalJSFunction',
name: messageObject.name,
arguments: args
}))
})
break
case "invokeMethod":
console.log(messageObject.objectName)
console.log(messageObject.functionName)
let args = []
for (let i = 0; i < messageObject.values.length; i++) {
let value = messageObject.values[i]
if (value.type === 0) {
args.push(null)
} else if (value.type === 1) {
args.push(parseFloat(value.value))
} else if (value.type === 2) {
args.push((value.value == 'true'))
} else if (value.type === 3) {
args.push(value.value.toString())
} else if (value.type === 4) {
args.push(JSON.parse(value.value))
} else if (value.type === 5) {
args.push(JSON.parse(value.value))
}
}
console.log(args)
console.log(messageObject.hashKey)
let object = Reflect.get(global, messageObject.objectName)
let method = Reflect.get(object, messageObject.functionName)
let result = Reflect.apply(method, undefined, args)
console.log(result)
ws.send(JSON.stringify({
cmd: 'invokeMethod',
result: result
}))
break
}
})
})
debugServer.on('listening', function connection(ws: WebSocket) {
console.log('debugger server started on 2080')
})
global.injectGlobal = (objName: string, obj: string) => { global.injectGlobal = (objName: string, obj: string) => {
Reflect.set(global, objName, JSON.parse(obj)) Reflect.set(global, objName, JSON.parse(obj))
} }

View File

@ -36,33 +36,111 @@ function initNativeEnvironment(source) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
// dev kit client // dev kit client
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const devClient = new WebSocket('ws://localhost:7777'); const ws = new WebSocket('ws://localhost:7777')
devClient.on('open', () => { .on('open', () => {
console.log('Connectted Devkit on port', '7777'); console.log('Connectted Devkit on port', '7777');
devClient.send(JSON.stringify({ ws.send(JSON.stringify({
type: "D2C", type: "D2C",
cmd: "DEBUG_REQ",
payload: { payload: {
cmd: "DEBUG_REQ",
source, source,
}, },
})); }));
}); })
devClient.on('message', (data) => { .on('message', (data) => {
console.log(data); var _a;
const msg = JSON.parse(data); const msg = JSON.parse(data);
switch (msg.cwd) { const payload = msg.payload;
switch (msg.cmd) {
case "DEBUG_RES": case "DEBUG_RES":
const contextId = msg.contextId; const contextId = msg.payload.contextId;
if ((contextId === null || contextId === void 0 ? void 0 : contextId.length) > 0) { resolve(contextId);
resolve(contextId); break;
case "injectGlobalJSObject":
console.log("injectGlobalJSObject", payload);
const type = payload.type;
const value = payload.value;
let arg;
if (type === 0) {
arg = null;
} }
else { else if (type === 1) {
reject(`Cannot find applicable context in client for source ${source}`); arg = parseFloat(value);
} }
else if (type === 2) {
arg = (value == 'true');
}
else if (type === 3) {
arg = value.toString();
}
else if (type === 4) {
arg = JSON.parse(value);
}
else if (type === 5) {
arg = JSON.parse(value);
}
Reflect.set(global, payload.name, arg);
break;
case "injectGlobalJSFunction":
console.log("injectGlobalJSFunction", payload);
Reflect.set(global, payload.name, function () {
let args = [].slice.call(arguments);
console.log(args);
console.log("injected", payload.name, args);
ws.send(JSON.stringify({
type: "D2C",
cmd: 'injectGlobalJSFunction',
payload: {
name: payload.name,
arguments: args
}
}));
});
break;
case "invokeMethod":
console.log("invokeMethod", payload);
const values = payload.values;
let args = [];
for (let i = 0; i < values.length; i++) {
let value = values[i];
if (value.type === 0) {
args.push(null);
}
else if (value.type === 1) {
args.push(parseFloat(value.value));
}
else if (value.type === 2) {
args.push((value.value == 'true'));
}
else if (value.type === 3) {
args.push(value.value.toString());
}
else if (value.type === 4) {
args.push(JSON.parse(value.value));
}
else if (value.type === 5) {
args.push(JSON.parse(value.value));
}
}
const object = Reflect.get(global, payload.objectName);
const method = Reflect.get(object, payload.functionName);
const result = Reflect.apply(method, undefined, args);
console.log(result);
ws.send(JSON.stringify({
type: "D2C",
cmd: 'invokeMethod',
payload: {
result
}
}));
break;
case "DEBUG_STOP":
console.log(((_a = msg.payload) === null || _a === void 0 ? void 0 : _a.msg) || "Stop debugging");
process.exit(0);
break; break;
} }
}); })
devClient.on('error', (error) => { .on('error', (error) => {
console.log(error); console.log(error);
reject(error); reject(error);
}); });
@ -87,98 +165,10 @@ global.Entry = function () {
console.log("debugging context id: " + contextId); console.log("debugging context id: " + contextId);
global.context = doric.jsObtainContext(contextId); global.context = doric.jsObtainContext(contextId);
Reflect.apply(doric.jsObtainEntry(contextId), doric, args); Reflect.apply(doric.jsObtainEntry(contextId), doric, args);
}).catch(error => console.error(error)); });
return arguments[0]; return arguments[0];
} }
}; };
// debug server
const debugServer = new WebSocket.Server({ port: 2080 });
debugServer.on('connection', (ws) => {
console.log('connected');
ws.on('message', (message) => {
let messageObject = JSON.parse(message);
switch (messageObject.cmd) {
case "injectGlobalJSObject":
console.log(messageObject.name);
let type = messageObject.type;
let value = messageObject.value;
let arg;
if (type.type === 0) {
arg = null;
}
else if (type === 1) {
arg = parseFloat(value);
}
else if (type === 2) {
arg = (value == 'true');
}
else if (type === 3) {
arg = value.toString();
}
else if (type === 4) {
arg = JSON.parse(value);
}
else if (type === 5) {
arg = JSON.parse(value);
}
Reflect.set(global, messageObject.name, arg);
break;
case "injectGlobalJSFunction":
console.log(messageObject.name);
Reflect.set(global, messageObject.name, function () {
let args = [].slice.call(arguments);
console.log("===============================");
console.log(args);
console.log("===============================");
ws.send(JSON.stringify({
cmd: 'injectGlobalJSFunction',
name: messageObject.name,
arguments: args
}));
});
break;
case "invokeMethod":
console.log(messageObject.objectName);
console.log(messageObject.functionName);
let args = [];
for (let i = 0; i < messageObject.values.length; i++) {
let value = messageObject.values[i];
if (value.type === 0) {
args.push(null);
}
else if (value.type === 1) {
args.push(parseFloat(value.value));
}
else if (value.type === 2) {
args.push((value.value == 'true'));
}
else if (value.type === 3) {
args.push(value.value.toString());
}
else if (value.type === 4) {
args.push(JSON.parse(value.value));
}
else if (value.type === 5) {
args.push(JSON.parse(value.value));
}
}
console.log(args);
console.log(messageObject.hashKey);
let object = Reflect.get(global, messageObject.objectName);
let method = Reflect.get(object, messageObject.functionName);
let result = Reflect.apply(method, undefined, args);
console.log(result);
ws.send(JSON.stringify({
cmd: 'invokeMethod',
result: result
}));
break;
}
});
});
debugServer.on('listening', function connection(ws) {
console.log('debugger server started on 2080');
});
global.injectGlobal = (objName, obj) => { global.injectGlobal = (objName, obj) => {
Reflect.set(global, objName, JSON.parse(obj)); Reflect.set(global, objName, JSON.parse(obj));
}; };