mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
remote: first cut at 'inline' remote resolvers
For web, it seems the most feasible direction for resolvers as we make existing remote extensions 'web enabled' is to allow them to run in the extension host. However, in no case will there just be a simple websocket we can connect to ordinarily. This PR implements a first cut at 'inline' resolvers where messaging is done in the extension host. I have not yet tested them on web, where I think some more wiring is needed to mirror desktop. Also, resolution of URLs is not in yet. I think for this we'd want to do some service-worker -based 'loopback' approach to run requests inline in the remote connection, similar to what I did for tunnels... Resolvers are not yet run in a dedicated extension host, but I think that should happen, at least on web where resolvers will always(?) be 'inline'. Most of the actual changes are genericizing places where we specified the "host" and "port" previously into an enum. Additionally, instead of having a single ISocketFactory, there's now a collection of them, which the extension host manager registers into when a managed resolution happens.
This commit is contained in:
parent
b242a8730c
commit
f5427eed53
|
@ -66,6 +66,11 @@
|
|||
"category": "Remote-TestResolver",
|
||||
"command": "vscode-testresolver.currentWindow"
|
||||
},
|
||||
{
|
||||
"title": "Connect to TestResolver in Current Window with Managed Connection",
|
||||
"category": "Remote-TestResolver",
|
||||
"command": "vscode-testresolver.currentWindowManaged"
|
||||
},
|
||||
{
|
||||
"title": "Show TestResolver Log",
|
||||
"category": "Remote-TestResolver",
|
||||
|
|
|
@ -27,7 +27,30 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
let connectionPaused = false;
|
||||
const connectionPausedEvent = new vscode.EventEmitter<boolean>();
|
||||
|
||||
function doResolve(_authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise<vscode.ResolvedAuthority> {
|
||||
function getTunnelFeatures(): vscode.TunnelInformation['tunnelFeatures'] {
|
||||
return {
|
||||
elevation: true,
|
||||
privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
|
||||
{
|
||||
id: 'public',
|
||||
label: 'Public',
|
||||
themeIcon: 'eye'
|
||||
},
|
||||
{
|
||||
id: 'other',
|
||||
label: 'Other',
|
||||
themeIcon: 'circuit-board'
|
||||
},
|
||||
{
|
||||
id: 'private',
|
||||
label: 'Private',
|
||||
themeIcon: 'eye-closed'
|
||||
}
|
||||
] : []
|
||||
};
|
||||
}
|
||||
|
||||
function doResolve(authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise<vscode.ResolverResult> {
|
||||
if (connectionPaused) {
|
||||
throw vscode.RemoteAuthorityResolverError.TemporarilyNotAvailable('Not available right now');
|
||||
}
|
||||
|
@ -150,7 +173,35 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
}
|
||||
});
|
||||
});
|
||||
return serverPromise.then(serverAddr => {
|
||||
|
||||
return serverPromise.then((serverAddr): Promise<vscode.ResolverResult> => {
|
||||
if (authority.includes('managed')) {
|
||||
console.log('Connecting via a managed authority');
|
||||
return Promise.resolve(new vscode.ManagedResolvedAuthority(async () => {
|
||||
const remoteSocket = net.createConnection({ port: serverAddr.port });
|
||||
const dataEmitter = new vscode.EventEmitter<Uint8Array>();
|
||||
const closeEmitter = new vscode.EventEmitter<Error | undefined>();
|
||||
const endEmitter = new vscode.EventEmitter<void>();
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
remoteSocket.on('data', d => dataEmitter.fire(d))
|
||||
.on('error', err => { rej(); closeEmitter.fire(err); })
|
||||
.on('close', () => endEmitter.fire())
|
||||
.on('end', () => endEmitter.fire())
|
||||
.on('connect', res);
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
onDidReceiveMessage: dataEmitter.event,
|
||||
onDidClose: closeEmitter.event,
|
||||
onDidEnd: endEmitter.event,
|
||||
dataHandler: d => remoteSocket.write(d),
|
||||
endHandler: () => remoteSocket.end(),
|
||||
};
|
||||
}, connectionToken));
|
||||
}
|
||||
|
||||
return new Promise<vscode.ResolvedAuthority>((res, _rej) => {
|
||||
const proxyServer = net.createServer(proxySocket => {
|
||||
outputChannel.appendLine(`Proxy connection accepted`);
|
||||
|
@ -228,28 +279,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
proxyServer.listen(0, '127.0.0.1', () => {
|
||||
const port = (<net.AddressInfo>proxyServer.address()).port;
|
||||
outputChannel.appendLine(`Going through proxy at port ${port}`);
|
||||
const r: vscode.ResolverResult = new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken);
|
||||
r.tunnelFeatures = {
|
||||
elevation: true,
|
||||
privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [
|
||||
{
|
||||
id: 'public',
|
||||
label: 'Public',
|
||||
themeIcon: 'eye'
|
||||
},
|
||||
{
|
||||
id: 'other',
|
||||
label: 'Other',
|
||||
themeIcon: 'circuit-board'
|
||||
},
|
||||
{
|
||||
id: 'private',
|
||||
label: 'Private',
|
||||
themeIcon: 'eye-closed'
|
||||
}
|
||||
] : []
|
||||
};
|
||||
res(r);
|
||||
res(new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken));
|
||||
});
|
||||
context.subscriptions.push({
|
||||
dispose: () => {
|
||||
|
@ -264,12 +294,16 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
async getCanonicalURI(uri: vscode.Uri): Promise<vscode.Uri> {
|
||||
return vscode.Uri.file(uri.path);
|
||||
},
|
||||
resolve(_authority: string): Thenable<vscode.ResolvedAuthority> {
|
||||
resolve(_authority: string): Thenable<vscode.ResolverResult> {
|
||||
return vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'Open TestResolver Remote ([details](command:vscode-testresolver.showLog))',
|
||||
cancellable: false
|
||||
}, (progress) => doResolve(_authority, progress));
|
||||
}, async (progress) => {
|
||||
const rr = await doResolve(_authority, progress);
|
||||
rr.tunnelFeatures = getTunnelFeatures();
|
||||
return rr;
|
||||
});
|
||||
},
|
||||
tunnelFactory,
|
||||
showCandidatePort
|
||||
|
@ -282,6 +316,9 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindow', () => {
|
||||
return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+test', reuseWindow: true });
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindowManaged', () => {
|
||||
return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+managed', reuseWindow: true });
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.newWindowWithError', () => {
|
||||
return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+error' });
|
||||
}));
|
||||
|
|
|
@ -1406,6 +1406,11 @@ export class IntervalCounter {
|
|||
|
||||
export type ValueCallback<T = unknown> = (value: T | Promise<T>) => void;
|
||||
|
||||
const enum DeferredOutcome {
|
||||
Resolved,
|
||||
Rejected
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a promise whose resolution or rejection can be controlled imperatively.
|
||||
*/
|
||||
|
@ -1413,19 +1418,22 @@ export class DeferredPromise<T> {
|
|||
|
||||
private completeCallback!: ValueCallback<T>;
|
||||
private errorCallback!: (err: unknown) => void;
|
||||
private rejected = false;
|
||||
private resolved = false;
|
||||
private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T };
|
||||
|
||||
public get isRejected() {
|
||||
return this.rejected;
|
||||
return this.outcome?.outcome === DeferredOutcome.Rejected;
|
||||
}
|
||||
|
||||
public get isResolved() {
|
||||
return this.resolved;
|
||||
return this.outcome?.outcome === DeferredOutcome.Resolved;
|
||||
}
|
||||
|
||||
public get isSettled() {
|
||||
return this.rejected || this.resolved;
|
||||
return !!this.outcome;
|
||||
}
|
||||
|
||||
public get value() {
|
||||
return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined;
|
||||
}
|
||||
|
||||
public readonly p: Promise<T>;
|
||||
|
@ -1440,7 +1448,7 @@ export class DeferredPromise<T> {
|
|||
public complete(value: T) {
|
||||
return new Promise<void>(resolve => {
|
||||
this.completeCallback(value);
|
||||
this.resolved = true;
|
||||
this.outcome = { outcome: DeferredOutcome.Resolved, value };
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
@ -1448,17 +1456,13 @@ export class DeferredPromise<T> {
|
|||
public error(err: unknown) {
|
||||
return new Promise<void>(resolve => {
|
||||
this.errorCallback(err);
|
||||
this.rejected = true;
|
||||
this.outcome = { outcome: DeferredOutcome.Rejected, value: err };
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
new Promise<void>(resolve => {
|
||||
this.errorCallback(new CancellationError());
|
||||
this.rejected = true;
|
||||
resolve();
|
||||
});
|
||||
return this.error(new CancellationError());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import * as streams from 'vs/base/common/stream';
|
||||
|
||||
declare const Buffer: any;
|
||||
|
||||
const hasBuffer = (typeof Buffer !== 'undefined');
|
||||
const indexOfTable = new Lazy(() => new Uint8Array(256));
|
||||
|
||||
let textEncoder: TextEncoder | null;
|
||||
let textDecoder: TextDecoder | null;
|
||||
|
@ -169,6 +171,52 @@ export class VSBuffer {
|
|||
writeUInt8(value: number, offset: number): void {
|
||||
writeUInt8(this.buffer, value, offset);
|
||||
}
|
||||
|
||||
indexOf(subarray: VSBuffer | Uint8Array) {
|
||||
const needle = subarray instanceof VSBuffer ? subarray.buffer : subarray;
|
||||
const needleLen = needle.byteLength;
|
||||
const haystack = this.buffer;
|
||||
const haystackLen = haystack.byteLength;
|
||||
|
||||
if (needleLen === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (needleLen === 1) {
|
||||
return haystack.indexOf(needle[0]);
|
||||
}
|
||||
|
||||
if (needleLen > haystackLen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// find index of the subarray using boyer-moore-horspool algorithm
|
||||
const table = indexOfTable.value;
|
||||
table.fill(needle.length);
|
||||
for (let i = 0; i < needle.length; i++) {
|
||||
table[needle[i]] = needle.length - i - 1;
|
||||
}
|
||||
|
||||
let i = needle.length - 1;
|
||||
let j = i;
|
||||
let result = -1;
|
||||
while (i < haystackLen) {
|
||||
if (haystack[i] === needle[j]) {
|
||||
if (j === 0) {
|
||||
result = i;
|
||||
break;
|
||||
}
|
||||
|
||||
i--;
|
||||
j--;
|
||||
} else {
|
||||
i += Math.max(needle.length - j, table[haystack[i]]);
|
||||
j = needle.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function readUInt16LE(source: Uint8Array, offset: number): number {
|
||||
|
|
|
@ -413,6 +413,22 @@ suite('Buffer', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('indexOf', () => {
|
||||
const haystack = VSBuffer.fromString('abcaabbccaaabbbccc');
|
||||
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('')), 0);
|
||||
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('a'.repeat(100))), -1);
|
||||
|
||||
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('a')), 0);
|
||||
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('c')), 2);
|
||||
|
||||
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('abcaa')), 0);
|
||||
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('caaab')), 8);
|
||||
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('ccc')), 15);
|
||||
|
||||
assert.strictEqual(haystack.indexOf(VSBuffer.fromString('cccb')), -1);
|
||||
|
||||
});
|
||||
|
||||
suite('base64', () => {
|
||||
/*
|
||||
Generated with:
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
|||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ISocket, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, WebSocketMessagingPassing } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
|
||||
export interface IWebSocketFactory {
|
||||
create(url: string, debugLabel: string): IWebSocket;
|
||||
|
@ -265,14 +265,14 @@ class BrowserSocket implements ISocket {
|
|||
}
|
||||
|
||||
|
||||
export class BrowserSocketFactory implements ISocketFactory {
|
||||
export class BrowserSocketFactory implements ISocketFactory<WebSocketMessagingPassing> {
|
||||
private readonly _webSocketFactory: IWebSocketFactory;
|
||||
|
||||
constructor(webSocketFactory: IWebSocketFactory | null | undefined) {
|
||||
this._webSocketFactory = webSocketFactory || defaultWebSocketFactory;
|
||||
}
|
||||
|
||||
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
|
||||
connect({ host, port }: WebSocketMessagingPassing, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
|
||||
const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws');
|
||||
const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel);
|
||||
const errorListener = socket.onError((err) => callback(err, undefined));
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DeferredPromise } from 'vs/base/common/async';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { RemoteAuthorities } from 'vs/base/common/network';
|
||||
|
@ -11,7 +13,7 @@ import { StopWatch } from 'vs/base/common/stopwatch';
|
|||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteAuthorityResolverService, IRemoteConnectionData, MessagePassingType, ResolvedAuthority, ResolvedOptions, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts';
|
||||
|
||||
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
|
||||
|
@ -21,12 +23,14 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
private readonly _onDidChangeConnectionData = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeConnectionData = this._onDidChangeConnectionData.event;
|
||||
|
||||
private readonly _promiseCache = new Map<string, Promise<ResolverResult>>();
|
||||
private readonly _resolveAuthorityRequests = new Map<string, DeferredPromise<ResolverResult>>();
|
||||
private readonly _cache = new Map<string, ResolverResult>();
|
||||
private readonly _connectionToken: Promise<string> | string | undefined;
|
||||
private readonly _connectionTokens: Map<string, string>;
|
||||
private readonly _isWorkbenchOptionsBasedResolution: boolean;
|
||||
|
||||
constructor(
|
||||
isWorkbenchOptionsBasedResolution: boolean,
|
||||
connectionToken: Promise<string> | string | undefined,
|
||||
resourceUriProvider: ((uri: URI) => URI) | undefined,
|
||||
@IProductService productService: IProductService,
|
||||
|
@ -35,6 +39,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
super();
|
||||
this._connectionToken = connectionToken;
|
||||
this._connectionTokens = new Map<string, string>();
|
||||
this._isWorkbenchOptionsBasedResolution = isWorkbenchOptionsBasedResolution;
|
||||
if (resourceUriProvider) {
|
||||
RemoteAuthorities.setDelegate(resourceUriProvider);
|
||||
}
|
||||
|
@ -42,15 +47,20 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
}
|
||||
|
||||
async resolveAuthority(authority: string): Promise<ResolverResult> {
|
||||
let result = this._promiseCache.get(authority);
|
||||
let result = this._resolveAuthorityRequests.get(authority);
|
||||
if (!result) {
|
||||
result = this._doResolveAuthority(authority);
|
||||
this._promiseCache.set(authority, result);
|
||||
result = new DeferredPromise<ResolverResult>();
|
||||
this._resolveAuthorityRequests.set(authority, result);
|
||||
if (this._isWorkbenchOptionsBasedResolution) {
|
||||
this._doResolveAuthority(authority).then(v => result!.complete(v), (err) => result!.error(err));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
return result.p;
|
||||
}
|
||||
|
||||
async getCanonicalURI(uri: URI): Promise<URI> {
|
||||
// todo@connor4312 make this work for web
|
||||
return uri;
|
||||
}
|
||||
|
||||
|
@ -61,8 +71,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
const resolverResult = this._cache.get(authority)!;
|
||||
const connectionToken = this._connectionTokens.get(authority) || resolverResult.authority.connectionToken;
|
||||
return {
|
||||
host: resolverResult.authority.host,
|
||||
port: resolverResult.authority.port,
|
||||
connectTo: resolverResult.authority.messaging,
|
||||
connectionToken: connectionToken
|
||||
};
|
||||
}
|
||||
|
@ -77,20 +86,42 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
this._logService.info(`Resolved connection token (${authorityPrefix}) after ${sw.elapsed()} ms`);
|
||||
const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80);
|
||||
const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort);
|
||||
const result: ResolverResult = { authority: { authority, host: host, port: port, connectionToken } };
|
||||
RemoteAuthorities.set(authority, result.authority.host, result.authority.port);
|
||||
const result: ResolverResult = { authority: { authority, messaging: { type: MessagePassingType.WebSocket, host: host, port: port }, connectionToken } };
|
||||
RemoteAuthorities.set(authority, host, port);
|
||||
this._cache.set(authority, result);
|
||||
this._onDidChangeConnectionData.fire();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
_clearResolvedAuthority(authority: string): void {
|
||||
if (this._resolveAuthorityRequests.has(authority)) {
|
||||
this._resolveAuthorityRequests.get(authority)!.cancel();
|
||||
this._resolveAuthorityRequests.delete(authority);
|
||||
}
|
||||
}
|
||||
|
||||
_setResolvedAuthority(resolvedAuthority: ResolvedAuthority) {
|
||||
_setResolvedAuthority(resolvedAuthority: ResolvedAuthority, options?: ResolvedOptions): void {
|
||||
if (this._resolveAuthorityRequests.has(resolvedAuthority.authority)) {
|
||||
const request = this._resolveAuthorityRequests.get(resolvedAuthority.authority)!;
|
||||
if (resolvedAuthority.messaging.type === MessagePassingType.WebSocket) {
|
||||
// todo@connor4312 need to implement some kind of loopback for ext host based messaging
|
||||
RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.messaging.host, resolvedAuthority.messaging.port);
|
||||
}
|
||||
if (resolvedAuthority.connectionToken) {
|
||||
RemoteAuthorities.setConnectionToken(resolvedAuthority.authority, resolvedAuthority.connectionToken);
|
||||
}
|
||||
request.complete({ authority: resolvedAuthority, options });
|
||||
this._onDidChangeConnectionData.fire();
|
||||
}
|
||||
}
|
||||
|
||||
_setResolvedAuthorityError(authority: string, err: any): void {
|
||||
if (this._resolveAuthorityRequests.has(authority)) {
|
||||
const request = this._resolveAuthorityRequests.get(authority)!;
|
||||
// Avoid that this error makes it to telemetry
|
||||
request.error(errors.ErrorNoTelemetry.fromError(err));
|
||||
}
|
||||
}
|
||||
|
||||
_setAuthorityConnectionToken(authority: string, connectionToken: string): void {
|
||||
|
|
26
src/vs/platform/remote/common/managedSocket.ts
Normal file
26
src/vs/platform/remote/common/managedSocket.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer, encodeBase64 } from 'vs/base/common/buffer';
|
||||
|
||||
export const makeRawSocketHeaders = (path: string, query: string, deubgLabel: string) => {
|
||||
// https://tools.ietf.org/html/rfc6455#section-4
|
||||
const buffer = new Uint8Array(16);
|
||||
for (let i = 0; i < 16; i++) {
|
||||
buffer[i] = Math.round(Math.random() * 256);
|
||||
}
|
||||
const nonce = encodeBase64(VSBuffer.wrap(buffer));
|
||||
|
||||
const headers = [
|
||||
`GET ws://localhost${path}?${query}&skipWebSocketFrames=true HTTP/1.1`,
|
||||
`Connection: Upgrade`,
|
||||
`Upgrade: websocket`,
|
||||
`Sec-WebSocket-Key: ${nonce}`
|
||||
];
|
||||
|
||||
return headers.join('\r\n') + '\r\n\r\n';
|
||||
};
|
||||
|
||||
export const socketRawEndHeaderSequence = VSBuffer.fromString('\r\n\r\n');
|
|
@ -16,7 +16,7 @@ import { IIPCLogger } from 'vs/base/parts/ipc/common/ipc';
|
|||
import { Client, ConnectionHealth, ISocket, PersistentProtocol, ProtocolConstants, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { RemoteAuthorityResolverError, ResolvedAuthorityMessagePassing } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
|
||||
|
@ -71,15 +71,14 @@ export interface OKMessage {
|
|||
export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage;
|
||||
|
||||
|
||||
interface ISimpleConnectionOptions {
|
||||
interface ISimpleConnectionOptions<T = unknown> {
|
||||
commit: string | undefined;
|
||||
quality: string | undefined;
|
||||
host: string;
|
||||
port: number;
|
||||
connectTo: T;
|
||||
connectionToken: string | undefined;
|
||||
reconnectionToken: string;
|
||||
reconnectionProtocol: PersistentProtocol | null;
|
||||
socketFactory: ISocketFactory;
|
||||
socketFactory: ISocketFactory<T>;
|
||||
signService: ISignService;
|
||||
logService: ILogService;
|
||||
}
|
||||
|
@ -88,8 +87,8 @@ export interface IConnectCallback {
|
|||
(err: any | undefined, socket: ISocket | undefined): void;
|
||||
}
|
||||
|
||||
export interface ISocketFactory {
|
||||
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void;
|
||||
export interface ISocketFactory<T> {
|
||||
connect(connectTo: T, path: string, query: string, debugLabel: string, callback: IConnectCallback): void;
|
||||
}
|
||||
|
||||
function createTimeoutCancellation(millis: number): CancellationToken {
|
||||
|
@ -192,12 +191,12 @@ function readOneControlMessage<T>(protocol: PersistentProtocol, timeoutCancellat
|
|||
return result.promise;
|
||||
}
|
||||
|
||||
function createSocket(logService: ILogService, socketFactory: ISocketFactory, host: string, port: number, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
|
||||
function createSocket<T>(logService: ILogService, socketFactory: ISocketFactory<T>, connectTo: T, path: string, query: string, debugConnectionType: string, debugLabel: string, timeoutCancellationToken: CancellationToken): Promise<ISocket> {
|
||||
const result = new PromiseWithTimeout<ISocket>(timeoutCancellationToken);
|
||||
const sw = StopWatch.create(false);
|
||||
logService.info(`Creating a socket (${debugLabel})...`);
|
||||
performance.mark(`code/willCreateSocket/${debugConnectionType}`);
|
||||
socketFactory.connect(host, port, path, query, debugLabel, (err: any, socket: ISocket | undefined) => {
|
||||
socketFactory.connect(connectTo, path, query, debugLabel, (err: any, socket: ISocket | undefined) => {
|
||||
if (result.didTimeout) {
|
||||
performance.mark(`code/didCreateSocketError/${debugConnectionType}`);
|
||||
logService.info(`Creating a socket (${debugLabel}) finished after ${sw.elapsed()} ms, but this is too late and has timed out already.`);
|
||||
|
@ -237,14 +236,14 @@ function raceWithTimeoutCancellation<T>(promise: Promise<T>, timeoutCancellation
|
|||
return result.promise;
|
||||
}
|
||||
|
||||
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean }> {
|
||||
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions<unknown>, connectionType: ConnectionType, args: any | undefined, timeoutCancellationToken: CancellationToken): Promise<{ protocol: PersistentProtocol; ownsProtocol: boolean }> {
|
||||
const logPrefix = connectLogPrefix(options, connectionType);
|
||||
|
||||
options.logService.trace(`${logPrefix} 1/6. invoking socketFactory.connect().`);
|
||||
|
||||
let socket: ISocket;
|
||||
try {
|
||||
socket = await createSocket(options.logService, options.socketFactory, options.host, options.port, getRemoteServerRootPath(options), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, connectionTypeToString(connectionType), `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken);
|
||||
socket = await createSocket(options.logService, options.socketFactory, options.connectTo, getRemoteServerRootPath(options), `reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`, connectionTypeToString(connectionType), `renderer-${connectionTypeToString(connectionType)}-${options.reconnectionToken}`, timeoutCancellationToken);
|
||||
} catch (error) {
|
||||
options.logService.error(`${logPrefix} socketFactory.connect() failed or timed out. Error:`);
|
||||
options.logService.error(error);
|
||||
|
@ -389,23 +388,22 @@ async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, sta
|
|||
return protocol;
|
||||
}
|
||||
|
||||
export interface IConnectionOptions {
|
||||
export interface IConnectionOptions<T = unknown> {
|
||||
commit: string | undefined;
|
||||
quality: string | undefined;
|
||||
socketFactory: ISocketFactory;
|
||||
addressProvider: IAddressProvider;
|
||||
socketFactory: ISocketFactory<T>;
|
||||
addressProvider: IAddressProvider<T>;
|
||||
signService: ISignService;
|
||||
logService: ILogService;
|
||||
ipcLogger: IIPCLogger | null;
|
||||
}
|
||||
|
||||
async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise<ISimpleConnectionOptions> {
|
||||
const { host, port, connectionToken } = await options.addressProvider.getAddress();
|
||||
async function resolveConnectionOptions<T>(options: IConnectionOptions<T>, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise<ISimpleConnectionOptions<T>> {
|
||||
const { connectTo, connectionToken } = await options.addressProvider.getAddress();
|
||||
return {
|
||||
commit: options.commit,
|
||||
quality: options.quality,
|
||||
host: host,
|
||||
port: port,
|
||||
connectTo,
|
||||
connectionToken: connectionToken,
|
||||
reconnectionToken: reconnectionToken,
|
||||
reconnectionProtocol: reconnectionProtocol,
|
||||
|
@ -415,14 +413,13 @@ async function resolveConnectionOptions(options: IConnectionOptions, reconnectio
|
|||
};
|
||||
}
|
||||
|
||||
export interface IAddress {
|
||||
host: string;
|
||||
port: number;
|
||||
export interface IAddress<T = ResolvedAuthorityMessagePassing> {
|
||||
connectTo: T;
|
||||
connectionToken: string | undefined;
|
||||
}
|
||||
|
||||
export interface IAddressProvider {
|
||||
getAddress(): Promise<IAddress>;
|
||||
export interface IAddressProvider<T = ResolvedAuthorityMessagePassing> {
|
||||
getAddress(): Promise<IAddress<T>>;
|
||||
}
|
||||
|
||||
export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise<ManagementPersistentConnection> {
|
||||
|
@ -448,7 +445,7 @@ export async function connectRemoteAgentExtensionHost(options: IConnectionOption
|
|||
/**
|
||||
* Will attempt to connect 5 times. If it fails 5 consecutive times, it will give up.
|
||||
*/
|
||||
async function createInitialConnection<T extends PersistentConnection>(options: IConnectionOptions, connectionFactory: (simpleOptions: ISimpleConnectionOptions) => Promise<T>): Promise<T> {
|
||||
async function createInitialConnection<T extends PersistentConnection, O>(options: IConnectionOptions<O>, connectionFactory: (simpleOptions: ISimpleConnectionOptions<O>) => Promise<T>): Promise<T> {
|
||||
const MAX_ATTEMPTS = 5;
|
||||
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
|
@ -691,7 +688,7 @@ export abstract class PersistentConnection extends Disposable {
|
|||
this._onDidStateChange.fire(new ReconnectionRunningEvent(this.reconnectionToken, this.protocol.getMillisSinceLastIncomingData(), attempt + 1));
|
||||
this._options.logService.info(`${logPrefix} resolving connection...`);
|
||||
const simpleOptions = await resolveConnectionOptions(this._options, this.reconnectionToken, this.protocol);
|
||||
this._options.logService.info(`${logPrefix} connecting to ${simpleOptions.host}:${simpleOptions.port}...`);
|
||||
this._options.logService.info(`${logPrefix} connecting to ${simpleOptions.connectTo}...`);
|
||||
await this._reconnect(simpleOptions, createTimeoutCancellation(RECONNECT_TIMEOUT));
|
||||
this._options.logService.info(`${logPrefix} reconnected!`);
|
||||
this._onDidStateChange.fire(new ConnectionGainEvent(this.reconnectionToken, this.protocol.getMillisSinceLastIncomingData(), attempt + 1));
|
||||
|
@ -832,7 +829,7 @@ function commonLogPrefix(connectionType: ConnectionType, reconnectionToken: stri
|
|||
}
|
||||
|
||||
function connectLogPrefix(options: ISimpleConnectionOptions, connectionType: ConnectionType): string {
|
||||
return `${commonLogPrefix(connectionType, options.reconnectionToken, !!options.reconnectionProtocol)}[${options.host}:${options.port}]`;
|
||||
return `${commonLogPrefix(connectionType, options.reconnectionToken, !!options.reconnectionProtocol)}[${options.connectTo}]`;
|
||||
}
|
||||
|
||||
function logElapsed(startTime: number): string {
|
||||
|
|
|
@ -10,10 +10,29 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
|||
|
||||
export const IRemoteAuthorityResolverService = createDecorator<IRemoteAuthorityResolverService>('remoteAuthorityResolverService');
|
||||
|
||||
export const enum MessagePassingType {
|
||||
WebSocket,
|
||||
Managed
|
||||
}
|
||||
|
||||
export interface ManagedMessagingPassing {
|
||||
type: MessagePassingType.Managed;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface WebSocketMessagingPassing {
|
||||
type: MessagePassingType.WebSocket;
|
||||
host: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export type ResolvedAuthorityMessagePassing = WebSocketMessagingPassing | ManagedMessagingPassing;
|
||||
|
||||
export type MessagePassingOfType<T extends MessagePassingType> = ResolvedAuthorityMessagePassing & { type: T };
|
||||
|
||||
export interface ResolvedAuthority {
|
||||
readonly authority: string;
|
||||
readonly host: string;
|
||||
readonly port: number;
|
||||
readonly messaging: ResolvedAuthorityMessagePassing;
|
||||
readonly connectionToken: string | undefined;
|
||||
}
|
||||
|
||||
|
@ -50,8 +69,7 @@ export interface ResolverResult {
|
|||
}
|
||||
|
||||
export interface IRemoteConnectionData {
|
||||
host: string;
|
||||
port: number;
|
||||
connectTo: ResolvedAuthorityMessagePassing;
|
||||
connectionToken: string | undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mapFind } from 'vs/base/common/arrays';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { MessagePassingOfType, MessagePassingType, ResolvedAuthorityMessagePassing } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
|
||||
export const IRemoteSocketFactoryCollection = createDecorator<IRemoteSocketFactoryCollection>('remoteSocketFactoryCollection');
|
||||
|
||||
export interface IRemoteSocketFactoryCollection {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Register a socket factory for the given message passing type
|
||||
* @param type passing type to register for
|
||||
* @param factory function that returns the socket factory, or undefined if
|
||||
* it can't handle the data.
|
||||
*/
|
||||
register<T extends MessagePassingType>(
|
||||
type: T,
|
||||
factory: (messagePassing: MessagePassingOfType<T>) => ISocketFactory<MessagePassingOfType<T>> | undefined
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Gets a socket factory for the given message passing data.
|
||||
*/
|
||||
create<T extends ResolvedAuthorityMessagePassing>(messagePassing: T): ISocketFactory<T> | undefined;
|
||||
}
|
||||
|
||||
export class RemoteSocketFactoryCollection implements IRemoteSocketFactoryCollection {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly factories: { [T in MessagePassingType]?: ((messagePassing: MessagePassingOfType<T>) => ISocketFactory<MessagePassingOfType<T>> | undefined)[] } = {};
|
||||
|
||||
|
||||
public register<T extends MessagePassingType>(
|
||||
type: T,
|
||||
factory: (messagePassing: MessagePassingOfType<T>) => ISocketFactory<MessagePassingOfType<T>> | undefined
|
||||
): void {
|
||||
this.factories[type] ??= [];
|
||||
this.factories[type]!.push(factory);
|
||||
}
|
||||
|
||||
public create<T extends ResolvedAuthorityMessagePassing>(messagePassing: T): ISocketFactory<T> | undefined {
|
||||
return mapFind(
|
||||
(this.factories[messagePassing.type] || []) as ((messagePassing: T) => ISocketFactory<T> | undefined)[],
|
||||
factory => factory(messagePassing),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,41 +3,16 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
//
|
||||
import { DeferredPromise } from 'vs/base/common/async';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { RemoteAuthorities } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolvedOptions, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteAuthorityResolverService, IRemoteConnectionData, MessagePassingType, ResolvedAuthority, ResolvedOptions, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
||||
|
||||
class PendingPromise<I, R> {
|
||||
public readonly promise: Promise<R>;
|
||||
public readonly input: I;
|
||||
public result: R | null;
|
||||
private _resolve!: (value: R) => void;
|
||||
private _reject!: (err: any) => void;
|
||||
|
||||
constructor(request: I) {
|
||||
this.input = request;
|
||||
this.promise = new Promise<R>((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
});
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
resolve(result: R): void {
|
||||
this.result = result;
|
||||
this._resolve(this.result);
|
||||
}
|
||||
|
||||
reject(err: any): void {
|
||||
this._reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
@ -45,16 +20,16 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
private readonly _onDidChangeConnectionData = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeConnectionData = this._onDidChangeConnectionData.event;
|
||||
|
||||
private readonly _resolveAuthorityRequests: Map<string, PendingPromise<string, ResolverResult>>;
|
||||
private readonly _resolveAuthorityRequests: Map<string, DeferredPromise<ResolverResult>>;
|
||||
private readonly _connectionTokens: Map<string, string>;
|
||||
private readonly _canonicalURIRequests: Map<string, PendingPromise<URI, URI>>;
|
||||
private readonly _canonicalURIRequests: Map<string, DeferredPromise<URI>>;
|
||||
private _canonicalURIProvider: ((uri: URI) => Promise<URI>) | null;
|
||||
|
||||
constructor(@IProductService productService: IProductService) {
|
||||
super();
|
||||
this._resolveAuthorityRequests = new Map<string, PendingPromise<string, ResolverResult>>();
|
||||
this._resolveAuthorityRequests = new Map<string, DeferredPromise<ResolverResult>>();
|
||||
this._connectionTokens = new Map<string, string>();
|
||||
this._canonicalURIRequests = new Map<string, PendingPromise<URI, URI>>();
|
||||
this._canonicalURIRequests = new Map<string, DeferredPromise<URI>>();
|
||||
this._canonicalURIProvider = null;
|
||||
|
||||
RemoteAuthorities.setServerRootPath(getRemoteServerRootPath(productService));
|
||||
|
@ -62,19 +37,19 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
|
||||
resolveAuthority(authority: string): Promise<ResolverResult> {
|
||||
if (!this._resolveAuthorityRequests.has(authority)) {
|
||||
this._resolveAuthorityRequests.set(authority, new PendingPromise<string, ResolverResult>(authority));
|
||||
this._resolveAuthorityRequests.set(authority, new DeferredPromise());
|
||||
}
|
||||
return this._resolveAuthorityRequests.get(authority)!.promise;
|
||||
return this._resolveAuthorityRequests.get(authority)!.p;
|
||||
}
|
||||
|
||||
async getCanonicalURI(uri: URI): Promise<URI> {
|
||||
const key = uri.toString();
|
||||
if (!this._canonicalURIRequests.has(key)) {
|
||||
const request = new PendingPromise<URI, URI>(uri);
|
||||
this._canonicalURIProvider?.(request.input).then((uri) => request.resolve(uri), (err) => request.reject(err));
|
||||
const request = new DeferredPromise<URI>();
|
||||
this._canonicalURIProvider?.(uri).then((uri) => request.complete(uri), (err) => request.error(err));
|
||||
this._canonicalURIRequests.set(key, request);
|
||||
}
|
||||
return this._canonicalURIRequests.get(key)!.promise;
|
||||
return this._canonicalURIRequests.get(key)!.p;
|
||||
}
|
||||
|
||||
getConnectionData(authority: string): IRemoteConnectionData | null {
|
||||
|
@ -82,20 +57,19 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
return null;
|
||||
}
|
||||
const request = this._resolveAuthorityRequests.get(authority)!;
|
||||
if (!request.result) {
|
||||
if (!request.isResolved) {
|
||||
return null;
|
||||
}
|
||||
const connectionToken = this._connectionTokens.get(authority);
|
||||
return {
|
||||
host: request.result.authority.host,
|
||||
port: request.result.authority.port,
|
||||
connectTo: request.value!.authority.messaging,
|
||||
connectionToken: connectionToken
|
||||
};
|
||||
}
|
||||
|
||||
_clearResolvedAuthority(authority: string): void {
|
||||
if (this._resolveAuthorityRequests.has(authority)) {
|
||||
this._resolveAuthorityRequests.get(authority)!.reject(errors.canceled());
|
||||
this._resolveAuthorityRequests.get(authority)!.cancel();
|
||||
this._resolveAuthorityRequests.delete(authority);
|
||||
}
|
||||
}
|
||||
|
@ -103,11 +77,14 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
_setResolvedAuthority(resolvedAuthority: ResolvedAuthority, options?: ResolvedOptions): void {
|
||||
if (this._resolveAuthorityRequests.has(resolvedAuthority.authority)) {
|
||||
const request = this._resolveAuthorityRequests.get(resolvedAuthority.authority)!;
|
||||
RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.host, resolvedAuthority.port);
|
||||
if (resolvedAuthority.messaging.type === MessagePassingType.WebSocket) {
|
||||
// todo@connor4312 need to implement some kind of loopback for ext host based messaging
|
||||
RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.messaging.host, resolvedAuthority.messaging.port);
|
||||
}
|
||||
if (resolvedAuthority.connectionToken) {
|
||||
RemoteAuthorities.setConnectionToken(resolvedAuthority.authority, resolvedAuthority.connectionToken);
|
||||
}
|
||||
request.resolve({ authority: resolvedAuthority, options });
|
||||
request.complete({ authority: resolvedAuthority, options });
|
||||
this._onDidChangeConnectionData.fire();
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +93,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
if (this._resolveAuthorityRequests.has(authority)) {
|
||||
const request = this._resolveAuthorityRequests.get(authority)!;
|
||||
// Avoid that this error makes it to telemetry
|
||||
request.reject(errors.ErrorNoTelemetry.fromError(err));
|
||||
request.error(errors.ErrorNoTelemetry.fromError(err));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,8 +105,8 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
|||
|
||||
_setCanonicalURIProvider(provider: (uri: URI) => Promise<URI>): void {
|
||||
this._canonicalURIProvider = provider;
|
||||
this._canonicalURIRequests.forEach((value) => {
|
||||
this._canonicalURIProvider!(value.input).then((uri) => value.resolve(uri), (err) => value.reject(err));
|
||||
this._canonicalURIRequests.forEach((value, key) => {
|
||||
this._canonicalURIProvider!(URI.parse(key)).then((uri) => value.complete(uri), (err) => value.error(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,29 +5,18 @@
|
|||
|
||||
import * as net from 'net';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { makeRawSocketHeaders } from 'vs/platform/remote/common/managedSocket';
|
||||
import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { WebSocketMessagingPassing } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
|
||||
export const nodeSocketFactory = new class implements ISocketFactory {
|
||||
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
|
||||
export const nodeSocketFactory = new class implements ISocketFactory<WebSocketMessagingPassing> {
|
||||
connect({ host, port }: WebSocketMessagingPassing, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
|
||||
const errorListener = (err: any) => callback(err, undefined);
|
||||
|
||||
const socket = net.createConnection({ host: host, port: port }, () => {
|
||||
socket.removeListener('error', errorListener);
|
||||
|
||||
// https://tools.ietf.org/html/rfc6455#section-4
|
||||
const buffer = Buffer.alloc(16);
|
||||
for (let i = 0; i < 16; i++) {
|
||||
buffer[i] = Math.round(Math.random() * 256);
|
||||
}
|
||||
const nonce = buffer.toString('base64');
|
||||
|
||||
const headers = [
|
||||
`GET ws://${/:/.test(host) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=true HTTP/1.1`,
|
||||
`Connection: Upgrade`,
|
||||
`Upgrade: websocket`,
|
||||
`Sec-WebSocket-Key: ${nonce}`
|
||||
];
|
||||
socket.write(headers.join('\r\n') + '\r\n\r\n');
|
||||
socket.write(makeRawSocketHeaders(path, query, debugLabel));
|
||||
|
||||
const onData = (data: Buffer) => {
|
||||
const strData = data.toString();
|
||||
|
|
|
@ -110,7 +110,7 @@ export interface ITunnel {
|
|||
export interface ISharedTunnelsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
|
||||
openTunnel(authority: string, addressProvider: IAddressProvider<unknown> | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
|
||||
}
|
||||
|
||||
export interface ITunnelService {
|
||||
|
@ -126,7 +126,7 @@ export interface ITunnelService {
|
|||
readonly onAddedTunnelProvider: Event<void>;
|
||||
|
||||
canTunnel(uri: URI): boolean;
|
||||
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
|
||||
openTunnel(addressProvider: IAddressProvider<unknown> | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
|
||||
getExistingTunnel(remoteHost: string, remotePort: number): Promise<RemoteTunnel | undefined>;
|
||||
setEnvironmentTunnel(remoteHost: string, remotePort: number, localAddress: string, privacy: string, protocol: string): void;
|
||||
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
|
||||
|
|
|
@ -7,17 +7,17 @@ import * as net from 'net';
|
|||
import * as os from 'os';
|
||||
import { BROWSER_RESTRICTED_PORTS, findFreePortFaster } from 'vs/base/node/ports';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
|
||||
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { AbstractTunnelService, isAllInterfaces, ISharedTunnelsService as ISharedTunnelsService, isLocalhost, isPortPrivileged, ITunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { OS } from 'vs/base/common/platform';
|
||||
import { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
|
||||
async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
|
||||
let readyTunnel: NodeRemoteTunnel | undefined;
|
||||
|
@ -155,7 +155,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
|
|||
|
||||
export class BaseTunnelService extends AbstractTunnelService {
|
||||
public constructor(
|
||||
private readonly socketFactory: ISocketFactory,
|
||||
@IRemoteSocketFactoryCollection private readonly socketFactories: IRemoteSocketFactoryCollection,
|
||||
@ILogService logService: ILogService,
|
||||
@ISignService private readonly signService: ISignService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
|
@ -179,32 +179,40 @@ export class BaseTunnelService extends AbstractTunnelService {
|
|||
return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol);
|
||||
} else {
|
||||
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
|
||||
const options: IConnectionOptions = {
|
||||
commit: this.productService.commit,
|
||||
quality: this.productService.quality,
|
||||
socketFactory: this.socketFactory,
|
||||
addressProvider,
|
||||
signService: this.signService,
|
||||
logService: this.logService,
|
||||
ipcLogger: null
|
||||
};
|
||||
return addressProvider.getAddress().then(address => {
|
||||
const socketFactory = this.socketFactories.create(address.connectTo);
|
||||
if (!socketFactory) {
|
||||
throw new Error(`No socket factory found for ${address.connectTo}`);
|
||||
}
|
||||
|
||||
const tunnel = createRemoteTunnel(options, localHost, remoteHost, remotePort, localPort);
|
||||
this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.');
|
||||
this.addTunnelToMap(remoteHost, remotePort, tunnel);
|
||||
return tunnel;
|
||||
const options: IConnectionOptions = {
|
||||
commit: this.productService.commit,
|
||||
quality: this.productService.quality,
|
||||
socketFactory,
|
||||
addressProvider,
|
||||
signService: this.signService,
|
||||
logService: this.logService,
|
||||
ipcLogger: null
|
||||
};
|
||||
|
||||
const tunnel = createRemoteTunnel(options, localHost, remoteHost, remotePort, localPort);
|
||||
this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.');
|
||||
this.addTunnelToMap(remoteHost, remotePort, tunnel);
|
||||
return tunnel;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TunnelService extends BaseTunnelService {
|
||||
public constructor(
|
||||
@IRemoteSocketFactoryCollection socketFactories: IRemoteSocketFactoryCollection,
|
||||
@ILogService logService: ILogService,
|
||||
@ISignService signService: ISignService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(nodeSocketFactory, logService, signService, productService, configurationService);
|
||||
super(socketFactories, logService, signService, productService, configurationService);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,6 +221,7 @@ export class SharedTunnelsService extends Disposable implements ISharedTunnelsSe
|
|||
private readonly _tunnelServices: Map<string, ITunnelService> = new Map();
|
||||
|
||||
public constructor(
|
||||
@IRemoteSocketFactoryCollection protected readonly socketFactories: IRemoteSocketFactoryCollection,
|
||||
@ILogService protected readonly logService: ILogService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@ISignService private readonly signService: ISignService,
|
||||
|
@ -224,7 +233,7 @@ export class SharedTunnelsService extends Disposable implements ISharedTunnelsSe
|
|||
async openTunnel(authority: string, addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost: string, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> {
|
||||
this.logService.trace(`ForwardedPorts: (SharedTunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`);
|
||||
if (!this._tunnelServices.has(authority)) {
|
||||
const tunnelService = new TunnelService(this.logService, this.signService, this.productService, this.configurationService);
|
||||
const tunnelService = new TunnelService(this.socketFactories, this.logService, this.signService, this.productService, this.configurationService);
|
||||
this._register(tunnelService);
|
||||
this._tunnelServices.set(authority, tunnelService);
|
||||
tunnelService.onTunnelClosed(async () => {
|
||||
|
|
|
@ -16,7 +16,7 @@ import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensio
|
|||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteConnectionData, MessagePassingType } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { ExtHostContext, ExtHostExtensionServiceShape, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
|
@ -25,7 +25,7 @@ import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/exten
|
|||
import { IExtensionDescriptionDelta } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
import { IExtensionHostProxy, IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
|
||||
import { ActivationKind, ExtensionActivationReason, IExtensionService, IInternalExtensionService, MissingExtensionDependency } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { extHostNamedCustomer, IExtHostContext, IInternalExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { extHostNamedCustomer, IExtHostContext, IInternalExtHostContext, IManagedSocketCallbacks } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
|
||||
|
||||
|
@ -34,6 +34,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
|
|||
|
||||
private readonly _extensionHostKind: ExtensionHostKind;
|
||||
private readonly _internalExtensionService: IInternalExtensionService;
|
||||
private readonly _managedSocketCallbacks: IManagedSocketCallbacks;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
|
@ -50,6 +51,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
|
|||
|
||||
const internalExtHostContext = (<IInternalExtHostContext>extHostContext);
|
||||
this._internalExtensionService = internalExtHostContext.internalExtensionService;
|
||||
this._managedSocketCallbacks = internalExtHostContext.managedSocketCallbacks;
|
||||
internalExtHostContext._setExtensionHostProxy(
|
||||
new ExtensionHostProxy(extHostContext.getProxy(ExtHostContext.ExtHostExtensionService))
|
||||
);
|
||||
|
@ -59,6 +61,18 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
|
|||
public dispose(): void {
|
||||
}
|
||||
|
||||
$onDidRemoteSocketHaveData(id: number, data: VSBuffer): void {
|
||||
this._managedSocketCallbacks.onDidRemoteSocketHaveData(id, data);
|
||||
}
|
||||
|
||||
$onDidRemoteSocketClose(id: number, error: string | undefined): void {
|
||||
this._managedSocketCallbacks.onDidRemoteSocketClose(id, error ? new Error(error) : undefined);
|
||||
}
|
||||
|
||||
$onDidRemoteSocketEnd(id: number): void {
|
||||
this._managedSocketCallbacks.onDidRemoteSocketEnd(id);
|
||||
}
|
||||
|
||||
$getExtension(extensionId: string) {
|
||||
return this._extensionService.getExtension(extensionId);
|
||||
}
|
||||
|
@ -199,8 +213,15 @@ class ExtensionHostProxy implements IExtensionHostProxy {
|
|||
private readonly _actual: ExtHostExtensionServiceShape
|
||||
) { }
|
||||
|
||||
resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {
|
||||
return this._actual.$resolveAuthority(remoteAuthority, resolveAttempt);
|
||||
async resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {
|
||||
const resolved = await this._actual.$resolveAuthority(remoteAuthority, resolveAttempt);
|
||||
if (resolved.type === 'ok') {
|
||||
resolved.value.authority.toString = function () {
|
||||
return this.messaging.type === MessagePassingType.Managed ? `ManagedSocket#${this.messaging.id}` : `${this.messaging.host}:${this.messaging.type}`;
|
||||
};
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
async getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI | null> {
|
||||
const uriComponents = await this._actual.$getCanonicalURI(remoteAuthority, uri);
|
||||
|
@ -236,4 +257,16 @@ class ExtensionHostProxy implements IExtensionHostProxy {
|
|||
test_down(size: number): Promise<VSBuffer> {
|
||||
return this._actual.$test_down(size);
|
||||
}
|
||||
openRemoteSocket(factoryId: number): Promise<number> {
|
||||
return this._actual.$openRemoteSocket(factoryId);
|
||||
}
|
||||
remoteSocketWrite(socketId: number, buffer: VSBuffer): void {
|
||||
return this._actual.$remoteSocketWrite(socketId, buffer);
|
||||
}
|
||||
remoteSocketEnd(socketId: number): void {
|
||||
return this._actual.$remoteSocketEnd(socketId);
|
||||
}
|
||||
remoteSocketDrain(socketId: number): Promise<void> {
|
||||
return this._actual.$remoteSocketDrain(socketId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1420,6 +1420,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
InlayHintKind: extHostTypes.InlayHintKind,
|
||||
RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError,
|
||||
ResolvedAuthority: extHostTypes.ResolvedAuthority,
|
||||
ManagedResolvedAuthority: extHostTypes.ManagedResolvedAuthority,
|
||||
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
|
||||
ExtensionRuntime: extHostTypes.ExtensionRuntime,
|
||||
TimelineItem: extHostTypes.TimelineItem,
|
||||
|
|
|
@ -1237,6 +1237,10 @@ export interface MainThreadExtensionServiceShape extends IDisposable {
|
|||
$onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void;
|
||||
$setPerformanceMarks(marks: performance.PerformanceMark[]): Promise<void>;
|
||||
$asBrowserUri(uri: UriComponents): Promise<UriComponents>;
|
||||
|
||||
$onDidRemoteSocketHaveData(id: number, data: VSBuffer): void;
|
||||
$onDidRemoteSocketClose(id: number, error: string | undefined): void;
|
||||
$onDidRemoteSocketEnd(id: number): void;
|
||||
}
|
||||
|
||||
export interface SCMProviderFeatures {
|
||||
|
@ -1593,6 +1597,11 @@ export interface ExtHostExtensionServiceShape {
|
|||
$test_latency(n: number): Promise<number>;
|
||||
$test_up(b: VSBuffer): Promise<number>;
|
||||
$test_down(size: number): Promise<VSBuffer>;
|
||||
|
||||
$openRemoteSocket(factoryId: number): Promise<number>;
|
||||
$remoteSocketWrite(socketId: number, buffer: VSBuffer): void;
|
||||
$remoteSocketEnd(socketId: number): void;
|
||||
$remoteSocketDrain(socketId: number): Promise<void>;
|
||||
}
|
||||
|
||||
export interface FileSystemEvents {
|
||||
|
|
|
@ -8,7 +8,7 @@ import * as path from 'vs/base/common/path';
|
|||
import * as performance from 'vs/base/common/performance';
|
||||
import { originalFSPath, joinPath, extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
|
||||
import { asPromise, Barrier, IntervalTimer, timeout } from 'vs/base/common/async';
|
||||
import { dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { TernarySearchTree } from 'vs/base/common/ternarySearchTree';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
@ -25,8 +25,8 @@ import type * as vscode from 'vscode';
|
|||
import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
|
||||
import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime, ResolvedAuthority as ExtHostResolvedAuthority } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData, getRemoteAuthorityPrefix, TunnelInformation, MessagePassingType } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
|
||||
|
@ -79,6 +79,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
private static remoteSocketIdCounter = 0;
|
||||
|
||||
abstract readonly extensionRuntime: ExtensionRuntime;
|
||||
|
||||
private readonly _onDidChangeRemoteConnectionData = this._register(new Emitter<void>());
|
||||
|
@ -118,6 +120,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
private _started: boolean;
|
||||
private _isTerminating: boolean = false;
|
||||
private _remoteConnectionData: IRemoteConnectionData | null;
|
||||
private readonly _managedSocketFactories: Map<number, () => Thenable<vscode.ManagedMessagePassing>>;
|
||||
private readonly _managedRemoteSockets: Map<number, {
|
||||
object: vscode.ManagedMessagePassing;
|
||||
disposer: DisposableStore;
|
||||
}>;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instaService: IInstantiationService,
|
||||
|
@ -191,6 +198,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
this._resolvers = Object.create(null);
|
||||
this._started = false;
|
||||
this._remoteConnectionData = this._initData.remote.connectionData;
|
||||
this._managedSocketFactories = new Map();
|
||||
this._managedRemoteSockets = new Map();
|
||||
}
|
||||
|
||||
public getRemoteConnectionData(): IRemoteConnectionData | null {
|
||||
|
@ -822,30 +831,44 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
const result = await resolver.resolve(remoteAuthority, { resolveAttempt });
|
||||
performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`);
|
||||
intervalLogger.dispose();
|
||||
logInfo(`returned ${result.host}:${result.port}`);
|
||||
|
||||
const tunnelInformation: TunnelInformation = {
|
||||
environmentTunnels: result.environmentTunnels,
|
||||
features: result.tunnelFeatures
|
||||
};
|
||||
|
||||
// Split merged API result into separate authority/options
|
||||
const authority: ResolvedAuthority = {
|
||||
authority: remoteAuthority,
|
||||
host: result.host,
|
||||
port: result.port,
|
||||
connectionToken: result.connectionToken
|
||||
};
|
||||
const options: ResolvedOptions = {
|
||||
extensionHostEnv: result.extensionHostEnv,
|
||||
isTrusted: result.isTrusted,
|
||||
authenticationSession: result.authenticationSessionForInitializingExtensions ? { id: result.authenticationSessionForInitializingExtensions.id, providerId: result.authenticationSessionForInitializingExtensions.providerId } : undefined
|
||||
};
|
||||
|
||||
logInfo(`returned ${result instanceof ExtHostResolvedAuthority ? `${result.host}:${result.port}` : 'managed authority'}`);
|
||||
|
||||
let authority: ResolvedAuthority;
|
||||
if (result instanceof ExtHostResolvedAuthority) {
|
||||
authority = {
|
||||
authority: remoteAuthority,
|
||||
messaging: { type: MessagePassingType.WebSocket, host: result.host, port: result.port },
|
||||
connectionToken: result.connectionToken
|
||||
};
|
||||
} else {
|
||||
const factoryId = AbstractExtHostExtensionService.remoteSocketIdCounter++;
|
||||
this._managedSocketFactories.set(factoryId, result.makeConnection);
|
||||
authority = {
|
||||
authority: remoteAuthority,
|
||||
messaging: { type: MessagePassingType.Managed, id: factoryId },
|
||||
connectionToken: result.connectionToken
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'ok',
|
||||
value: {
|
||||
authority,
|
||||
options,
|
||||
tunnelInformation: {
|
||||
environmentTunnels: result.environmentTunnels,
|
||||
features: result.tunnelFeatures
|
||||
}
|
||||
tunnelInformation,
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
|
@ -866,6 +889,47 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
}
|
||||
}
|
||||
|
||||
public async $openRemoteSocket(factoryId: number): Promise<number> {
|
||||
const factory = this._managedSocketFactories.get(factoryId);
|
||||
if (!factory) {
|
||||
throw new Error(`No socket factory with id ${factoryId}`);
|
||||
}
|
||||
|
||||
const id = AbstractExtHostExtensionService.remoteSocketIdCounter++;
|
||||
const socket = await factory();
|
||||
const disposable = new DisposableStore();
|
||||
|
||||
this._managedRemoteSockets.set(id, { object: socket, disposer: disposable });
|
||||
disposable.add(toDisposable(() => this._managedRemoteSockets.delete(id)));
|
||||
disposable.add(socket.onDidEnd(() => {
|
||||
this._mainThreadExtensionsProxy.$onDidRemoteSocketEnd(id);
|
||||
disposable.dispose();
|
||||
}));
|
||||
disposable.add(socket.onDidClose(e => {
|
||||
this._mainThreadExtensionsProxy.$onDidRemoteSocketClose(id, e?.stack ?? e?.message);
|
||||
disposable.dispose();
|
||||
}));
|
||||
disposable.add(socket.onDidReceiveMessage(e => this._mainThreadExtensionsProxy.$onDidRemoteSocketHaveData(id, VSBuffer.wrap(e))));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public $remoteSocketDrain(id: number): Promise<void> {
|
||||
return this._managedRemoteSockets.get(id)?.object.drainHandler?.() ?? Promise.resolve();
|
||||
}
|
||||
|
||||
public $remoteSocketEnd(id: number): void {
|
||||
const socket = this._managedRemoteSockets.get(id);
|
||||
if (socket) {
|
||||
socket.object.endHandler();
|
||||
socket.disposer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public $remoteSocketWrite(id: number, buffer: VSBuffer): void {
|
||||
this._managedRemoteSockets.get(id)?.object.dataHandler(buffer.buffer);
|
||||
}
|
||||
|
||||
public async $getCanonicalURI(remoteAuthority: string, uriComponents: UriComponents): Promise<UriComponents | null> {
|
||||
this._logService.info(`$getCanonicalURI invoked for authority (${getRemoteAuthorityPrefix(remoteAuthority)})`);
|
||||
|
||||
|
|
|
@ -479,6 +479,13 @@ export class Selection extends Range {
|
|||
}
|
||||
}
|
||||
|
||||
const validateConnectionToken = (connectionToken: string) => {
|
||||
if (typeof connectionToken !== 'string' || connectionToken.length === 0 || !/^[0-9A-Za-z_\-]+$/.test(connectionToken)) {
|
||||
throw illegalArgument('connectionToken');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export class ResolvedAuthority {
|
||||
readonly host: string;
|
||||
readonly port: number;
|
||||
|
@ -492,9 +499,7 @@ export class ResolvedAuthority {
|
|||
throw illegalArgument('port');
|
||||
}
|
||||
if (typeof connectionToken !== 'undefined') {
|
||||
if (typeof connectionToken !== 'string' || connectionToken.length === 0 || !/^[0-9A-Za-z_\-]+$/.test(connectionToken)) {
|
||||
throw illegalArgument('connectionToken');
|
||||
}
|
||||
validateConnectionToken(connectionToken);
|
||||
}
|
||||
this.host = host;
|
||||
this.port = Math.round(port);
|
||||
|
@ -502,6 +507,14 @@ export class ResolvedAuthority {
|
|||
}
|
||||
}
|
||||
|
||||
export class ManagedResolvedAuthority {
|
||||
constructor(public readonly makeConnection: () => Thenable<vscode.ManagedMessagePassing>, public readonly connectionToken?: string) {
|
||||
if (typeof connectionToken !== 'undefined') {
|
||||
validateConnectionToken(connectionToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteAuthorityResolverError extends Error {
|
||||
|
||||
static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError {
|
||||
|
|
|
@ -18,7 +18,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
|||
import product from 'vs/platform/product/common/product';
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentService';
|
||||
import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteAuthorityResolverService, MessagePassingType } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
|
@ -84,6 +84,8 @@ import { BrowserUserDataProfilesService } from 'vs/platform/userDataProfile/brow
|
|||
import { timeout } from 'vs/base/common/async';
|
||||
import { windowLogId } from 'vs/workbench/services/log/common/logConstants';
|
||||
import { LogService } from 'vs/platform/log/common/logService';
|
||||
import { IRemoteSocketFactoryCollection, RemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
|
||||
|
||||
export class BrowserMain extends Disposable {
|
||||
|
||||
|
@ -253,7 +255,8 @@ export class BrowserMain extends Disposable {
|
|||
|
||||
// Remote
|
||||
const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName);
|
||||
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(connectionToken, this.configuration.resourceUriProvider, productService, logService);
|
||||
const expectResolverExtension = !!environmentService.remoteAuthority?.includes('+') && !environmentService.options.webSocketFactory;
|
||||
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!expectResolverExtension, connectionToken, this.configuration.resourceUriProvider, productService, logService);
|
||||
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
|
||||
|
||||
// Signing
|
||||
|
@ -292,7 +295,10 @@ export class BrowserMain extends Disposable {
|
|||
serviceCollection.set(IUserDataProfileService, userDataProfileService);
|
||||
|
||||
// Remote Agent
|
||||
const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
|
||||
const socketFactories = new RemoteSocketFactoryCollection();
|
||||
socketFactories.register(MessagePassingType.WebSocket, () => new BrowserSocketFactory(this.configuration.webSocketFactory));
|
||||
serviceCollection.set(IRemoteSocketFactoryCollection, socketFactories);
|
||||
const remoteAgentService = this._register(new RemoteAgentService(socketFactories, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
|
||||
serviceCollection.set(IRemoteAgentService, remoteAgentService);
|
||||
|
||||
await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, bufferLogger, logService, loggerService, logsPath);
|
||||
|
|
|
@ -782,7 +782,10 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
|
|||
private async localLocalhost(id: string, origin: string) {
|
||||
const authority = this._environmentService.remoteAuthority;
|
||||
const resolveAuthority = authority ? await this._remoteAuthorityResolverService.resolveAuthority(authority) : undefined;
|
||||
const redirect = resolveAuthority ? await this._portMappingManager.getRedirect(resolveAuthority.authority, origin) : undefined;
|
||||
const redirect = resolveAuthority ? await this._portMappingManager.getRedirect({
|
||||
connectionToken: resolveAuthority.authority.connectionToken,
|
||||
connectTo: resolveAuthority.authority.messaging,
|
||||
}, origin) : undefined;
|
||||
return this._send('did-load-localhost', {
|
||||
id,
|
||||
origin,
|
||||
|
|
|
@ -25,7 +25,7 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services
|
|||
import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService';
|
||||
import { SharedProcessService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService';
|
||||
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteAuthorityResolverService, MessagePassingType } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentService';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
|
@ -55,6 +55,8 @@ import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc';
|
|||
import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy';
|
||||
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
|
||||
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
|
||||
import { RemoteSocketFactoryCollection, IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
|
||||
export class DesktopMain extends Disposable {
|
||||
|
||||
|
@ -236,7 +238,10 @@ export class DesktopMain extends Disposable {
|
|||
serviceCollection.set(IUserDataProfileService, userDataProfileService);
|
||||
|
||||
// Remote Agent
|
||||
const remoteAgentService = this._register(new RemoteAgentService(userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
|
||||
const socketFactories = new RemoteSocketFactoryCollection();
|
||||
socketFactories.register(MessagePassingType.WebSocket, () => new BrowserSocketFactory(null));
|
||||
serviceCollection.set(IRemoteSocketFactoryCollection, socketFactories);
|
||||
const remoteAgentService = this._register(new RemoteAgentService(socketFactories, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
|
||||
serviceCollection.set(IRemoteAgentService, remoteAgentService);
|
||||
|
||||
// Remote Files
|
||||
|
|
|
@ -842,7 +842,8 @@ export class NativeWindow extends Disposable {
|
|||
const remoteAuthority = this.environmentService.remoteAuthority;
|
||||
const addressProvider: IAddressProvider | undefined = remoteAuthority ? {
|
||||
getAddress: async (): Promise<IAddress> => {
|
||||
return (await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority)).authority;
|
||||
const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority);
|
||||
return { connectTo: authority.messaging, connectionToken: authority.connectionToken };
|
||||
}
|
||||
} : undefined;
|
||||
let tunnel = await this.tunnelService.getExistingTunnel(portMappingRequest.address, portMappingRequest.port);
|
||||
|
|
|
@ -113,7 +113,7 @@ suite('ConfigurationEditing', () => {
|
|||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
|
||||
userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
|
||||
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null));
|
||||
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService));
|
||||
disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService))));
|
||||
instantiationService.stub(IFileService, fileService);
|
||||
instantiationService.stub(IRemoteAgentService, remoteAgentService);
|
||||
|
|
|
@ -51,6 +51,7 @@ import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
|
|||
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
|
||||
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { TasksSchemaProperties } from 'vs/workbench/contrib/tasks/common/tasks';
|
||||
import { RemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
|
||||
function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier {
|
||||
return {
|
||||
|
@ -89,7 +90,7 @@ suite('WorkspaceContextService - Folder', () => {
|
|||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
|
||||
const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
|
||||
testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryCollection(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
|
||||
});
|
||||
|
||||
|
@ -132,7 +133,7 @@ suite('WorkspaceContextService - Folder', () => {
|
|||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
|
||||
const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
|
||||
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryCollection(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
|
||||
|
||||
const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a'));
|
||||
|
@ -155,7 +156,7 @@ suite('WorkspaceContextService - Folder', () => {
|
|||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
|
||||
const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
|
||||
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryCollection(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
|
||||
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
|
||||
|
||||
|
||||
|
@ -199,7 +200,7 @@ suite('WorkspaceContextService - Workspace', () => {
|
|||
|
||||
const instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
|
||||
const environmentService = TestEnvironmentService;
|
||||
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null));
|
||||
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService));
|
||||
instantiationService.stub(IRemoteAgentService, remoteAgentService);
|
||||
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
|
||||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
|
@ -259,7 +260,7 @@ suite('WorkspaceContextService - Workspace Editing', () => {
|
|||
|
||||
const instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
|
||||
const environmentService = TestEnvironmentService;
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
|
||||
instantiationService.stub(IRemoteAgentService, remoteAgentService);
|
||||
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
|
||||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
|
@ -505,7 +506,7 @@ suite('WorkspaceService - Initialization', () => {
|
|||
|
||||
const instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
|
||||
environmentService = TestEnvironmentService;
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
|
||||
instantiationService.stub(IRemoteAgentService, remoteAgentService);
|
||||
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
|
||||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
|
@ -766,7 +767,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
|
|||
instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
|
||||
environmentService = TestEnvironmentService;
|
||||
environmentService.policyFile = joinPath(folder, 'policies.json');
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
|
||||
instantiationService.stub(IRemoteAgentService, remoteAgentService);
|
||||
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
|
||||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
|
@ -1562,7 +1563,7 @@ suite('WorkspaceConfigurationService - Profiles', () => {
|
|||
instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
|
||||
environmentService = TestEnvironmentService;
|
||||
environmentService.policyFile = joinPath(folder, 'policies.json');
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
|
||||
instantiationService.stub(IRemoteAgentService, remoteAgentService);
|
||||
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
|
||||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
|
@ -1801,7 +1802,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
|
|||
|
||||
const instantiationService = <TestInstantiationService>workbenchInstantiationService(undefined, disposables);
|
||||
environmentService = TestEnvironmentService;
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
|
||||
const remoteAgentService = instantiationService.createInstance(RemoteAgentService);
|
||||
instantiationService.stub(IRemoteAgentService, remoteAgentService);
|
||||
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
|
||||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
|
|
|
@ -170,7 +170,7 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory {
|
|||
case ExtensionHostKind.Remote: {
|
||||
const remoteAgentConnection = this._remoteAgentService.getConnection();
|
||||
if (remoteAgentConnection) {
|
||||
return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory);
|
||||
return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -938,7 +938,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
|||
},
|
||||
_onExtensionRuntimeError: (extensionId: ExtensionIdentifier, err: Error): void => {
|
||||
return this._onExtensionRuntimeError(extensionId, err);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { BrandedService, IConstructorSignature } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensionHostKind';
|
||||
|
@ -15,8 +16,15 @@ export interface IExtHostContext extends IRPCProtocol {
|
|||
readonly extensionHostKind: ExtensionHostKind;
|
||||
}
|
||||
|
||||
export interface IManagedSocketCallbacks {
|
||||
onDidRemoteSocketHaveData(id: number, data: VSBuffer): void;
|
||||
onDidRemoteSocketEnd(id: number): void;
|
||||
onDidRemoteSocketClose(id: number, error: Error | undefined): void;
|
||||
}
|
||||
|
||||
export interface IInternalExtHostContext extends IExtHostContext {
|
||||
readonly internalExtensionService: IInternalExtensionService;
|
||||
readonly managedSocketCallbacks: IManagedSocketCallbacks;
|
||||
_setExtensionHostProxy(extensionHostProxy: IExtensionHostProxy): void;
|
||||
_setAllMainProxyIdentifiers(mainProxyIdentifiers: ProxyIdentifier<any>[]): void;
|
||||
}
|
||||
|
|
|
@ -11,13 +11,15 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
|||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { ManagedMessagingPassing, MessagePassingType, RemoteAuthorityResolverErrorCode, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
|
@ -29,6 +31,7 @@ import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/commo
|
|||
import { ActivationKind, ExtensionActivationReason, ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IInternalExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { Proxied, ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
|
||||
import { ManagedSocket } from 'vs/workbench/services/remote/common/managedSocket';
|
||||
|
||||
// Enable to see detailed message communication between window and extension host
|
||||
const LOG_EXTENSION_HOST_COMMUNICATION = false;
|
||||
|
@ -85,6 +88,13 @@ type ExtensionHostStartupEvent = {
|
|||
errorStack?: string;
|
||||
};
|
||||
|
||||
|
||||
interface RemoteSocketHalf {
|
||||
onData: Emitter<VSBuffer>;
|
||||
onClose: Emitter<SocketCloseEvent>;
|
||||
onEnd: Emitter<void>;
|
||||
}
|
||||
|
||||
class ExtensionHostManager extends Disposable implements IExtensionHostManager {
|
||||
|
||||
public readonly onDidExit: Event<[number, string | null]>;
|
||||
|
@ -102,6 +112,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
|
|||
private readonly _extensionHost: IExtensionHost;
|
||||
private _proxy: Promise<IExtensionHostProxy | null> | null;
|
||||
private _hasStarted = false;
|
||||
private readonly _remoteSockets = new Map<number, RemoteSocketHalf>();
|
||||
|
||||
public get kind(): ExtensionHostKind {
|
||||
return this._extensionHost.runningLocation.kind;
|
||||
|
@ -115,6 +126,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
|
|||
extensionHost: IExtensionHost,
|
||||
initialActivationEvents: string[],
|
||||
private readonly _internalExtensionService: IInternalExtensionService,
|
||||
@IRemoteSocketFactoryCollection private readonly _remoteSocketFactoryCollection: IRemoteSocketFactoryCollection,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
|
@ -287,6 +299,23 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
|
|||
|
||||
//#region internal
|
||||
internalExtensionService: this._internalExtensionService,
|
||||
managedSocketCallbacks: {
|
||||
onDidRemoteSocketHaveData: (id, data) => {
|
||||
this._remoteSockets.get(id)?.onData.fire(data);
|
||||
},
|
||||
onDidRemoteSocketEnd: id => {
|
||||
this._remoteSockets.get(id)?.onEnd.fire();
|
||||
this._remoteSockets.delete(id);
|
||||
},
|
||||
onDidRemoteSocketClose: (id, error) => {
|
||||
this._remoteSockets.get(id)?.onClose.fire({
|
||||
type: SocketCloseEventType.NodeSocketCloseEvent,
|
||||
error,
|
||||
hadError: !!error
|
||||
});
|
||||
this._remoteSockets.delete(id);
|
||||
},
|
||||
},
|
||||
_setExtensionHostProxy: (value: IExtensionHostProxy): void => {
|
||||
extensionHostProxy = value;
|
||||
},
|
||||
|
@ -409,7 +438,10 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
|
|||
const resolverResult = await proxy.resolveAuthority(remoteAuthority, resolveAttempt);
|
||||
intervalLogger.dispose();
|
||||
if (resolverResult.type === 'ok') {
|
||||
logInfo(`returned ${resolverResult.value.authority.host}:${resolverResult.value.authority.port}`);
|
||||
logInfo(`returned ${resolverResult.value.authority}`);
|
||||
if (resolverResult.value.authority.messaging.type === MessagePassingType.Managed) {
|
||||
this.registerManagedSocketFactory(resolverResult.value.authority.messaging, proxy);
|
||||
}
|
||||
} else {
|
||||
logError(`returned an error`, resolverResult.error);
|
||||
}
|
||||
|
@ -428,6 +460,38 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
|
|||
}
|
||||
}
|
||||
|
||||
private registerManagedSocketFactory(messaging: ManagedMessagingPassing, proxy: IExtensionHostProxy) {
|
||||
this._remoteSocketFactoryCollection.register(MessagePassingType.Managed, resolved => {
|
||||
if (resolved.id !== messaging.id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
connect: ({ id: factoryId }, path, query, debugLabel, callback) => {
|
||||
proxy.openRemoteSocket(factoryId).then(socketId => {
|
||||
const half: RemoteSocketHalf = {
|
||||
onClose: new Emitter(),
|
||||
onData: new Emitter(),
|
||||
onEnd: new Emitter(),
|
||||
};
|
||||
this._remoteSockets.set(socketId, half);
|
||||
|
||||
ManagedSocket.connect(socketId, proxy, path, query, debugLabel, half)
|
||||
.then(
|
||||
socket => {
|
||||
socket.onDidDispose(() => this._remoteSockets.delete(socketId));
|
||||
callback(undefined, socket);
|
||||
},
|
||||
err => {
|
||||
this._remoteSockets.delete(socketId);
|
||||
callback(err, undefined);
|
||||
});
|
||||
}).catch(err => callback(err, undefined));
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public async getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI | null> {
|
||||
const proxy = await this._proxy;
|
||||
if (!proxy) {
|
||||
|
|
|
@ -42,4 +42,9 @@ export interface IExtensionHostProxy {
|
|||
test_latency(n: number): Promise<number>;
|
||||
test_up(b: VSBuffer): Promise<number>;
|
||||
test_down(size: number): Promise<VSBuffer>;
|
||||
|
||||
openRemoteSocket(factoryId: number): Promise<number>;
|
||||
remoteSocketWrite(socketId: number, buffer: VSBuffer): void;
|
||||
remoteSocketEnd(socketId: number): void;
|
||||
remoteSocketDrain(socketId: number): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
|
|||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ILogService, ILoggerService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IConnectionOptions, IRemoteExtensionHostStartParams, ISocketFactory, connectRemoteAgentExtensionHost } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IConnectionOptions, IRemoteExtensionHostStartParams, connectRemoteAgentExtensionHost } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IRemoteAuthorityResolverService, IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { isLoggingOnly } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
|
@ -61,7 +62,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
|
|||
constructor(
|
||||
public readonly runningLocation: RemoteRunningLocation,
|
||||
private readonly _initDataProvider: IRemoteExtensionHostDataProvider,
|
||||
private readonly _socketFactory: ISocketFactory,
|
||||
@IRemoteSocketFactoryCollection private readonly socketFactories: IRemoteSocketFactoryCollection,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
|
@ -84,21 +85,26 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
|
|||
}
|
||||
|
||||
public start(): Promise<IMessagePassingProtocol> {
|
||||
const options: IConnectionOptions = {
|
||||
commit: this._productService.commit,
|
||||
quality: this._productService.quality,
|
||||
socketFactory: this._socketFactory,
|
||||
addressProvider: {
|
||||
getAddress: async () => {
|
||||
const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
|
||||
return { host: authority.host, port: authority.port, connectionToken: authority.connectionToken };
|
||||
}
|
||||
},
|
||||
signService: this._signService,
|
||||
logService: this._logService,
|
||||
ipcLogger: null
|
||||
};
|
||||
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => {
|
||||
const socketFactory = this.socketFactories.create(resolverResult.authority.messaging);
|
||||
if (!socketFactory) {
|
||||
throw new Error('No socket factory found for remote authority');
|
||||
}
|
||||
|
||||
const options: IConnectionOptions = {
|
||||
commit: this._productService.commit,
|
||||
quality: this._productService.quality,
|
||||
socketFactory,
|
||||
addressProvider: {
|
||||
getAddress: async () => {
|
||||
const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
|
||||
return { connectTo: authority.messaging, connectionToken: authority.connectionToken };
|
||||
}
|
||||
},
|
||||
signService: this._signService,
|
||||
logService: this._logService,
|
||||
ipcLogger: null
|
||||
};
|
||||
|
||||
const startParams: IRemoteExtensionHostStartParams = {
|
||||
language: platform.language,
|
||||
|
|
|
@ -29,7 +29,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
|
|||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteAuthorityResolverService, MessagePassingType, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
|
||||
import { getRemoteName, parseAuthorityWithPort } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { updateProxyConfigurationsScope } from 'vs/platform/request/common/request';
|
||||
|
@ -277,15 +277,22 @@ export class NativeExtensionService extends AbstractExtensionService implements
|
|||
const authorityPlusIndex = remoteAuthority.indexOf('+');
|
||||
if (authorityPlusIndex === -1) {
|
||||
// This authority does not need to be resolved, simply parse the port number
|
||||
const { host, port } = parseAuthorityWithPort(remoteAuthority);
|
||||
return {
|
||||
authority: {
|
||||
authority: remoteAuthority,
|
||||
host,
|
||||
port,
|
||||
connectionToken: undefined
|
||||
}
|
||||
};
|
||||
try {
|
||||
const { host, port } = parseAuthorityWithPort(remoteAuthority);
|
||||
return {
|
||||
authority: {
|
||||
authority: remoteAuthority,
|
||||
messaging: {
|
||||
type: MessagePassingType.WebSocket,
|
||||
host,
|
||||
port
|
||||
},
|
||||
connectionToken: undefined
|
||||
}
|
||||
};
|
||||
} catch {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess);
|
||||
|
@ -391,7 +398,7 @@ export class NativeExtensionService extends AbstractExtensionService implements
|
|||
performance.mark(`code/willResolveAuthority/${authorityPrefix}`);
|
||||
const result = await this._resolveAuthority(remoteAuthority);
|
||||
performance.mark(`code/didResolveAuthorityOK/${authorityPrefix}`);
|
||||
this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority.host}:${result.authority.port}' after ${sw.elapsed()} ms`);
|
||||
this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority}' after ${sw.elapsed()} ms`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
performance.mark(`code/didResolveAuthorityError/${authorityPrefix}`);
|
||||
|
@ -631,7 +638,7 @@ class NativeExtensionHostFactory implements IExtensionHostFactory {
|
|||
case ExtensionHostKind.Remote: {
|
||||
const remoteAgentConnection = this._remoteAgentService.getConnection();
|
||||
if (remoteAgentConnection) {
|
||||
return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory);
|
||||
return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
|
|||
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IWebSocketFactory, BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
|
@ -19,11 +18,12 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } f
|
|||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
|
||||
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
|
||||
|
||||
constructor(
|
||||
webSocketFactory: IWebSocketFactory | null | undefined,
|
||||
@IRemoteSocketFactoryCollection socketFactories: IRemoteSocketFactoryCollection,
|
||||
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IProductService productService: IProductService,
|
||||
|
@ -31,7 +31,7 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR
|
|||
@ISignService signService: ISignService,
|
||||
@ILogService logService: ILogService
|
||||
) {
|
||||
super(new BrowserSocketFactory(webSocketFactory), userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService);
|
||||
super(socketFactories, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
|||
import { IChannel, IServerChannel, getDelayedChannel, IPCLogger } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { connectRemoteAgentManagement, IConnectionOptions, ISocketFactory, ManagementPersistentConnection, PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { connectRemoteAgentManagement, IConnectionOptions, ManagementPersistentConnection, PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
|
@ -19,17 +19,17 @@ import { ILogService } from 'vs/platform/log/common/log';
|
|||
import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
|
||||
export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
public readonly socketFactory: ISocketFactory;
|
||||
private readonly _connection: IRemoteAgentConnection | null;
|
||||
private _environment: Promise<IRemoteAgentEnvironment | null> | null;
|
||||
|
||||
constructor(
|
||||
socketFactory: ISocketFactory,
|
||||
@IRemoteSocketFactoryCollection private readonly socketFactories: IRemoteSocketFactoryCollection,
|
||||
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
|
||||
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@IProductService productService: IProductService,
|
||||
|
@ -38,9 +38,8 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I
|
|||
@ILogService logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this.socketFactory = socketFactory;
|
||||
if (this._environmentService.remoteAuthority) {
|
||||
this._connection = this._register(new RemoteAgentConnection(this._environmentService.remoteAuthority, productService.commit, productService.quality, this.socketFactory, this._remoteAuthorityResolverService, signService, logService));
|
||||
this._connection = this._register(new RemoteAgentConnection(this._environmentService.remoteAuthority, productService.commit, productService.quality, this.socketFactories, this._remoteAuthorityResolverService, signService, logService));
|
||||
} else {
|
||||
this._connection = null;
|
||||
}
|
||||
|
@ -150,7 +149,7 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection
|
|||
remoteAuthority: string,
|
||||
private readonly _commit: string | undefined,
|
||||
private readonly _quality: string | undefined,
|
||||
private readonly _socketFactory: ISocketFactory,
|
||||
private readonly _socketFactories: IRemoteSocketFactoryCollection,
|
||||
private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
private readonly _signService: ISignService,
|
||||
private readonly _logService: ILogService
|
||||
|
@ -193,28 +192,35 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection
|
|||
|
||||
private async _createConnection(): Promise<Client<RemoteAgentConnectionContext>> {
|
||||
let firstCall = true;
|
||||
const options: IConnectionOptions = {
|
||||
commit: this._commit,
|
||||
quality: this._quality,
|
||||
socketFactory: this._socketFactory,
|
||||
addressProvider: {
|
||||
getAddress: async () => {
|
||||
if (firstCall) {
|
||||
firstCall = false;
|
||||
} else {
|
||||
this._onReconnecting.fire(undefined);
|
||||
}
|
||||
const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
|
||||
return { host: authority.host, port: authority.port, connectionToken: authority.connectionToken };
|
||||
}
|
||||
},
|
||||
signService: this._signService,
|
||||
logService: this._logService,
|
||||
ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null
|
||||
};
|
||||
let connection: ManagementPersistentConnection;
|
||||
const start = Date.now();
|
||||
try {
|
||||
const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
|
||||
const socketFactory = this._socketFactories.create(authority.messaging);
|
||||
if (!socketFactory) {
|
||||
throw new Error(`No socket factory found for ${authority}`);
|
||||
}
|
||||
|
||||
const options: IConnectionOptions = {
|
||||
commit: this._commit,
|
||||
quality: this._quality,
|
||||
socketFactory,
|
||||
addressProvider: {
|
||||
getAddress: async () => {
|
||||
if (firstCall) {
|
||||
firstCall = false;
|
||||
} else {
|
||||
this._onReconnecting.fire(undefined);
|
||||
}
|
||||
const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
|
||||
return { connectTo: authority.messaging, connectionToken: authority.connectionToken };
|
||||
}
|
||||
},
|
||||
signService: this._signService,
|
||||
logService: this._logService,
|
||||
ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null
|
||||
};
|
||||
|
||||
connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`));
|
||||
} finally {
|
||||
this._initialConnectionMs = Date.now() - start;
|
||||
|
|
91
src/vs/workbench/services/remote/common/managedSocket.ts
Normal file
91
src/vs/workbench/services/remote/common/managedSocket.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ISocket, SocketCloseEvent, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { makeRawSocketHeaders, socketRawEndHeaderSequence } from 'vs/platform/remote/common/managedSocket';
|
||||
import { IExtensionHostProxy } from 'vs/workbench/services/extensions/common/extensionHostProxy';
|
||||
|
||||
export class ManagedSocket extends Disposable implements ISocket {
|
||||
public static connect(
|
||||
socketId: number,
|
||||
proxy: IExtensionHostProxy,
|
||||
path: string, query: string, debugLabel: string,
|
||||
|
||||
half: {
|
||||
onClose: Emitter<SocketCloseEvent>;
|
||||
onData: Emitter<VSBuffer>;
|
||||
onEnd: Emitter<void>;
|
||||
}
|
||||
): Promise<ManagedSocket> {
|
||||
const socket = new ManagedSocket(socketId, proxy, debugLabel, half.onClose, half.onData, half.onEnd);
|
||||
|
||||
socket.write(VSBuffer.fromString(makeRawSocketHeaders(path, query, debugLabel)));
|
||||
|
||||
const d = new DisposableStore();
|
||||
return new Promise<ManagedSocket>((resolve, reject) => {
|
||||
d.add(socket.onData(d => {
|
||||
if (d.indexOf(socketRawEndHeaderSequence) !== -1) {
|
||||
resolve(socket);
|
||||
}
|
||||
}));
|
||||
|
||||
d.add(socket.onClose(err => reject(err ?? new Error('socket closed'))));
|
||||
d.add(socket.onEnd(() => reject(new Error('socket ended'))));
|
||||
}).finally(() => d.dispose());
|
||||
}
|
||||
|
||||
public onData: Event<VSBuffer>;
|
||||
public onClose: Event<SocketCloseEvent>;
|
||||
public onEnd: Event<void>;
|
||||
|
||||
private readonly didDisposeEmitter = this._register(new Emitter<void>());
|
||||
public onDidDispose = this.didDisposeEmitter.event;
|
||||
|
||||
private ended = false;
|
||||
|
||||
private constructor(
|
||||
private readonly socketId: number,
|
||||
private readonly proxy: IExtensionHostProxy,
|
||||
private readonly debugLabel: string,
|
||||
onCloseEmitter: Emitter<SocketCloseEvent>,
|
||||
onDataEmitter: Emitter<VSBuffer>,
|
||||
onEndEmitter: Emitter<void>,
|
||||
) {
|
||||
super();
|
||||
this.onClose = this._register(onCloseEmitter).event;
|
||||
this.onData = this._register(onDataEmitter).event;
|
||||
this.onEnd = this._register(onEndEmitter).event;
|
||||
}
|
||||
|
||||
write(buffer: VSBuffer): void {
|
||||
this.proxy.remoteSocketWrite(this.socketId, buffer);
|
||||
}
|
||||
|
||||
end(): void {
|
||||
this.ended = true;
|
||||
this.proxy.remoteSocketEnd(this.socketId);
|
||||
}
|
||||
|
||||
drain(): Promise<void> {
|
||||
return this.proxy.remoteSocketDrain(this.socketId);
|
||||
}
|
||||
|
||||
traceSocketEvent(type: SocketDiagnosticsEventType, data?: any): void {
|
||||
SocketDiagnostics.traceSocketEvent(this, this.debugLabel, type, data);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
if (!this.ended) {
|
||||
this.proxy.remoteSocketEnd(this.socketId);
|
||||
}
|
||||
|
||||
this.didDisposeEmitter.fire();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platfo
|
|||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export const IRemoteAgentService = createDecorator<IRemoteAgentService>('remoteAgentService');
|
||||
|
@ -16,8 +16,6 @@ export const IRemoteAgentService = createDecorator<IRemoteAgentService>('remoteA
|
|||
export interface IRemoteAgentService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly socketFactory: ISocketFactory;
|
||||
|
||||
getConnection(): IRemoteAgentConnection | null;
|
||||
/**
|
||||
* Get the remote environment. In case of an error, returns `null`.
|
||||
|
|
|
@ -621,7 +621,10 @@ export class TunnelModel extends Disposable {
|
|||
if (!existingTunnel) {
|
||||
const authority = this.environmentService.remoteAuthority;
|
||||
const addressProvider: IAddressProvider | undefined = authority ? {
|
||||
getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; }
|
||||
getAddress: async () => {
|
||||
const r = await this.remoteAuthorityResolverService.resolveAuthority(authority);
|
||||
return { connectTo: r.authority.messaging, connectionToken: r.authority.connectionToken };
|
||||
}
|
||||
} : undefined;
|
||||
|
||||
const key = makeAddress(tunnelProperties.remote.host, tunnelProperties.remote.port);
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteAuthorityResolverService, MessagePassingType, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
|
||||
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
@ -21,9 +20,11 @@ import { INativeHostService } from 'vs/platform/native/common/native';
|
|||
import { URI } from 'vs/base/common/uri';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { IRemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
|
||||
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
|
||||
constructor(
|
||||
@IRemoteSocketFactoryCollection socketFactoryCollection: IRemoteSocketFactoryCollection,
|
||||
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IProductService productService: IProductService,
|
||||
|
@ -31,7 +32,7 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR
|
|||
@ISignService signService: ISignService,
|
||||
@ILogService logService: ILogService,
|
||||
) {
|
||||
super(new BrowserSocketFactory(null), userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService);
|
||||
super(socketFactoryCollection, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,12 +80,12 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr
|
|||
return null;
|
||||
}
|
||||
const connectionData = this._remoteAuthorityResolverService.getConnectionData(remoteAgentConnection.remoteAuthority);
|
||||
if (!connectionData) {
|
||||
if (!connectionData || connectionData.connectTo.type !== MessagePassingType.WebSocket) {
|
||||
return null;
|
||||
}
|
||||
return URI.from({
|
||||
scheme: 'http',
|
||||
authority: `${connectionData.host}:${connectionData.port}`,
|
||||
authority: `${connectionData.connectTo.host}:${connectionData.connectTo.port}`,
|
||||
path: `/version`
|
||||
});
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@ import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions,
|
|||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover';
|
||||
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
|
||||
import { IRemoteSocketFactoryCollection, RemoteSocketFactoryCollection } from 'vs/platform/remote/common/remoteSocketFactoryCollection';
|
||||
|
||||
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
||||
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined);
|
||||
|
@ -325,6 +326,7 @@ export function workbenchInstantiationService(
|
|||
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
|
||||
instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService());
|
||||
instantiationService.stub(IElevatedFileService, new BrowserElevatedFileService());
|
||||
instantiationService.stub(IRemoteSocketFactoryCollection, new RemoteSocketFactoryCollection());
|
||||
|
||||
return instantiationService;
|
||||
}
|
||||
|
@ -1937,7 +1939,7 @@ export class TestRemoteAgentService implements IRemoteAgentService {
|
|||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
socketFactory: ISocketFactory = {
|
||||
socketFactory: ISocketFactory<any> = {
|
||||
connect() { }
|
||||
};
|
||||
|
||||
|
|
19
src/vscode-dts/vscode.proposed.resolvers.d.ts
vendored
19
src/vscode-dts/vscode.proposed.resolvers.d.ts
vendored
|
@ -26,6 +26,23 @@ declare module 'vscode' {
|
|||
constructor(host: string, port: number, connectionToken?: string);
|
||||
}
|
||||
|
||||
export interface ManagedMessagePassing {
|
||||
onDidReceiveMessage: Event<Uint8Array>;
|
||||
onDidClose: Event<Error | undefined>;
|
||||
onDidEnd: Event<void>;
|
||||
|
||||
dataHandler: (data: Uint8Array) => void;
|
||||
endHandler: () => void;
|
||||
drainHandler?: () => void;
|
||||
}
|
||||
|
||||
export class ManagedResolvedAuthority {
|
||||
readonly makeConnection: () => Thenable<ManagedMessagePassing>;
|
||||
readonly connectionToken: string | undefined;
|
||||
|
||||
constructor(makeConnection: () => Thenable<ManagedMessagePassing>, connectionToken?: string);
|
||||
}
|
||||
|
||||
export interface ResolvedOptions {
|
||||
extensionHostEnv?: { [key: string]: string | null };
|
||||
|
||||
|
@ -109,7 +126,7 @@ declare module 'vscode' {
|
|||
Output = 2
|
||||
}
|
||||
|
||||
export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation;
|
||||
export type ResolverResult = (ResolvedAuthority | ManagedResolvedAuthority) & ResolvedOptions & TunnelInformation;
|
||||
|
||||
export class RemoteAuthorityResolverError extends Error {
|
||||
static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError;
|
||||
|
|
Loading…
Reference in a new issue