mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 17:23:10 -05:00
Merge VS Code 1.31.1 (#4283)
This commit is contained in:
@@ -4,14 +4,17 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class ExtensionDescriptionRegistry {
|
||||
private readonly _onDidChange = new Emitter<void>();
|
||||
public readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private _extensionDescriptions: IExtensionDescription[];
|
||||
private _extensionsMap: { [extensionId: string]: IExtensionDescription; };
|
||||
private _extensionsMap: Map<string, IExtensionDescription>;
|
||||
private _extensionsArr: IExtensionDescription[];
|
||||
private _activationMap: { [activationEvent: string]: IExtensionDescription[]; };
|
||||
private _activationMap: Map<string, IExtensionDescription[]>;
|
||||
|
||||
constructor(extensionDescriptions: IExtensionDescription[]) {
|
||||
this._extensionDescriptions = extensionDescriptions;
|
||||
@@ -19,64 +22,68 @@ export class ExtensionDescriptionRegistry {
|
||||
}
|
||||
|
||||
private _initialize(): void {
|
||||
this._extensionsMap = {};
|
||||
this._extensionsMap = new Map<string, IExtensionDescription>();
|
||||
this._extensionsArr = [];
|
||||
this._activationMap = {};
|
||||
this._activationMap = new Map<string, IExtensionDescription[]>();
|
||||
|
||||
for (let i = 0, len = this._extensionDescriptions.length; i < len; i++) {
|
||||
let extensionDescription = this._extensionDescriptions[i];
|
||||
|
||||
if (hasOwnProperty.call(this._extensionsMap, extensionDescription.id)) {
|
||||
for (const extensionDescription of this._extensionDescriptions) {
|
||||
if (this._extensionsMap.has(ExtensionIdentifier.toKey(extensionDescription.identifier))) {
|
||||
// No overwriting allowed!
|
||||
console.error('Extension `' + extensionDescription.id + '` is already registered');
|
||||
console.error('Extension `' + extensionDescription.identifier.value + '` is already registered');
|
||||
continue;
|
||||
}
|
||||
|
||||
this._extensionsMap[extensionDescription.id] = extensionDescription;
|
||||
this._extensionsMap.set(ExtensionIdentifier.toKey(extensionDescription.identifier), extensionDescription);
|
||||
this._extensionsArr.push(extensionDescription);
|
||||
|
||||
if (Array.isArray(extensionDescription.activationEvents)) {
|
||||
for (let j = 0, lenJ = extensionDescription.activationEvents.length; j < lenJ; j++) {
|
||||
let activationEvent = extensionDescription.activationEvents[j];
|
||||
|
||||
for (let activationEvent of extensionDescription.activationEvents) {
|
||||
// TODO@joao: there's no easy way to contribute this
|
||||
if (activationEvent === 'onUri') {
|
||||
activationEvent = `onUri:${extensionDescription.id}`;
|
||||
activationEvent = `onUri:${ExtensionIdentifier.toKey(extensionDescription.identifier)}`;
|
||||
}
|
||||
|
||||
this._activationMap[activationEvent] = this._activationMap[activationEvent] || [];
|
||||
this._activationMap[activationEvent].push(extensionDescription);
|
||||
if (!this._activationMap.has(activationEvent)) {
|
||||
this._activationMap.set(activationEvent, []);
|
||||
}
|
||||
this._activationMap.get(activationEvent)!.push(extensionDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public keepOnly(extensionIds: string[]): void {
|
||||
let toKeep = new Set<string>();
|
||||
extensionIds.forEach(extensionId => toKeep.add(extensionId));
|
||||
this._extensionDescriptions = this._extensionDescriptions.filter(extension => toKeep.has(extension.id));
|
||||
public keepOnly(extensionIds: ExtensionIdentifier[]): void {
|
||||
const toKeep = new Set<string>();
|
||||
extensionIds.forEach(extensionId => toKeep.add(ExtensionIdentifier.toKey(extensionId)));
|
||||
this._extensionDescriptions = this._extensionDescriptions.filter(extension => toKeep.has(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
this._initialize();
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]) {
|
||||
this._extensionDescriptions = this._extensionDescriptions.concat(toAdd);
|
||||
const toRemoveSet = new Set<string>();
|
||||
toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId)));
|
||||
this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
this._initialize();
|
||||
this._onDidChange.fire(undefined);
|
||||
}
|
||||
|
||||
public containsActivationEvent(activationEvent: string): boolean {
|
||||
return hasOwnProperty.call(this._activationMap, activationEvent);
|
||||
return this._activationMap.has(activationEvent);
|
||||
}
|
||||
|
||||
public getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[] {
|
||||
if (!hasOwnProperty.call(this._activationMap, activationEvent)) {
|
||||
return [];
|
||||
}
|
||||
return this._activationMap[activationEvent].slice(0);
|
||||
const extensions = this._activationMap.get(activationEvent);
|
||||
return extensions ? extensions.slice(0) : [];
|
||||
}
|
||||
|
||||
public getAllExtensionDescriptions(): IExtensionDescription[] {
|
||||
return this._extensionsArr.slice(0);
|
||||
}
|
||||
|
||||
public getExtensionDescription(extensionId: string): IExtensionDescription | null {
|
||||
if (!hasOwnProperty.call(this._extensionsMap, extensionId)) {
|
||||
return null;
|
||||
}
|
||||
return this._extensionsMap[extensionId];
|
||||
public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | null {
|
||||
const extension = this._extensionsMap.get(ExtensionIdentifier.toKey(extensionId));
|
||||
return extension ? extension : null;
|
||||
}
|
||||
}
|
||||
|
||||
162
src/vs/workbench/services/extensions/node/extensionHostMain.ts
Normal file
162
src/vs/workbench/services/extensions/node/extensionHostMain.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Counter } from 'vs/base/common/numbers';
|
||||
import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri';
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { IEnvironment, IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
|
||||
|
||||
// we don't (yet) throw when extensions parse
|
||||
// uris that have no scheme
|
||||
setUriThrowOnMissingScheme(false);
|
||||
|
||||
const nativeExit = process.exit.bind(process);
|
||||
function patchProcess(allowExit: boolean) {
|
||||
process.exit = function (code?: number) {
|
||||
if (allowExit) {
|
||||
exit(code);
|
||||
} else {
|
||||
const err = new Error('An extension called process.exit() and this was prevented.');
|
||||
console.warn(err.stack);
|
||||
}
|
||||
} as (code?: number) => never;
|
||||
|
||||
process.crash = function () {
|
||||
const err = new Error('An extension called process.crash() and this was prevented.');
|
||||
console.warn(err.stack);
|
||||
};
|
||||
}
|
||||
|
||||
export function exit(code?: number) {
|
||||
nativeExit(code);
|
||||
}
|
||||
|
||||
export class ExtensionHostMain {
|
||||
|
||||
|
||||
private _isTerminating: boolean;
|
||||
private readonly _environment: IEnvironment;
|
||||
private readonly _extensionService: ExtHostExtensionService;
|
||||
private readonly _extHostConfiguration: ExtHostConfiguration;
|
||||
private readonly _extHostLogService: ExtHostLogService;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
private _searchRequestIdProvider: Counter;
|
||||
|
||||
constructor(protocol: IMessagePassingProtocol, initData: IInitData) {
|
||||
this._isTerminating = false;
|
||||
const uriTransformer: IURITransformer = null;
|
||||
const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
|
||||
|
||||
// ensure URIs are transformed and revived
|
||||
initData = this.transform(initData, rpcProtocol);
|
||||
this._environment = initData.environment;
|
||||
|
||||
const allowExit = !!this._environment.extensionTestsPath; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708)
|
||||
patchProcess(allowExit);
|
||||
|
||||
this._patchPatchedConsole(rpcProtocol.getProxy(MainContext.MainThreadConsole));
|
||||
|
||||
// services
|
||||
this._extHostLogService = new ExtHostLogService(initData.logLevel, initData.logsLocation.fsPath);
|
||||
this.disposables.push(this._extHostLogService);
|
||||
|
||||
this._searchRequestIdProvider = new Counter();
|
||||
const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, initData.workspace, this._extHostLogService, this._searchRequestIdProvider);
|
||||
|
||||
this._extHostLogService.info('extension host started');
|
||||
this._extHostLogService.trace('initData', initData);
|
||||
|
||||
this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace);
|
||||
this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService);
|
||||
|
||||
// error forwarding and stack trace scanning
|
||||
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
|
||||
const extensionErrors = new WeakMap<Error, IExtensionDescription>();
|
||||
this._extensionService.getExtensionPathIndex().then(map => {
|
||||
(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
|
||||
let stackTraceMessage = '';
|
||||
let extension: IExtensionDescription;
|
||||
let fileName: string;
|
||||
for (const call of stackTrace) {
|
||||
stackTraceMessage += `\n\tat ${call.toString()}`;
|
||||
fileName = call.getFileName();
|
||||
if (!extension && fileName) {
|
||||
extension = map.findSubstr(fileName);
|
||||
}
|
||||
|
||||
}
|
||||
extensionErrors.set(error, extension);
|
||||
return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
|
||||
};
|
||||
});
|
||||
|
||||
const mainThreadExtensions = rpcProtocol.getProxy(MainContext.MainThreadExtensionService);
|
||||
const mainThreadErrors = rpcProtocol.getProxy(MainContext.MainThreadErrors);
|
||||
errors.setUnexpectedErrorHandler(err => {
|
||||
const data = errors.transformErrorForSerialization(err);
|
||||
const extension = extensionErrors.get(err);
|
||||
if (extension) {
|
||||
mainThreadExtensions.$onExtensionRuntimeError(extension.identifier, data);
|
||||
} else {
|
||||
mainThreadErrors.$onUnexpectedError(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
|
||||
// The console is already patched to use `process.send()`
|
||||
const nativeProcessSend = process.send;
|
||||
process.send = (...args: any[]) => {
|
||||
if (args.length === 0 || !args[0] || args[0].type !== '__$console') {
|
||||
return nativeProcessSend.apply(process, args);
|
||||
}
|
||||
|
||||
mainThreadConsole.$logExtensionHostMessage(args[0]);
|
||||
};
|
||||
}
|
||||
|
||||
terminate(): void {
|
||||
if (this._isTerminating) {
|
||||
// we are already shutting down...
|
||||
return;
|
||||
}
|
||||
this._isTerminating = true;
|
||||
|
||||
this.disposables = dispose(this.disposables);
|
||||
|
||||
errors.setUnexpectedErrorHandler((err) => {
|
||||
// TODO: write to log once we have one
|
||||
});
|
||||
|
||||
const extensionsDeactivated = this._extensionService.deactivateAll();
|
||||
|
||||
// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds
|
||||
setTimeout(() => {
|
||||
Promise.race([timeout(4000), extensionsDeactivated]).then(() => exit(), () => exit());
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
private transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData {
|
||||
initData.extensions.forEach((ext) => (<any>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)));
|
||||
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
|
||||
initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome));
|
||||
initData.environment.extensionDevelopmentLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionDevelopmentLocationURI));
|
||||
initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome));
|
||||
initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation));
|
||||
initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace);
|
||||
return initData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nativeWatchdog from 'native-watchdog';
|
||||
import { createConnection } from 'net';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { Protocol } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { IInitData } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol';
|
||||
import { exit, ExtensionHostMain } from 'vs/workbench/services/extensions/node/extensionHostMain';
|
||||
|
||||
// With Electron 2.x and node.js 8.x the "natives" module
|
||||
// can cause a native crash (see https://github.com/nodejs/node/issues/19891 and
|
||||
// https://github.com/electron/electron/issues/10905). To prevent this from
|
||||
// happening we essentially blocklist this module from getting loaded in any
|
||||
// extension by patching the node require() function.
|
||||
(function () {
|
||||
const Module = require.__$__nodeRequire('module') as any;
|
||||
const originalLoad = Module._load;
|
||||
|
||||
Module._load = function (request) {
|
||||
if (request === 'natives') {
|
||||
throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more');
|
||||
}
|
||||
|
||||
return originalLoad.apply(this, arguments);
|
||||
};
|
||||
})();
|
||||
|
||||
interface IRendererConnection {
|
||||
protocol: IMessagePassingProtocol;
|
||||
initData: IInitData;
|
||||
}
|
||||
|
||||
// This calls exit directly in case the initialization is not finished and we need to exit
|
||||
// Otherwise, if initialization completed we go to extensionHostMain.terminate()
|
||||
let onTerminate = function () {
|
||||
exit();
|
||||
};
|
||||
|
||||
function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
|
||||
|
||||
const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST;
|
||||
|
||||
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
|
||||
|
||||
const socket = createConnection(pipeName, () => {
|
||||
socket.removeListener('error', reject);
|
||||
resolve(new Protocol(socket));
|
||||
});
|
||||
socket.once('error', reject);
|
||||
|
||||
}).then(protocol => {
|
||||
|
||||
return new class implements IMessagePassingProtocol {
|
||||
|
||||
private _terminating = false;
|
||||
|
||||
readonly onMessage: Event<any> = Event.filter(protocol.onMessage, msg => {
|
||||
if (!isMessageOfType(msg, MessageType.Terminate)) {
|
||||
return true;
|
||||
}
|
||||
this._terminating = true;
|
||||
onTerminate();
|
||||
return false;
|
||||
});
|
||||
|
||||
send(msg: any): void {
|
||||
if (!this._terminating) {
|
||||
protocol.send(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRendererConnection> {
|
||||
return new Promise<IRendererConnection>((c, e) => {
|
||||
|
||||
// Listen init data message
|
||||
const first = protocol.onMessage(raw => {
|
||||
first.dispose();
|
||||
|
||||
const initData = <IInitData>JSON.parse(raw.toString());
|
||||
|
||||
const rendererCommit = initData.commit;
|
||||
const myCommit = product.commit;
|
||||
|
||||
if (rendererCommit && myCommit) {
|
||||
// Running in the built version where commits are defined
|
||||
if (rendererCommit !== myCommit) {
|
||||
exit(55);
|
||||
}
|
||||
}
|
||||
|
||||
// Print a console message when rejection isn't handled within N seconds. For details:
|
||||
// see https://nodejs.org/api/process.html#process_event_unhandledrejection
|
||||
// and https://nodejs.org/api/process.html#process_event_rejectionhandled
|
||||
const unhandledPromises: Promise<any>[] = [];
|
||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
||||
unhandledPromises.push(promise);
|
||||
setTimeout(() => {
|
||||
const idx = unhandledPromises.indexOf(promise);
|
||||
if (idx >= 0) {
|
||||
unhandledPromises.splice(idx, 1);
|
||||
console.warn('rejected promise not handled within 1 second');
|
||||
onUnexpectedError(reason);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
process.on('rejectionHandled', (promise: Promise<any>) => {
|
||||
const idx = unhandledPromises.indexOf(promise);
|
||||
if (idx >= 0) {
|
||||
unhandledPromises.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Print a console message when an exception isn't handled.
|
||||
process.on('uncaughtException', function (err: Error) {
|
||||
onUnexpectedError(err);
|
||||
});
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
process.on('SIGPIPE', () => {
|
||||
onUnexpectedError(new Error('Unexpected SIGPIPE'));
|
||||
});
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
// Kill oneself if one's parent dies. Much drama.
|
||||
setInterval(function () {
|
||||
try {
|
||||
process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
|
||||
} catch (e) {
|
||||
onTerminate();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// In certain cases, the event loop can become busy and never yield
|
||||
// e.g. while-true or process.nextTick endless loops
|
||||
// So also use the native node module to do it from a separate thread
|
||||
let watchdog: typeof nativeWatchdog;
|
||||
try {
|
||||
watchdog = require.__$__nodeRequire('native-watchdog');
|
||||
watchdog.start(initData.parentPid);
|
||||
} catch (err) {
|
||||
// no problem...
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
|
||||
// Tell the outside that we are initialized
|
||||
protocol.send(createMessageOfType(MessageType.Initialized));
|
||||
|
||||
c({ protocol, initData });
|
||||
});
|
||||
|
||||
// Tell the outside that we are ready to receive messages
|
||||
protocol.send(createMessageOfType(MessageType.Ready));
|
||||
});
|
||||
}
|
||||
|
||||
patchExecArgv();
|
||||
|
||||
createExtHostProtocol().then(protocol => {
|
||||
// connect to main side
|
||||
return connectToRenderer(protocol);
|
||||
}).then(renderer => {
|
||||
// setup things
|
||||
const extensionHostMain = new ExtensionHostMain(renderer.protocol, renderer.initData);
|
||||
onTerminate = () => extensionHostMain.terminate();
|
||||
}).catch(err => console.error(err));
|
||||
|
||||
function patchExecArgv() {
|
||||
// when encountering the prevent-inspect flag we delete this
|
||||
// and the prior flag
|
||||
if (process.env.VSCODE_PREVENT_FOREIGN_INSPECT) {
|
||||
for (let i = 0; i < process.execArgv.length; i++) {
|
||||
if (process.execArgv[i].match(/--inspect-brk=\d+|--inspect=\d+/)) {
|
||||
process.execArgv.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,28 +42,4 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class SingleServerExtensionManagementServerService implements IExtensionManagementServerService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
|
||||
constructor(
|
||||
private readonly extensionManagementServer: IExtensionManagementServer
|
||||
) {
|
||||
}
|
||||
|
||||
getExtensionManagementServer(location: URI): IExtensionManagementServer | null {
|
||||
const authority = location.scheme === Schemas.file ? localExtensionManagementServerAuthority : location.authority;
|
||||
return this.extensionManagementServer.authority === authority ? this.extensionManagementServer : null;
|
||||
}
|
||||
|
||||
get localExtensionManagementServer(): IExtensionManagementServer | null {
|
||||
return this.extensionManagementServer.authority === localExtensionManagementServerAuthority ? this.extensionManagementServer : null;
|
||||
}
|
||||
|
||||
get remoteExtensionManagementServer(): IExtensionManagementServer | null {
|
||||
return this.extensionManagementServer.authority !== localExtensionManagementServerAuthority ? this.extensionManagementServer : null;
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { getGalleryExtensionId, getLocalExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, ExtensionIdentifierWithVersion } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
const MANIFEST_FILE = 'package.json';
|
||||
|
||||
@@ -304,6 +304,8 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
// Relax the readonly properties here, it is the one place where we check and normalize values
|
||||
export interface IRelaxedExtensionDescription {
|
||||
id: string;
|
||||
uuid?: string;
|
||||
identifier: ExtensionIdentifier;
|
||||
name: string;
|
||||
version: string;
|
||||
publisher: string;
|
||||
@@ -343,6 +345,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
|
||||
|
||||
// id := `publisher.name`
|
||||
extensionDescription.id = `${extensionDescription.publisher}.${extensionDescription.name}`;
|
||||
extensionDescription.identifier = new ExtensionIdentifier(extensionDescription.id);
|
||||
|
||||
// main := absolutePath(`main`)
|
||||
if (extensionDescription.main) {
|
||||
@@ -507,7 +510,7 @@ export class ExtensionScanner {
|
||||
/**
|
||||
* Read the extension defined in `absoluteFolderPath`
|
||||
*/
|
||||
public static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> {
|
||||
private static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): Promise<IExtensionDescription | null> {
|
||||
absoluteFolderPath = path.normalize(absoluteFolderPath);
|
||||
|
||||
let parser = new ExtensionManifestParser(version, log, absoluteFolderPath, isBuiltin, isUnderDevelopment);
|
||||
@@ -557,31 +560,17 @@ export class ExtensionScanner {
|
||||
refs.sort((a, b) => a.name < b.name ? -1 : 1);
|
||||
|
||||
if (!isBuiltin) {
|
||||
// TODO: align with extensionsService
|
||||
const nonGallery: IExtensionReference[] = [];
|
||||
const gallery: IExtensionReference[] = [];
|
||||
|
||||
refs.forEach(ref => {
|
||||
if (ref.name.indexOf('.') !== 0) { // Do not consider user extension folder starting with `.`
|
||||
const { id, version } = getIdAndVersionFromLocalExtensionId(ref.name);
|
||||
if (!id || !version) {
|
||||
nonGallery.push(ref);
|
||||
} else {
|
||||
gallery.push(ref);
|
||||
}
|
||||
}
|
||||
});
|
||||
refs = [...nonGallery, ...gallery];
|
||||
refs = refs.filter(ref => ref.name.indexOf('.') !== 0); // Do not consider user extension folder starting with `.`
|
||||
}
|
||||
|
||||
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
|
||||
let _extensionDescriptions = await Promise.all(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig)));
|
||||
let extensionDescriptions = arrays.coalesce(_extensionDescriptions);
|
||||
extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[getLocalExtensionId(getGalleryExtensionId(item.publisher, item.name), item.version)]);
|
||||
extensionDescriptions = extensionDescriptions.filter(item => item !== null && !obsolete[new ExtensionIdentifierWithVersion({ id: getGalleryExtensionId(item.publisher, item.name) }, item.version).key()]);
|
||||
|
||||
if (!isBuiltin) {
|
||||
// Filter out outdated extensions
|
||||
const byExtension: IExtensionDescription[][] = groupByExtension(extensionDescriptions, e => ({ id: e.id, uuid: e.uuid }));
|
||||
const byExtension: IExtensionDescription[][] = groupByExtension(extensionDescriptions, e => ({ id: e.identifier.value, uuid: e.uuid }));
|
||||
extensionDescriptions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]);
|
||||
}
|
||||
|
||||
@@ -624,15 +613,23 @@ export class ExtensionScanner {
|
||||
});
|
||||
}
|
||||
|
||||
public static scanSingleExtension(input: ExtensionScannerInput, log: ILog): Promise<IExtensionDescription | null> {
|
||||
const absoluteFolderPath = input.absoluteFolderPath;
|
||||
const isBuiltin = input.isBuiltin;
|
||||
const isUnderDevelopment = input.isUnderDevelopment;
|
||||
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
|
||||
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig);
|
||||
}
|
||||
|
||||
public static mergeBuiltinExtensions(builtinExtensions: Promise<IExtensionDescription[]>, extraBuiltinExtensions: Promise<IExtensionDescription[]>): Promise<IExtensionDescription[]> {
|
||||
return Promise.all([builtinExtensions, extraBuiltinExtensions]).then(([builtinExtensions, extraBuiltinExtensions]) => {
|
||||
let resultMap: { [id: string]: IExtensionDescription; } = Object.create(null);
|
||||
for (let i = 0, len = builtinExtensions.length; i < len; i++) {
|
||||
resultMap[builtinExtensions[i].id] = builtinExtensions[i];
|
||||
resultMap[ExtensionIdentifier.toKey(builtinExtensions[i].identifier)] = builtinExtensions[i];
|
||||
}
|
||||
// Overwrite with extensions found in extra
|
||||
for (let i = 0, len = extraBuiltinExtensions.length; i < len; i++) {
|
||||
resultMap[extraBuiltinExtensions[i].id] = extraBuiltinExtensions[i];
|
||||
resultMap[ExtensionIdentifier.toKey(extraBuiltinExtensions[i].identifier)] = extraBuiltinExtensions[i];
|
||||
}
|
||||
|
||||
let resultArr = Object.keys(resultMap).map((id) => resultMap[id]);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
export class LazyPromise implements Thenable<any> {
|
||||
export class LazyPromise implements Promise<any> {
|
||||
|
||||
private _actual: Promise<any> | null;
|
||||
private _actualOk: ((value?: any) => any) | null;
|
||||
@@ -78,4 +78,12 @@ export class LazyPromise implements Thenable<any> {
|
||||
public then(success: any, error: any): any {
|
||||
return this._ensureActual().then(success, error);
|
||||
}
|
||||
|
||||
public catch(error: any): any {
|
||||
return this._ensureActual().then(undefined, error);
|
||||
}
|
||||
|
||||
public finally(callback): any {
|
||||
return this._ensureActual().finally(callback);
|
||||
}
|
||||
}
|
||||
|
||||
317
src/vs/workbench/services/extensions/node/proxyResolver.ts
Normal file
317
src/vs/workbench/services/extensions/node/proxyResolver.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as nodeurl from 'url';
|
||||
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { ExtHostConfigProvider } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ProxyAgent } from 'vscode-proxy-agent';
|
||||
import { MainThreadTelemetryShape } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
interface ConnectionResult {
|
||||
proxy: string;
|
||||
connection: string;
|
||||
code: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export function connectProxyResolver(
|
||||
extHostWorkspace: ExtHostWorkspace,
|
||||
configProvider: ExtHostConfigProvider,
|
||||
extensionService: ExtHostExtensionService,
|
||||
extHostLogService: ExtHostLogService,
|
||||
mainThreadTelemetry: MainThreadTelemetryShape
|
||||
) {
|
||||
const agents = createProxyAgents(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry);
|
||||
const lookup = createPatchedModules(configProvider, agents);
|
||||
return configureModuleLoading(extensionService, lookup);
|
||||
}
|
||||
|
||||
const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache'.
|
||||
|
||||
function createProxyAgents(
|
||||
extHostWorkspace: ExtHostWorkspace,
|
||||
configProvider: ExtHostConfigProvider,
|
||||
extHostLogService: ExtHostLogService,
|
||||
mainThreadTelemetry: MainThreadTelemetryShape
|
||||
) {
|
||||
let settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http')
|
||||
.get<string>('proxy'));
|
||||
configProvider.onDidChangeConfiguration(e => {
|
||||
settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http')
|
||||
.get<string>('proxy'));
|
||||
});
|
||||
const env = process.env;
|
||||
let envProxy = proxyFromConfigURL(env.https_proxy || env.HTTPS_PROXY || env.http_proxy || env.HTTP_PROXY); // Not standardized.
|
||||
|
||||
let cacheRolls = 0;
|
||||
let oldCache = new Map<string, string>();
|
||||
let cache = new Map<string, string>();
|
||||
function getCacheKey(url: nodeurl.UrlWithStringQuery) {
|
||||
// Expecting proxies to usually be the same per scheme://host:port. Assuming that for performance.
|
||||
return nodeurl.format({ ...url, ...{ pathname: undefined, search: undefined, hash: undefined } });
|
||||
}
|
||||
function getCachedProxy(key: string) {
|
||||
let proxy = cache.get(key);
|
||||
if (proxy) {
|
||||
return proxy;
|
||||
}
|
||||
proxy = oldCache.get(key);
|
||||
if (proxy) {
|
||||
oldCache.delete(key);
|
||||
cacheProxy(key, proxy);
|
||||
}
|
||||
return proxy;
|
||||
}
|
||||
function cacheProxy(key: string, proxy: string) {
|
||||
cache.set(key, proxy);
|
||||
if (cache.size >= maxCacheEntries) {
|
||||
oldCache = cache;
|
||||
cache = new Map();
|
||||
cacheRolls++;
|
||||
extHostLogService.trace('ProxyResolver#cacheProxy cacheRolls', cacheRolls);
|
||||
}
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timer | undefined;
|
||||
let count = 0;
|
||||
let duration = 0;
|
||||
let errorCount = 0;
|
||||
let cacheCount = 0;
|
||||
let envCount = 0;
|
||||
let settingsCount = 0;
|
||||
let localhostCount = 0;
|
||||
let results: ConnectionResult[] = [];
|
||||
function logEvent() {
|
||||
timeout = undefined;
|
||||
/* __GDPR__
|
||||
"resolveProxy" : {
|
||||
"count": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"errorCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"cacheCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"cacheSize": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"cacheRolls": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"envCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"settingsCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"localhostCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"results": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
|
||||
}
|
||||
*/
|
||||
mainThreadTelemetry.$publicLog('resolveProxy', { count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, results });
|
||||
count = duration = errorCount = cacheCount = envCount = settingsCount = localhostCount = 0;
|
||||
results = [];
|
||||
}
|
||||
|
||||
function resolveProxy(req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) {
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(logEvent, 10 * 60 * 1000);
|
||||
}
|
||||
|
||||
const parsedUrl = nodeurl.parse(url); // Coming from Node's URL, sticking with that.
|
||||
|
||||
const hostname = parsedUrl.hostname;
|
||||
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '::ffff:127.0.0.1') {
|
||||
localhostCount++;
|
||||
callback('DIRECT');
|
||||
extHostLogService.trace('ProxyResolver#resolveProxy localhost', url, 'DIRECT');
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingsProxy) {
|
||||
settingsCount++;
|
||||
callback(settingsProxy);
|
||||
extHostLogService.trace('ProxyResolver#resolveProxy settings', url, settingsProxy);
|
||||
return;
|
||||
}
|
||||
|
||||
if (envProxy) {
|
||||
envCount++;
|
||||
callback(envProxy);
|
||||
extHostLogService.trace('ProxyResolver#resolveProxy env', url, envProxy);
|
||||
return;
|
||||
}
|
||||
|
||||
const key = getCacheKey(parsedUrl);
|
||||
const proxy = getCachedProxy(key);
|
||||
if (proxy) {
|
||||
cacheCount++;
|
||||
collectResult(results, proxy, parsedUrl.protocol === 'https:' ? 'HTTPS' : 'HTTP', req);
|
||||
callback(proxy);
|
||||
extHostLogService.trace('ProxyResolver#resolveProxy cached', url, proxy);
|
||||
return;
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
extHostWorkspace.resolveProxy(url) // Use full URL to ensure it is an actually used one.
|
||||
.then(proxy => {
|
||||
cacheProxy(key, proxy);
|
||||
collectResult(results, proxy, parsedUrl.protocol === 'https:' ? 'HTTPS' : 'HTTP', req);
|
||||
callback(proxy);
|
||||
extHostLogService.debug('ProxyResolver#resolveProxy', url, proxy);
|
||||
}).then(() => {
|
||||
count++;
|
||||
duration = Date.now() - start + duration;
|
||||
}, err => {
|
||||
errorCount++;
|
||||
callback();
|
||||
extHostLogService.error('ProxyResolver#resolveProxy', toErrorMessage(err));
|
||||
});
|
||||
}
|
||||
|
||||
const httpAgent: http.Agent = new ProxyAgent({ resolveProxy });
|
||||
(<any>httpAgent).defaultPort = 80;
|
||||
const httpsAgent: http.Agent = new ProxyAgent({ resolveProxy });
|
||||
(<any>httpsAgent).defaultPort = 443;
|
||||
return { http: httpAgent, https: httpsAgent };
|
||||
}
|
||||
|
||||
function collectResult(results: ConnectionResult[], resolveProxy: string, connection: string, req: http.ClientRequest) {
|
||||
const proxy = resolveProxy ? String(resolveProxy).trim().split(/\s+/, 1)[0] : 'EMPTY';
|
||||
req.on('response', res => {
|
||||
const code = `HTTP_${res.statusCode}`;
|
||||
const result = findOrCreateResult(results, proxy, connection, code);
|
||||
result.count++;
|
||||
});
|
||||
req.on('error', err => {
|
||||
const code = err && typeof (<any>err).code === 'string' && (<any>err).code || 'UNKNOWN_ERROR';
|
||||
const result = findOrCreateResult(results, proxy, connection, code);
|
||||
result.count++;
|
||||
});
|
||||
}
|
||||
|
||||
function findOrCreateResult(results: ConnectionResult[], proxy: string, connection: string, code: string): ConnectionResult | undefined {
|
||||
for (const result of results) {
|
||||
if (result.proxy === proxy && result.connection === connection && result.code === code) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
const result = { proxy, connection, code, count: 0 };
|
||||
results.push(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
function proxyFromConfigURL(configURL: string) {
|
||||
const url = (configURL || '').trim();
|
||||
const i = url.indexOf('://');
|
||||
if (i === -1) {
|
||||
return undefined;
|
||||
}
|
||||
const scheme = url.substr(0, i).toLowerCase();
|
||||
const proxy = url.substr(i + 3);
|
||||
if (scheme === 'http') {
|
||||
return 'PROXY ' + proxy;
|
||||
} else if (scheme === 'https') {
|
||||
return 'HTTPS ' + proxy;
|
||||
} else if (scheme === 'socks') {
|
||||
return 'SOCKS ' + proxy;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function createPatchedModules(configProvider: ExtHostConfigProvider, agents: { http: http.Agent; https: http.Agent; }) {
|
||||
const setting = {
|
||||
config: configProvider.getConfiguration('http')
|
||||
.get<string>('proxySupport') || 'off'
|
||||
};
|
||||
configProvider.onDidChangeConfiguration(e => {
|
||||
setting.config = configProvider.getConfiguration('http')
|
||||
.get<string>('proxySupport') || 'off';
|
||||
});
|
||||
|
||||
return {
|
||||
http: {
|
||||
off: assign({}, http, patches(http, agents.http, agents.https, { config: 'off' }, true)),
|
||||
on: assign({}, http, patches(http, agents.http, agents.https, { config: 'on' }, true)),
|
||||
override: assign({}, http, patches(http, agents.http, agents.https, { config: 'override' }, true)),
|
||||
onRequest: assign({}, http, patches(http, agents.http, agents.https, setting, true)),
|
||||
default: assign(http, patches(http, agents.http, agents.https, setting, false)) // run last
|
||||
},
|
||||
https: {
|
||||
off: assign({}, https, patches(https, agents.https, agents.http, { config: 'off' }, true)),
|
||||
on: assign({}, https, patches(https, agents.https, agents.http, { config: 'on' }, true)),
|
||||
override: assign({}, https, patches(https, agents.https, agents.http, { config: 'override' }, true)),
|
||||
onRequest: assign({}, https, patches(https, agents.https, agents.http, setting, true)),
|
||||
default: assign(https, patches(https, agents.https, agents.http, setting, false)) // run last
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function patches(originals: typeof http | typeof https, agent: http.Agent, otherAgent: http.Agent, setting: { config: string; }, onRequest: boolean) {
|
||||
return {
|
||||
get: patch(originals.get),
|
||||
request: patch(originals.request)
|
||||
};
|
||||
|
||||
function patch(original: typeof http.get) {
|
||||
function patched(url: string | URL, options?: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest {
|
||||
if (typeof url !== 'string' && !(url && (<any>url).searchParams)) {
|
||||
callback = <any>options;
|
||||
options = url;
|
||||
url = null;
|
||||
}
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
options = options || {};
|
||||
|
||||
const config = onRequest && ((<any>options)._vscodeProxySupport || /* LS */ (<any>options)._vscodeSystemProxy) || setting.config;
|
||||
if (config === 'off') {
|
||||
return original.apply(null, arguments as unknown as any[]);
|
||||
}
|
||||
|
||||
if (!options.socketPath && (config === 'override' || config === 'on' && !options.agent) && options.agent !== agent && options.agent !== otherAgent) {
|
||||
if (url) {
|
||||
const parsed = typeof url === 'string' ? new nodeurl.URL(url) : url;
|
||||
const urlOptions = {
|
||||
protocol: parsed.protocol,
|
||||
hostname: parsed.hostname.lastIndexOf('[', 0) === 0 ? parsed.hostname.slice(1, -1) : parsed.hostname,
|
||||
port: parsed.port,
|
||||
path: `${parsed.pathname}${parsed.search}`
|
||||
};
|
||||
if (parsed.username || parsed.password) {
|
||||
options.auth = `${parsed.username}:${parsed.password}`;
|
||||
}
|
||||
options = { ...urlOptions, ...options };
|
||||
} else {
|
||||
options = { ...options };
|
||||
}
|
||||
options.agent = agent;
|
||||
return original(options, callback);
|
||||
}
|
||||
|
||||
return original.apply(null, arguments as unknown as any[]);
|
||||
}
|
||||
return patched;
|
||||
}
|
||||
}
|
||||
|
||||
function configureModuleLoading(extensionService: ExtHostExtensionService, lookup: ReturnType<typeof createPatchedModules>): Promise<void> {
|
||||
return extensionService.getExtensionPathIndex()
|
||||
.then(extensionPaths => {
|
||||
const node_module = <any>require.__$__nodeRequire('module');
|
||||
const original = node_module._load;
|
||||
node_module._load = function load(request: string, parent: any, isMain: any) {
|
||||
if (request !== 'http' && request !== 'https') {
|
||||
return original.apply(this, arguments);
|
||||
}
|
||||
|
||||
const modules = lookup[request];
|
||||
const ext = extensionPaths.findSubstr(URI.file(parent.filename).fsPath);
|
||||
if (ext && ext.enableProposedApi) {
|
||||
return modules[(<any>ext).proxySupport] || modules.onRequest;
|
||||
}
|
||||
return modules.default;
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -9,9 +9,7 @@ import { CharCode } from 'vs/base/common/charCode';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { MarshalledObject } from 'vs/base/common/marshalling';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { IURITransformer, transformIncomingURIs } from 'vs/base/common/uriIpc';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { LazyPromise } from 'vs/workbench/services/extensions/node/lazyPromise';
|
||||
import { IRPCProtocol, ProxyIdentifier, getStringIdentifierForProxy } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
||||
@@ -40,75 +38,6 @@ function createURIReplacer(transformer: IURITransformer | null): JSONStringifyRe
|
||||
};
|
||||
}
|
||||
|
||||
function _transformOutgoingURIs(obj: any, transformer: IURITransformer, depth: number): any {
|
||||
|
||||
if (!obj || depth > 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
if (obj instanceof URI) {
|
||||
return transformer.transformOutgoing(obj);
|
||||
}
|
||||
|
||||
// walk object (or array)
|
||||
for (let key in obj) {
|
||||
if (Object.hasOwnProperty.call(obj, key)) {
|
||||
const r = _transformOutgoingURIs(obj[key], transformer, depth + 1);
|
||||
if (r !== null) {
|
||||
obj[key] = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function transformOutgoingURIs<T>(obj: T, transformer: IURITransformer): T {
|
||||
const result = _transformOutgoingURIs(obj, transformer, 0);
|
||||
if (result === null) {
|
||||
// no change
|
||||
return obj;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function _transformIncomingURIs(obj: any, transformer: IURITransformer, depth: number): any {
|
||||
|
||||
if (!obj || depth > 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
|
||||
if ((<MarshalledObject>obj).$mid === 1) {
|
||||
return transformer.transformIncoming(obj);
|
||||
}
|
||||
|
||||
// walk object (or array)
|
||||
for (let key in obj) {
|
||||
if (Object.hasOwnProperty.call(obj, key)) {
|
||||
const r = _transformIncomingURIs(obj[key], transformer, depth + 1);
|
||||
if (r !== null) {
|
||||
obj[key] = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function transformIncomingURIs<T>(obj: T, transformer: IURITransformer): T {
|
||||
const result = _transformIncomingURIs(obj, transformer, 0);
|
||||
if (result === null) {
|
||||
// no change
|
||||
return obj;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const enum RequestInitiator {
|
||||
LocalSide = 0,
|
||||
OtherSide = 1
|
||||
@@ -354,7 +283,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
|
||||
}
|
||||
const callId = String(req);
|
||||
|
||||
let promise: Thenable<any>;
|
||||
let promise: Promise<any>;
|
||||
let cancel: () => void;
|
||||
if (usesCancellationToken) {
|
||||
const cancellationTokenSource = new CancellationTokenSource();
|
||||
@@ -441,7 +370,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
|
||||
pendingReply.resolveErr(err);
|
||||
}
|
||||
|
||||
private _invokeHandler(rpcId: number, methodName: string, args: any[]): Thenable<any> {
|
||||
private _invokeHandler(rpcId: number, methodName: string, args: any[]): Promise<any> {
|
||||
try {
|
||||
return Promise.resolve(this._doInvokeHandler(rpcId, methodName, args));
|
||||
} catch (err) {
|
||||
@@ -461,7 +390,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
|
||||
return method.apply(actor, args);
|
||||
}
|
||||
|
||||
private _remoteCall(rpcId: number, methodName: string, args: any[]): Thenable<any> {
|
||||
private _remoteCall(rpcId: number, methodName: string, args: any[]): Promise<any> {
|
||||
if (this._isDisposed) {
|
||||
return Promise.reject<any>(errors.canceled());
|
||||
}
|
||||
@@ -592,7 +521,7 @@ class MessageBuffer {
|
||||
return buff;
|
||||
}
|
||||
|
||||
public static sizeMixedArray(arr: (string | Buffer)[], arrLengths: number[]): number {
|
||||
public static sizeMixedArray(arr: Array<string | Buffer>, arrLengths: number[]): number {
|
||||
let size = 0;
|
||||
size += 1; // arr length
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
@@ -608,7 +537,7 @@ class MessageBuffer {
|
||||
return size;
|
||||
}
|
||||
|
||||
public writeMixedArray(arr: (string | Buffer)[], arrLengths: number[]): void {
|
||||
public writeMixedArray(arr: Array<string | Buffer>, arrLengths: number[]): void {
|
||||
this._buff.writeUInt8(arr.length, this._offset, true); this._offset += 1;
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
const el = arr[i];
|
||||
@@ -623,9 +552,9 @@ class MessageBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
public readMixedArray(): (string | Buffer)[] {
|
||||
public readMixedArray(): Array<string | Buffer> {
|
||||
const arrLen = this._buff.readUInt8(this._offset, true); this._offset += 1;
|
||||
let arr: (string | Buffer)[] = new Array(arrLen);
|
||||
let arr: Array<string | Buffer> = new Array(arrLen);
|
||||
for (let i = 0; i < arrLen; i++) {
|
||||
const argType = <ArgType>this.readUInt8();
|
||||
if (argType === ArgType.String) {
|
||||
@@ -651,7 +580,7 @@ class MessageIO {
|
||||
|
||||
public static serializeRequest(req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean, replacer: JSONStringifyReplacer | null): Buffer {
|
||||
if (this._arrayContainsBuffer(args)) {
|
||||
let massagedArgs: (string | Buffer)[] = new Array(args.length);
|
||||
let massagedArgs: Array<string | Buffer> = new Array(args.length);
|
||||
let argsLengths: number[] = new Array(args.length);
|
||||
for (let i = 0, len = args.length; i < len; i++) {
|
||||
const arg = args[i];
|
||||
@@ -695,7 +624,7 @@ class MessageIO {
|
||||
};
|
||||
}
|
||||
|
||||
private static _requestMixedArgs(req: number, rpcId: number, method: string, args: (string | Buffer)[], argsLengths: number[], usesCancellationToken: boolean): Buffer {
|
||||
private static _requestMixedArgs(req: number, rpcId: number, method: string, args: Array<string | Buffer>, argsLengths: number[], usesCancellationToken: boolean): Buffer {
|
||||
const methodByteLength = Buffer.byteLength(method, 'utf8');
|
||||
|
||||
let len = 0;
|
||||
|
||||
Reference in New Issue
Block a user