diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 5b5a6084268..a156b9bc205 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -56,7 +56,7 @@ import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc'; import * as errors from 'vs/base/common/errors'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; -import { connectRemoteAgentManagement, ManagementPersistentConnection, IConnectionOptions } from 'vs/platform/remote/node/remoteAgentConnection'; +import { connectRemoteAgentManagement, ManagementPersistentConnection, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService'; import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc'; @@ -80,6 +80,7 @@ import { HistoryMainService } from 'vs/platform/history/electron-main/historyMai import { URLService } from 'vs/platform/url/common/urlService'; import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory'; export class CodeApplication extends Disposable { @@ -687,6 +688,7 @@ export class CodeApplication extends Disposable { const options: IConnectionOptions = { isBuilt: isBuilt, commit: product.commit, + webSocketFactory: nodeWebSocketFactory, addressProvider: { getAddress: () => { return Promise.resolve({ host, port }); diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts new file mode 100644 index 00000000000..7a85b6a2d6e --- /dev/null +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -0,0 +1,207 @@ +/*--------------------------------------------------------------------------------------------- + * 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; +} diff --git a/src/vs/platform/remote/node/nodeWebSocketFactory.ts b/src/vs/platform/remote/node/nodeWebSocketFactory.ts new file mode 100644 index 00000000000..dd69ea1a5ee --- /dev/null +++ b/src/vs/platform/remote/node/nodeWebSocketFactory.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWebSocketFactory, IConnectCallback } from 'vs/platform/remote/common/remoteAgentConnection'; + +export const nodeWebSocketFactory = new class implements IWebSocketFactory { + connect(host: string, port: number, query: string, callback: IConnectCallback): void { + throw new Error(`Not implemented`); + } +}; diff --git a/src/vs/platform/remote/node/remoteAgentConnection.ts b/src/vs/platform/remote/node/remoteAgentConnection.ts deleted file mode 100644 index 3c0950c5884..00000000000 --- a/src/vs/platform/remote/node/remoteAgentConnection.ts +++ /dev/null @@ -1,82 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 } from 'vs/base/parts/ipc/common/ipc.net'; -import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { Disposable } from 'vs/base/common/lifecycle'; - -export interface IRemoteExtensionHostStartParams { - language: string; - debugId?: string; - break?: boolean; - port?: number | null; - updatePort?: boolean; -} - -export interface IConnectionOptions { - isBuilt: boolean; - commit: string | undefined; - addressProvider: IAddressProvider; -} - -export interface IAddress { - host: string; - port: number; -} - -export interface IAddressProvider { - getAddress(): Promise; -} - -export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise { - throw new Error(`Not implemented`); -} - -export async function connectRemoteAgentExtensionHost(options: IConnectionOptions, startArguments: IRemoteExtensionHostStartParams): Promise { - throw new Error(`Not implemented`); -} - -export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise { - throw new Error(`Not implemented`); -} - -export class RemoteTunnel extends Disposable { -} - -abstract class PersistentConnection extends Disposable { - - public readonly reconnectionToken: string; - public readonly protocol: PersistentProtocol; - - constructor(reconnectionToken: string, protocol: PersistentProtocol) { - super(); - this.reconnectionToken = reconnectionToken; - this.protocol = protocol; - } -} - -export class ManagementPersistentConnection extends PersistentConnection { - - public readonly client: Client; - - constructor(remoteAuthority: string, host: string, port: number, clientId: string, isBuilt: boolean, reconnectionToken: string, protocol: PersistentProtocol) { - super(reconnectionToken, protocol); - - this.client = this._register(new Client(protocol, { - remoteAuthority: remoteAuthority, - clientId: clientId - })); - } -} - -export class ExtensionHostPersistentConnection extends PersistentConnection { - - public readonly debugPort: number | undefined; - - constructor(host: string, port: number, startArguments: IRemoteExtensionHostStartParams, isBuilt: boolean, reconnectionToken: string, protocol: PersistentProtocol, debugPort: number | undefined) { - super(reconnectionToken, protocol); - this.debugPort = debugPort; - } -} diff --git a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts index e3c641c03ef..c71c4b23cb0 100644 --- a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts @@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { connectRemoteAgentManagement, IConnectionOptions } from 'vs/platform/remote/node/remoteAgentConnection'; +import { connectRemoteAgentManagement, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -19,6 +19,7 @@ import { RemoteExtensionEnvironmentChannelClient } from 'vs/workbench/services/r import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import product from 'vs/platform/product/node/product'; +import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory'; export class RemoteAgentService extends Disposable implements IRemoteAgentService { @@ -90,6 +91,7 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection const options: IConnectionOptions = { isBuilt: this._environmentService.isBuilt, commit: product.commit, + webSocketFactory: nodeWebSocketFactory, addressProvider: { getAddress: async () => { const { host, port } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);