/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Client, PersistentProtocol, ISocket } from 'vs/base/parts/ipc/common/ipc.net'; import { generateUuid } from 'vs/base/common/uuid'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { Disposable } from 'vs/base/common/lifecycle'; export const enum ConnectionType { Management = 1, ExtensionHost = 2, Tunnel = 3, } interface ISimpleConnectionOptions { isBuilt: boolean; commit: string | undefined; host: string; port: number; reconnectionToken: string; reconnectionProtocol: PersistentProtocol | null; webSocketFactory: IWebSocketFactory; } export interface IConnectCallback { (err: any | undefined, socket: ISocket | undefined): void; } export interface IWebSocketFactory { connect(host: string, port: number, query: string, callback: IConnectCallback): void; } async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise { throw new Error(`Not implemented`); } interface IManagementConnectionResult { protocol: PersistentProtocol; } async function doConnectRemoteAgentManagement(options: ISimpleConnectionOptions): Promise { const protocol = await connectToRemoteExtensionHostAgent(options, ConnectionType.Management, undefined); return new Promise((c, e) => { const registration = protocol.onControlMessage(raw => { registration.dispose(); const msg = JSON.parse(raw.toString()); const error = getErrorFromMessage(msg); if (error) { return e(error); } if (options.reconnectionProtocol) { options.reconnectionProtocol.endAcceptReconnection(); } c({ protocol }); }); }); } export interface IRemoteExtensionHostStartParams { language: string; debugId?: string; break?: boolean; port?: number | null; updatePort?: boolean; } interface IExtensionHostConnectionResult { protocol: PersistentProtocol; debugPort?: number; } async function doConnectRemoteAgentExtensionHost(options: ISimpleConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise { const protocol = await connectToRemoteExtensionHostAgent(options, ConnectionType.ExtensionHost, startArguments); return new Promise((c, e) => { const registration = protocol.onControlMessage(raw => { registration.dispose(); const msg = JSON.parse(raw.toString()); const error = getErrorFromMessage(msg); if (error) { return e(error); } const debugPort = msg && msg.debugPort; if (options.reconnectionProtocol) { options.reconnectionProtocol.endAcceptReconnection(); } c({ protocol, debugPort }); }); }); } export interface ITunnelConnectionStartParams { port: number; } async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, startParams: ITunnelConnectionStartParams): Promise { const protocol = await connectToRemoteExtensionHostAgent(options, ConnectionType.Tunnel, startParams); return protocol; } export interface IConnectionOptions { isBuilt: boolean; commit: string | undefined; webSocketFactory: IWebSocketFactory; addressProvider: IAddressProvider; } async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise { const { host, port } = await options.addressProvider.getAddress(); return { isBuilt: options.isBuilt, commit: options.commit, host: host, port: port, reconnectionToken: reconnectionToken, reconnectionProtocol: reconnectionProtocol, webSocketFactory: options.webSocketFactory, }; } export interface IAddress { host: string; port: number; } export interface IAddressProvider { getAddress(): Promise; } export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise { const reconnectionToken = generateUuid(); const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null); const { protocol } = await doConnectRemoteAgentManagement(simpleOptions); return new ManagementPersistentConnection(options, remoteAuthority, clientId, reconnectionToken, protocol); } export async function connectRemoteAgentExtensionHost(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise { const reconnectionToken = generateUuid(); const simpleOptions = await resolveConnectionOptions(options, reconnectionToken, null); const { protocol, debugPort } = await doConnectRemoteAgentExtensionHost(simpleOptions, startArguments); return new ExtensionHostPersistentConnection(options, startArguments, reconnectionToken, protocol, debugPort); } export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise { const simpleOptions = await resolveConnectionOptions(options, generateUuid(), null); const protocol = await doConnectRemoteAgentTunnel(simpleOptions, { port: tunnelRemotePort }); return protocol; } abstract class PersistentConnection extends Disposable { protected readonly _options: IConnectionOptions; public readonly reconnectionToken: string; public readonly protocol: PersistentProtocol; constructor(options: IConnectionOptions, reconnectionToken: string, protocol: PersistentProtocol) { super(); this._options = options; this.reconnectionToken = reconnectionToken; this.protocol = protocol; } protected abstract _reconnect(options: ISimpleConnectionOptions): Promise; } export class ManagementPersistentConnection extends PersistentConnection { public readonly client: Client; constructor(options: IConnectionOptions, remoteAuthority: string, clientId: string, reconnectionToken: string, protocol: PersistentProtocol) { super(options, reconnectionToken, protocol); this.client = this._register(new Client(protocol, { remoteAuthority: remoteAuthority, clientId: clientId })); } protected async _reconnect(options: ISimpleConnectionOptions): Promise { await doConnectRemoteAgentManagement(options); } } export class ExtensionHostPersistentConnection extends PersistentConnection { private readonly _startArguments: IRemoteExtensionHostStartParams; public readonly debugPort: number | undefined; constructor(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams, reconnectionToken: string, protocol: PersistentProtocol, debugPort: number | undefined) { super(options, reconnectionToken, protocol); this._startArguments = startArguments; this.debugPort = debugPort; } protected async _reconnect(options: ISimpleConnectionOptions): Promise { await doConnectRemoteAgentExtensionHost(options, this._startArguments); } } function getErrorFromMessage(msg: any): Error | null { if (msg && msg.type === 'error') { const error = new Error(`Connection error: ${msg.reason}`); (error).code = 'VSCODE_CONNECTION_ERROR'; return error; } return null; }