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