ports: support resolver tunnel factories on the web (#186937)

@alexr00 I just copied the non-Node.js-specific code into the base port
class and things seem to work. What do you think?

Fixes https://github.com/microsoft/vscode-remote-release/issues/8595
This commit is contained in:
Connor Peet 2023-07-04 03:11:09 -07:00 committed by GitHub
parent 37b304f2f3
commit 0207c66c87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 199 additions and 220 deletions

View file

@ -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,

View file

@ -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>('IExtHostTunnelService');
export class ExtHostTunnelService implements IExtHostTunnelService {
declare readonly _serviceBrand: undefined;
onDidChangeTunnels: vscode.Event<void> = (new Emitter<void>()).event;
export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService {
readonly _serviceBrand: undefined;
protected readonly _proxy: MainThreadTunnelServiceShape;
private _forwardPortProvider: ((tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable<vscode.Tunnel> | undefined) | undefined;
private _showCandidatePort: (host: string, port: number, detail: string) => Thenable<boolean> = () => { return Promise.resolve(true); };
private _extensionTunnels: Map<string, Map<number, { tunnel: vscode.Tunnel; disposeListener: IDisposable }>> = new Map();
private _onDidChangeTunnels: Emitter<void> = new Emitter<void>();
onDidChangeTunnels: vscode.Event<void> = this._onDidChangeTunnels.event;
private _providerHandleCounter: number = 0;
private _portAttributesProviders: Map<number, { provider: vscode.PortAttributesProvider; selector: PortAttributesSelector }> = new Map();
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostInitDataService initData: IExtHostInitDataService,
@ILogService protected readonly logService: ILogService
) {
}
async $applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]> {
return candidates;
super();
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
}
async openTunnel(extension: IExtensionDescription, forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
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<vscode.TunnelDescription[]> {
return [];
return this._proxy.$getTunnels();
}
async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
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<ProvidedPortAttributes[]> {
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: <ProvidedOnAutoForward><unknown>attributes.providedAttributes.autoForwardAction,
port: attributes.port
};
}) : [];
}
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> { return undefined; }
async $closeTunnel(remote: { host: string; port: number }): Promise<void> { }
async $onDidTunnelsChange(): Promise<void> { }
async $registerCandidateFinder(): Promise<void> { }
async $registerCandidateFinder(_enable: boolean): Promise<void> { }
async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
// 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<void> {
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<void> {
this._onDidChangeTunnels.fire();
}
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> {
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<CandidatePort[]> {
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;
}
}

View file

@ -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);

View file

@ -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<string, { pid: number; socket: number }> {
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<vscode.Tunnel> | undefined) | undefined;
private _showCandidatePort: (host: string, port: number, detail: string) => Thenable<boolean> = () => { return Promise.resolve(true); };
private _extensionTunnels: Map<string, Map<number, { tunnel: vscode.Tunnel; disposeListener: IDisposable }>> = new Map();
private _onDidChangeTunnels: Emitter<void> = new Emitter<void>();
onDidChangeTunnels: vscode.Event<void> = this._onDidChangeTunnels.event;
private _candidateFindingEnabled: boolean = false;
private _foundRootPorts: Map<number, CandidatePort & { ppid: number }> = new Map();
export class NodeExtHostTunnelService extends ExtHostTunnelService {
private _initialCandidates: CandidatePort[] | undefined = undefined;
private _providerHandleCounter: number = 0;
private _portAttributesProviders: Map<number, { provider: vscode.PortAttributesProvider; selector: PortAttributesSelector }> = new Map();
private _foundRootPorts: Map<number, CandidatePort & { ppid: number }> = 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<vscode.Tunnel | undefined> {
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<void> {
this._initialCandidates = await this.findCandidatePorts();
this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) Initial candidates found: ${this._initialCandidates.map(c => c.port).join(', ')}`);
}
async getTunnels(): Promise<vscode.TunnelDescription[]> {
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<ProvidedPortAttributes[]> {
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: <ProvidedOnAutoForward><unknown>attributes.providedAttributes.autoForwardAction,
port: attributes.port
};
}) : [];
}
async $registerCandidateFinder(enable: boolean): Promise<void> {
override async $registerCandidateFinder(enable: boolean): Promise<void> {
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<IDisposable> {
// 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<void> {
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<void> {
this._onDidChangeTunnels.fire();
}
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> {
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<CandidatePort[]> {
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<void> {
this._initialCandidates = await this.findCandidatePorts();
this.logService.trace(`ForwardedPorts: (ExtHostTunnelService) Initial candidates found: ${this._initialCandidates.map(c => c.port).join(', ')}`);
}
private async findCandidatePorts(): Promise<CandidatePort[]> {