mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-05 20:44:43 +01:00
Merge pull request #180263 from microsoft/connor4312/inline-remote-resolver
remote: first cut at 'inline' remote resolvers
This commit is contained in:
@@ -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,
|
||||
send: d => remoteSocket.write(d),
|
||||
end: () => 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' });
|
||||
}));
|
||||
|
||||
+16
-12
@@ -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 {
|
||||
|
||||
@@ -1171,6 +1171,10 @@ export class PauseableEmitter<T> extends Emitter<T> {
|
||||
protected _eventQueue = new LinkedList<T>();
|
||||
private _mergeFn?: (input: T[]) => T;
|
||||
|
||||
public get isPaused(): boolean {
|
||||
return this._isPaused !== 0;
|
||||
}
|
||||
|
||||
constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) {
|
||||
super(options);
|
||||
this._mergeFn = options?.merge;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -341,7 +341,12 @@ export interface IExtension {
|
||||
*/
|
||||
export class ExtensionIdentifier {
|
||||
public readonly value: string;
|
||||
private readonly _lower: string;
|
||||
|
||||
/**
|
||||
* Do not use directly. This is public to avoid mangling and thus
|
||||
* allow compatibility between running from source and a built version.
|
||||
*/
|
||||
readonly _lower: string;
|
||||
|
||||
constructor(value: string) {
|
||||
this.value = value;
|
||||
|
||||
@@ -9,8 +9,8 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
||||
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 { ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, RemoteConnectionType, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
|
||||
export interface IWebSocketFactory {
|
||||
create(url: string, debugLabel: string): IWebSocket;
|
||||
@@ -265,23 +265,27 @@ class BrowserSocket implements ISocket {
|
||||
}
|
||||
|
||||
|
||||
export class BrowserSocketFactory implements ISocketFactory {
|
||||
export class BrowserSocketFactory implements ISocketFactory<RemoteConnectionType.WebSocket> {
|
||||
|
||||
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 {
|
||||
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));
|
||||
socket.onOpen(() => {
|
||||
errorListener.dispose();
|
||||
callback(undefined, new BrowserSocket(socket, debugLabel));
|
||||
supports(connectTo: WebSocketRemoteConnection): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
|
||||
return new Promise<ISocket>((resolve, reject) => {
|
||||
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(reject);
|
||||
socket.onOpen(() => {
|
||||
errorListener.dispose();
|
||||
resolve(new BrowserSocket(socket, debugLabel));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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, RemoteConnectionType, ResolvedAuthority, ResolvedOptions, ResolverResult, WebSocketRemoteConnection, 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.connectTo,
|
||||
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, connectTo: new WebSocketRemoteConnection(host, 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.connectTo.type === RemoteConnectionType.WebSocket) {
|
||||
// todo@connor4312 need to implement some kind of loopback for ext host based messaging
|
||||
RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.connectTo.host, resolvedAuthority.connectTo.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 {
|
||||
|
||||
@@ -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,8 +16,9 @@ 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, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
|
||||
const RECONNECT_TIMEOUT = 30 * 1000 /* 30s */;
|
||||
@@ -71,27 +72,18 @@ export interface OKMessage {
|
||||
export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage;
|
||||
|
||||
|
||||
interface ISimpleConnectionOptions {
|
||||
interface ISimpleConnectionOptions<T extends RemoteConnection = RemoteConnection> {
|
||||
commit: string | undefined;
|
||||
quality: string | undefined;
|
||||
host: string;
|
||||
port: number;
|
||||
connectTo: T;
|
||||
connectionToken: string | undefined;
|
||||
reconnectionToken: string;
|
||||
reconnectionProtocol: PersistentProtocol | null;
|
||||
socketFactory: ISocketFactory;
|
||||
remoteSocketFactoryService: IRemoteSocketFactoryService;
|
||||
signService: ISignService;
|
||||
logService: ILogService;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function createTimeoutCancellation(millis: number): CancellationToken {
|
||||
const source = new CancellationTokenSource();
|
||||
setTimeout(() => source.cancel(), millis);
|
||||
@@ -192,31 +184,29 @@ 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 extends RemoteConnection>(logService: ILogService, remoteSocketFactoryService: IRemoteSocketFactoryService, 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) => {
|
||||
|
||||
remoteSocketFactoryService.connect(connectTo, path, query, debugLabel).then((socket) => {
|
||||
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.`);
|
||||
if (err) {
|
||||
logService.error(err);
|
||||
}
|
||||
socket?.dispose();
|
||||
} else {
|
||||
if (err || !socket) {
|
||||
performance.mark(`code/didCreateSocketError/${debugConnectionType}`);
|
||||
logService.info(`Creating a socket (${debugLabel}) returned an error after ${sw.elapsed()} ms.`);
|
||||
result.reject(err);
|
||||
} else {
|
||||
performance.mark(`code/didCreateSocketOK/${debugConnectionType}`);
|
||||
logService.info(`Creating a socket (${debugLabel}) was successful after ${sw.elapsed()} ms.`);
|
||||
result.resolve(socket);
|
||||
}
|
||||
performance.mark(`code/didCreateSocketOK/${debugConnectionType}`);
|
||||
logService.info(`Creating a socket (${debugLabel}) was successful after ${sw.elapsed()} ms.`);
|
||||
result.resolve(socket);
|
||||
}
|
||||
}, (err) => {
|
||||
performance.mark(`code/didCreateSocketError/${debugConnectionType}`);
|
||||
logService.info(`Creating a socket (${debugLabel}) returned an error after ${sw.elapsed()} ms.`);
|
||||
logService.error(err);
|
||||
result.reject(err);
|
||||
});
|
||||
|
||||
return result.promise;
|
||||
}
|
||||
|
||||
@@ -237,14 +227,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<T extends RemoteConnection>(options: ISimpleConnectionOptions<T>, 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.remoteSocketFactoryService, 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,40 +379,38 @@ async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, sta
|
||||
return protocol;
|
||||
}
|
||||
|
||||
export interface IConnectionOptions {
|
||||
export interface IConnectionOptions<T extends RemoteConnection = RemoteConnection> {
|
||||
commit: string | undefined;
|
||||
quality: string | undefined;
|
||||
socketFactory: ISocketFactory;
|
||||
addressProvider: IAddressProvider;
|
||||
addressProvider: IAddressProvider<T>;
|
||||
remoteSocketFactoryService: IRemoteSocketFactoryService;
|
||||
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 extends RemoteConnection>(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,
|
||||
socketFactory: options.socketFactory,
|
||||
remoteSocketFactoryService: options.remoteSocketFactoryService,
|
||||
signService: options.signService,
|
||||
logService: options.logService
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAddress {
|
||||
host: string;
|
||||
port: number;
|
||||
export interface IAddress<T extends RemoteConnection = RemoteConnection> {
|
||||
connectTo: T;
|
||||
connectionToken: string | undefined;
|
||||
}
|
||||
|
||||
export interface IAddressProvider {
|
||||
getAddress(): Promise<IAddress>;
|
||||
export interface IAddressProvider<T extends RemoteConnection = RemoteConnection> {
|
||||
getAddress(): Promise<IAddress<T>>;
|
||||
}
|
||||
|
||||
export async function connectRemoteAgentManagement(options: IConnectionOptions, remoteAuthority: string, clientId: string): Promise<ManagementPersistentConnection> {
|
||||
@@ -448,7 +436,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 extends RemoteConnection>(options: IConnectionOptions<O>, connectionFactory: (simpleOptions: ISimpleConnectionOptions<O>) => Promise<T>): Promise<T> {
|
||||
const MAX_ATTEMPTS = 5;
|
||||
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
@@ -691,7 +679,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 +820,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,43 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
||||
|
||||
export const IRemoteAuthorityResolverService = createDecorator<IRemoteAuthorityResolverService>('remoteAuthorityResolverService');
|
||||
|
||||
export const enum RemoteConnectionType {
|
||||
WebSocket,
|
||||
Managed
|
||||
}
|
||||
|
||||
export class ManagedRemoteConnection {
|
||||
public readonly type = RemoteConnectionType.Managed;
|
||||
|
||||
constructor(
|
||||
public readonly id: number
|
||||
) { }
|
||||
|
||||
public toString(): string {
|
||||
return `Managed(${this.id})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class WebSocketRemoteConnection {
|
||||
public readonly type = RemoteConnectionType.WebSocket;
|
||||
|
||||
constructor(
|
||||
public readonly host: string,
|
||||
public readonly port: number,
|
||||
) { }
|
||||
|
||||
public toString(): string {
|
||||
return `WebSocket(${this.host}:${this.port})`;
|
||||
}
|
||||
}
|
||||
|
||||
export type RemoteConnection = WebSocketRemoteConnection | ManagedRemoteConnection;
|
||||
|
||||
export type RemoteConnectionOfType<T extends RemoteConnectionType> = RemoteConnection & { type: T };
|
||||
|
||||
export interface ResolvedAuthority {
|
||||
readonly authority: string;
|
||||
readonly host: string;
|
||||
readonly port: number;
|
||||
readonly connectTo: RemoteConnection;
|
||||
readonly connectionToken: string | undefined;
|
||||
}
|
||||
|
||||
@@ -50,8 +83,7 @@ export interface ResolverResult {
|
||||
}
|
||||
|
||||
export interface IRemoteConnectionData {
|
||||
host: string;
|
||||
port: number;
|
||||
connectTo: RemoteConnection;
|
||||
connectionToken: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { RemoteConnectionOfType, RemoteConnectionType, RemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
|
||||
export const IRemoteSocketFactoryService = createDecorator<IRemoteSocketFactoryService>('remoteSocketFactoryService');
|
||||
|
||||
export interface IRemoteSocketFactoryService {
|
||||
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 RemoteConnectionType>(type: T, factory: ISocketFactory<T>): IDisposable;
|
||||
|
||||
connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket>;
|
||||
}
|
||||
|
||||
export interface ISocketFactory<T extends RemoteConnectionType> {
|
||||
supports(connectTo: RemoteConnectionOfType<T>): boolean;
|
||||
connect(connectTo: RemoteConnectionOfType<T>, path: string, query: string, debugLabel: string): Promise<ISocket>;
|
||||
}
|
||||
|
||||
export class RemoteSocketFactoryService implements IRemoteSocketFactoryService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly factories: { [T in RemoteConnectionType]?: ISocketFactory<T>[] } = {};
|
||||
|
||||
public register<T extends RemoteConnectionType>(type: T, factory: ISocketFactory<T>): IDisposable {
|
||||
this.factories[type] ??= [];
|
||||
this.factories[type]!.push(factory);
|
||||
return toDisposable(() => {
|
||||
const idx = this.factories[type]?.indexOf(factory);
|
||||
if (typeof idx === 'number' && idx >= 0) {
|
||||
this.factories[type]?.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getSocketFactory<T extends RemoteConnectionType>(messagePassing: RemoteConnectionOfType<T>): ISocketFactory<T> | undefined {
|
||||
const factories = (this.factories[messagePassing.type] || []) as ISocketFactory<T>[];
|
||||
return factories.find(factory => factory.supports(messagePassing));
|
||||
}
|
||||
|
||||
public connect(connectTo: RemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
|
||||
const socketFactory = this.getSocketFactory(connectTo);
|
||||
if (!socketFactory) {
|
||||
throw new Error(`No socket factory found for ${connectTo}`);
|
||||
}
|
||||
return socketFactory.connect(connectTo, path, query, debugLabel);
|
||||
}
|
||||
}
|
||||
@@ -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, RemoteConnectionType, 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, { input: URI; result: 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();
|
||||
this._canonicalURIProvider = null;
|
||||
|
||||
RemoteAuthorities.setServerRootPath(getRemoteServerRootPath(productService));
|
||||
@@ -62,19 +37,22 @@ 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));
|
||||
this._canonicalURIRequests.set(key, request);
|
||||
const existing = this._canonicalURIRequests.get(key);
|
||||
if (existing) {
|
||||
return existing.result.p;
|
||||
}
|
||||
return this._canonicalURIRequests.get(key)!.promise;
|
||||
|
||||
const result = new DeferredPromise<URI>();
|
||||
this._canonicalURIProvider?.(uri).then((uri) => result.complete(uri), (err) => result.error(err));
|
||||
this._canonicalURIRequests.set(key, { input: uri, result });
|
||||
return result.p;
|
||||
}
|
||||
|
||||
getConnectionData(authority: string): IRemoteConnectionData | null {
|
||||
@@ -82,20 +60,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.connectTo,
|
||||
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 +80,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.connectTo.type === RemoteConnectionType.WebSocket) {
|
||||
// todo@connor4312 need to implement some kind of loopback for ext host based messaging
|
||||
RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.connectTo.host, resolvedAuthority.connectTo.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 +96,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 +108,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(({ result, input }) => {
|
||||
this._canonicalURIProvider!(input).then((uri) => result.complete(uri), (err) => result.error(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,43 +4,38 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as net from 'net';
|
||||
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { IConnectCallback, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { makeRawSocketHeaders } from 'vs/platform/remote/common/managedSocket';
|
||||
import { RemoteConnectionType, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
|
||||
export const nodeSocketFactory = new class implements ISocketFactory {
|
||||
connect(host: string, port: number, path: string, query: string, debugLabel: string, callback: IConnectCallback): void {
|
||||
const errorListener = (err: any) => callback(err, undefined);
|
||||
export const nodeSocketFactory = new class implements ISocketFactory<RemoteConnectionType.WebSocket> {
|
||||
|
||||
const socket = net.createConnection({ host: host, port: port }, () => {
|
||||
socket.removeListener('error', errorListener);
|
||||
supports(connectTo: WebSocketRemoteConnection): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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');
|
||||
connect({ host, port }: WebSocketRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
|
||||
return new Promise<ISocket>((resolve, reject) => {
|
||||
const socket = net.createConnection({ host: host, port: port }, () => {
|
||||
socket.removeListener('error', reject);
|
||||
|
||||
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();
|
||||
if (strData.indexOf('\r\n\r\n') >= 0) {
|
||||
// headers received OK
|
||||
socket.off('data', onData);
|
||||
callback(undefined, new NodeSocket(socket, debugLabel));
|
||||
}
|
||||
};
|
||||
socket.on('data', onData);
|
||||
const onData = (data: Buffer) => {
|
||||
const strData = data.toString();
|
||||
if (strData.indexOf('\r\n\r\n') >= 0) {
|
||||
// headers received OK
|
||||
socket.off('data', onData);
|
||||
resolve(new NodeSocket(socket, debugLabel));
|
||||
}
|
||||
};
|
||||
socket.on('data', onData);
|
||||
});
|
||||
// Disable Nagle's algorithm.
|
||||
socket.setNoDelay(true);
|
||||
socket.once('error', reject);
|
||||
});
|
||||
// Disable Nagle's algorithm.
|
||||
socket.setNoDelay(true);
|
||||
socket.once('error', errorListener);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
|
||||
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,
|
||||
@IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
|
||||
@ILogService logService: ILogService,
|
||||
@ISignService private readonly signService: ISignService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@@ -182,8 +182,8 @@ export class BaseTunnelService extends AbstractTunnelService {
|
||||
const options: IConnectionOptions = {
|
||||
commit: this.productService.commit,
|
||||
quality: this.productService.quality,
|
||||
socketFactory: this.socketFactory,
|
||||
addressProvider,
|
||||
remoteSocketFactoryService: this.remoteSocketFactoryService,
|
||||
signService: this.signService,
|
||||
logService: this.logService,
|
||||
ipcLogger: null
|
||||
@@ -199,12 +199,13 @@ export class BaseTunnelService extends AbstractTunnelService {
|
||||
|
||||
export class TunnelService extends BaseTunnelService {
|
||||
public constructor(
|
||||
@IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService,
|
||||
@ILogService logService: ILogService,
|
||||
@ISignService signService: ISignService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(nodeSocketFactory, logService, signService, productService, configurationService);
|
||||
super(remoteSocketFactoryService, logService, signService, productService, configurationService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +214,7 @@ export class SharedTunnelsService extends Disposable implements ISharedTunnelsSe
|
||||
private readonly _tunnelServices: Map<string, ITunnelService> = new Map();
|
||||
|
||||
public constructor(
|
||||
@IRemoteSocketFactoryService protected readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
|
||||
@ILogService protected readonly logService: ILogService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@ISignService private readonly signService: ISignService,
|
||||
@@ -224,7 +226,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.remoteSocketFactoryService, this.logService, this.signService, this.productService, this.configurationService);
|
||||
this._register(tunnelService);
|
||||
this._tunnelServices.set(authority, tunnelService);
|
||||
tunnelService.onTunnelClosed(async () => {
|
||||
|
||||
@@ -43,6 +43,7 @@ import './mainThreadLanguageFeatures';
|
||||
import './mainThreadLanguages';
|
||||
import './mainThreadLogService';
|
||||
import './mainThreadMessageService';
|
||||
import './mainThreadManagedSockets';
|
||||
import './mainThreadOutputService';
|
||||
import './mainThreadProgress';
|
||||
import './mainThreadQuickDiff';
|
||||
|
||||
@@ -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, ManagedRemoteConnection, RemoteConnection, RemoteConnectionType, ResolvedAuthority, WebSocketRemoteConnection } 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';
|
||||
@@ -26,6 +26,7 @@ import { IExtensionDescriptionDelta } from 'vs/workbench/services/extensions/com
|
||||
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 { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
|
||||
|
||||
@@ -199,8 +200,9 @@ 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 = reviveResolveAuthorityResult(await this._actual.$resolveAuthority(remoteAuthority, resolveAttempt));
|
||||
return resolved;
|
||||
}
|
||||
async getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI | null> {
|
||||
const uriComponents = await this._actual.$getCanonicalURI(remoteAuthority, uri);
|
||||
@@ -237,3 +239,31 @@ class ExtensionHostProxy implements IExtensionHostProxy {
|
||||
return this._actual.$test_down(size);
|
||||
}
|
||||
}
|
||||
|
||||
function reviveResolveAuthorityResult(result: Dto<IResolveAuthorityResult>): IResolveAuthorityResult {
|
||||
if (result.type === 'ok') {
|
||||
return {
|
||||
type: 'ok',
|
||||
value: {
|
||||
...result.value,
|
||||
authority: reviveResolvedAuthority(result.value.authority),
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function reviveResolvedAuthority(resolvedAuthority: Dto<ResolvedAuthority>): ResolvedAuthority {
|
||||
return {
|
||||
...resolvedAuthority,
|
||||
connectTo: reviveConnection(resolvedAuthority.connectTo),
|
||||
};
|
||||
}
|
||||
|
||||
function reviveConnection(connection: Dto<RemoteConnection>): RemoteConnection {
|
||||
if (connection.type === RemoteConnectionType.WebSocket) {
|
||||
return new WebSocketRemoteConnection(connection.host, connection.port);
|
||||
}
|
||||
return new ManagedRemoteConnection(connection.id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainContext, ExtHostContext, MainThreadManagedSocketsShape, ExtHostManagedSocketsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ManagedRemoteConnection, RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IRemoteSocketFactoryService, ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
import { ISocket, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event';
|
||||
import { makeRawSocketHeaders, socketRawEndHeaderSequence } from 'vs/platform/remote/common/managedSocket';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadManagedSockets)
|
||||
export class MainThreadManagedSockets extends Disposable implements MainThreadManagedSocketsShape {
|
||||
|
||||
private readonly _proxy: ExtHostManagedSocketsShape;
|
||||
private readonly _registrations = new Map<number, IDisposable>();
|
||||
private readonly _remoteSockets = new Map<number, RemoteSocketHalf>();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IRemoteSocketFactoryService private readonly _remoteSocketFactoryService: IRemoteSocketFactoryService,
|
||||
) {
|
||||
super();
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostManagedSockets);
|
||||
}
|
||||
|
||||
async $registerSocketFactory(socketFactoryId: number): Promise<void> {
|
||||
const that = this;
|
||||
const socketFactory = new class implements ISocketFactory<RemoteConnectionType.Managed> {
|
||||
|
||||
supports(connectTo: ManagedRemoteConnection): boolean {
|
||||
return (connectTo.id === socketFactoryId);
|
||||
}
|
||||
|
||||
connect(connectTo: ManagedRemoteConnection, path: string, query: string, debugLabel: string): Promise<ISocket> {
|
||||
return new Promise<ISocket>((resolve, reject) => {
|
||||
if (connectTo.id !== socketFactoryId) {
|
||||
return reject(new Error('Invalid connectTo'));
|
||||
}
|
||||
|
||||
const factoryId = connectTo.id;
|
||||
that._proxy.$openRemoteSocket(factoryId).then(socketId => {
|
||||
const half: RemoteSocketHalf = {
|
||||
onClose: new Emitter(),
|
||||
onData: new Emitter(),
|
||||
onEnd: new Emitter(),
|
||||
};
|
||||
that._remoteSockets.set(socketId, half);
|
||||
|
||||
ManagedSocket.connect(socketId, that._proxy, path, query, debugLabel, half)
|
||||
.then(
|
||||
socket => {
|
||||
socket.onDidDispose(() => that._remoteSockets.delete(socketId));
|
||||
resolve(socket);
|
||||
},
|
||||
err => {
|
||||
that._remoteSockets.delete(socketId);
|
||||
reject(err);
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
};
|
||||
this._registrations.set(socketFactoryId, this._remoteSocketFactoryService.register(RemoteConnectionType.Managed, socketFactory));
|
||||
|
||||
}
|
||||
|
||||
async $unregisterSocketFactory(socketFactoryId: number): Promise<void> {
|
||||
this._registrations.get(socketFactoryId)?.dispose();
|
||||
}
|
||||
|
||||
$onDidManagedSocketHaveData(socketId: number, data: VSBuffer): void {
|
||||
this._remoteSockets.get(socketId)?.onData.fire(data);
|
||||
}
|
||||
|
||||
$onDidManagedSocketClose(socketId: number, error: string | undefined): void {
|
||||
this._remoteSockets.get(socketId)?.onClose.fire({
|
||||
type: SocketCloseEventType.NodeSocketCloseEvent,
|
||||
error: error ? new Error(error) : undefined,
|
||||
hadError: !!error
|
||||
});
|
||||
this._remoteSockets.delete(socketId);
|
||||
}
|
||||
|
||||
$onDidManagedSocketEnd(socketId: number): void {
|
||||
this._remoteSockets.get(socketId)?.onEnd.fire();
|
||||
}
|
||||
}
|
||||
|
||||
export interface RemoteSocketHalf {
|
||||
onData: Emitter<VSBuffer>;
|
||||
onClose: Emitter<SocketCloseEvent>;
|
||||
onEnd: Emitter<void>;
|
||||
}
|
||||
|
||||
export class ManagedSocket extends Disposable implements ISocket {
|
||||
public static connect(
|
||||
socketId: number,
|
||||
proxy: ExtHostManagedSocketsShape,
|
||||
path: string, query: string, debugLabel: string,
|
||||
|
||||
half: RemoteSocketHalf
|
||||
): 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) => {
|
||||
let dataSoFar: VSBuffer | undefined;
|
||||
d.add(socket.onData(d => {
|
||||
if (!dataSoFar) {
|
||||
dataSoFar = d;
|
||||
} else {
|
||||
dataSoFar = VSBuffer.concat([dataSoFar, d], dataSoFar.byteLength + d.byteLength);
|
||||
}
|
||||
|
||||
const index = dataSoFar.indexOf(socketRawEndHeaderSequence);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(socket);
|
||||
// pause data events until the socket consumer is hooked up. We may
|
||||
// immediately emit remaining data, but if not there may still be
|
||||
// microtasks queued which would fire data into the abyss.
|
||||
socket.pauseData();
|
||||
|
||||
const rest = dataSoFar.slice(index + socketRawEndHeaderSequence.byteLength);
|
||||
if (rest.byteLength) {
|
||||
half.onData.fire(rest);
|
||||
}
|
||||
}));
|
||||
|
||||
d.add(socket.onClose(err => reject(err ?? new Error('socket closed'))));
|
||||
d.add(socket.onEnd(() => reject(new Error('socket ended'))));
|
||||
}).finally(() => d.dispose());
|
||||
}
|
||||
|
||||
private readonly pausableDataEmitter = this._register(new PauseableEmitter<VSBuffer>());
|
||||
|
||||
public onData: Event<VSBuffer> = (...args) => {
|
||||
if (this.pausableDataEmitter.isPaused) {
|
||||
queueMicrotask(() => this.pausableDataEmitter.resume());
|
||||
}
|
||||
return this.pausableDataEmitter.event(...args);
|
||||
};
|
||||
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: ExtHostManagedSocketsShape,
|
||||
private readonly debugLabel: string,
|
||||
onCloseEmitter: Emitter<SocketCloseEvent>,
|
||||
onDataEmitter: Emitter<VSBuffer>,
|
||||
onEndEmitter: Emitter<void>,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(onDataEmitter);
|
||||
this._register(onDataEmitter.event(data => this.pausableDataEmitter.fire(data)));
|
||||
|
||||
this.onClose = this._register(onCloseEmitter).event;
|
||||
this.onEnd = this._register(onEndEmitter).event;
|
||||
}
|
||||
|
||||
/** Pauses data events until a new listener comes in onData() */
|
||||
pauseData() {
|
||||
this.pausableDataEmitter.pause();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,7 @@ import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInterac
|
||||
import { ExtHostNotebookDocumentSaveParticipant } from 'vs/workbench/api/common/extHostNotebookDocumentSaveParticipant';
|
||||
import { ExtHostSemanticSimilarity } from 'vs/workbench/api/common/extHostSemanticSimilarity';
|
||||
import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter';
|
||||
import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
|
||||
|
||||
export interface IExtensionRegistries {
|
||||
mine: ExtensionDescriptionRegistry;
|
||||
@@ -136,6 +137,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const extHostWindow = accessor.get(IExtHostWindow);
|
||||
const extHostSecretState = accessor.get(IExtHostSecretState);
|
||||
const extHostEditorTabs = accessor.get(IExtHostEditorTabs);
|
||||
const extHostManagedSockets = accessor.get(IExtHostManagedSockets);
|
||||
|
||||
// register addressable instances
|
||||
rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo);
|
||||
@@ -149,6 +151,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
rpcProtocol.set(ExtHostContext.ExtHostSecretState, extHostSecretState);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostTelemetry, extHostTelemetry);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostEditorTabs, extHostEditorTabs);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostManagedSockets, extHostManagedSockets);
|
||||
|
||||
// automatically create and register addressable instances
|
||||
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
|
||||
@@ -1444,6 +1447,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,
|
||||
|
||||
@@ -27,6 +27,7 @@ import { ExtHostLoggerService } from 'vs/workbench/api/common/extHostLoggerServi
|
||||
import { ILoggerService } from 'vs/platform/log/common/log';
|
||||
import { ExtHostVariableResolverProviderService, IExtHostVariableResolverProvider } from 'vs/workbench/api/common/extHostVariableResolverService';
|
||||
import { ExtHostLocalizationService, IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService';
|
||||
import { ExtHostManagedSockets, IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
|
||||
|
||||
registerSingleton(IExtHostLocalizationService, ExtHostLocalizationService, InstantiationType.Delayed);
|
||||
registerSingleton(ILoggerService, ExtHostLoggerService, InstantiationType.Delayed);
|
||||
@@ -37,6 +38,7 @@ registerSingleton(IExtHostConsumerFileSystem, ExtHostConsumerFileSystem, Instant
|
||||
registerSingleton(IExtHostDebugService, WorkerExtHostDebugService, InstantiationType.Eager);
|
||||
registerSingleton(IExtHostDecorations, ExtHostDecorations, InstantiationType.Eager);
|
||||
registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors, InstantiationType.Eager);
|
||||
registerSingleton(IExtHostManagedSockets, ExtHostManagedSockets, InstantiationType.Eager);
|
||||
registerSingleton(IExtHostFileSystemInfo, ExtHostFileSystemInfo, InstantiationType.Eager);
|
||||
registerSingleton(IExtHostOutputService, ExtHostOutputService, InstantiationType.Delayed);
|
||||
registerSingleton(IExtHostSearch, ExtHostSearch, InstantiationType.Eager);
|
||||
|
||||
@@ -957,6 +957,21 @@ export interface ExtHostWebviewViewsShape {
|
||||
$disposeWebviewView(webviewHandle: WebviewHandle): void;
|
||||
}
|
||||
|
||||
export interface MainThreadManagedSocketsShape extends IDisposable {
|
||||
$registerSocketFactory(socketFactoryId: number): Promise<void>;
|
||||
$unregisterSocketFactory(socketFactoryId: number): Promise<void>;
|
||||
$onDidManagedSocketHaveData(socketId: number, data: VSBuffer): void;
|
||||
$onDidManagedSocketClose(socketId: number, error: string | undefined): void;
|
||||
$onDidManagedSocketEnd(socketId: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostManagedSocketsShape {
|
||||
$openRemoteSocket(socketFactoryId: number): Promise<number>;
|
||||
$remoteSocketWrite(socketId: number, buffer: VSBuffer): void;
|
||||
$remoteSocketEnd(socketId: number): void;
|
||||
$remoteSocketDrain(socketId: number): Promise<void>;
|
||||
}
|
||||
|
||||
export enum CellOutputKind {
|
||||
Text = 1,
|
||||
Error = 2,
|
||||
@@ -1590,7 +1605,7 @@ export interface ExtHostSearchShape {
|
||||
}
|
||||
|
||||
export interface ExtHostExtensionServiceShape {
|
||||
$resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult>;
|
||||
$resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<Dto<IResolveAuthorityResult>>;
|
||||
/**
|
||||
* Returns `null` if no resolver for `remoteAuthority` is found.
|
||||
*/
|
||||
@@ -2518,6 +2533,7 @@ export const MainContext = {
|
||||
MainThreadInteractiveEditor: createProxyIdentifier<MainThreadInteractiveEditorShape>('MainThreadInteractiveEditor'),
|
||||
MainThreadTheming: createProxyIdentifier<MainThreadThemingShape>('MainThreadTheming'),
|
||||
MainThreadTunnelService: createProxyIdentifier<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
|
||||
MainThreadManagedSockets: createProxyIdentifier<MainThreadManagedSocketsShape>('MainThreadManagedSockets'),
|
||||
MainThreadTimeline: createProxyIdentifier<MainThreadTimelineShape>('MainThreadTimeline'),
|
||||
MainThreadTesting: createProxyIdentifier<MainThreadTestingShape>('MainThreadTesting'),
|
||||
MainThreadLocalization: createProxyIdentifier<MainThreadLocalizationShape>('MainThreadLocalizationShape'),
|
||||
@@ -2579,6 +2595,7 @@ export const ExtHostContext = {
|
||||
ExtHostSemanticSimilarity: createProxyIdentifier<ExtHostSemanticSimilarityShape>('ExtHostSemanticSimilarity'),
|
||||
ExtHostTheming: createProxyIdentifier<ExtHostThemingShape>('ExtHostTheming'),
|
||||
ExtHostTunnelService: createProxyIdentifier<ExtHostTunnelServiceShape>('ExtHostTunnelService'),
|
||||
ExtHostManagedSockets: createProxyIdentifier<ExtHostManagedSocketsShape>('ExtHostManagedSockets'),
|
||||
ExtHostAuthentication: createProxyIdentifier<ExtHostAuthenticationShape>('ExtHostAuthentication'),
|
||||
ExtHostTimeline: createProxyIdentifier<ExtHostTimelineShape>('ExtHostTimeline'),
|
||||
ExtHostTesting: createProxyIdentifier<ExtHostTestingShape>('ExtHostTesting'),
|
||||
|
||||
@@ -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, ManagedRemoteConnection, WebSocketRemoteConnection } 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';
|
||||
@@ -43,6 +43,8 @@ import { IResolveAuthorityResult } from 'vs/workbench/services/extensions/common
|
||||
import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { setTimeout0 } from 'vs/base/common/platform';
|
||||
import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
|
||||
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
|
||||
|
||||
interface ITestRunner {
|
||||
/** Old test runner API, as exported from `vscode/lib/testrunner` */
|
||||
@@ -130,7 +132,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
@IExtensionStoragePaths storagePath: IExtensionStoragePaths,
|
||||
@IExtHostTunnelService extHostTunnelService: IExtHostTunnelService,
|
||||
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService,
|
||||
@IExtHostLocalizationService extHostLocalizationService: IExtHostLocalizationService
|
||||
@IExtHostLocalizationService extHostLocalizationService: IExtHostLocalizationService,
|
||||
@IExtHostManagedSockets private readonly _extHostManagedSockets: IExtHostManagedSockets,
|
||||
) {
|
||||
super();
|
||||
this._hostUtils = hostUtils;
|
||||
@@ -791,7 +794,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
||||
return { authorityPrefix, resolver: this._resolvers[authorityPrefix] };
|
||||
}
|
||||
|
||||
public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {
|
||||
public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<Dto<IResolveAuthorityResult>> {
|
||||
const sw = StopWatch.create(false);
|
||||
const prefix = () => `[resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthority)},${resolveAttempt})][${sw.elapsed()}ms] `;
|
||||
const logInfo = (msg: string) => this._logService.info(`${prefix()}${msg}`);
|
||||
@@ -822,30 +825,49 @@ 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,
|
||||
connectTo: new WebSocketRemoteConnection(result.host, result.port),
|
||||
connectionToken: result.connectionToken
|
||||
};
|
||||
} else {
|
||||
// The socket factory is identified by the `resolveAttempt`, since that is a number which
|
||||
// always increments and is unique over all resolve() calls in a workbench session.
|
||||
const socketFactoryId = resolveAttempt;
|
||||
|
||||
// There is only on managed socket factory at a time, so we can just overwrite the old one.
|
||||
this._extHostManagedSockets.setFactory(socketFactoryId, result.makeConnection);
|
||||
|
||||
authority = {
|
||||
authority: remoteAuthority,
|
||||
connectTo: new ManagedRemoteConnection(socketFactoryId),
|
||||
connectionToken: result.connectionToken
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'ok',
|
||||
value: {
|
||||
authority,
|
||||
authority: authority as Dto<ResolvedAuthority>,
|
||||
options,
|
||||
tunnelInformation: {
|
||||
environmentTunnels: result.environmentTunnels,
|
||||
features: result.tunnelFeatures
|
||||
}
|
||||
tunnelInformation,
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtHostManagedSocketsShape, MainContext, MainThreadManagedSocketsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
export interface IExtHostManagedSockets extends ExtHostManagedSocketsShape {
|
||||
setFactory(socketFactoryId: number, makeConnection: () => Thenable<vscode.ManagedMessagePassing>): void;
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
export const IExtHostManagedSockets = createDecorator<IExtHostManagedSockets>('IExtHostManagedSockets');
|
||||
|
||||
export class ExtHostManagedSockets implements IExtHostManagedSockets {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _proxy: MainThreadManagedSocketsShape;
|
||||
private _remoteSocketIdCounter = 0;
|
||||
private _factory: ManagedSocketFactory | null = null;
|
||||
private readonly _managedRemoteSockets: Map<number, ManagedSocket> = new Map();
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService extHostRpc: IExtHostRpcService,
|
||||
) {
|
||||
this._proxy = extHostRpc.getProxy(MainContext.MainThreadManagedSockets);
|
||||
}
|
||||
|
||||
setFactory(socketFactoryId: number, makeConnection: () => Thenable<vscode.ManagedMessagePassing>): void {
|
||||
// Terminate all previous sockets
|
||||
for (const socket of this._managedRemoteSockets.values()) {
|
||||
// calling dispose() will lead to it removing itself from the map
|
||||
socket.dispose();
|
||||
}
|
||||
// Unregister previous factory
|
||||
if (this._factory) {
|
||||
this._proxy.$unregisterSocketFactory(this._factory.socketFactoryId);
|
||||
}
|
||||
|
||||
this._factory = new ManagedSocketFactory(socketFactoryId, makeConnection);
|
||||
this._proxy.$registerSocketFactory(this._factory.socketFactoryId);
|
||||
}
|
||||
|
||||
async $openRemoteSocket(socketFactoryId: number): Promise<number> {
|
||||
if (!this._factory || this._factory.socketFactoryId !== socketFactoryId) {
|
||||
throw new Error(`No socket factory with id ${socketFactoryId}`);
|
||||
}
|
||||
|
||||
const id = (++this._remoteSocketIdCounter);
|
||||
const socket = await this._factory.makeConnection();
|
||||
const disposable = new DisposableStore();
|
||||
this._managedRemoteSockets.set(id, new ManagedSocket(id, socket, disposable));
|
||||
|
||||
disposable.add(toDisposable(() => this._managedRemoteSockets.delete(id)));
|
||||
disposable.add(socket.onDidEnd(() => {
|
||||
this._proxy.$onDidManagedSocketEnd(id);
|
||||
disposable.dispose();
|
||||
}));
|
||||
disposable.add(socket.onDidClose(e => {
|
||||
this._proxy.$onDidManagedSocketClose(id, e?.stack ?? e?.message);
|
||||
disposable.dispose();
|
||||
}));
|
||||
disposable.add(socket.onDidReceiveMessage(e => this._proxy.$onDidManagedSocketHaveData(id, VSBuffer.wrap(e))));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
$remoteSocketWrite(socketId: number, buffer: VSBuffer): void {
|
||||
this._managedRemoteSockets.get(socketId)?.actual.send(buffer.buffer);
|
||||
}
|
||||
|
||||
$remoteSocketEnd(socketId: number): void {
|
||||
const socket = this._managedRemoteSockets.get(socketId);
|
||||
if (socket) {
|
||||
socket.actual.end();
|
||||
socket.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
async $remoteSocketDrain(socketId: number): Promise<void> {
|
||||
await this._managedRemoteSockets.get(socketId)?.actual.drain?.();
|
||||
}
|
||||
}
|
||||
|
||||
class ManagedSocketFactory {
|
||||
constructor(
|
||||
public readonly socketFactoryId: number,
|
||||
public readonly makeConnection: () => Thenable<vscode.ManagedMessagePassing>,
|
||||
) { }
|
||||
}
|
||||
|
||||
class ManagedSocket extends Disposable {
|
||||
constructor(
|
||||
public readonly socketId: number,
|
||||
public readonly actual: vscode.ManagedMessagePassing,
|
||||
disposer: DisposableStore,
|
||||
) {
|
||||
super();
|
||||
this._register(disposer);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { disposableTimeout, timeout } from 'vs/base/common/async';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { SocketCloseEvent } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { mock } from 'vs/base/test/common/mock';
|
||||
import { ManagedSocket, RemoteSocketHalf } from 'vs/workbench/api/browser/mainThreadManagedSockets';
|
||||
import { ExtHostManagedSocketsShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
|
||||
suite('MainThreadManagedSockets', () => {
|
||||
|
||||
suite('ManagedSocket', () => {
|
||||
let extHost: ExtHostMock;
|
||||
let half: RemoteSocketHalf;
|
||||
|
||||
class ExtHostMock extends mock<ExtHostManagedSocketsShape>() {
|
||||
private onDidFire = new Emitter<void>();
|
||||
public readonly events: any[] = [];
|
||||
|
||||
override $remoteSocketWrite(socketId: number, buffer: VSBuffer): void {
|
||||
this.events.push({ socketId, data: buffer.toString() });
|
||||
this.onDidFire.fire();
|
||||
}
|
||||
|
||||
override $remoteSocketDrain(socketId: number) {
|
||||
this.events.push({ socketId, event: 'drain' });
|
||||
this.onDidFire.fire();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
override $remoteSocketEnd(socketId: number) {
|
||||
this.events.push({ socketId, event: 'end' });
|
||||
this.onDidFire.fire();
|
||||
}
|
||||
|
||||
expectEvent(test: (evt: any) => void, message: string) {
|
||||
if (this.events.some(test)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const d = new DisposableStore();
|
||||
return new Promise<void>(resolve => {
|
||||
d.add(this.onDidFire.event(() => {
|
||||
if (this.events.some(test)) {
|
||||
return;
|
||||
}
|
||||
}));
|
||||
d.add(disposableTimeout(() => {
|
||||
throw new Error(`Expected ${message} but only had ${JSON.stringify(this.events, null, 2)}`);
|
||||
}, 1000));
|
||||
}).finally(() => d.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
setup(() => {
|
||||
extHost = new ExtHostMock();
|
||||
half = {
|
||||
onClose: new Emitter<SocketCloseEvent>(),
|
||||
onData: new Emitter<VSBuffer>(),
|
||||
onEnd: new Emitter<void>(),
|
||||
};
|
||||
});
|
||||
|
||||
async function doConnect() {
|
||||
const socket = ManagedSocket.connect(1, extHost, '/hello', 'world=true', '', half);
|
||||
await extHost.expectEvent(evt => evt.data && evt.data.startsWith('GET ws://localhost/hello?world=true&skipWebSocketFrames=true HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key:'), 'websocket open event');
|
||||
half.onData.fire(VSBuffer.fromString('Opened successfully ;)\r\n\r\n'));
|
||||
return await socket;
|
||||
}
|
||||
|
||||
test('connects', async () => {
|
||||
await doConnect();
|
||||
});
|
||||
|
||||
test('includes trailing connection data', async () => {
|
||||
const socketProm = ManagedSocket.connect(1, extHost, '/hello', 'world=true', '', half);
|
||||
await extHost.expectEvent(evt => evt.data && evt.data.includes('GET ws://localhost'), 'websocket open event');
|
||||
half.onData.fire(VSBuffer.fromString('Opened successfully ;)\r\n\r\nSome trailing data'));
|
||||
const socket = await socketProm;
|
||||
|
||||
const data: string[] = [];
|
||||
socket.onData(d => data.push(d.toString()));
|
||||
await timeout(1); // allow microtasks to flush
|
||||
assert.deepStrictEqual(data, ['Some trailing data']);
|
||||
});
|
||||
|
||||
test('round trips data', async () => {
|
||||
const socket = await doConnect();
|
||||
const data: string[] = [];
|
||||
socket.onData(d => data.push(d.toString()));
|
||||
|
||||
socket.write(VSBuffer.fromString('ping'));
|
||||
await extHost.expectEvent(evt => evt.data === 'ping', 'expected ping');
|
||||
half.onData.fire(VSBuffer.fromString("pong"));
|
||||
assert.deepStrictEqual(data, ['pong']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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, RemoteConnectionType } 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 { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { UserDataProfileInitializer } from 'vs/workbench/services/userDataProfile/browser/userDataProfileInit';
|
||||
@@ -257,7 +259,7 @@ export class BrowserMain extends Disposable {
|
||||
|
||||
// Remote
|
||||
const connectionToken = environmentService.options.connectionToken || getCookieValue(connectionTokenCookieName);
|
||||
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(connectionToken, this.configuration.resourceUriProvider, productService, logService);
|
||||
const remoteAuthorityResolverService = new RemoteAuthorityResolverService(!environmentService.expectsResolverExtension, connectionToken, this.configuration.resourceUriProvider, productService, logService);
|
||||
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
|
||||
|
||||
// Signing
|
||||
@@ -300,7 +302,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 remoteSocketFactoryService = new RemoteSocketFactoryService();
|
||||
remoteSocketFactoryService.register(RemoteConnectionType.WebSocket, new BrowserSocketFactory(this.configuration.webSocketFactory));
|
||||
serviceCollection.set(IRemoteSocketFactoryService, remoteSocketFactoryService);
|
||||
const remoteAgentService = this._register(new RemoteAgentService(remoteSocketFactoryService, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
|
||||
serviceCollection.set(IRemoteAgentService, remoteAgentService);
|
||||
this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService));
|
||||
|
||||
@@ -514,7 +519,7 @@ export class BrowserMain extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private async createWorkspaceService(workspace: IAnyWorkspaceIdentifier, environmentService: IWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, userDataProfilesService: IUserDataProfilesService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise<WorkspaceService> {
|
||||
private async createWorkspaceService(workspace: IAnyWorkspaceIdentifier, environmentService: IBrowserWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, userDataProfilesService: IUserDataProfilesService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise<WorkspaceService> {
|
||||
|
||||
// Temporary workspaces do not exist on startup because they are
|
||||
// just in memory. As such, detect this case and eagerly create
|
||||
|
||||
@@ -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, RemoteConnectionType } 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 { RemoteSocketFactoryService, IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
|
||||
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 remoteSocketFactoryService = new RemoteSocketFactoryService();
|
||||
remoteSocketFactoryService.register(RemoteConnectionType.WebSocket, new BrowserSocketFactory(null));
|
||||
serviceCollection.set(IRemoteSocketFactoryService, remoteSocketFactoryService);
|
||||
const remoteAgentService = this._register(new RemoteAgentService(remoteSocketFactoryService, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService));
|
||||
serviceCollection.set(IRemoteAgentService, remoteAgentService);
|
||||
|
||||
// Remote Files
|
||||
|
||||
@@ -45,6 +45,7 @@ import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userData
|
||||
import { updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
|
||||
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
|
||||
function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined {
|
||||
return userDataProfile.isDefault
|
||||
@@ -105,7 +106,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
|
||||
|
||||
constructor(
|
||||
{ remoteAuthority, configurationCache }: { remoteAuthority?: string; configurationCache: IConfigurationCache },
|
||||
environmentService: IWorkbenchEnvironmentService,
|
||||
environmentService: IBrowserWorkbenchEnvironmentService,
|
||||
private readonly userDataProfileService: IUserDataProfileService,
|
||||
private readonly userDataProfilesService: IUserDataProfilesService,
|
||||
private readonly fileService: IFileService,
|
||||
|
||||
@@ -40,12 +40,12 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentService';
|
||||
import { getSingleFolderWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
|
||||
import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService';
|
||||
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 { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
|
||||
const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });
|
||||
|
||||
@@ -60,7 +60,7 @@ suite('ConfigurationEditing', () => {
|
||||
|
||||
let instantiationService: TestInstantiationService;
|
||||
let userDataProfileService: IUserDataProfileService;
|
||||
let environmentService: IWorkbenchEnvironmentService;
|
||||
let environmentService: IBrowserWorkbenchEnvironmentService;
|
||||
let fileService: IFileService;
|
||||
let workspaceService: WorkspaceService;
|
||||
let testObject: ConfigurationEditing;
|
||||
@@ -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);
|
||||
|
||||
@@ -39,7 +39,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
|
||||
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
||||
import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
import { BrowserWorkbenchEnvironmentService, IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentService';
|
||||
import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
@@ -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 { RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
|
||||
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 RemoteSocketFactoryService(), 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 RemoteSocketFactoryService(), 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 RemoteSocketFactoryService(), 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);
|
||||
@@ -695,7 +696,7 @@ suite('WorkspaceService - Initialization', () => {
|
||||
|
||||
suite('WorkspaceConfigurationService - Folder', () => {
|
||||
|
||||
let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService;
|
||||
let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IBrowserWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService;
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
|
||||
@@ -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);
|
||||
@@ -1519,7 +1520,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
|
||||
|
||||
suite('WorkspaceConfigurationService - Profiles', () => {
|
||||
|
||||
let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService;
|
||||
let testObject: WorkspaceService, workspaceService: WorkspaceService, fileService: IFileService, environmentService: IBrowserWorkbenchEnvironmentService, userDataProfileService: IUserDataProfileService, instantiationService: TestInstantiationService;
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -32,6 +32,11 @@ export interface IBrowserWorkbenchEnvironmentService extends IWorkbenchEnvironme
|
||||
* Options used to configure the workbench.
|
||||
*/
|
||||
readonly options?: IWorkbenchConstructionOptions;
|
||||
|
||||
/**
|
||||
* Gets whether a resolver extension is expected for the environment.
|
||||
*/
|
||||
readonly expectsResolverExtension: boolean;
|
||||
}
|
||||
|
||||
export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService {
|
||||
@@ -41,6 +46,11 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi
|
||||
@memoize
|
||||
get remoteAuthority(): string | undefined { return this.options.remoteAuthority; }
|
||||
|
||||
@memoize
|
||||
get expectsResolverExtension(): boolean {
|
||||
return !!this.options.remoteAuthority?.includes('+') && !this.options.webSocketFactory;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get isBuilt(): boolean { return !!this.productService.commit; }
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment
|
||||
@memoize
|
||||
get remoteAuthority() { return this.configuration.remoteAuthority; }
|
||||
|
||||
@memoize
|
||||
get expectsResolverExtension() { return !!this.configuration.remoteAuthority?.includes('+'); }
|
||||
|
||||
@memoize
|
||||
get execPath() { return this.configuration.execPath; }
|
||||
|
||||
|
||||
@@ -14,25 +14,28 @@ import { IAutomatedWindow, getLogs } from 'vs/platform/log/browser/log';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
|
||||
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
|
||||
import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IWebWorkerExtensionHostDataProvider, IWebWorkerExtensionHostInitData, WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
|
||||
import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider';
|
||||
import { AbstractExtensionService, IExtensionHostFactory, ResolvedExtensions } from 'vs/workbench/services/extensions/common/abstractExtensionService';
|
||||
import { AbstractExtensionService, IExtensionHostFactory, ResolvedExtensions, checkEnabledAndProposedAPI } from 'vs/workbench/services/extensions/common/abstractExtensionService';
|
||||
import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/extensionHostKind';
|
||||
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
|
||||
import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation';
|
||||
import { ExtensionRunningLocationTracker } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker';
|
||||
import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker';
|
||||
import { ExtensionHostStartup, IExtensionHost, IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionsProposedApi } from 'vs/workbench/services/extensions/common/extensionsProposedApi';
|
||||
import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { IRemoteExtensionHostDataProvider, RemoteExtensionHost } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
|
||||
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
|
||||
@@ -41,7 +44,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IBrowserWorkbenchEnvironmentService private readonly _browserEnvironmentService: IBrowserWorkbenchEnvironmentService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@IFileService fileService: IFileService,
|
||||
@@ -58,13 +61,18 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService,
|
||||
@IUserDataProfileService private readonly _userDataProfileService: IUserDataProfileService,
|
||||
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
|
||||
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
|
||||
) {
|
||||
const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi);
|
||||
const extensionHostFactory = new BrowserExtensionHostFactory(
|
||||
extensionsProposedApi,
|
||||
() => this._scanWebExtensions(),
|
||||
() => this._getExtensions(),
|
||||
instantiationService,
|
||||
remoteAgentService,
|
||||
remoteAuthorityResolverService
|
||||
remoteAuthorityResolverService,
|
||||
extensionEnablementService
|
||||
);
|
||||
super(
|
||||
extensionsProposedApi,
|
||||
@@ -72,7 +80,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
new BrowserExtensionHostKindPicker(logService),
|
||||
instantiationService,
|
||||
notificationService,
|
||||
environmentService,
|
||||
_browserEnvironmentService,
|
||||
telemetryService,
|
||||
extensionEnablementService,
|
||||
fileService,
|
||||
@@ -84,7 +92,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
logService,
|
||||
remoteAgentService,
|
||||
remoteExtensionsScannerService,
|
||||
lifecycleService
|
||||
lifecycleService,
|
||||
remoteAuthorityResolverService
|
||||
);
|
||||
|
||||
// Initialize installed extensions first and do it only after workbench is ready
|
||||
@@ -129,8 +138,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
return dedupExtensions(system, user, development, this._logService);
|
||||
}
|
||||
|
||||
protected async _resolveExtensions(): Promise<ResolvedExtensions> {
|
||||
// fetch the remote environment
|
||||
protected async _resolveExtensionsDefault() {
|
||||
const [localExtensions, remoteExtensions] = await Promise.all([
|
||||
this._scanWebExtensions(),
|
||||
this._remoteExtensionsScannerService.scanExtensions()
|
||||
@@ -139,6 +147,50 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
return new ResolvedExtensions(localExtensions, remoteExtensions, /*hasLocalProcess*/false, /*allowRemoteExtensionsInLocalWebWorker*/true);
|
||||
}
|
||||
|
||||
protected async _resolveExtensions(): Promise<ResolvedExtensions> {
|
||||
if (!this._browserEnvironmentService.expectsResolverExtension) {
|
||||
return this._resolveExtensionsDefault();
|
||||
}
|
||||
|
||||
const remoteAuthority = this._environmentService.remoteAuthority!;
|
||||
|
||||
// Now that the canonical URI provider has been registered, we need to wait for the trust state to be
|
||||
// calculated. The trust state will be used while resolving the authority, however the resolver can
|
||||
// override the trust state through the resolver result.
|
||||
await this._workspaceTrustManagementService.workspaceResolved;
|
||||
|
||||
|
||||
let resolverResult: ResolverResult;
|
||||
try {
|
||||
resolverResult = await this._resolveAuthorityInitial(remoteAuthority);
|
||||
} catch (err) {
|
||||
if (RemoteAuthorityResolverError.isHandled(err)) {
|
||||
console.log(`Error handled: Not showing a notification for the error`);
|
||||
}
|
||||
this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);
|
||||
|
||||
// Proceed with the local extension host
|
||||
return this._resolveExtensionsDefault();
|
||||
}
|
||||
|
||||
// set the resolved authority
|
||||
this._remoteAuthorityResolverService._setResolvedAuthority(resolverResult.authority, resolverResult.options);
|
||||
this._remoteExplorerService.setTunnelInformation(resolverResult.tunnelInformation);
|
||||
|
||||
// monitor for breakage
|
||||
const connection = this._remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
connection.onDidStateChange(async (e) => {
|
||||
if (e.type === PersistentConnectionEventType.ConnectionLost) {
|
||||
this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);
|
||||
}
|
||||
});
|
||||
connection.onReconnecting(() => this._resolveAuthorityAgain());
|
||||
}
|
||||
|
||||
return this._resolveExtensionsDefault();
|
||||
}
|
||||
|
||||
protected async _onExtensionHostExit(code: number): Promise<void> {
|
||||
// Dispose everything associated with the extension host
|
||||
this._doStopExtensionHosts();
|
||||
@@ -149,15 +201,22 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
automatedWindow.codeAutomationExit(code, await getLogs(this._fileService, this._environmentService));
|
||||
}
|
||||
}
|
||||
|
||||
protected async _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {
|
||||
return this._resolveAuthorityOnExtensionHosts(ExtensionHostKind.LocalWebWorker, remoteAuthority);
|
||||
}
|
||||
}
|
||||
|
||||
class BrowserExtensionHostFactory implements IExtensionHostFactory {
|
||||
|
||||
constructor(
|
||||
private readonly _extensionsProposedApi: ExtensionsProposedApi,
|
||||
private readonly _scanWebExtensions: () => Promise<IExtensionDescription[]>,
|
||||
private readonly _getExtensions: () => Promise<IExtensionDescription[]>,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
) { }
|
||||
|
||||
createExtensionHost(runningLocations: ExtensionRunningLocationTracker, runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null {
|
||||
@@ -166,27 +225,39 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory {
|
||||
return null;
|
||||
}
|
||||
case ExtensionHostKind.LocalWebWorker: {
|
||||
return this._instantiationService.createInstance(WebWorkerExtensionHost, runningLocation, ExtensionHostStartup.EagerAutoStart, this._createLocalExtensionHostDataProvider(runningLocations, runningLocation));
|
||||
return this._instantiationService.createInstance(WebWorkerExtensionHost, runningLocation, ExtensionHostStartup.EagerAutoStart, this._createLocalExtensionHostDataProvider(runningLocations, runningLocation, isInitialStart));
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _createLocalExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: ExtensionRunningLocation): IWebWorkerExtensionHostDataProvider {
|
||||
private _createLocalExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: ExtensionRunningLocation, isInitialStart: boolean): IWebWorkerExtensionHostDataProvider {
|
||||
return {
|
||||
getInitData: async (): Promise<IWebWorkerExtensionHostInitData> => {
|
||||
const allExtensions = await this._getExtensions();
|
||||
const localWebWorkerExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation);
|
||||
return {
|
||||
allExtensions: allExtensions,
|
||||
myExtensions: localWebWorkerExtensions.map(extension => extension.identifier)
|
||||
};
|
||||
if (isInitialStart) {
|
||||
// Here we load even extensions that would be disabled by workspace trust
|
||||
const localExtensions = checkEnabledAndProposedAPI(this._extensionEnablementService, this._extensionsProposedApi, await this._scanWebExtensions(), /* ignore workspace trust */true);
|
||||
const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false);
|
||||
const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation));
|
||||
return {
|
||||
allExtensions: localExtensions,
|
||||
myExtensions: myExtensions.map(extension => extension.identifier)
|
||||
};
|
||||
} else {
|
||||
// restart case
|
||||
const allExtensions = await this._getExtensions();
|
||||
const myExtensions = runningLocations.filterByRunningLocation(allExtensions, desiredRunningLocation);
|
||||
return {
|
||||
allExtensions: allExtensions,
|
||||
myExtensions: myExtensions.map(extension => extension.identifier)
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import * as perf from 'vs/base/common/performance';
|
||||
import { isEqualOrParent } from 'vs/base/common/resources';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -21,6 +22,7 @@ import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, ResolverResult, getRemoteAuthorityPrefix } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
@@ -30,6 +32,7 @@ import { ExtensionDescriptionRegistryLock, IActivationEventsReader, LockableExte
|
||||
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
|
||||
import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString } from 'vs/workbench/services/extensions/common/extensionHostKind';
|
||||
import { IExtensionHostManager, createExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
|
||||
import { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
|
||||
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
|
||||
import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation, RemoteRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation';
|
||||
import { ExtensionRunningLocationTracker, filterExtensionIdentifiers } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker';
|
||||
@@ -79,6 +82,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
|
||||
private _extensionHostManagers: IExtensionHostManager[] = [];
|
||||
|
||||
private _resolveAuthorityAttempt: number = 0;
|
||||
|
||||
constructor(
|
||||
private readonly _extensionsProposedApi: ExtensionsProposedApi,
|
||||
private readonly _extensionHostFactory: IExtensionHostFactory,
|
||||
@@ -97,7 +102,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
@ILogService protected readonly _logService: ILogService,
|
||||
@IRemoteAgentService protected readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteExtensionsScannerService protected readonly _remoteExtensionsScannerService: IRemoteExtensionsScannerService,
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
|
||||
@IRemoteAuthorityResolverService protected readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -521,6 +527,98 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
|
||||
}
|
||||
|
||||
//#region remote authority resolving
|
||||
|
||||
protected async _resolveAuthorityInitial(remoteAuthority: string): Promise<ResolverResult> {
|
||||
const MAX_ATTEMPTS = 5;
|
||||
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
try {
|
||||
return this._resolveAuthorityWithLogging(remoteAuthority);
|
||||
} catch (err) {
|
||||
if (RemoteAuthorityResolverError.isNoResolverFound(err)) {
|
||||
// There is no point in retrying if there is no resolver found
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (RemoteAuthorityResolverError.isNotAvailable(err)) {
|
||||
// The resolver is not available and asked us to not retry
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (attempt >= MAX_ATTEMPTS) {
|
||||
// Too many failed attempts, give up
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async _resolveAuthorityAgain(): Promise<void> {
|
||||
const remoteAuthority = this._environmentService.remoteAuthority;
|
||||
if (!remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);
|
||||
try {
|
||||
const result = await this._resolveAuthorityWithLogging(remoteAuthority);
|
||||
this._remoteAuthorityResolverService._setResolvedAuthority(result.authority, result.options);
|
||||
} catch (err) {
|
||||
this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);
|
||||
}
|
||||
}
|
||||
|
||||
private async _resolveAuthorityWithLogging(remoteAuthority: string): Promise<ResolverResult> {
|
||||
const authorityPrefix = getRemoteAuthorityPrefix(remoteAuthority);
|
||||
const sw = StopWatch.create(false);
|
||||
this._logService.info(`Invoking resolveAuthority(${authorityPrefix})...`);
|
||||
try {
|
||||
perf.mark(`code/willResolveAuthority/${authorityPrefix}`);
|
||||
const result = await this._resolveAuthority(remoteAuthority);
|
||||
perf.mark(`code/didResolveAuthorityOK/${authorityPrefix}`);
|
||||
this._logService.info(`resolveAuthority(${authorityPrefix}) returned '${result.authority.connectTo}' after ${sw.elapsed()} ms`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
perf.mark(`code/didResolveAuthorityError/${authorityPrefix}`);
|
||||
this._logService.error(`resolveAuthority(${authorityPrefix}) returned an error after ${sw.elapsed()} ms`, err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
protected async _resolveAuthorityOnExtensionHosts(kind: ExtensionHostKind, remoteAuthority: string): Promise<ResolverResult> {
|
||||
|
||||
const extensionHosts = this._getExtensionHostManagers(kind);
|
||||
if (extensionHosts.length === 0) {
|
||||
// no local process extension hosts
|
||||
throw new Error(`Cannot resolve authority`);
|
||||
}
|
||||
|
||||
this._resolveAuthorityAttempt++;
|
||||
const results = await Promise.all(extensionHosts.map(extHost => extHost.resolveAuthority(remoteAuthority, this._resolveAuthorityAttempt)));
|
||||
|
||||
let bestErrorResult: IResolveAuthorityErrorResult | null = null;
|
||||
for (const result of results) {
|
||||
if (result.type === 'ok') {
|
||||
return result.value;
|
||||
}
|
||||
if (!bestErrorResult) {
|
||||
bestErrorResult = result;
|
||||
continue;
|
||||
}
|
||||
const bestErrorIsUnknown = (bestErrorResult.error.code === RemoteAuthorityResolverErrorCode.Unknown);
|
||||
const errorIsUnknown = (result.error.code === RemoteAuthorityResolverErrorCode.Unknown);
|
||||
if (bestErrorIsUnknown && !errorIsUnknown) {
|
||||
bestErrorResult = result;
|
||||
}
|
||||
}
|
||||
|
||||
// we can only reach this if there is an error
|
||||
throw new RemoteAuthorityResolverError(bestErrorResult!.error.message, bestErrorResult!.error.code, bestErrorResult!.error.detail);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Stopping / Starting / Restarting
|
||||
|
||||
public stopExtensionHosts(): Promise<boolean>;
|
||||
@@ -1019,6 +1117,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
protected abstract _resolveExtensions(): Promise<ResolvedExtensions>;
|
||||
protected abstract _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null>;
|
||||
protected abstract _onExtensionHostExit(code: number): void;
|
||||
protected abstract _resolveAuthority(remoteAuthority: string): Promise<ResolverResult>;
|
||||
}
|
||||
|
||||
export class ResolvedExtensions {
|
||||
|
||||
@@ -409,7 +409,7 @@ 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.connectTo}`);
|
||||
} else {
|
||||
logError(`returned an error`, resolverResult.error);
|
||||
}
|
||||
|
||||
@@ -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 { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
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,
|
||||
@IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@@ -87,13 +88,13 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
|
||||
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 };
|
||||
return { connectTo: authority.connectTo, connectionToken: authority.connectionToken };
|
||||
}
|
||||
},
|
||||
remoteSocketFactoryService: this.remoteSocketFactoryService,
|
||||
signService: this._signService,
|
||||
logService: this._logService,
|
||||
ipcLogger: null
|
||||
|
||||
@@ -8,7 +8,6 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { isCI } from 'vs/base/common/platform';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Categories } from 'vs/platform/action/common/actionCommonCategories';
|
||||
@@ -29,7 +28,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, RemoteConnectionType, RemoteAuthorityResolverError, 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';
|
||||
@@ -44,7 +43,6 @@ import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/commo
|
||||
import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString, extensionRunningPreferenceToString } from 'vs/workbench/services/extensions/common/extensionHostKind';
|
||||
import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
|
||||
import { ExtensionHostExitCode } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
import { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
|
||||
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
|
||||
import { ExtensionRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation';
|
||||
import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from 'vs/workbench/services/extensions/common/extensionRunningLocationTracker';
|
||||
@@ -62,7 +60,6 @@ export class NativeExtensionService extends AbstractExtensionService implements
|
||||
|
||||
private readonly _extensionScanner: CachedExtensionScanner;
|
||||
private readonly _localCrashTracker = new ExtensionHostCrashTracker();
|
||||
private _resolveAuthorityAttempt: number = 0;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@@ -80,7 +77,7 @@ export class NativeExtensionService extends AbstractExtensionService implements
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@INativeHostService private readonly _nativeHostService: INativeHostService,
|
||||
@IHostService private readonly _hostService: IHostService,
|
||||
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
|
||||
@@ -98,7 +95,7 @@ export class NativeExtensionService extends AbstractExtensionService implements
|
||||
extensionEnablementService,
|
||||
configurationService,
|
||||
remoteAgentService,
|
||||
_remoteAuthorityResolverService
|
||||
remoteAuthorityResolverService
|
||||
);
|
||||
super(
|
||||
extensionsProposedApi,
|
||||
@@ -118,7 +115,8 @@ export class NativeExtensionService extends AbstractExtensionService implements
|
||||
logService,
|
||||
remoteAgentService,
|
||||
remoteExtensionsScannerService,
|
||||
lifecycleService
|
||||
lifecycleService,
|
||||
remoteAuthorityResolverService
|
||||
);
|
||||
|
||||
this._extensionScanner = extensionScanner;
|
||||
@@ -272,7 +270,7 @@ export class NativeExtensionService extends AbstractExtensionService implements
|
||||
|
||||
// --- impl
|
||||
|
||||
private async _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {
|
||||
protected async _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {
|
||||
|
||||
const authorityPlusIndex = remoteAuthority.indexOf('+');
|
||||
if (authorityPlusIndex === -1) {
|
||||
@@ -281,40 +279,17 @@ export class NativeExtensionService extends AbstractExtensionService implements
|
||||
return {
|
||||
authority: {
|
||||
authority: remoteAuthority,
|
||||
host,
|
||||
port,
|
||||
connectTo: {
|
||||
type: RemoteConnectionType.WebSocket,
|
||||
host,
|
||||
port
|
||||
},
|
||||
connectionToken: undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const localProcessExtensionHosts = this._getExtensionHostManagers(ExtensionHostKind.LocalProcess);
|
||||
if (localProcessExtensionHosts.length === 0) {
|
||||
// no local process extension hosts
|
||||
throw new Error(`Cannot resolve authority`);
|
||||
}
|
||||
|
||||
this._resolveAuthorityAttempt++;
|
||||
const results = await Promise.all(localProcessExtensionHosts.map(extHost => extHost.resolveAuthority(remoteAuthority, this._resolveAuthorityAttempt)));
|
||||
|
||||
let bestErrorResult: IResolveAuthorityErrorResult | null = null;
|
||||
for (const result of results) {
|
||||
if (result.type === 'ok') {
|
||||
return result.value;
|
||||
}
|
||||
if (!bestErrorResult) {
|
||||
bestErrorResult = result;
|
||||
continue;
|
||||
}
|
||||
const bestErrorIsUnknown = (bestErrorResult.error.code === RemoteAuthorityResolverErrorCode.Unknown);
|
||||
const errorIsUnknown = (result.error.code === RemoteAuthorityResolverErrorCode.Unknown);
|
||||
if (bestErrorIsUnknown && !errorIsUnknown) {
|
||||
bestErrorResult = result;
|
||||
}
|
||||
}
|
||||
|
||||
// we can only reach this if there is an error
|
||||
throw new RemoteAuthorityResolverError(bestErrorResult!.error.message, bestErrorResult!.error.code, bestErrorResult!.error.detail);
|
||||
return this._resolveAuthorityOnExtensionHosts(ExtensionHostKind.LocalProcess, remoteAuthority);
|
||||
}
|
||||
|
||||
private async _getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI> {
|
||||
@@ -343,63 +318,6 @@ export class NativeExtensionService extends AbstractExtensionService implements
|
||||
throw new Error(`Cannot get canonical URI because no extension is installed to resolve ${getRemoteAuthorityPrefix(remoteAuthority)}`);
|
||||
}
|
||||
|
||||
private async _resolveAuthorityInitial(remoteAuthority: string): Promise<ResolverResult> {
|
||||
const MAX_ATTEMPTS = 5;
|
||||
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
try {
|
||||
return this._resolveAuthorityWithLogging(remoteAuthority);
|
||||
} catch (err) {
|
||||
if (RemoteAuthorityResolverError.isNoResolverFound(err)) {
|
||||
// There is no point in retrying if there is no resolver found
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (RemoteAuthorityResolverError.isNotAvailable(err)) {
|
||||
// The resolver is not available and asked us to not retry
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (attempt >= MAX_ATTEMPTS) {
|
||||
// Too many failed attempts, give up
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _resolveAuthorityAgain(): Promise<void> {
|
||||
const remoteAuthority = this._environmentService.remoteAuthority;
|
||||
if (!remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);
|
||||
try {
|
||||
const result = await this._resolveAuthorityWithLogging(remoteAuthority);
|
||||
this._remoteAuthorityResolverService._setResolvedAuthority(result.authority, result.options);
|
||||
} catch (err) {
|
||||
this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);
|
||||
}
|
||||
}
|
||||
|
||||
private async _resolveAuthorityWithLogging(remoteAuthority: string): Promise<ResolverResult> {
|
||||
const authorityPrefix = getRemoteAuthorityPrefix(remoteAuthority);
|
||||
const sw = StopWatch.create(false);
|
||||
this._logService.info(`Invoking resolveAuthority(${authorityPrefix})...`);
|
||||
try {
|
||||
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`);
|
||||
return result;
|
||||
} catch (err) {
|
||||
performance.mark(`code/didResolveAuthorityError/${authorityPrefix}`);
|
||||
this._logService.error(`resolveAuthority(${authorityPrefix}) returned an error after ${sw.elapsed()} ms`, err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
protected async _resolveExtensions(): Promise<ResolvedExtensions> {
|
||||
this._extensionScanner.startScanningExtensions();
|
||||
|
||||
@@ -631,7 +549,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;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService';
|
||||
import { IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
@@ -145,6 +147,7 @@ suite('ExtensionService', () => {
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
) {
|
||||
const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi);
|
||||
const extensionHostFactory = new class implements IExtensionHostFactory {
|
||||
@@ -172,7 +175,8 @@ suite('ExtensionService', () => {
|
||||
logService,
|
||||
remoteAgentService,
|
||||
remoteExtensionsScannerService,
|
||||
lifecycleService
|
||||
lifecycleService,
|
||||
remoteAuthorityResolverService
|
||||
);
|
||||
}
|
||||
|
||||
@@ -205,6 +209,9 @@ suite('ExtensionService', () => {
|
||||
protected _onExtensionHostExit(code: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
protected _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
let disposables: DisposableStore;
|
||||
@@ -236,6 +243,7 @@ suite('ExtensionService', () => {
|
||||
[IUserDataProfileService, TestUserDataProfileService],
|
||||
[IUriIdentityService, UriIdentityService],
|
||||
[IRemoteExtensionsScannerService, TestRemoteExtensionsScannerService],
|
||||
[IRemoteAuthorityResolverService, RemoteAuthorityResolverService]
|
||||
]);
|
||||
extService = <MyTestExtensionService>instantiationService.get(IExtensionService);
|
||||
});
|
||||
|
||||
@@ -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 { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
|
||||
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
|
||||
|
||||
constructor(
|
||||
webSocketFactory: IWebSocketFactory | null | undefined,
|
||||
@IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService,
|
||||
@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(remoteSocketFactoryService, 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 { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
|
||||
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,
|
||||
@IRemoteSocketFactoryService private readonly remoteSocketFactoryService: IRemoteSocketFactoryService,
|
||||
@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.remoteSocketFactoryService, 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 _remoteSocketFactoryService: IRemoteSocketFactoryService,
|
||||
private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
private readonly _signService: ISignService,
|
||||
private readonly _logService: ILogService
|
||||
@@ -196,7 +195,6 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection
|
||||
const options: IConnectionOptions = {
|
||||
commit: this._commit,
|
||||
quality: this._quality,
|
||||
socketFactory: this._socketFactory,
|
||||
addressProvider: {
|
||||
getAddress: async () => {
|
||||
if (firstCall) {
|
||||
@@ -205,9 +203,10 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection
|
||||
this._onReconnecting.fire(undefined);
|
||||
}
|
||||
const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
|
||||
return { host: authority.host, port: authority.port, connectionToken: authority.connectionToken };
|
||||
return { connectTo: authority.connectTo, connectionToken: authority.connectionToken };
|
||||
}
|
||||
},
|
||||
remoteSocketFactoryService: this._remoteSocketFactoryService,
|
||||
signService: this._signService,
|
||||
logService: this._logService,
|
||||
ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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, RemoteConnectionType, 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 { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
|
||||
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
|
||||
constructor(
|
||||
@IRemoteSocketFactoryService remoteSocketFactoryService: IRemoteSocketFactoryService,
|
||||
@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(remoteSocketFactoryService, 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 !== RemoteConnectionType.WebSocket) {
|
||||
return null;
|
||||
}
|
||||
return URI.from({
|
||||
scheme: 'http',
|
||||
authority: `${connectionData.host}:${connectionData.port}`,
|
||||
authority: `${connectionData.connectTo.host}:${connectionData.connectTo.port}`,
|
||||
path: `/version`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,7 +156,6 @@ import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } f
|
||||
import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
|
||||
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
|
||||
import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { ILayoutOffsetInfo } from 'vs/platform/layout/browser/layoutService';
|
||||
import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
|
||||
@@ -167,6 +166,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 { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService';
|
||||
|
||||
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
||||
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined);
|
||||
@@ -325,6 +325,7 @@ export function workbenchInstantiationService(
|
||||
instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService());
|
||||
instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService());
|
||||
instantiationService.stub(IElevatedFileService, new BrowserElevatedFileService());
|
||||
instantiationService.stub(IRemoteSocketFactoryService, new RemoteSocketFactoryService());
|
||||
|
||||
return instantiationService;
|
||||
}
|
||||
@@ -1938,10 +1939,6 @@ export class TestRemoteAgentService implements IRemoteAgentService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
socketFactory: ISocketFactory = {
|
||||
connect() { }
|
||||
};
|
||||
|
||||
getConnection(): IRemoteAgentConnection | null { return null; }
|
||||
async getEnvironment(): Promise<IRemoteAgentEnvironment | null> { return null; }
|
||||
async getRawEnvironment(): Promise<IRemoteAgentEnvironment | null> { return null; }
|
||||
|
||||
+18
-1
@@ -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>;
|
||||
|
||||
send: (data: Uint8Array) => void;
|
||||
end: () => void;
|
||||
drain?: () => Thenable<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;
|
||||
|
||||
Reference in New Issue
Block a user