diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 78187325d0798..ea05c94aaaf8b 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -11,8 +11,9 @@ export const ITunnelService = createDecorator('tunnelService'); export interface RemoteTunnel { readonly tunnelRemotePort: number; + readonly tunnelRemoteHost: string; readonly tunnelLocalPort: number; - readonly localAddress?: string; + readonly localAddress: string; dispose(): void; } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 945c9bd2aae76..fa31dcb8423bd 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -37,12 +37,11 @@ declare module 'vscode' { remote: { port: number, host: string }; localPort?: number; name?: string; - closeable?: boolean; } export interface Tunnel extends Disposable { remote: { port: number, host: string }; - local: { port: number, host: string }; + localAddress: string; } /** @@ -54,7 +53,7 @@ declare module 'vscode' { * The localAddress should be the complete local address(ex. localhost:1234) for connecting to the port. Tunnels provided through * detected are read-only from the forwarded ports UI. */ - detectedTunnels?: { remotePort: number, localAddress: string }[]; + detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[]; } export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; @@ -78,7 +77,7 @@ declare module 'vscode' { export namespace workspace { /** - * Forwards a port. + * Forwards a port. Currently only works for a remote host of localhost. * @param forward The `localPort` is a suggestion only. If that port is not available another will be chosen. */ export function makeTunnel(forward: TunnelOptions): Thenable; diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 2905c524113be..d29c9c770fb0c 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -56,6 +56,7 @@ import './mainThreadWorkspace'; import './mainThreadComments'; import './mainThreadTask'; import './mainThreadLabelService'; +import './mainThreadTunnelService'; import 'vs/workbench/api/common/apiCommands'; export class ExtensionPoints implements IWorkbenchContribution { diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts new file mode 100644 index 0000000000000..d53e3cf329e3a --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; + +@extHostNamedCustomer(MainContext.MainThreadTunnelService) +export class MainThreadTunnelService implements MainThreadTunnelServiceShape { + // @ts-ignore + private readonly _proxy: ExtHostTunnelServiceShape; + + constructor( + extHostContext: IExtHostContext, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService); + } + + async $openTunnel(tunnelOptions: TunnelOptions): Promise { + const tunnel = await this.remoteExplorerService.tunnelModel.forward(tunnelOptions.remote.port, tunnelOptions.localPort, tunnelOptions.name); + if (tunnel) { + return { remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress }; + } + return undefined; + } + + async $closeTunnel(remotePort: number): Promise { + return this.remoteExplorerService.tunnelModel.close(remotePort); + } + + dispose(): void { + // + } +} diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index dc89753a16226..0286e781fc935 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -47,6 +47,7 @@ import { createExtHostContextProxyIdentifier as createExtId, createMainContextPr import * as search from 'vs/workbench/services/search/common/search'; import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; +import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -776,6 +777,11 @@ export interface MainThreadWindowShape extends IDisposable { $asExternalUri(uri: UriComponents, options: IOpenUriOptions): Promise; } +export interface MainThreadTunnelServiceShape extends IDisposable { + $openTunnel(tunnelOptions: TunnelOptions): Promise; + $closeTunnel(remotePort: number): Promise; +} + // -- extension host export interface ExtHostCommandsShape { @@ -1435,7 +1441,8 @@ export const MainContext = { MainThreadSearch: createMainId('MainThreadSearch'), MainThreadTask: createMainId('MainThreadTask'), MainThreadWindow: createMainId('MainThreadWindow'), - MainThreadLabelService: createMainId('MainThreadLabelService') + MainThreadLabelService: createMainId('MainThreadLabelService'), + MainThreadTunnelService: createMainId('MainThreadTunnelService') }; export const ExtHostContext = { diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 20ddef9845d86..2f061642da4ad 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,19 +3,54 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import * as vscode from 'vscode'; +import { ExtHostTunnelServiceShape, MainThreadTunnelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import * as vscode from 'vscode'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export interface TunnelOptions { + remote: { port: number, host: string }; + localPort?: number; + name?: string; + closeable?: boolean; +} + +export interface TunnelDto { + remote: { port: number, host: string }; + localAddress: string; +} export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { - makeTunnel(forward: vscode.TunnelOptions): Promise; + makeTunnel(forward: TunnelOptions): Promise; } export const IExtHostTunnelService = createDecorator('IExtHostTunnelService'); -export class ExtHostTunnelService implements IExtHostTunnelService { - makeTunnel(forward: vscode.TunnelOptions): Promise { - throw new Error('Method not implemented.'); + +export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService { + private readonly _proxy: MainThreadTunnelServiceShape; + + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService + ) { + super(); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); + } + async makeTunnel(forward: TunnelOptions): Promise { + const tunnel = await this._proxy.$openTunnel(forward); + if (tunnel) { + const disposableTunnel: vscode.Tunnel = { + remote: tunnel.remote, + localAddress: tunnel.localAddress, + dispose: () => { + return this._proxy.$closeTunnel(tunnel.remote.port); + } + }; + this._register(disposableTunnel); + return disposableTunnel; + } + return undefined; } } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 631de08910117..cd2838a28c94d 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -10,7 +10,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ITunnelService } from 'vs/platform/remote/common/tunnel'; +import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; @@ -63,7 +63,8 @@ export class TunnelModel extends Disposable { this.forwarded.set(tunnel.tunnelRemotePort, { remote: tunnel.tunnelRemotePort, localAddress: tunnel.localAddress, - local: tunnel.tunnelLocalPort + local: tunnel.tunnelLocalPort, + closeable: true }); } this._onForwardPort.fire(this.forwarded.get(tunnel.tunnelRemotePort)!); @@ -76,7 +77,7 @@ export class TunnelModel extends Disposable { })); } - async forward(remote: number, local?: number, name?: string): Promise { + async forward(remote: number, local?: number, name?: string): Promise { if (!this.forwarded.has(remote)) { const tunnel = await this.tunnelService.openTunnel(remote, local); if (tunnel && tunnel.localAddress) { @@ -89,6 +90,7 @@ export class TunnelModel extends Disposable { }; this.forwarded.set(remote, newForward); this._onForwardPort.fire(newForward); + return tunnel; } } } diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 8809b75528d45..74f93537d480f 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -28,7 +28,8 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { public readonly tunnelRemotePort: number; public tunnelLocalPort!: number; - public localAddress?: string; + public tunnelRemoteHost: string = 'localhost'; + public localAddress!: string; private readonly _options: IConnectionOptions; private readonly _server: net.Server; @@ -145,6 +146,7 @@ export class TunnelService implements ITunnelService { private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel { return { tunnelRemotePort: tunnel.tunnelRemotePort, + tunnelRemoteHost: tunnel.tunnelRemoteHost, tunnelLocalPort: tunnel.tunnelLocalPort, localAddress: tunnel.localAddress, dispose: () => {