From af31014e2e0140b7e77f872338a05422fe5cb720 Mon Sep 17 00:00:00 2001 From: pengfeizhou Date: Sun, 21 Feb 2021 01:51:59 +0800 Subject: [PATCH] debugger:complete android part --- .../main/java/pub/doric/devkit/DevKit.java | 65 ++++-- .../doric/devkit/DoricContextDebuggable.java | 32 ++- .../pub/doric/devkit/DoricDebugDriver.java | 23 +- .../pub/doric/devkit/DoricDebugJSEngine.java | 9 +- .../main/java/pub/doric/devkit/IDevKit.java | 4 + .../pub/doric/devkit/IStatusCallback.java | 5 - .../main/java/pub/doric/devkit/WSClient.java | 45 ++-- .../devkit/remote/DoricRemoteJSExecutor.java | 6 +- .../doric/devkit/remote/RemoteJSExecutor.java | 170 ++++++-------- .../src/main/java/pub/doric/DoricContext.java | 3 + doric-cli/assets/_launch.json | 6 - doric-cli/src/server.ts | 106 +++++---- doric-demo/.vscode/launch.json | 6 - doric-js/bundle/doric-vm.js | 198 ++++++++-------- doric-js/index.debug.ts | 211 ++++++++---------- doric-js/lib/index.debug.js | 198 ++++++++-------- 16 files changed, 526 insertions(+), 561 deletions(-) delete mode 100644 doric-android/devkit/src/main/java/pub/doric/devkit/IStatusCallback.java diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/DevKit.java b/doric-android/devkit/src/main/java/pub/doric/devkit/DevKit.java index 32273f28..e79f1da4 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/DevKit.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/DevKit.java @@ -2,6 +2,8 @@ package pub.doric.devkit; import android.widget.Toast; +import com.github.pengfeizhou.jscore.JSONBuilder; + import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; 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.OpenEvent; import pub.doric.devkit.event.StopDebugEvent; +import pub.doric.utils.DoricLog; public class DevKit implements IDevKit { - public static boolean isRunningInEmulator = false; public static String ip = ""; - private static class Inner { private static final DevKit sInstance = new DevKit(); } @@ -38,6 +39,10 @@ public class DevKit implements IDevKit { private WSClient wsClient; + boolean devKitConnected = false; + + private DoricContextDebuggable debuggable; + @Override public void connectDevKit(String url) { wsClient = new WSClient(url); @@ -54,9 +59,39 @@ public class DevKit implements IDevKit { 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) public void onOpenEvent(OpenEvent openEvent) { @@ -78,10 +113,10 @@ public class DevKit implements IDevKit { @Subscribe(threadMode = ThreadMode.MAIN) public void onQuitDebugEvent(StopDebugEvent quitDebugEvent) { - doricContextDebuggable.stopDebug(); + stopDebugging(true); } - public DoricContext requestDebugContext(String source) { + public DoricContext matchContext(String source) { for (DoricContext context : DoricContextManager.aliveContexts()) { if (source.contains(context.getSource()) || context.getSource().equals("__dev__")) { return context; @@ -91,16 +126,14 @@ public class DevKit implements IDevKit { } public void reload(String source, String script) { - for (DoricContext context : DoricContextManager.aliveContexts()) { - if (doricContextDebuggable != null && - doricContextDebuggable.isDebugging && - doricContextDebuggable.getContext().getContextId().equals(context.getContextId())) { - System.out.println("is debugging context id: " + context.getContextId()); - } else { - if (source.contains(context.getSource()) || context.getSource().equals("__dev__")) { - context.reload(script); - } - } + DoricContext context = matchContext(source); + if (context == null) { + DoricLog.d("Cannot find context source %s for reload", source); + } else if (context.getDriver() instanceof DoricDebugDriver) { + DoricLog.d("Context source %s in debugging,skip reload", source); + } else { + DoricLog.d("Context reload :id %s,source %s ", context.getContextId(), source); + context.reload(script); } } } diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricContextDebuggable.java b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricContextDebuggable.java index d031a99d..0db3b7fc 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricContextDebuggable.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricContextDebuggable.java @@ -5,34 +5,28 @@ import pub.doric.IDoricDriver; public class DoricContextDebuggable { private final DoricContext doricContext; - private DoricDebugDriver debugDriver; 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.isDebugging = true; this.nativeDriver = this.doricContext.getDriver(); } public void startDebug() { - debugDriver = new DoricDebugDriver(new IStatusCallback() { - @Override - public void start() { - doricContext.setDriver(debugDriver); - doricContext.reload(doricContext.getScript()); - } - }); - } - - public void stopDebug() { - isDebugging = false; - doricContext.setDriver(nativeDriver); - debugDriver.destroy(); + doricContext.setDriver(new DoricDebugDriver(this.wsClient)); doricContext.reload(doricContext.getScript()); } - public DoricContext getContext() { - return doricContext; + public void stopDebug(boolean resume) { + IDoricDriver doricDriver = doricContext.getDriver(); + if (doricDriver instanceof DoricDebugDriver) { + ((DoricDebugDriver) doricDriver).destroy(); + } + if (resume) { + doricContext.setDriver(nativeDriver); + doricContext.reload(doricContext.getScript()); + } } } diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDebugDriver.java b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDebugDriver.java index 28d1b3b6..0c214c31 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDebugDriver.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDebugDriver.java @@ -28,7 +28,6 @@ import pub.doric.DoricRegistry; import pub.doric.IDoricDriver; import pub.doric.async.AsyncCall; import pub.doric.async.AsyncResult; -import pub.doric.engine.DoricJSEngine; import pub.doric.utils.DoricConstant; import pub.doric.utils.DoricLog; import pub.doric.utils.ThreadMode; @@ -39,17 +38,17 @@ import pub.doric.utils.ThreadMode; * @CreateDate: 2019-07-18 */ public class DoricDebugDriver implements IDoricDriver { - private final DoricJSEngine doricJSEngine; + private final DoricDebugJSEngine doricDebugJSEngine; private final ExecutorService mBridgeExecutor; private final Handler mUIHandler; private final Handler mJSHandler; + private String theContextId = null; - - public DoricDebugDriver(IStatusCallback statusCallback) { - doricJSEngine = new DoricDebugJSEngine(statusCallback); + public DoricDebugDriver(WSClient wsClient) { + doricDebugJSEngine = new DoricDebugJSEngine(wsClient); mBridgeExecutor = Executors.newCachedThreadPool(); mUIHandler = new Handler(Looper.getMainLooper()); - mJSHandler = doricJSEngine.getJSHandler(); + mJSHandler = doricDebugJSEngine.getJSHandler(); } @Override @@ -69,7 +68,7 @@ public class DoricDebugDriver implements IDoricDriver { @Override public JSDecoder call() { try { - return doricJSEngine.invokeDoricMethod(method, args); + return doricDebugJSEngine.invokeDoricMethod(method, args); } catch (Exception e) { DoricLog.e("invokeDoricMethod(%s,...),error is %s", method, e.getLocalizedMessage()); return new JSDecoder(null); @@ -97,7 +96,7 @@ public class DoricDebugDriver implements IDoricDriver { @Override public Boolean call() { try { - doricJSEngine.prepareContext(contextId, script, source); + theContextId = contextId; return true; } catch (Exception e) { DoricLog.e("createContext %s error is %s", source, e.getLocalizedMessage()); @@ -113,7 +112,9 @@ public class DoricDebugDriver implements IDoricDriver { @Override public Boolean call() { try { - doricJSEngine.destroyContext(contextId); + if (contextId.equals(theContextId)) { + DevKit.getInstance().stopDebugging(false); + } return true; } catch (Exception e) { DoricLog.e("destroyContext %s error is %s", contextId, e.getLocalizedMessage()); @@ -125,11 +126,11 @@ public class DoricDebugDriver implements IDoricDriver { @Override public DoricRegistry getRegistry() { - return doricJSEngine.getRegistry(); + return doricDebugJSEngine.getRegistry(); } public void destroy() { - doricJSEngine.teardown(); + doricDebugJSEngine.teardown(); mBridgeExecutor.shutdown(); } } diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDebugJSEngine.java b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDebugJSEngine.java index dde27193..a8d66b39 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDebugJSEngine.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDebugJSEngine.java @@ -4,16 +4,15 @@ import pub.doric.devkit.remote.DoricRemoteJSExecutor; import pub.doric.engine.DoricJSEngine; public class DoricDebugJSEngine extends DoricJSEngine { + private final WSClient wsClient; - private IStatusCallback statusCallback; - - public DoricDebugJSEngine(IStatusCallback statusCallback) { + public DoricDebugJSEngine(WSClient wsClient) { super(); - this.statusCallback = statusCallback; + this.wsClient = wsClient; } @Override protected void initJSEngine() { - mDoricJSE = new DoricRemoteJSExecutor(statusCallback); + mDoricJSE = new DoricRemoteJSExecutor(this.wsClient); } } diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/IDevKit.java b/doric-android/devkit/src/main/java/pub/doric/devkit/IDevKit.java index 55289a3c..18a6b0e0 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/IDevKit.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/IDevKit.java @@ -14,4 +14,8 @@ public interface IDevKit { void sendDevCommand(IDevKit.Command command, JSONObject jsonObject); void disconnectDevKit(); + + void startDebugging(String source); + + void stopDebugging(boolean resume); } diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/IStatusCallback.java b/doric-android/devkit/src/main/java/pub/doric/devkit/IStatusCallback.java deleted file mode 100644 index 88c9a701..00000000 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/IStatusCallback.java +++ /dev/null @@ -1,5 +0,0 @@ -package pub.doric.devkit; - -public interface IStatusCallback { - void start(); -} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/WSClient.java b/doric-android/devkit/src/main/java/pub/doric/devkit/WSClient.java index 39a591be..3b5d6a62 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/WSClient.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/WSClient.java @@ -23,6 +23,8 @@ import org.json.JSONObject; import java.io.EOFException; import java.net.ConnectException; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; @@ -43,6 +45,12 @@ import pub.doric.devkit.event.OpenEvent; public class WSClient extends WebSocketListener { private final WebSocket webSocket; + public interface Interceptor { + boolean intercept(String type, String command, JSONObject payload) throws JSONException; + } + + private Set interceptors = new HashSet<>(); + public WSClient(String url) { OkHttpClient okHttpClient = new OkHttpClient .Builder() @@ -53,6 +61,15 @@ public class WSClient extends WebSocketListener { webSocket = okHttpClient.newWebSocket(request, this); } + public void addInterceptor(Interceptor interceptor) { + interceptors.add(interceptor); + } + + + public void removeInterceptor(Interceptor interceptor) { + interceptors.remove(interceptor); + } + public void close() { webSocket.close(1000, "Close"); } @@ -72,23 +89,21 @@ public class WSClient extends WebSocketListener { String type = jsonObject.optString("type"); String cmd = jsonObject.optString("cmd"); JSONObject payload = jsonObject.optJSONObject("payload"); - if ("D2C".equals(type)) { - if ("DEBUG_REQ".equals(cmd)) { - String source = payload.optString("source"); - 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); + for (Interceptor interceptor : interceptors) { + if (interceptor.intercept(type, cmd, payload)) { + return; } } - + 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) { e.printStackTrace(); } diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/remote/DoricRemoteJSExecutor.java b/doric-android/devkit/src/main/java/pub/doric/devkit/remote/DoricRemoteJSExecutor.java index 3e9c0de3..88b5b59a 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/remote/DoricRemoteJSExecutor.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/remote/DoricRemoteJSExecutor.java @@ -20,15 +20,15 @@ import com.github.pengfeizhou.jscore.JSRuntimeException; import com.github.pengfeizhou.jscore.JavaFunction; import com.github.pengfeizhou.jscore.JavaValue; -import pub.doric.devkit.IStatusCallback; +import pub.doric.devkit.WSClient; import pub.doric.engine.IDoricJSE; public class DoricRemoteJSExecutor implements IDoricJSE { private final RemoteJSExecutor mRemoteJSExecutor; - public DoricRemoteJSExecutor(IStatusCallback statusCallback) { - this.mRemoteJSExecutor = new RemoteJSExecutor(statusCallback); + public DoricRemoteJSExecutor(WSClient wsClient) { + this.mRemoteJSExecutor = new RemoteJSExecutor(wsClient); } @Override diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/remote/RemoteJSExecutor.java b/doric-android/devkit/src/main/java/pub/doric/devkit/remote/RemoteJSExecutor.java index 2d394088..2ca5a6f8 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/remote/RemoteJSExecutor.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/remote/RemoteJSExecutor.java @@ -5,102 +5,26 @@ import com.github.pengfeizhou.jscore.JSONBuilder; import com.github.pengfeizhou.jscore.JavaFunction; import com.github.pengfeizhou.jscore.JavaValue; -import org.greenrobot.eventbus.EventBus; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.io.EOFException; -import java.net.ConnectException; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; -import okhttp3.OkHttpClient; -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; +import pub.doric.devkit.WSClient; -public class RemoteJSExecutor { - private final WebSocket webSocket; +public class RemoteJSExecutor implements WSClient.Interceptor { private final Map globalFunctions = new HashMap<>(); private JSDecoder temp; + private final WSClient wsClient; + private final Thread currentThread; - public RemoteJSExecutor(final IStatusCallback statusCallback) { - OkHttpClient okHttpClient = new OkHttpClient - .Builder() - .readTimeout(10, TimeUnit.SECONDS) - .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 RemoteJSExecutor(WSClient wsClient) { + this.wsClient = wsClient; + this.wsClient.addInterceptor(this); + currentThread = Thread.currentThread(); } public String loadJS(String script, String source) { @@ -113,17 +37,22 @@ public class RemoteJSExecutor { public void injectGlobalJSFunction(String name, JavaFunction javaFunction) { globalFunctions.put(name, javaFunction); - webSocket.send(new JSONBuilder().put("cmd", "injectGlobalJSFunction") - .put("name", name).toString() - ); + wsClient.sendToDebugger( + "injectGlobalJSFunction", + new JSONBuilder() + .put("cmd", "injectGlobalJSFunction") + .put("name", name) + .toJSONObject()); } public void injectGlobalJSObject(String name, JavaValue javaValue) { - webSocket.send(new JSONBuilder().put("cmd", "injectGlobalJSObject") - .put("name", name) - .put("type", javaValue.getType()) - .put("value", javaValue.getValue()).toString() - ); + wsClient.sendToDebugger( + "injectGlobalJSObject", + new JSONBuilder() + .put("name", name) + .put("type", javaValue.getType()) + .put("value", javaValue.getValue()) + .toJSONObject()); } public JSDecoder invokeMethod(String objectName, String functionName, JavaValue[] javaValues, boolean hashKey) { @@ -134,19 +63,60 @@ public class RemoteJSExecutor { .put("value", javaValue.getValue()) .toJSONObject()); } - webSocket.send(new JSONBuilder() - .put("cmd", "invokeMethod") - .put("objectName", objectName) - .put("functionName", functionName) - .put("values", jsonArray) - .put("hashKey", hashKey) - .toString()); + wsClient.sendToDebugger( + "invokeMethod", + new JSONBuilder() + .put("cmd", "invokeMethod") + .put("objectName", objectName) + .put("functionName", functionName) + .put("values", jsonArray) + .put("hashKey", hashKey) + .toJSONObject()); LockSupport.park(Thread.currentThread()); return temp; } 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; } } diff --git a/doric-android/doric/src/main/java/pub/doric/DoricContext.java b/doric-android/doric/src/main/java/pub/doric/DoricContext.java index d41b421d..7e81d911 100644 --- a/doric-android/doric/src/main/java/pub/doric/DoricContext.java +++ b/doric-android/doric/src/main/java/pub/doric/DoricContext.java @@ -19,6 +19,8 @@ import android.animation.AnimatorSet; import android.content.Context; import android.content.Intent; import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; import androidx.fragment.app.Fragment; @@ -209,6 +211,7 @@ public class DoricContext { mPluginMap.clear(); this.script = script; this.mRootNode.setId(""); + this.mRootNode.clearSubModel(); getDriver().createContext(mContextId, script, source); init(this.extra); callEntity(DoricConstant.DORIC_ENTITY_CREATE); diff --git a/doric-cli/assets/_launch.json b/doric-cli/assets/_launch.json index 1f8ce169..9355a3ec 100644 --- a/doric-cli/assets/_launch.json +++ b/doric-cli/assets/_launch.json @@ -6,13 +6,7 @@ "request": "launch", "name": "Doric Debugger", "program": "${workspaceFolder}/${relativeFile}", - "preLaunchTask": "Doric Build", "sourceMaps": true, - "serverReadyAction": { - "pattern": "listening on port ([0-9]+)", - "uriFormat": "http://localhost:%s", - "action": "openExternally" - }, "outFiles": [ "${workspaceFolder}/bundle/**/*.js" ] diff --git a/doric-cli/src/server.ts b/doric-cli/src/server.ts index 0ef993e5..34ab5604 100644 --- a/doric-cli/src/server.ts +++ b/doric-cli/src/server.ts @@ -1,10 +1,5 @@ -import fs from "fs"; import WebSocket from "ws" import "colors"; -import path from "path"; -import { delay, glob } from "./util"; -import { Shell } from "./shell"; - export type MSG = { type: "D2C" | "C2D" | "C2S" | "D2S" | "S2C" | "S2D", @@ -13,59 +8,43 @@ export type MSG = { } export async function createServer() { - let contextId: string = "0" - let clientConnection: WebSocket | undefined = undefined - let debuggerConnection: WebSocket | undefined = undefined + let client: WebSocket | undefined = undefined; + let debug: WebSocket | undefined = undefined; let deviceId = 0 - return new WebSocket.Server({ port: 7777 }) + const wss = new WebSocket.Server({ port: 7777 }) .on("connection", (ws, request) => { let thisDeviceId: string console.log('Connected', request.headers.host) if (request.headers.host?.startsWith("localhost")) { thisDeviceId = `Debugger#${deviceId++}` - console.log(`Debugger ${thisDeviceId} attached to dev kit`.green) - debuggerConnection = ws - clientConnection?.send(JSON.stringify({ - cmd: 'SWITCH_TO_DEBUG', - contextId: contextId - })) + console.log(`${thisDeviceId} attached to dev kit`.green) + debug = ws; } else { thisDeviceId = `Client#${deviceId++}` console.log(`${thisDeviceId} attached to dev kit`.green) } ws.on('message', async function (result: string) { const resultObject = JSON.parse(result) as MSG - if (resultObject.type === "D2C" || resultObject.type === "C2D") { - ws.send(result); + if (resultObject.type === "D2C") { + 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") { 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': console.log(resultObject.payload.source.red); console.log(resultObject.payload.exception.red); @@ -87,19 +66,38 @@ export async function createServer() { break } } - }) - ws.on('connect', function (code: number) { - console.log('connect', code) - }) + }); ws.on('close', function (code: number) { - console.log('close: code = ' + code, thisDeviceId) - console.log("quit debugging"); - (ws as any).debugging = false - }) + console.log('close: code = ' + code, thisDeviceId); + (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) { 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; } diff --git a/doric-demo/.vscode/launch.json b/doric-demo/.vscode/launch.json index f882247f..a5ee63c6 100644 --- a/doric-demo/.vscode/launch.json +++ b/doric-demo/.vscode/launch.json @@ -9,13 +9,7 @@ "request": "launch", "name": "Doric Debugger", "program": "${workspaceFolder}/${relativeFile}", - "preLaunchTask": "Doric Build", "sourceMaps": true, - "serverReadyAction": { - "pattern": "listening on port ([0-9]+)", - "uriFormat": "http://localhost:%s", - "action": "openExternally" - }, "outFiles": [ "${workspaceFolder}/bundle/**/*.js" ] diff --git a/doric-js/bundle/doric-vm.js b/doric-js/bundle/doric-vm.js index 32cf9636..561cc6b6 100644 --- a/doric-js/bundle/doric-vm.js +++ b/doric-js/bundle/doric-vm.js @@ -4236,33 +4236,111 @@ function initNativeEnvironment(source) { return __awaiter$1(this, void 0, void 0, function* () { // dev kit client return new Promise((resolve, reject) => { - const devClient = new WebSocket__default['default']('ws://localhost:7777'); - devClient.on('open', () => { + const ws = new WebSocket__default['default']('ws://localhost:7777') + .on('open', () => { console.log('Connectted Devkit on port', '7777'); - devClient.send(JSON.stringify({ + ws.send(JSON.stringify({ type: "D2C", + cmd: "DEBUG_REQ", payload: { - cmd: "DEBUG_REQ", source, }, })); - }); - devClient.on('message', (data) => { - console.log(data); + }) + .on('message', (data) => { + var _a; const msg = JSON.parse(data); - switch (msg.cwd) { + const payload = msg.payload; + switch (msg.cmd) { case "DEBUG_RES": - const contextId = msg.contextId; - if ((contextId === null || contextId === void 0 ? void 0 : contextId.length) > 0) { - resolve(contextId); + const contextId = msg.payload.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 { - reject(`Cannot find applicable context in client for source ${source}`); + 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, 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; } - }); - devClient.on('error', (error) => { + }) + .on('error', (error) => { console.log(error); reject(error); }); @@ -4287,98 +4365,10 @@ global$2.Entry = function () { console.log("debugging context id: " + contextId); global$2.context = jsObtainContext(contextId); Reflect.apply(jsObtainEntry(contextId), doric, args); - }).catch(error => console.error(error)); + }); 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) => { Reflect.set(global$2, objName, JSON.parse(obj)); }; diff --git a/doric-js/index.debug.ts b/doric-js/index.debug.ts index eae950a7..2efc095a 100644 --- a/doric-js/index.debug.ts +++ b/doric-js/index.debug.ts @@ -20,7 +20,7 @@ import path from 'path' type MSG = { type: "D2C" | "C2D" | "C2S" | "D2S" | "S2C" | "S2D", cmd: string, - payload: { [index: string]: string } + payload: { [index: string]: string | number | { type: number, value: string }[] } } let contextId: string | undefined = undefined; @@ -36,35 +36,103 @@ global.doric = doric async function initNativeEnvironment(source: string) { // dev kit client return new Promise((resolve, reject) => { - const devClient = new WebSocket('ws://localhost:7777') - devClient.on('open', () => { - console.log('Connectted Devkit on port', '7777') - devClient.send(JSON.stringify({ - type: "D2C", - payload: { + const ws = new WebSocket('ws://localhost:7777') + .on('open', () => { + console.log('Connectted Devkit on port', '7777') + ws.send(JSON.stringify({ + type: "D2C", cmd: "DEBUG_REQ", - source, - }, - })) - }) - devClient.on('message', (data) => { - console.log(data) - const msg = JSON.parse(data as string) as { type: string, cwd: string, [idx: string]: string } - switch (msg.cwd) { - case "DEBUG_RES": - const contextId = msg.contextId; - if (contextId?.length > 0) { - resolve(contextId) - } else { - reject(`Cannot find applicable context in client for source ${source}`) - } - break; - } - }) - devClient.on('error', (error) => { - console.log(error) - reject(error) - }) + payload: { + source, + }, + } as MSG)) + }) + .on('message', (data) => { + const msg = JSON.parse(data as string) as MSG + const payload = msg.payload + switch (msg.cmd) { + case "DEBUG_RES": + const contextId = msg.payload.contextId as string; + resolve(contextId); + break; + case "injectGlobalJSObject": + console.log("injectGlobalJSObject", payload); + const type = payload.type as number + const value = payload.value as string + let arg + if (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, 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); global.context = doric.jsObtainContext(contextId); Reflect.apply(doric.jsObtainEntry(contextId), doric, args); - }).catch(error => console.error(error)); + }); 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) => { Reflect.set(global, objName, JSON.parse(obj)) } diff --git a/doric-js/lib/index.debug.js b/doric-js/lib/index.debug.js index 34f22053..2afae50e 100644 --- a/doric-js/lib/index.debug.js +++ b/doric-js/lib/index.debug.js @@ -36,33 +36,111 @@ function initNativeEnvironment(source) { return __awaiter(this, void 0, void 0, function* () { // dev kit client return new Promise((resolve, reject) => { - const devClient = new WebSocket('ws://localhost:7777'); - devClient.on('open', () => { + const ws = new WebSocket('ws://localhost:7777') + .on('open', () => { console.log('Connectted Devkit on port', '7777'); - devClient.send(JSON.stringify({ + ws.send(JSON.stringify({ type: "D2C", + cmd: "DEBUG_REQ", payload: { - cmd: "DEBUG_REQ", source, }, })); - }); - devClient.on('message', (data) => { - console.log(data); + }) + .on('message', (data) => { + var _a; const msg = JSON.parse(data); - switch (msg.cwd) { + const payload = msg.payload; + switch (msg.cmd) { case "DEBUG_RES": - const contextId = msg.contextId; - if ((contextId === null || contextId === void 0 ? void 0 : contextId.length) > 0) { - resolve(contextId); + const contextId = msg.payload.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 { - reject(`Cannot find applicable context in client for source ${source}`); + 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, 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; } - }); - devClient.on('error', (error) => { + }) + .on('error', (error) => { console.log(error); reject(error); }); @@ -87,98 +165,10 @@ global.Entry = function () { console.log("debugging context id: " + contextId); global.context = doric.jsObtainContext(contextId); Reflect.apply(doric.jsObtainEntry(contextId), doric, args); - }).catch(error => console.error(error)); + }); 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) => { Reflect.set(global, objName, JSON.parse(obj)); };