Skip to content

Commit acf5d15

Browse files
authored
Expose tunnel factory API to non-resolvers (microsoft#188270)
1 parent 16ca37c commit acf5d15

File tree

10 files changed

+108
-22
lines changed

10 files changed

+108
-22
lines changed

src/vs/platform/tunnel/common/tunnel.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ export interface ITunnelProvider {
6464
forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined;
6565
}
6666

67+
export function isTunnelProvider(addressOrTunnelProvider: IAddressProvider | ITunnelProvider): addressOrTunnelProvider is ITunnelProvider {
68+
return !!(addressOrTunnelProvider as ITunnelProvider).forwardPort;
69+
}
70+
6771
export enum ProvidedOnAutoForward {
6872
Notify = 1,
6973
OpenBrowser = 2,
@@ -315,7 +319,8 @@ export abstract class AbstractTunnelService implements ITunnelService {
315319

316320
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded: boolean = false, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
317321
this.logService.trace(`ForwardedPorts: (TunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`);
318-
if (!addressProvider) {
322+
const addressOrTunnelProvider = this._tunnelProvider ?? addressProvider;
323+
if (!addressOrTunnelProvider) {
319324
return undefined;
320325
}
321326

@@ -332,7 +337,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
332337
return;
333338
}
334339

335-
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded, privacy, protocol);
340+
const resolvedTunnel = this.retainOrCreateTunnel(addressOrTunnelProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded, privacy, protocol);
336341
if (!resolvedTunnel) {
337342
this.logService.trace(`ForwardedPorts: (TunnelService) Tunnel was not created.`);
338343
return resolvedTunnel;
@@ -454,7 +459,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
454459

455460
public abstract isPortPrivileged(port: number): boolean;
456461

457-
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
462+
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
458463

459464
protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
460465
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel with provider ${remoteHost}:${remotePort} on local port ${localPort}.`);

src/vs/platform/tunnel/node/tunnelService.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
1414
import { ILogService } from 'vs/platform/log/common/log';
1515
import { IProductService } from 'vs/platform/product/common/productService';
1616
import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
17-
import { AbstractTunnelService, isAllInterfaces, ISharedTunnelsService as ISharedTunnelsService, isLocalhost, isPortPrivileged, ITunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel';
17+
import { AbstractTunnelService, isAllInterfaces, ISharedTunnelsService as ISharedTunnelsService, isLocalhost, isPortPrivileged, isTunnelProvider, ITunnelProvider, ITunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel';
1818
import { ISignService } from 'vs/platform/sign/common/sign';
1919
import { OS } from 'vs/base/common/platform';
2020
import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
@@ -168,21 +168,21 @@ export class BaseTunnelService extends AbstractTunnelService {
168168
return isPortPrivileged(port, this.defaultTunnelHost, OS, os.release());
169169
}
170170

171-
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
171+
protected retainOrCreateTunnel(addressOrTunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
172172
const existing = this.getTunnelFromMap(remoteHost, remotePort);
173173
if (existing) {
174174
++existing.refcount;
175175
return existing.value;
176176
}
177177

178-
if (this._tunnelProvider) {
179-
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
178+
if (isTunnelProvider(addressOrTunnelProvider)) {
179+
return this.createWithProvider(addressOrTunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
180180
} else {
181181
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
182182
const options: IConnectionOptions = {
183183
commit: this.productService.commit,
184184
quality: this.productService.quality,
185-
addressProvider,
185+
addressProvider: addressOrTunnelProvider,
186186
remoteSocketFactoryService: this.remoteSocketFactoryService,
187187
signService: this.signService,
188188
logService: this.logService,

src/vs/workbench/api/browser/mainThreadTunnelService.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
1818
import { CancellationToken } from 'vs/base/common/cancellation';
1919
import { Registry } from 'vs/platform/registry/common/platform';
2020
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
21+
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
22+
import { forwardedPortsViewEnabled } from 'vs/workbench/contrib/remote/browser/tunnelView';
2123

2224
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
2325
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider {
@@ -32,7 +34,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
3234
@INotificationService private readonly notificationService: INotificationService,
3335
@IConfigurationService private readonly configurationService: IConfigurationService,
3436
@ILogService private readonly logService: ILogService,
35-
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService
37+
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
38+
@IContextKeyService private readonly contextKeyService: IContextKeyService
3639
) {
3740
super();
3841
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService);
@@ -192,6 +195,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
192195
if (features) {
193196
this.tunnelService.setTunnelFeatures(features);
194197
}
198+
// At this point we clearly want the ports view/features since we have a tunnel factory
199+
this.contextKeyService.createKey(forwardedPortsViewEnabled.key, true);
195200
}
196201

197202
async $setCandidateFilter(): Promise<void> {

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
10881088
checkProposedApiEnabled(extension, 'portsAttributes');
10891089
return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider);
10901090
},
1091+
registerTunnelProvider: (tunnelProvider: vscode.TunnelProvider, information: vscode.TunnelInformation) => {
1092+
checkProposedApiEnabled(extension, 'tunnelFactory');
1093+
return extHostTunnelService.registerTunnelProvider(tunnelProvider, information);
1094+
},
10911095
registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => {
10921096
checkProposedApiEnabled(extension, 'timeline');
10931097
return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter);

src/vs/workbench/api/common/extHostTunnelService.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { CancellationTokenSource } from 'vs/base/common/cancellation';
67
import { Emitter } from 'vs/base/common/event';
78
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
89
import * as nls from 'vs/nls';
@@ -55,14 +56,15 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
5556
onDidChangeTunnels: vscode.Event<void>;
5657
setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
5758
registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): IDisposable;
59+
registerTunnelProvider(provider: vscode.TunnelProvider, information: vscode.TunnelInformation): Promise<IDisposable>;
5860
}
5961

6062
export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IExtHostTunnelService');
6163

6264
export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService {
6365
readonly _serviceBrand: undefined;
6466
protected readonly _proxy: MainThreadTunnelServiceShape;
65-
private _forwardPortProvider: ((tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable<vscode.Tunnel> | undefined) | undefined;
67+
private _forwardPortProvider: ((tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions, token?: vscode.CancellationToken) => Thenable<vscode.Tunnel | undefined> | undefined) | undefined;
6668
private _showCandidatePort: (host: string, port: number, detail: string) => Thenable<boolean> = () => { return Promise.resolve(true); };
6769
private _extensionTunnels: Map<string, Map<number, { tunnel: vscode.Tunnel; disposeListener: IDisposable }>> = new Map();
6870
private _onDidChangeTunnels: Emitter<void> = new Emitter<void>();
@@ -135,6 +137,27 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
135137

136138
async $registerCandidateFinder(_enable: boolean): Promise<void> { }
137139

140+
registerTunnelProvider(provider: vscode.TunnelProvider, information: vscode.TunnelInformation): Promise<IDisposable> {
141+
if (this._forwardPortProvider) {
142+
throw new Error('A tunnel provider has already been registered. Only the first tunnel provider to be registered will be used.');
143+
}
144+
this._forwardPortProvider = async (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => {
145+
const result = await provider.provideTunnel(tunnelOptions, tunnelCreationOptions, new CancellationTokenSource().token);
146+
return result ?? undefined;
147+
};
148+
149+
const tunnelFeatures = information.tunnelFeatures ? {
150+
elevation: !!information.tunnelFeatures?.elevation,
151+
privacyOptions: information.tunnelFeatures?.privacyOptions
152+
} : undefined;
153+
154+
this._proxy.$setTunnelProvider(tunnelFeatures);
155+
return Promise.resolve(toDisposable(() => {
156+
this._forwardPortProvider = undefined;
157+
this._proxy.$setTunnelProvider(undefined);
158+
}));
159+
}
160+
138161
async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
139162
// Do not wait for any of the proxy promises here.
140163
// It will delay startup and there is nothing that needs to be waited for.
@@ -201,11 +224,15 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
201224
if (this._forwardPortProvider) {
202225
try {
203226
this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Getting tunnel from provider.');
204-
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
227+
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions,);
205228
this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Got tunnel promise from provider.');
206229
if (providedPort !== undefined) {
207230
const tunnel = await providedPort;
208231
this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Successfully awaited tunnel from provider.');
232+
if (tunnel === undefined) {
233+
this.logService.error('ForwardedPorts: (ExtHostTunnelService) Resolved tunnel is undefined');
234+
return undefined;
235+
}
209236
if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) {
210237
this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map());
211238
}

src/vs/workbench/contrib/remote/browser/remoteExplorer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,15 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu
7676

7777
const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService);
7878

79-
if (this.environmentService.remoteAuthority && viewEnabled) {
79+
if (viewEnabled) {
8080
const viewContainer = await this.getViewContainer();
8181
const tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService, this.tunnelService), this.environmentService);
8282
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
8383
if (viewContainer) {
8484
this.remoteExplorerService.enablePortsFeatures();
8585
viewsRegistry.registerViews([tunnelPanelDescriptor!], viewContainer);
8686
}
87-
} else if (this.environmentService.remoteAuthority) {
87+
} else {
8888
this.contextKeyListener = this.contextKeyService.onDidChangeContext(e => {
8989
if (e.affectsSome(new Set(forwardedPortsViewEnabled.keys()))) {
9090
this.enableForwardedPortsView();

src/vs/workbench/services/extensions/common/extensionsApiProposals.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export const allApiProposals = Object.freeze({
9393
tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts',
9494
treeViewActiveItem: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts',
9595
treeViewReveal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts',
96+
tunnelFactory: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts',
9697
tunnels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts',
9798
windowActivity: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.windowActivity.d.ts',
9899
workspaceTrust: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts'

src/vs/workbench/services/tunnel/browser/tunnelService.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
88
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
99
import { ILogService } from 'vs/platform/log/common/log';
1010
import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection';
11-
import { AbstractTunnelService, ITunnelService, RemoteTunnel } from 'vs/platform/tunnel/common/tunnel';
11+
import { AbstractTunnelService, ITunnelProvider, ITunnelService, RemoteTunnel, isTunnelProvider } from 'vs/platform/tunnel/common/tunnel';
1212
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
1313

1414
export class TunnelService extends AbstractTunnelService {
@@ -24,15 +24,15 @@ export class TunnelService extends AbstractTunnelService {
2424
return false;
2525
}
2626

27-
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, _localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
27+
protected retainOrCreateTunnel(tunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, _localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
2828
const existing = this.getTunnelFromMap(remoteHost, remotePort);
2929
if (existing) {
3030
++existing.refcount;
3131
return existing.value;
3232
}
3333

34-
if (this._tunnelProvider) {
35-
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
34+
if (isTunnelProvider(tunnelProvider)) {
35+
return this.createWithProvider(tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
3636
}
3737
return undefined;
3838
}

src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ILogService } from 'vs/platform/log/common/log';
77
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
88
import { URI } from 'vs/base/common/uri';
99
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
10-
import { ITunnelService, AbstractTunnelService, RemoteTunnel, TunnelPrivacyId, isPortPrivileged } from 'vs/platform/tunnel/common/tunnel';
10+
import { ITunnelService, AbstractTunnelService, RemoteTunnel, TunnelPrivacyId, isPortPrivileged, ITunnelProvider, isTunnelProvider } from 'vs/platform/tunnel/common/tunnel';
1111
import { Disposable } from 'vs/base/common/lifecycle';
1212
import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection';
1313
import { ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedProcessTunnelService';
@@ -79,19 +79,19 @@ export class TunnelService extends AbstractTunnelService {
7979
return isPortPrivileged(port, this.defaultTunnelHost, OS, this._nativeWorkbenchEnvironmentService.os.release);
8080
}
8181

82-
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
82+
protected retainOrCreateTunnel(addressOrTunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
8383
const existing = this.getTunnelFromMap(remoteHost, remotePort);
8484
if (existing) {
8585
++existing.refcount;
8686
return existing.value;
8787
}
8888

89-
if (this._tunnelProvider) {
90-
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
89+
if (isTunnelProvider(addressOrTunnelProvider)) {
90+
return this.createWithProvider(addressOrTunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
9191
} else {
9292
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
9393

94-
const tunnel = this._createSharedProcessTunnel(addressProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded);
94+
const tunnel = this._createSharedProcessTunnel(addressOrTunnelProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded);
9595
this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.');
9696
this.addTunnelToMap(remoteHost, remotePort, tunnel);
9797
return tunnel;

0 commit comments

Comments
 (0)