package com.tns; import android.content.Context; import android.os.Handler; import android.util.Log; import android.util.Pair; import android.webkit.MimeTypeMap; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.NanoWSD; class AndroidJsV8Inspector { private static boolean DEBUG_LOG_ENABLED = false; private JsV8InspectorServer server; private Context context; private static String applicationDir; protected native final void init(); protected native final void connect(Object connection); protected static native void disconnect(); protected native final void dispatchMessage(String message); private Handler mainHandler; private LinkedBlockingQueue<String> inspectorMessages = new LinkedBlockingQueue<String>(); AndroidJsV8Inspector(Context context, Logger logger) { this.context = context; applicationDir = context.getFilesDir().getAbsolutePath(); } public void start() throws IOException { if (this.server == null) { Runtime currentRuntime = Runtime.getCurrentRuntime(); mainHandler = currentRuntime.getHandler(); this.server = new JsV8InspectorServer(this.context.getPackageName() + "-inspectorServer"); this.server.start(-1); if (DEBUG_LOG_ENABLED) { Log.d("V8Inspector", "start debugger ThreadId:" + Thread.currentThread().getId()); } init(); } } @RuntimeCallable private static void sendToDevToolsConsole(Object connection, String message, String level) { try { JSONObject consoleMessage = new JSONObject(); JSONObject params = new JSONObject(); params.put("type", level); params.put("executionContextId", 0); params.put("timestamp", 0.000000000000000); JSONArray args = new JSONArray(); args.put(message); params.put("args", args); consoleMessage.put("method", "Runtime.consoleAPICalled"); consoleMessage.put("params", params); String sendingText = consoleMessage.toString(); AndroidJsV8Inspector.send(connection, sendingText); } catch (JSONException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } @RuntimeCallable private static void send(Object connection, String payload) throws IOException { ((JsV8InspectorWebSocket) connection).send(payload); } @RuntimeCallable private static String getInspectorMessage(Object connection) { return ((JsV8InspectorWebSocket) connection).getInspectorMessage(); } @RuntimeCallable public static Pair<String, String>[] getPageResources() { // necessary to align the data dir returned by context (emulator) and that used by the v8 inspector if (applicationDir.startsWith("/data/user/0/")) { applicationDir = applicationDir.replaceFirst("/data/user/0/", "/data/data/"); } String dataDir = applicationDir; File rootFilesDir = new File(dataDir, "app"); List<Pair<String, String>> resources = traverseResources(rootFilesDir); return resources.toArray((Pair<String, String>[]) Array.newInstance(resources.get(0).getClass(), resources.size())); } private static List<Pair<String, String>> traverseResources(File dir) { List<Pair<String, String>> resources = new ArrayList<>(); Queue<File> directories = new LinkedList<>(); directories.add(dir); while (!directories.isEmpty()) { File currentDir = directories.poll(); File[] files = currentDir.listFiles(); for (File file : files) { if (file.isDirectory()) { directories.add(file); } else { resources.add(new Pair<>("file://" + file.getAbsolutePath(), getMimeType(file.getAbsolutePath()))); } } } return resources; } private static String getMimeType(String url) { String type = null; String extension = MimeTypeMap.getFileExtensionFromUrl(url); if (!extension.isEmpty()) { type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); // getMimeType may sometime return incorrect results in the context of NativeScript // e.g. `.ts` returns video/MP2TS switch (extension) { case "js": type = "text/javascript"; break; case "json": type = "application/json"; break; case "css": type = "text/css"; break; case "ts": type = "text/typescript"; break; // handle shared libraries so they are marked properly and don't appear in the sources tab case "so": type = "application/binary"; break; } } return type; } class JsV8InspectorServer extends NanoWSD { public JsV8InspectorServer(String name) { super(name); } @Override protected Response serveHttp(IHTTPSession session) { if (DEBUG_LOG_ENABLED) { Log.d("{N}.v8-inspector", "http request for " + session.getUri()); } return super.serveHttp(session); } @Override protected WebSocket openWebSocket(IHTTPSession handshake) { return new JsV8InspectorWebSocket(handshake); } } class JsV8InspectorWebSocket extends NanoWSD.WebSocket { public JsV8InspectorWebSocket(NanoHTTPD.IHTTPSession handshakeRequest) { super(handshakeRequest); } @Override protected void onOpen() { if (DEBUG_LOG_ENABLED) { Log.d("V8Inspector", "onOpen: ThreadID: " + Thread.currentThread().getId()); } mainHandler.post(new Runnable() { @Override public void run() { if (DEBUG_LOG_ENABLED) { Log.d("V8Inspector", "Connecting. threadID : " + Thread.currentThread().getId()); } connect(JsV8InspectorWebSocket.this); } }); } @Override protected void onClose(NanoWSD.WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) { if (DEBUG_LOG_ENABLED) { Log.d("V8Inspector", "onClose"); } mainHandler.post(new Runnable() { @Override public void run() { if (DEBUG_LOG_ENABLED) { Log.d("V8Inspector", "Disconnecting"); } disconnect(); } }); } @Override protected void onMessage(final NanoWSD.WebSocketFrame message) { if (DEBUG_LOG_ENABLED) { Log.d("V8Inspector", "To dbg backend: " + message.getTextPayload() + " ThreadId:" + Thread.currentThread().getId()); } inspectorMessages.offer(message.getTextPayload()); mainHandler.post(new Runnable() { @Override public void run() { String nextMessage = inspectorMessages.poll(); while (nextMessage != null) { dispatchMessage(nextMessage); nextMessage = inspectorMessages.poll(); } } }); } @Override public void send(String payload) throws IOException { if (DEBUG_LOG_ENABLED) { Log.d("V8Inspector", "To dbg client: " + payload); } super.send(payload); } public String getInspectorMessage() { try { String message = inspectorMessages.take(); return message; } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void onPong(NanoWSD.WebSocketFrame pong) { } @Override protected void onException(IOException exception) { exception.printStackTrace(); disconnect(); } } }