• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  JSONParse,
5  JSONStringify,
6  SafeMap,
7  Symbol,
8} = primordials;
9
10const {
11  ERR_INSPECTOR_ALREADY_ACTIVATED,
12  ERR_INSPECTOR_ALREADY_CONNECTED,
13  ERR_INSPECTOR_CLOSED,
14  ERR_INSPECTOR_COMMAND,
15  ERR_INSPECTOR_NOT_AVAILABLE,
16  ERR_INSPECTOR_NOT_CONNECTED,
17  ERR_INSPECTOR_NOT_ACTIVE,
18  ERR_INSPECTOR_NOT_WORKER,
19} = require('internal/errors').codes;
20
21const { hasInspector } = internalBinding('config');
22if (!hasInspector)
23  throw new ERR_INSPECTOR_NOT_AVAILABLE();
24
25const EventEmitter = require('events');
26const { queueMicrotask } = require('internal/process/task_queues');
27const {
28  isUint32,
29  validateFunction,
30  validateInt32,
31  validateObject,
32  validateString,
33} = require('internal/validators');
34const { isMainThread } = require('worker_threads');
35const { _debugEnd } = internalBinding('process_methods');
36
37const {
38  Connection,
39  MainThreadConnection,
40  open,
41  url,
42  isEnabled,
43  waitForDebugger,
44  console,
45} = internalBinding('inspector');
46
47const connectionSymbol = Symbol('connectionProperty');
48const messageCallbacksSymbol = Symbol('messageCallbacks');
49const nextIdSymbol = Symbol('nextId');
50const onMessageSymbol = Symbol('onMessage');
51
52class Session extends EventEmitter {
53  constructor() {
54    super();
55    this[connectionSymbol] = null;
56    this[nextIdSymbol] = 1;
57    this[messageCallbacksSymbol] = new SafeMap();
58  }
59
60  /**
61   * Connects the session to the inspector back-end.
62   * @returns {void}
63   */
64  connect() {
65    if (this[connectionSymbol])
66      throw new ERR_INSPECTOR_ALREADY_CONNECTED('The inspector session');
67    this[connectionSymbol] =
68      new Connection((message) => this[onMessageSymbol](message));
69  }
70
71  /**
72   * Connects the session to the main thread
73   * inspector back-end.
74   * @returns {void}
75   */
76  connectToMainThread() {
77    if (isMainThread)
78      throw new ERR_INSPECTOR_NOT_WORKER();
79    if (this[connectionSymbol])
80      throw new ERR_INSPECTOR_ALREADY_CONNECTED('The inspector session');
81    this[connectionSymbol] =
82      new MainThreadConnection(
83        (message) => queueMicrotask(() => this[onMessageSymbol](message)));
84  }
85
86  [onMessageSymbol](message) {
87    const parsed = JSONParse(message);
88    try {
89      if (parsed.id) {
90        const callback = this[messageCallbacksSymbol].get(parsed.id);
91        this[messageCallbacksSymbol].delete(parsed.id);
92        if (callback) {
93          if (parsed.error) {
94            return callback(new ERR_INSPECTOR_COMMAND(parsed.error.code,
95                                                      parsed.error.message));
96          }
97
98          callback(null, parsed.result);
99        }
100      } else {
101        this.emit(parsed.method, parsed);
102        this.emit('inspectorNotification', parsed);
103      }
104    } catch (error) {
105      process.emitWarning(error);
106    }
107  }
108
109  /**
110   * Posts a message to the inspector back-end.
111   * @param {string} method
112   * @param {Record<unknown, unknown>} [params]
113   * @param {Function} [callback]
114   * @returns {void}
115   */
116  post(method, params, callback) {
117    validateString(method, 'method');
118    if (!callback && typeof params === 'function') {
119      callback = params;
120      params = null;
121    }
122    if (params) {
123      validateObject(params, 'params');
124    }
125    if (callback) {
126      validateFunction(callback, 'callback');
127    }
128
129    if (!this[connectionSymbol]) {
130      throw new ERR_INSPECTOR_NOT_CONNECTED();
131    }
132    const id = this[nextIdSymbol]++;
133    const message = { id, method };
134    if (params) {
135      message.params = params;
136    }
137    if (callback) {
138      this[messageCallbacksSymbol].set(id, callback);
139    }
140    this[connectionSymbol].dispatch(JSONStringify(message));
141  }
142
143  /**
144   * Immediately closes the session, all pending
145   * message callbacks will be called with an
146   * error.
147   * @returns {void}
148   */
149  disconnect() {
150    if (!this[connectionSymbol])
151      return;
152    this[connectionSymbol].disconnect();
153    this[connectionSymbol] = null;
154    const remainingCallbacks = this[messageCallbacksSymbol].values();
155    for (const callback of remainingCallbacks) {
156      process.nextTick(callback, new ERR_INSPECTOR_CLOSED());
157    }
158    this[messageCallbacksSymbol].clear();
159    this[nextIdSymbol] = 1;
160  }
161}
162
163/**
164 * Activates inspector on host and port.
165 * @param {number} [port]
166 * @param {string} [host]
167 * @param {boolean} [wait]
168 * @returns {void}
169 */
170function inspectorOpen(port, host, wait) {
171  if (isEnabled()) {
172    throw new ERR_INSPECTOR_ALREADY_ACTIVATED();
173  }
174  // inspectorOpen() currently does not typecheck its arguments and adding
175  // such checks would be a potentially breaking change. However, the native
176  // open() function requires the port to fit into a 16-bit unsigned integer,
177  // causing an integer overflow otherwise, so we at least need to prevent that.
178  if (isUint32(port)) {
179    validateInt32(port, 'port', 0, 65535);
180  }
181  open(port, host);
182  if (wait)
183    waitForDebugger();
184}
185
186/**
187 * Blocks until a client (existing or connected later)
188 * has sent the `Runtime.runIfWaitingForDebugger`
189 * command.
190 * @returns {void}
191 */
192function inspectorWaitForDebugger() {
193  if (!waitForDebugger())
194    throw new ERR_INSPECTOR_NOT_ACTIVE();
195}
196
197module.exports = {
198  open: inspectorOpen,
199  close: _debugEnd,
200  url,
201  waitForDebugger: inspectorWaitForDebugger,
202  console,
203  Session,
204};
205