parent
9c78fa40ca
commit
06044789bf
10 changed files with 255 additions and 25 deletions
|
@ -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';
|
||||
|
@ -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 };
|
||||
|
||||
|
|
33
src/vs/vscode.proposed.d.ts
vendored
33
src/vs/vscode.proposed.d.ts
vendored
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
@ -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>;
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
@ -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');
|
||||
|
@ -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> { }
|
||||
|
|
|
@ -3369,3 +3369,11 @@ export enum WorkspaceTrustState {
|
|||
Trusted = 1,
|
||||
Unknown = 2
|
||||
}
|
||||
|
||||
export enum PortAutoForwardAction {
|
||||
Notify = 1,
|
||||
OpenBrowser = 2,
|
||||
OpenPreview = 3,
|
||||
Silent = 4,
|
||||
Ignore = 5
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
|
|
@ -6,7 +6,7 @@ import * as nls from 'vs/nls';
|
|||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { Extensions, IViewContainersRegistry, IViewsRegistry, IViewsService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PortsAttributes, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TUNNEL_VIEW_CONTAINER_ID, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TUNNEL_VIEW_CONTAINER_ID, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanel, TunnelPanelDescriptor, TunnelViewModel, OpenPortInPreviewAction } from 'vs/workbench/contrib/remote/browser/tunnelView';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
|
@ -222,8 +222,7 @@ class OnAutoForwardedAction extends Disposable {
|
|||
private readonly openerService: IOpenerService,
|
||||
private readonly externalOpenerService: IExternalUriOpenerService,
|
||||
private readonly tunnelService: ITunnelService,
|
||||
private readonly hostService: IHostService,
|
||||
private readonly portsAttributes: PortsAttributes) {
|
||||
private readonly hostService: IHostService) {
|
||||
super();
|
||||
this.lastNotifyTime = new Date();
|
||||
this.lastNotifyTime.setFullYear(this.lastNotifyTime.getFullYear() - 1);
|
||||
|
@ -233,7 +232,7 @@ class OnAutoForwardedAction extends Disposable {
|
|||
this.doActionTunnels = tunnels;
|
||||
const tunnel = await this.portNumberHeuristicDelay();
|
||||
if (tunnel) {
|
||||
switch (this.portsAttributes.getAttributes(tunnel.tunnelRemotePort)?.onAutoForward) {
|
||||
switch ((await this.remoteExplorerService.tunnelModel.getAttributes([tunnel.tunnelRemotePort]))?.get(tunnel.tunnelRemotePort)?.onAutoForward) {
|
||||
case OnPortForward.OpenBrowser: {
|
||||
const address = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort);
|
||||
await OpenPortInBrowserAction.run(this.remoteExplorerService.tunnelModel, this.openerService, address);
|
||||
|
@ -380,7 +379,6 @@ class OutputAutomaticPortForwarding extends Disposable {
|
|||
private portsFeatures?: IDisposable;
|
||||
private urlFinder?: UrlFinder;
|
||||
private notifier: OnAutoForwardedAction;
|
||||
private portsAttributes: PortsAttributes;
|
||||
|
||||
constructor(
|
||||
private readonly terminalService: ITerminalService,
|
||||
|
@ -396,8 +394,7 @@ class OutputAutomaticPortForwarding extends Disposable {
|
|||
readonly privilegedOnly: boolean
|
||||
) {
|
||||
super();
|
||||
this.portsAttributes = new PortsAttributes(configurationService);
|
||||
this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService, this.portsAttributes);
|
||||
this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService);
|
||||
this._register(configurationService.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) {
|
||||
this.tryStartStopUrlFinder();
|
||||
|
@ -430,7 +427,7 @@ class OutputAutomaticPortForwarding extends Disposable {
|
|||
if (mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.detected, localUrl.host, localUrl.port)) {
|
||||
return;
|
||||
}
|
||||
if (this.portsAttributes.getAttributes(localUrl.port)?.onAutoForward === OnPortForward.Ignore) {
|
||||
if ((await this.remoteExplorerService.tunnelModel.getAttributes([localUrl.port]))?.get(localUrl.port)?.onAutoForward === OnPortForward.Ignore) {
|
||||
return;
|
||||
}
|
||||
if (this.privilegedOnly && !isPortPrivileged(localUrl.port, (await this.remoteAgentService.getEnvironment())?.os)) {
|
||||
|
@ -458,7 +455,6 @@ class ProcAutomaticPortForwarding extends Disposable {
|
|||
private notifier: OnAutoForwardedAction;
|
||||
private initialCandidates: Set<string> = new Set();
|
||||
private portsFeatures: IDisposable | undefined;
|
||||
private portsAttributes: PortsAttributes;
|
||||
|
||||
constructor(
|
||||
private readonly configurationService: IConfigurationService,
|
||||
|
@ -470,8 +466,7 @@ class ProcAutomaticPortForwarding extends Disposable {
|
|||
readonly hostService: IHostService
|
||||
) {
|
||||
super();
|
||||
this.portsAttributes = new PortsAttributes(configurationService);
|
||||
this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService, this.portsAttributes);
|
||||
this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService);
|
||||
this._register(configurationService.onDidChangeConfiguration(async (e) => {
|
||||
if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) {
|
||||
await this.startStopCandidateListener();
|
||||
|
@ -531,6 +526,7 @@ class ProcAutomaticPortForwarding extends Disposable {
|
|||
}
|
||||
|
||||
private async forwardCandidates(): Promise<RemoteTunnel[] | undefined> {
|
||||
const attributes = await this.remoteExplorerService.tunnelModel.getAttributes(this.remoteExplorerService.tunnelModel.candidates.map(candidate => candidate.port));
|
||||
const allTunnels = <RemoteTunnel[]>(await Promise.all(this.remoteExplorerService.tunnelModel.candidates.map(async (value) => {
|
||||
const address = makeAddress(value.host, value.port);
|
||||
if (this.initialCandidates.has(address)) {
|
||||
|
@ -543,7 +539,7 @@ class ProcAutomaticPortForwarding extends Disposable {
|
|||
if (mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.detected, value.host, value.port)) {
|
||||
return undefined;
|
||||
}
|
||||
if (this.portsAttributes.getAttributes(value.port)?.onAutoForward === OnPortForward.Ignore) {
|
||||
if (attributes?.get(value.port)?.onAutoForward === OnPortForward.Ignore) {
|
||||
return undefined;
|
||||
}
|
||||
const forwarded = await this.remoteExplorerService.forward(value, undefined, undefined, undefined, undefined, undefined, false);
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
|||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
|
||||
import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, PortAttributesProvider, ProvidedOnAutoForward, ProvidedPortAttributes, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IEditableData } from 'vs/workbench/common/views';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -19,6 +19,8 @@ import { isNumber, isObject, isString } from 'vs/base/common/types';
|
|||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
|
||||
export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('remoteExplorerService');
|
||||
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
|
||||
|
@ -241,6 +243,17 @@ export class PortsAttributes extends Disposable {
|
|||
});
|
||||
return attributes;
|
||||
}
|
||||
|
||||
static providedActionToAction(providedAction: ProvidedOnAutoForward | undefined) {
|
||||
switch (providedAction) {
|
||||
case ProvidedOnAutoForward.Notify: return OnPortForward.Notify;
|
||||
case ProvidedOnAutoForward.OpenBrowser: return OnPortForward.OpenBrowser;
|
||||
case ProvidedOnAutoForward.OpenPreview: return OnPortForward.OpenPreview;
|
||||
case ProvidedOnAutoForward.Silent: return OnPortForward.Silent;
|
||||
case ProvidedOnAutoForward.Ignore: return OnPortForward.Ignore;
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TunnelModel extends Disposable {
|
||||
|
@ -262,7 +275,9 @@ export class TunnelModel extends Disposable {
|
|||
private _onEnvironmentTunnelsSet: Emitter<void> = new Emitter();
|
||||
public onEnvironmentTunnelsSet: Event<void> = this._onEnvironmentTunnelsSet.event;
|
||||
private _environmentTunnelsSet: boolean = false;
|
||||
private portsAttributes: PortsAttributes;
|
||||
private configPortsAttributes: PortsAttributes;
|
||||
|
||||
private portAttributesProviders: PortAttributesProvider[] = [];
|
||||
|
||||
constructor(
|
||||
@ITunnelService private readonly tunnelService: ITunnelService,
|
||||
|
@ -274,7 +289,7 @@ export class TunnelModel extends Disposable {
|
|||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this.portsAttributes = new PortsAttributes(configurationService);
|
||||
this.configPortsAttributes = new PortsAttributes(configurationService);
|
||||
this.tunnelRestoreValue = this.getTunnelRestoreValue();
|
||||
this.forwarded = new Map();
|
||||
this.remoteTunnels = new Map();
|
||||
|
@ -328,7 +343,7 @@ export class TunnelModel extends Disposable {
|
|||
}
|
||||
}));
|
||||
|
||||
this._register(this.portsAttributes.onDidChangeAttributes(this.updateAttributes, this));
|
||||
this._register(this.configPortsAttributes.onDidChangeAttributes(this.updateAttributes, this));
|
||||
}
|
||||
|
||||
private makeTunnelPrivacy(isPublic: boolean) {
|
||||
|
@ -391,7 +406,8 @@ export class TunnelModel extends Disposable {
|
|||
getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; }
|
||||
} : undefined;
|
||||
|
||||
const attributes = this.portsAttributes.getAttributes(local !== undefined ? local : remote.port);
|
||||
const port = local !== undefined ? local : remote.port;
|
||||
const attributes = (await this.getAttributes([port]))?.get(port);
|
||||
|
||||
const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, local, (!elevateIfNeeded) ? attributes?.elevateIfNeeded : elevateIfNeeded, isPublic);
|
||||
if (tunnel && tunnel.localAddress) {
|
||||
|
@ -541,12 +557,75 @@ export class TunnelModel extends Disposable {
|
|||
private async updateAttributes() {
|
||||
// If the label changes in the attributes, we should update it.
|
||||
for (let forwarded of this.forwarded.values()) {
|
||||
const attributes = this.portsAttributes.getAttributes(forwarded.remotePort);
|
||||
const attributes = (await this.getAttributes([forwarded.remotePort], false))?.get(forwarded.remotePort);
|
||||
if (attributes && attributes.label && attributes.label !== forwarded.name) {
|
||||
await this.name(forwarded.remoteHost, forwarded.remotePort, attributes.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getAttributes(ports: number[], checkProviders: boolean = true): Promise<Map<number, Attributes> | undefined> {
|
||||
const configAttributes: Map<number, Attributes> = new Map();
|
||||
ports.forEach(port => {
|
||||
const attributes = this.configPortsAttributes.getAttributes(port);
|
||||
if (attributes) {
|
||||
configAttributes.set(port, attributes);
|
||||
}
|
||||
});
|
||||
if ((this.portAttributesProviders.length === 0) || !checkProviders) {
|
||||
return (configAttributes.size > 0) ? configAttributes : undefined;
|
||||
}
|
||||
|
||||
const matchingCandidates: Map<number, CandidatePort> = new Map();
|
||||
const pidToPortsMapping: Map<number, number[]> = new Map();
|
||||
ports.forEach(port => {
|
||||
const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces<CandidatePort>(this._candidates ?? new Map(), LOCALHOST_ADDRESSES[0], port);
|
||||
if (matchingCandidate) {
|
||||
matchingCandidates.set(port, matchingCandidate);
|
||||
if (!pidToPortsMapping.has(matchingCandidate.pid)) {
|
||||
pidToPortsMapping.set(matchingCandidate.pid, []);
|
||||
}
|
||||
pidToPortsMapping.get(matchingCandidate.pid)?.push(port);
|
||||
}
|
||||
});
|
||||
// Group calls to provide attributes by pid.
|
||||
const allProviderResults = await Promise.all(flatten(this.portAttributesProviders.map(provider => {
|
||||
return Array.from(pidToPortsMapping.entries()).map(entry => {
|
||||
const portGroup = entry[1];
|
||||
const matchingCandidate = matchingCandidates.get(portGroup[1]);
|
||||
return provider.providePortAttributes(portGroup,
|
||||
matchingCandidate?.pid, matchingCandidate?.detail, new CancellationTokenSource().token);
|
||||
});
|
||||
})));
|
||||
const providedAttributes: Map<number, ProvidedPortAttributes> = new Map();
|
||||
allProviderResults.forEach(attributes => attributes.forEach(attribute => {
|
||||
if (attribute) {
|
||||
providedAttributes.set(attribute.port, attribute);
|
||||
}
|
||||
}));
|
||||
|
||||
if (!configAttributes && !providedAttributes) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Merge. The config wins.
|
||||
const mergedAttributes: Map<number, Attributes> = new Map();
|
||||
ports.forEach(port => {
|
||||
const config = configAttributes.get(port);
|
||||
const provider = providedAttributes.get(port);
|
||||
mergedAttributes.set(port, {
|
||||
elevateIfNeeded: config?.elevateIfNeeded,
|
||||
label: config?.label,
|
||||
onAutoForward: config?.onAutoForward ?? PortsAttributes.providedActionToAction(provider?.autoForwardAction)
|
||||
});
|
||||
});
|
||||
|
||||
return mergedAttributes;
|
||||
}
|
||||
|
||||
addAttributesProvider(provider: PortAttributesProvider) {
|
||||
this.portAttributesProviders.push(provider);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CandidatePort {
|
||||
|
|
Loading…
Reference in a new issue