Skip to content

Commit

Permalink
API proposal for PortAttributesProvider (#118446)
Browse files Browse the repository at this point in the history
Part of #115616
  • Loading branch information
alexr00 authored Mar 8, 2021
1 parent 9c78fa4 commit 0604478
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 25 deletions.
18 changes: 18 additions & 0 deletions src/vs/platform/remote/common/tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isWindows, OperatingSystem } from 'vs/base/common/platform';
Expand Down Expand Up @@ -42,6 +43,23 @@ export interface ITunnelProvider {
forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined;
}

export enum ProvidedOnAutoForward {
Notify = 1,
OpenBrowser = 2,
OpenPreview = 3,
Silent = 4,
Ignore = 5
}

export interface ProvidedPortAttributes {
port: number;
autoForwardAction: ProvidedOnAutoForward;
}

export interface PortAttributesProvider {
providePortAttributes(ports: number[], pid: number | undefined, commandLine: string | undefined, token: CancellationToken): Promise<ProvidedPortAttributes[]>;
}

export interface ITunnel {
remoteAddress: { port: number, host: string };

Expand Down
33 changes: 33 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2846,4 +2846,37 @@ declare module 'vscode' {
readonly triggerKind: CodeActionTriggerKind;
}
//#endregion

//#region https://github.com/microsoft/vscode/issues/115616 @alexr00
export enum PortAutoForwardAction {
Notify = 1,
OpenBrowser = 2,
OpenPreview = 3,
Silent = 4,
Ignore = 5
}

export interface PortAttributes {
port: number;
autoForwardAction: PortAutoForwardAction
}

export interface PortAttributesProvider {
providePortAttributes(ports: number[], pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult<PortAttributes[]>;
}

export namespace workspace {
/**
* If your extension listens on ports, consider registering a PortAttributesProvider to provide information
* about the ports. For example, a debug extension may know about debug ports in it's debuggee. By providing
* this information with a PortAttributesProvider the extension can tell VS Code that these ports should be
* ignored, since they don't need to be user facing.
*
* @param portSelector If registerPortAttributesProvider is called after you start your process then you may already
* know the range of ports or the pid of your process.
* @param provider The PortAttributesProvider
*/
export function registerPortAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: PortAttributesProvider): Disposable;
}
//#endregion
}
41 changes: 38 additions & 3 deletions src/vs/workbench/api/browser/mainThreadTunnelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@
*--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource } from 'vs/workbench/api/common/extHost.protocol';
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource, PortAttributesProviderSelector } from 'vs/workbench/api/common/extHost.protocol';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged } from 'vs/platform/remote/common/tunnel';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged, ProvidedPortAttributes, PortAttributesProvider } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { CancellationToken } from 'vs/base/common/cancellation';

@extHostNamedCustomer(MainContext.MainThreadTunnelService)
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape {
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider {
private readonly _proxy: ExtHostTunnelServiceShape;
private elevateionRetry: boolean = false;
private portsAttributesProviders: Map<number, PortAttributesProviderSelector> = new Map();

constructor(
extHostContext: IExtHostContext,
Expand Down Expand Up @@ -50,6 +52,39 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
}));
}

private _alreadyRegistered: boolean = false;
async $registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise<void> {
this.portsAttributesProviders.set(providerHandle, selector);
if (!this._alreadyRegistered) {
this.remoteExplorerService.tunnelModel.addAttributesProvider(this);
this._alreadyRegistered = true;
}
}

async $unregisterPortsAttributesProvider(providerHandle: number): Promise<void> {
this.portsAttributesProviders.delete(providerHandle);
}

async providePortAttributes(ports: number[], pid: number | undefined, commandLine: string | undefined, token: CancellationToken): Promise<ProvidedPortAttributes[]> {
if (this.portsAttributesProviders.size === 0) {
return [];
}

// Check all the selectors to make sure it's worth going to the extension host.
const appropriateHandles = Array.from(this.portsAttributesProviders.entries()).filter(entry => {
const selector = entry[1];
const portRange = selector.portRange;
const portInRange = portRange ? ports.some(port => portRange[0] <= port && port < portRange[1]) : true;
const pidMatches = !selector.pid || (selector.pid === pid);
return portInRange || pidMatches;
}).map(entry => entry[0]);

if (appropriateHandles.length === 0) {
return [];
}
return this._proxy.$providePortAttributes(appropriateHandles, ports, pid, commandLine, token);
}

async $openTunnel(tunnelOptions: TunnelOptions, source: string): Promise<TunnelDto | undefined> {
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, false);
if (tunnel) {
Expand Down
5 changes: 5 additions & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables);
},
registerPortAttributesProvider: (portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider) => {
checkProposedApiEnabled(extension);
return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider);
},
registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => {
checkProposedApiEnabled(extension);
return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter);
Expand Down Expand Up @@ -1173,6 +1177,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
MarkdownString: extHostTypes.MarkdownString,
OverviewRulerLane: OverviewRulerLane,
ParameterInformation: extHostTypes.ParameterInformation,
PortAutoForwardAction: extHostTypes.PortAutoForwardAction,
Position: extHostTypes.Position,
ProcessExecution: extHostTypes.ProcessExecution,
ProgressLocation: extHostTypes.ProgressLocation,
Expand Down
10 changes: 9 additions & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import * as search from 'vs/workbench/services/search/common/search';
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, ProvidedPortAttributes } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
Expand Down Expand Up @@ -1048,6 +1048,11 @@ export enum CandidatePortSource {
Output = 2
}

export interface PortAttributesProviderSelector {
pid?: number;
portRange?: [number, number];
}

export interface MainThreadTunnelServiceShape extends IDisposable {
$openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise<TunnelDto | undefined>;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
Expand All @@ -1057,6 +1062,8 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
$setCandidateFilter(): Promise<void>;
$onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void>;
$setCandidatePortSource(source: CandidatePortSource): Promise<void>;
$registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise<void>;
$unregisterPortsAttributesProvider(providerHandle: number): Promise<void>;
}

export interface MainThreadTimelineShape extends IDisposable {
Expand Down Expand Up @@ -1885,6 +1892,7 @@ export interface ExtHostTunnelServiceShape {
$onDidTunnelsChange(): Promise<void>;
$registerCandidateFinder(enable: boolean): Promise<void>;
$applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]>;
$providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: CancellationToken): Promise<ProvidedPortAttributes[]>;
}

export interface ExtHostTimelineShape {
Expand Down
11 changes: 10 additions & 1 deletion src/vs/workbench/api/common/extHostTunnelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as vscode from 'vscode';
import { RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { ProvidedPortAttributes, RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
Expand Down Expand Up @@ -46,6 +46,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
getTunnels(): Promise<vscode.TunnelDescription[]>;
onDidChangeTunnels: vscode.Event<void>;
setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider): IDisposable;
}

export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IExtHostTunnelService');
Expand All @@ -71,6 +72,14 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
return { dispose: () => { } };
}
registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider) {
return { dispose: () => { } };
}

async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise<ProvidedPortAttributes[]> {
return [];
}

async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> { return undefined; }
async $closeTunnel(remote: { host: string, port: number }): Promise<void> { }
async $onDidTunnelsChange(): Promise<void> { }
Expand Down
8 changes: 8 additions & 0 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3369,3 +3369,11 @@ export enum WorkspaceTrustState {
Trusted = 1,
Unknown = 2
}

export enum PortAutoForwardAction {
Notify = 1,
OpenBrowser = 2,
OpenPreview = 3,
Silent = 4,
Ignore = 5
}
43 changes: 41 additions & 2 deletions src/vs/workbench/api/node/extHostTunnelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { MainThreadTunnelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { MainThreadTunnelServiceShape, MainContext, PortAttributesProviderSelector } from 'vs/workbench/api/common/extHost.protocol';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import type * as vscode from 'vscode';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
Expand All @@ -13,14 +13,16 @@ import { exec } from 'child_process';
import * as resources from 'vs/base/common/resources';
import * as fs from 'fs';
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, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { Event, Emitter } from 'vs/base/common/event';
import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
import { TunnelOptions, TunnelCreationOptions, ProvidedPortAttributes, ProvidedOnAutoForward } from 'vs/platform/remote/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 { ILogService } from 'vs/platform/log/common/log';
import { flatten } from 'vs/base/common/arrays';

class ExtensionTunnel implements vscode.Tunnel {
private _onDispose: Emitter<void> = new Emitter();
Expand Down Expand Up @@ -139,6 +141,9 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
onDidChangeTunnels: vscode.Event<void> = this._onDidChangeTunnels.event;
private _candidateFindingEnabled: boolean = false;

private _providerHandleCounter: number = 0;
private _portAttributesProviders: Map<number, { provider: vscode.PortAttributesProvider, selector: PortAttributesProviderSelector }> = new Map();

constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostInitDataService initData: IExtHostInitDataService,
Expand Down Expand Up @@ -173,6 +178,40 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
return Math.max(movingAverage * 20, 2000);
}

private nextPortAttributesProviderHandle(): number {
return this._providerHandleCounter++;
}

registerPortsAttributesProvider(portSelector: PortAttributesProviderSelector, 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 = await Promise.all(handles.map(handle => {
const provider = this._portAttributesProviders.get(handle);
if (!provider) {
return [];
}
return provider.provider.providePortAttributes(ports, pid, commandline, cancellationToken);
}));

const allAttributes = <vscode.PortAttributes[][]>providedAttributes.filter(attribute => !!attribute && attribute.length > 0);

return (allAttributes.length > 0) ? flatten(allAttributes).map(attributes => {
return {
autoForwardAction: <ProvidedOnAutoForward><unknown>attributes.autoForwardAction,
port: attributes.port
};
}) : [];
}

async $registerCandidateFinder(enable: boolean): Promise<void> {
if (enable && this._candidateFindingEnabled) {
// already enabled
Expand Down
Loading

0 comments on commit 0604478

Please sign in to comment.