mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
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:
parent
37b304f2f3
commit
0207c66c87
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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[]> {
|
||||
|
|
Loading…
Reference in a new issue