| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import type * as Common from '../../../core/common/common.js'; |
| import * as Host from '../../../core/host/host.js'; |
| import * as i18n from '../../../core/i18n/i18n.js'; |
| import type * as Platform from '../../../core/platform/platform.js'; |
| import * as ProtocolClient from '../../../core/protocol_client/protocol_client.js'; |
| import * as SDK from '../../../core/sdk/sdk.js'; |
| import type * as ProtocolProxyApi from '../../../generated/protocol-proxy-api.js'; |
| import type * as Protocol from '../../../generated/protocol.js'; |
| import * as Components from '../../../ui/legacy/components/utils/utils.js'; |
| |
| const UIStrings = { |
| /** |
| * @description Text that refers to the main target |
| */ |
| main: 'Main', |
| /** |
| * @description Text in Node Main of the Sources panel when debugging a Node.js app |
| * @example {example.com} PH1 |
| */ |
| nodejsS: 'Node.js: {PH1}', |
| /** |
| * @description Text in DevTools window title when debugging a Node.js app |
| * @example {example.com} PH1 |
| */ |
| NodejsTitleS: 'DevTools - Node.js: {PH1}', |
| } as const; |
| const str_ = i18n.i18n.registerUIStrings('entrypoints/node_app/app/NodeMain.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| let nodeMainImplInstance: NodeMainImpl; |
| |
| export class NodeMainImpl implements Common.Runnable.Runnable { |
| static instance(opts: {forceNew: boolean|null} = {forceNew: null}): NodeMainImpl { |
| const {forceNew} = opts; |
| if (!nodeMainImplInstance || forceNew) { |
| nodeMainImplInstance = new NodeMainImpl(); |
| } |
| return nodeMainImplInstance; |
| } |
| async run(): Promise<void> { |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.ConnectToNodeJSFromFrontend); |
| void SDK.Connections.initMainConnection(async () => { |
| const target = SDK.TargetManager.TargetManager.instance().createTarget( |
| // TODO: Use SDK.Target.Type.NODE rather thatn BROWSER once DevTools is loaded appropriately in that case. |
| 'main', i18nString(UIStrings.main), SDK.Target.Type.BROWSER, null); |
| target.setInspectedURL('Node.js' as Platform.DevToolsPath.UrlString); |
| }, Components.TargetDetachedDialog.TargetDetachedDialog.connectionLost); |
| } |
| } |
| |
| export class NodeChildTargetManager extends SDK.SDKModel.SDKModel<void> implements ProtocolProxyApi.TargetDispatcher { |
| readonly #targetManager: SDK.TargetManager.TargetManager; |
| readonly #parentTarget: SDK.Target.Target; |
| readonly #targetAgent: ProtocolProxyApi.TargetApi; |
| readonly #childTargets = new Map<Protocol.Target.SessionID, SDK.Target.Target>(); |
| readonly #childConnections = new Map<string, NodeConnection>(); |
| constructor(parentTarget: SDK.Target.Target) { |
| super(parentTarget); |
| this.#targetManager = parentTarget.targetManager(); |
| this.#parentTarget = parentTarget; |
| this.#targetAgent = parentTarget.targetAgent(); |
| |
| parentTarget.registerTargetDispatcher(this); |
| void this.#targetAgent.invoke_setDiscoverTargets({discover: true}); |
| |
| Host.InspectorFrontendHost.InspectorFrontendHostInstance.events.addEventListener( |
| Host.InspectorFrontendHostAPI.Events.DevicesDiscoveryConfigChanged, this.#devicesDiscoveryConfigChanged, this); |
| Host.InspectorFrontendHost.InspectorFrontendHostInstance.setDevicesUpdatesEnabled(false); |
| Host.InspectorFrontendHost.InspectorFrontendHostInstance.setDevicesUpdatesEnabled(true); |
| } |
| |
| #devicesDiscoveryConfigChanged({data: config}: Common.EventTarget.EventTargetEvent<Adb.Config>): void { |
| const locations = []; |
| for (const address of config.networkDiscoveryConfig) { |
| const parts = address.split(':'); |
| const port = parseInt(parts[1], 10); |
| if (parts[0] && port) { |
| locations.push({host: parts[0], port}); |
| } |
| } |
| void this.#targetAgent.invoke_setRemoteLocations({locations}); |
| } |
| |
| override dispose(): void { |
| Host.InspectorFrontendHost.InspectorFrontendHostInstance.events.removeEventListener( |
| Host.InspectorFrontendHostAPI.Events.DevicesDiscoveryConfigChanged, this.#devicesDiscoveryConfigChanged, this); |
| |
| for (const sessionId of this.#childTargets.keys()) { |
| this.detachedFromTarget({sessionId}); |
| } |
| } |
| |
| targetCreated({targetInfo}: Protocol.Target.TargetCreatedEvent): void { |
| if (targetInfo.type === 'node' && !targetInfo.attached) { |
| void this.#targetAgent.invoke_attachToTarget({targetId: targetInfo.targetId, flatten: false}); |
| } else if (targetInfo.type === 'node_worker') { |
| void this.#targetAgent.invoke_setAutoAttach({autoAttach: true, waitForDebuggerOnStart: false}); |
| } |
| } |
| |
| targetInfoChanged(_event: Protocol.Target.TargetInfoChangedEvent): void { |
| } |
| |
| targetDestroyed(_event: Protocol.Target.TargetDestroyedEvent): void { |
| } |
| |
| attachedToTarget({sessionId, targetInfo}: Protocol.Target.AttachedToTargetEvent): void { |
| let target: SDK.Target.Target; |
| if (targetInfo.type === 'node_worker') { |
| target = this.#targetManager.createTarget( |
| targetInfo.targetId, targetInfo.title, SDK.Target.Type.NODE_WORKER, this.#parentTarget, sessionId, true, |
| undefined, targetInfo); |
| } else { |
| const name = i18nString(UIStrings.nodejsS, {PH1: targetInfo.url}); |
| document.title = i18nString(UIStrings.NodejsTitleS, {PH1: targetInfo.url}); |
| const connection = new NodeConnection(this.#targetAgent, sessionId); |
| this.#childConnections.set(sessionId, connection); |
| target = this.#targetManager.createTarget( |
| targetInfo.targetId, name, SDK.Target.Type.NODE, null, undefined, undefined, |
| new ProtocolClient.DevToolsCDPConnection.DevToolsCDPConnection(connection)); |
| } |
| this.#childTargets.set(sessionId, target); |
| void target.runtimeAgent().invoke_runIfWaitingForDebugger(); |
| } |
| |
| detachedFromTarget({sessionId}: Protocol.Target.DetachedFromTargetEvent): void { |
| const childTarget = this.#childTargets.get(sessionId); |
| if (childTarget) { |
| childTarget.dispose('target terminated'); |
| } |
| this.#childTargets.delete(sessionId); |
| this.#childConnections.delete(sessionId); |
| } |
| |
| receivedMessageFromTarget({sessionId, message}: Protocol.Target.ReceivedMessageFromTargetEvent): void { |
| const connection = this.#childConnections.get(sessionId); |
| const onMessage = connection ? connection.onMessage : null; |
| if (onMessage) { |
| onMessage.call(null, message); |
| } |
| } |
| |
| targetCrashed(_event: Protocol.Target.TargetCrashedEvent): void { |
| } |
| } |
| |
| export class NodeConnection implements ProtocolClient.ConnectionTransport.ConnectionTransport { |
| readonly #targetAgent: ProtocolProxyApi.TargetApi; |
| readonly #sessionId: Protocol.Target.SessionID; |
| onMessage: ((arg0: Object|string) => void)|null; |
| #onDisconnect: ((arg0: string) => void)|null; |
| constructor(targetAgent: ProtocolProxyApi.TargetApi, sessionId: Protocol.Target.SessionID) { |
| this.#targetAgent = targetAgent; |
| this.#sessionId = sessionId; |
| this.onMessage = null; |
| this.#onDisconnect = null; |
| } |
| |
| setOnMessage(onMessage: (arg0: Object|string) => void): void { |
| this.onMessage = onMessage; |
| } |
| |
| setOnDisconnect(onDisconnect: (arg0: string) => void): void { |
| this.#onDisconnect = onDisconnect; |
| } |
| |
| sendRawMessage(message: string): void { |
| void this.#targetAgent.invoke_sendMessageToTarget({message, sessionId: this.#sessionId}); |
| } |
| |
| async disconnect(): Promise<void> { |
| if (this.#onDisconnect) { |
| this.#onDisconnect.call(null, 'force disconnect'); |
| } |
| this.#onDisconnect = null; |
| this.onMessage = null; |
| await this.#targetAgent.invoke_detachFromTarget({sessionId: this.#sessionId}); |
| } |
| } |
| |
| SDK.SDKModel.SDKModel.register(NodeChildTargetManager, {capabilities: SDK.Target.Capability.TARGET, autostart: true}); |