All files / lib inspector.js

95.38% Statements 186/195
87.17% Branches 34/39
85.71% Functions 6/7
95.38% Lines 186/195

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 1963x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 103x 103x 103x 103x 103x 3x 3x 3x 3x 3x 3x 913x 913x 912x 912x 913x 3x 3x 3x 3x 3x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x 5880x 5880x 5880x 1202x 1202x 1202x 790x 5x 5x 5x 785x 785x 785x 5880x 4678x 4678x 4678x 5880x 2x 2x 5880x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1218x 1218x 5x 5x 5x 1212x 802x 802x 1218x 796x 796x 1203x 1218x 1x 1x 1202x 1202x 1202x 792x 792x 1218x 790x 790x 1202x 1218x 3x 3x 3x 3x 3x 3x 3x 3x 900x 900x 899x 899x 899x 900x     900x 900x 900x 3x 3x 3x 3x 3x 3x 3x 3x 3x 4x 4x 4x 4x       4x 3x 3x 3x 3x 3x 3x 3x         3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x  
'use strict';
 
const {
  JSONParse,
  JSONStringify,
  SafeMap,
  Symbol,
} = primordials;
 
const {
  ERR_INSPECTOR_ALREADY_ACTIVATED,
  ERR_INSPECTOR_ALREADY_CONNECTED,
  ERR_INSPECTOR_CLOSED,
  ERR_INSPECTOR_COMMAND,
  ERR_INSPECTOR_NOT_AVAILABLE,
  ERR_INSPECTOR_NOT_CONNECTED,
  ERR_INSPECTOR_NOT_ACTIVE,
  ERR_INSPECTOR_NOT_WORKER,
} = require('internal/errors').codes;
 
const { hasInspector } = internalBinding('config');
if (!hasInspector)
  throw new ERR_INSPECTOR_NOT_AVAILABLE();
 
const EventEmitter = require('events');
const { queueMicrotask } = require('internal/process/task_queues');
const {
  validateFunction,
  validateObject,
  validateString,
} = require('internal/validators');
const { isMainThread } = require('worker_threads');
 
const {
  Connection,
  MainThreadConnection,
  open,
  url,
  isEnabled,
  waitForDebugger
} = internalBinding('inspector');
 
const connectionSymbol = Symbol('connectionProperty');
const messageCallbacksSymbol = Symbol('messageCallbacks');
const nextIdSymbol = Symbol('nextId');
const onMessageSymbol = Symbol('onMessage');
 
class Session extends EventEmitter {
  constructor() {
    super();
    this[connectionSymbol] = null;
    this[nextIdSymbol] = 1;
    this[messageCallbacksSymbol] = new SafeMap();
  }
 
  /**
   * Connects the session to the inspector back-end.
   * @returns {void}
   */
  connect() {
    if (this[connectionSymbol])
      throw new ERR_INSPECTOR_ALREADY_CONNECTED('The inspector session');
    this[connectionSymbol] =
      new Connection((message) => this[onMessageSymbol](message));
  }
 
  /**
   * Connects the session to the main thread
   * inspector back-end.
   * @returns {void}
   */
  connectToMainThread() {
    if (isMainThread)
      throw new ERR_INSPECTOR_NOT_WORKER();
    if (this[connectionSymbol])
      throw new ERR_INSPECTOR_ALREADY_CONNECTED('The inspector session');
    this[connectionSymbol] =
      new MainThreadConnection(
        (message) => queueMicrotask(() => this[onMessageSymbol](message)));
  }
 
  [onMessageSymbol](message) {
    const parsed = JSONParse(message);
    try {
      if (parsed.id) {
        const callback = this[messageCallbacksSymbol].get(parsed.id);
        this[messageCallbacksSymbol].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);
    }
  }
 
  /**
   * Posts a message to the inspector back-end.
   * @param {string} method
   * @param {Record<unknown, unknown>} [params]
   * @param {Function} [callback]
   * @returns {void}
   */
  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[connectionSymbol]) {
      throw new ERR_INSPECTOR_NOT_CONNECTED();
    }
    const id = this[nextIdSymbol]++;
    const message = { id, method };
    if (params) {
      message.params = params;
    }
    if (callback) {
      this[messageCallbacksSymbol].set(id, callback);
    }
    this[connectionSymbol].dispatch(JSONStringify(message));
  }
 
  /**
   * Immediately closes the session, all pending
   * message callbacks will be called with an
   * error.
   * @returns {void}
   */
  disconnect() {
    if (!this[connectionSymbol])
      return;
    this[connectionSymbol].disconnect();
    this[connectionSymbol] = null;
    const remainingCallbacks = this[messageCallbacksSymbol].values();
    for (const callback of remainingCallbacks) {
      process.nextTick(callback, new ERR_INSPECTOR_CLOSED());
    }
    this[messageCallbacksSymbol].clear();
    this[nextIdSymbol] = 1;
  }
}
 
/**
 * Activates inspector on host and port.
 * @param {number} [port]
 * @param {string} [host]
 * @param {boolean} [wait]
 * @returns {void}
 */
function inspectorOpen(port, host, wait) {
  if (isEnabled()) {
    throw new ERR_INSPECTOR_ALREADY_ACTIVATED();
  }
  open(port, host);
  if (wait)
    waitForDebugger();
}
 
/**
 * Blocks until a client (existing or connected later)
 * has sent the `Runtime.runIfWaitingForDebugger`
 * command.
 * @returns {void}
 */
function inspectorWaitForDebugger() {
  if (!waitForDebugger())
    throw new ERR_INSPECTOR_NOT_ACTIVE();
}
 
module.exports = {
  open: inspectorOpen,
  close: process._debugEnd,
  url,
  waitForDebugger: inspectorWaitForDebugger,
  // This is dynamically added during bootstrap,
  // where the console from the VM is still available
  console: require('internal/util/inspector').consoleFromVM,
  Session
};