summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/inspector.js
diff options
context:
space:
mode:
authorsnek <snek@deno.com>2024-11-06 15:08:26 +0100
committerGitHub <noreply@github.com>2024-11-06 14:08:26 +0000
commit700f54a13cce0fcdcf19d1893e3254579c7347f4 (patch)
tree16e72baca781c32c1304f3bed009878854ae9edc /ext/node/polyfills/inspector.js
parent64e887083aa67047f5ad37b9d55c418274b03ea3 (diff)
fix(ext/node): better inspector support (#26471)
implement local inspector future changes: - wire up InspectorServer to enable open/close/url - wire up connectToMainThread Fixes https://github.com/denoland/deno/issues/25004
Diffstat (limited to 'ext/node/polyfills/inspector.js')
-rw-r--r--ext/node/polyfills/inspector.js210
1 files changed, 210 insertions, 0 deletions
diff --git a/ext/node/polyfills/inspector.js b/ext/node/polyfills/inspector.js
new file mode 100644
index 000000000..7eb15ce91
--- /dev/null
+++ b/ext/node/polyfills/inspector.js
@@ -0,0 +1,210 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+
+import process from "node:process";
+import { EventEmitter } from "node:events";
+import { primordials } from "ext:core/mod.js";
+import {
+ op_get_extras_binding_object,
+ op_inspector_close,
+ op_inspector_connect,
+ op_inspector_disconnect,
+ op_inspector_dispatch,
+ op_inspector_emit_protocol_event,
+ op_inspector_enabled,
+ op_inspector_open,
+ op_inspector_url,
+ op_inspector_wait,
+} from "ext:core/ops";
+import {
+ isUint32,
+ validateFunction,
+ validateInt32,
+ validateObject,
+ validateString,
+} from "ext:deno_node/internal/validators.mjs";
+import {
+ ERR_INSPECTOR_ALREADY_ACTIVATED,
+ ERR_INSPECTOR_ALREADY_CONNECTED,
+ ERR_INSPECTOR_CLOSED,
+ ERR_INSPECTOR_COMMAND,
+ ERR_INSPECTOR_NOT_ACTIVE,
+ ERR_INSPECTOR_NOT_CONNECTED,
+ ERR_INSPECTOR_NOT_WORKER,
+} from "ext:deno_node/internal/errors.ts";
+
+const {
+ SymbolDispose,
+ JSONParse,
+ JSONStringify,
+ SafeMap,
+} = primordials;
+
+class Session extends EventEmitter {
+ #connection = null;
+ #nextId = 1;
+ #messageCallbacks = new SafeMap();
+
+ connect() {
+ if (this.#connection) {
+ throw new ERR_INSPECTOR_ALREADY_CONNECTED("The inspector session");
+ }
+ this.#connection = op_inspector_connect(false, (m) => this.#onMessage(m));
+ }
+
+ connectToMainThread() {
+ if (isMainThread) {
+ throw new ERR_INSPECTOR_NOT_WORKER();
+ }
+ if (this.#connection) {
+ throw new ERR_INSPECTOR_ALREADY_CONNECTED("The inspector session");
+ }
+ this.#connection = op_inspector_connect(true, (m) => this.#onMessage(m));
+ }
+
+ #onMessage(message) {
+ const parsed = JSONParse(message);
+ try {
+ if (parsed.id) {
+ const callback = this.#messageCallbacks.get(parsed.id);
+ this.#messageCallbacks.delete(parsed.id);
+ if (callback) {
+ if (parsed.error) {
+ return callback(
+ new ERR_INSPECTOR_COMMAND(
+ parsed.error.code,
+ parsed.error.message,
+ ),
+ );
+ }
+
+ callback(null, parsed.result);
+ }
+ } else {
+ this.emit(parsed.method, parsed);
+ this.emit("inspectorNotification", parsed);
+ }
+ } catch (error) {
+ process.emitWarning(error);
+ }
+ }
+
+ post(method, params, callback) {
+ validateString(method, "method");
+ if (!callback && typeof params === "function") {
+ callback = params;
+ params = null;
+ }
+ if (params) {
+ validateObject(params, "params");
+ }
+ if (callback) {
+ validateFunction(callback, "callback");
+ }
+
+ if (!this.#connection) {
+ throw new ERR_INSPECTOR_NOT_CONNECTED();
+ }
+ const id = this.#nextId++;
+ const message = { id, method };
+ if (params) {
+ message.params = params;
+ }
+ if (callback) {
+ this.#messageCallbacks.set(id, callback);
+ }
+ op_inspector_dispatch(this.#connection, JSONStringify(message));
+ }
+
+ disconnect() {
+ if (!this.#connection) {
+ return;
+ }
+ op_inspector_disconnect(this.#connection);
+ this.#connection = null;
+ // deno-lint-ignore prefer-primordials
+ for (const callback of this.#messageCallbacks.values()) {
+ process.nextTick(callback, new ERR_INSPECTOR_CLOSED());
+ }
+ this.#messageCallbacks.clear();
+ this.#nextId = 1;
+ }
+}
+
+function open(port, host, wait) {
+ if (op_inspector_enabled()) {
+ throw new ERR_INSPECTOR_ALREADY_ACTIVATED();
+ }
+ // inspectorOpen() currently does not typecheck its arguments and adding
+ // such checks would be a potentially breaking change. However, the native
+ // open() function requires the port to fit into a 16-bit unsigned integer,
+ // causing an integer overflow otherwise, so we at least need to prevent that.
+ if (isUint32(port)) {
+ validateInt32(port, "port", 0, 65535);
+ } else {
+ // equiv of handling args[0]->IsUint32()
+ port = undefined;
+ }
+ if (typeof host !== "string") {
+ // equiv of handling args[1]->IsString()
+ host = undefined;
+ }
+ op_inspector_open(port, host);
+ if (wait) {
+ op_inspector_wait();
+ }
+
+ return {
+ __proto__: null,
+ [SymbolDispose]() {
+ _debugEnd();
+ },
+ };
+}
+
+function close() {
+ op_inspector_close();
+}
+
+function url() {
+ return op_inspector_url();
+}
+
+function waitForDebugger() {
+ if (!op_inspector_wait()) {
+ throw new ERR_INSPECTOR_NOT_ACTIVE();
+ }
+}
+
+function broadcastToFrontend(eventName, params) {
+ validateString(eventName, "eventName");
+ if (params) {
+ validateObject(params, "params");
+ }
+ op_inspector_emit_protocol_event(eventName, JSONStringify(params ?? {}));
+}
+
+const Network = {
+ requestWillBeSent: (params) =>
+ broadcastToFrontend("Network.requestWillBeSent", params),
+ responseReceived: (params) =>
+ broadcastToFrontend("Network.responseReceived", params),
+ loadingFinished: (params) =>
+ broadcastToFrontend("Network.loadingFinished", params),
+ loadingFailed: (params) =>
+ broadcastToFrontend("Network.loadingFailed", params),
+};
+
+const console = op_get_extras_binding_object().console;
+
+export { close, console, Network, open, Session, url, waitForDebugger };
+
+export default {
+ open,
+ close,
+ url,
+ waitForDebugger,
+ console,
+ Session,
+ Network,
+};