diff --git a/cli/src/tunnels/paths.rs b/cli/src/tunnels/paths.rs index fa06db5dd7a..a0cd43cd83c 100644 --- a/cli/src/tunnels/paths.rs +++ b/cli/src/tunnels/paths.rs @@ -91,10 +91,15 @@ impl InstalledServer { pub fn server_paths(&self, p: &LauncherPaths) -> ServerPaths { let server_dir = self.get_install_folder(p); ServerPaths { - executable: server_dir - .join(SERVER_FOLDER_NAME) - .join("bin") - .join(self.quality.server_entrypoint()), + // allow using the OSS server in development via an override + executable: if let Some(p) = option_env!("VSCODE_CLI_OVERRIDE_SERVER_PATH") { + PathBuf::from(p) + } else { + server_dir + .join(SERVER_FOLDER_NAME) + .join("bin") + .join(self.quality.server_entrypoint()) + }, logfile: server_dir.join("log.txt"), pidfile: server_dir.join("pid.txt"), server_dir, diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index dfdfc5ab5a2..4df3fc4c600 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,15 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtHostTunnelServiceShape, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import * as vscode from 'vscode'; -import { ProvidedPortAttributes, RemoteTunnel, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import * as nls from 'vs/nls'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { DisposableTunnel, ProvidedOnAutoForward, ProvidedPortAttributes, RemoteTunnel, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel'; +import { ExtHostTunnelServiceShape, MainContext, MainThreadTunnelServiceShape, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import * as types from 'vs/workbench/api/common/extHostTypes'; import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import * as vscode from 'vscode'; + +class ExtensionTunnel extends DisposableTunnel implements vscode.Tunnel { } export namespace TunnelDtoConverter { export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto { @@ -53,37 +59,176 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { export const IExtHostTunnelService = createDecorator('IExtHostTunnelService'); -export class ExtHostTunnelService implements IExtHostTunnelService { - declare readonly _serviceBrand: undefined; - onDidChangeTunnels: vscode.Event = (new Emitter()).event; +export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService { + readonly _serviceBrand: undefined; + protected readonly _proxy: MainThreadTunnelServiceShape; + private _forwardPortProvider: ((tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable | undefined) | undefined; + private _showCandidatePort: (host: string, port: number, detail: string) => Thenable = () => { return Promise.resolve(true); }; + private _extensionTunnels: Map> = new Map(); + private _onDidChangeTunnels: Emitter = new Emitter(); + onDidChangeTunnels: vscode.Event = this._onDidChangeTunnels.event; + + private _providerHandleCounter: number = 0; + private _portAttributesProviders: Map = new Map(); constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @ILogService protected readonly logService: ILogService ) { - } - async $applyCandidateFilter(candidates: CandidatePort[]): Promise { - return candidates; + super(); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); } async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise { + this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) ${extension.identifier.value} called openTunnel API for ${forward.remoteAddress.host}:${forward.remoteAddress.port}.`); + const tunnel = await this._proxy.$openTunnel(forward, extension.displayName); + if (tunnel) { + const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => { + return this._proxy.$closeTunnel(tunnel.remoteAddress); + }); + this._register(disposableTunnel); + return disposableTunnel; + } return undefined; } + async getTunnels(): Promise { - return []; + return this._proxy.$getTunnels(); } - async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise { - return { dispose: () => { } }; + private nextPortAttributesProviderHandle(): number { + return this._providerHandleCounter++; } - registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider) { - return { dispose: () => { } }; + + registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): vscode.Disposable { + const providerHandle = this.nextPortAttributesProviderHandle(); + this._portAttributesProviders.set(providerHandle, { selector: portSelector, provider }); + + this._proxy.$registerPortsAttributesProvider(portSelector, providerHandle); + return new types.Disposable(() => { + this._portAttributesProviders.delete(providerHandle); + this._proxy.$unregisterPortsAttributesProvider(providerHandle); + }); } async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise { - return []; + const providedAttributes: { providedAttributes: vscode.PortAttributes | null | undefined; port: number }[] = []; + for (const handle of handles) { + const provider = this._portAttributesProviders.get(handle); + if (!provider) { + return []; + } + providedAttributes.push(...(await Promise.all(ports.map(async (port) => { + return { providedAttributes: (await provider.provider.providePortAttributes(port, pid, commandline, cancellationToken)), port }; + })))); + } + + const allAttributes = <{ providedAttributes: vscode.PortAttributes; port: number }[]>providedAttributes.filter(attribute => !!attribute.providedAttributes); + + return (allAttributes.length > 0) ? allAttributes.map(attributes => { + return { + autoForwardAction: attributes.providedAttributes.autoForwardAction, + port: attributes.port + }; + }) : []; } - async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise { return undefined; } - async $closeTunnel(remote: { host: string; port: number }): Promise { } - async $onDidTunnelsChange(): Promise { } - async $registerCandidateFinder(): Promise { } + async $registerCandidateFinder(_enable: boolean): Promise { } + + async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise { + // Do not wait for any of the proxy promises here. + // It will delay startup and there is nothing that needs to be waited for. + if (provider) { + if (provider.candidatePortSource !== undefined) { + this._proxy.$setCandidatePortSource(provider.candidatePortSource); + } + if (provider.showCandidatePort) { + this._showCandidatePort = provider.showCandidatePort; + this._proxy.$setCandidateFilter(); + } + if (provider.tunnelFactory) { + this._forwardPortProvider = provider.tunnelFactory; + let privacyOptions = provider.tunnelFeatures?.privacyOptions ?? []; + if (provider.tunnelFeatures?.public && (privacyOptions.length === 0)) { + privacyOptions = [ + { + id: 'private', + label: nls.localize('tunnelPrivacy.private', "Private"), + themeIcon: 'lock' + }, + { + id: 'public', + label: nls.localize('tunnelPrivacy.public', "Public"), + themeIcon: 'eye' + } + ]; + } + + const tunnelFeatures = provider.tunnelFeatures ? { + elevation: !!provider.tunnelFeatures?.elevation, + public: !!provider.tunnelFeatures?.public, + privacyOptions + } : undefined; + + this._proxy.$setTunnelProvider(tunnelFeatures); + } + } else { + this._forwardPortProvider = undefined; + } + return toDisposable(() => { + this._forwardPortProvider = undefined; + }); + } + + async $closeTunnel(remote: { host: string; port: number }, silent?: boolean): Promise { + if (this._extensionTunnels.has(remote.host)) { + const hostMap = this._extensionTunnels.get(remote.host)!; + if (hostMap.has(remote.port)) { + if (silent) { + hostMap.get(remote.port)!.disposeListener.dispose(); + } + await hostMap.get(remote.port)!.tunnel.dispose(); + hostMap.delete(remote.port); + } + } + } + + async $onDidTunnelsChange(): Promise { + this._onDidChangeTunnels.fire(); + } + + async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise { + if (this._forwardPortProvider) { + try { + this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Getting tunnel from provider.'); + const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions); + this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Got tunnel promise from provider.'); + if (providedPort !== undefined) { + const tunnel = await providedPort; + this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Successfully awaited tunnel from provider.'); + if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) { + this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map()); + } + const disposeListener = this._register(tunnel.onDidDispose(() => { + this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Extension fired tunnel\'s onDidDispose.'); + return this._proxy.$closeTunnel(tunnel.remoteAddress); + })); + this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener }); + return TunnelDtoConverter.fromApiTunnel(tunnel); + } else { + this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Tunnel is undefined'); + } + } catch (e) { + this.logService.trace('ForwardedPorts: (ExtHostTunnelService) tunnel provider error'); + } + } + return undefined; + } + + async $applyCandidateFilter(candidates: CandidatePort[]): Promise { + const filter = await Promise.all(candidates.map(candidate => this._showCandidatePort(candidate.host, candidate.port, candidate.detail ?? ''))); + const result = candidates.filter((candidate, index) => filter[index]); + this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) filtered from ${candidates.map(port => port.port).join(', ')} to ${result.map(port => port.port).join(', ')}`); + return result; + } } diff --git a/src/vs/workbench/api/node/extHost.node.services.ts b/src/vs/workbench/api/node/extHost.node.services.ts index 6785393c611..9a4ffa8b033 100644 --- a/src/vs/workbench/api/node/extHost.node.services.ts +++ b/src/vs/workbench/api/node/extHost.node.services.ts @@ -9,7 +9,7 @@ import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; import { NativeExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; -import { ExtHostTunnelService } from 'vs/workbench/api/node/extHostTunnelService'; +import { NodeExtHostTunnelService } from 'vs/workbench/api/node/extHostTunnelService'; import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; @@ -40,5 +40,5 @@ registerSingleton(IExtHostDebugService, ExtHostDebugService, InstantiationType.E registerSingleton(IExtHostSearch, NativeExtHostSearch, InstantiationType.Eager); registerSingleton(IExtHostTask, ExtHostTask, InstantiationType.Eager); registerSingleton(IExtHostTerminalService, ExtHostTerminalService, InstantiationType.Eager); -registerSingleton(IExtHostTunnelService, ExtHostTunnelService, InstantiationType.Eager); +registerSingleton(IExtHostTunnelService, NodeExtHostTunnelService, InstantiationType.Eager); registerSingleton(IExtHostVariableResolverProvider, NodeExtHostVariableResolverProviderService, InstantiationType.Eager); diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index ae3cf3568f8..ac5e6717293 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -3,27 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainThreadTunnelServiceShape, MainContext, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import type * as vscode from 'vscode'; -import * as nls from 'vs/nls'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { URI } from 'vs/base/common/uri'; import { exec } from 'child_process'; -import * as resources from 'vs/base/common/resources'; -import * as pfs from 'vs/base/node/pfs'; -import * as types from 'vs/workbench/api/common/extHostTypes'; -import { isLinux } from 'vs/base/common/platform'; -import { IExtHostTunnelService, TunnelDtoConverter } from 'vs/workbench/api/common/extHostTunnelService'; -import { Emitter } from 'vs/base/common/event'; -import { TunnelOptions, TunnelCreationOptions, ProvidedPortAttributes, ProvidedOnAutoForward, isLocalhost, isAllInterfaces, DisposableTunnel } from 'vs/platform/tunnel/common/tunnel'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { MovingAverage } from 'vs/base/common/numbers'; -import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { isLinux } from 'vs/base/common/platform'; +import * as resources from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import * as pfs from 'vs/base/node/pfs'; import { ILogService } from 'vs/platform/log/common/log'; - -class ExtensionTunnel extends DisposableTunnel implements vscode.Tunnel { } +import { isAllInterfaces, isLocalhost } from 'vs/platform/tunnel/common/tunnel'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; +import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService'; export function getSockets(stdout: string): Record { const lines = stdout.trim().split('\n'); @@ -173,99 +164,24 @@ export function tryFindRootPorts(connections: { socket: number; ip: string; port return ports; } -export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService { - readonly _serviceBrand: undefined; - private readonly _proxy: MainThreadTunnelServiceShape; - private _forwardPortProvider: ((tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable | undefined) | undefined; - private _showCandidatePort: (host: string, port: number, detail: string) => Thenable = () => { return Promise.resolve(true); }; - private _extensionTunnels: Map> = new Map(); - private _onDidChangeTunnels: Emitter = new Emitter(); - onDidChangeTunnels: vscode.Event = this._onDidChangeTunnels.event; - private _candidateFindingEnabled: boolean = false; - private _foundRootPorts: Map = new Map(); +export class NodeExtHostTunnelService extends ExtHostTunnelService { private _initialCandidates: CandidatePort[] | undefined = undefined; - - private _providerHandleCounter: number = 0; - private _portAttributesProviders: Map = new Map(); + private _foundRootPorts: Map = new Map(); + private _candidateFindingEnabled: boolean = false; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, - @ILogService private readonly logService: ILogService + @ILogService logService: ILogService ) { - super(); - this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService); + super(extHostRpc, initData, logService); if (isLinux && initData.remote.isRemote && initData.remote.authority) { this._proxy.$setRemoteTunnelService(process.pid); this.setInitialCandidates(); } } - async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise { - this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) ${extension.identifier.value} called openTunnel API for ${forward.remoteAddress.host}:${forward.remoteAddress.port}.`); - const tunnel = await this._proxy.$openTunnel(forward, extension.displayName); - if (tunnel) { - const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => { - return this._proxy.$closeTunnel(tunnel.remoteAddress); - }); - this._register(disposableTunnel); - return disposableTunnel; - } - return undefined; - } - - private async setInitialCandidates(): Promise { - this._initialCandidates = await this.findCandidatePorts(); - this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) Initial candidates found: ${this._initialCandidates.map(c => c.port).join(', ')}`); - } - - async getTunnels(): Promise { - return this._proxy.$getTunnels(); - } - - private calculateDelay(movingAverage: number) { - // Some local testing indicated that the moving average might be between 50-100 ms. - return Math.max(movingAverage * 20, 2000); - } - - private nextPortAttributesProviderHandle(): number { - return this._providerHandleCounter++; - } - - registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): vscode.Disposable { - const providerHandle = this.nextPortAttributesProviderHandle(); - this._portAttributesProviders.set(providerHandle, { selector: portSelector, provider }); - - this._proxy.$registerPortsAttributesProvider(portSelector, providerHandle); - return new types.Disposable(() => { - this._portAttributesProviders.delete(providerHandle); - this._proxy.$unregisterPortsAttributesProvider(providerHandle); - }); - } - - async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise { - const providedAttributes: { providedAttributes: vscode.PortAttributes | null | undefined; port: number }[] = []; - for (const handle of handles) { - const provider = this._portAttributesProviders.get(handle); - if (!provider) { - return []; - } - providedAttributes.push(...(await Promise.all(ports.map(async (port) => { - return { providedAttributes: (await provider.provider.providePortAttributes(port, pid, commandline, cancellationToken)), port }; - })))); - } - - const allAttributes = <{ providedAttributes: vscode.PortAttributes; port: number }[]>providedAttributes.filter(attribute => !!attribute.providedAttributes); - - return (allAttributes.length > 0) ? allAttributes.map(attributes => { - return { - autoForwardAction: attributes.providedAttributes.autoForwardAction, - port: attributes.port - }; - }) : []; - } - - async $registerCandidateFinder(enable: boolean): Promise { + override async $registerCandidateFinder(enable: boolean): Promise { if (enable && this._candidateFindingEnabled) { // already enabled return; @@ -303,101 +219,14 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } } - async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise { - // Do not wait for any of the proxy promises here. - // It will delay startup and there is nothing that needs to be waited for. - if (provider) { - if (provider.candidatePortSource !== undefined) { - this._proxy.$setCandidatePortSource(provider.candidatePortSource); - } - if (provider.showCandidatePort) { - this._showCandidatePort = provider.showCandidatePort; - this._proxy.$setCandidateFilter(); - } - if (provider.tunnelFactory) { - this._forwardPortProvider = provider.tunnelFactory; - let privacyOptions = provider.tunnelFeatures?.privacyOptions ?? []; - if (provider.tunnelFeatures?.public && (privacyOptions.length === 0)) { - privacyOptions = [ - { - id: 'private', - label: nls.localize('tunnelPrivacy.private', "Private"), - themeIcon: 'lock' - }, - { - id: 'public', - label: nls.localize('tunnelPrivacy.public', "Public"), - themeIcon: 'eye' - } - ]; - } - - const tunnelFeatures = provider.tunnelFeatures ? { - elevation: !!provider.tunnelFeatures?.elevation, - public: !!provider.tunnelFeatures?.public, - privacyOptions - } : undefined; - - this._proxy.$setTunnelProvider(tunnelFeatures); - } - } else { - this._forwardPortProvider = undefined; - } - return toDisposable(() => { - this._forwardPortProvider = undefined; - }); + private calculateDelay(movingAverage: number) { + // Some local testing indicated that the moving average might be between 50-100 ms. + return Math.max(movingAverage * 20, 2000); } - async $closeTunnel(remote: { host: string; port: number }, silent?: boolean): Promise { - if (this._extensionTunnels.has(remote.host)) { - const hostMap = this._extensionTunnels.get(remote.host)!; - if (hostMap.has(remote.port)) { - if (silent) { - hostMap.get(remote.port)!.disposeListener.dispose(); - } - await hostMap.get(remote.port)!.tunnel.dispose(); - hostMap.delete(remote.port); - } - } - } - - async $onDidTunnelsChange(): Promise { - this._onDidChangeTunnels.fire(); - } - - async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise { - if (this._forwardPortProvider) { - try { - this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Getting tunnel from provider.'); - const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions); - this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Got tunnel promise from provider.'); - if (providedPort !== undefined) { - const tunnel = await providedPort; - this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Successfully awaited tunnel from provider.'); - if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) { - this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map()); - } - const disposeListener = this._register(tunnel.onDidDispose(() => { - this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Extension fired tunnel\'s onDidDispose.'); - return this._proxy.$closeTunnel(tunnel.remoteAddress); - })); - this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener }); - return TunnelDtoConverter.fromApiTunnel(tunnel); - } else { - this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Tunnel is undefined'); - } - } catch (e) { - this.logService.trace('ForwardedPorts: (ExtHostTunnelService) tunnel provider error'); - } - } - return undefined; - } - - async $applyCandidateFilter(candidates: CandidatePort[]): Promise { - const filter = await Promise.all(candidates.map(candidate => this._showCandidatePort(candidate.host, candidate.port, candidate.detail ?? ''))); - const result = candidates.filter((candidate, index) => filter[index]); - this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) filtered from ${candidates.map(port => port.port).join(', ')} to ${result.map(port => port.port).join(', ')}`); - return result; + private async setInitialCandidates(): Promise { + this._initialCandidates = await this.findCandidatePorts(); + this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) Initial candidates found: ${this._initialCandidates.map(c => c.port).join(', ')}`); } private async findCandidatePorts(): Promise {