Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -2,24 +2,29 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
const hasOwnProperty = Object.hasOwnProperty;
export class ExtensionDescriptionRegistry {
private _extensionDescriptions: IExtensionDescription[];
private _extensionsMap: { [extensionId: string]: IExtensionDescription; };
private _extensionsArr: IExtensionDescription[];
private _activationMap: { [activationEvent: string]: IExtensionDescription[]; };
constructor(extensionDescriptions: IExtensionDescription[]) {
this._extensionDescriptions = extensionDescriptions;
this._initialize();
}
private _initialize(): void {
this._extensionsMap = {};
this._extensionsArr = [];
this._activationMap = {};
for (let i = 0, len = extensionDescriptions.length; i < len; i++) {
let extensionDescription = extensionDescriptions[i];
for (let i = 0, len = this._extensionDescriptions.length; i < len; i++) {
let extensionDescription = this._extensionDescriptions[i];
if (hasOwnProperty.call(this._extensionsMap, extensionDescription.id)) {
// No overwriting allowed!
@@ -46,6 +51,13 @@ export class ExtensionDescriptionRegistry {
}
}
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));
this._initialize();
}
public containsActivationEvent(activationEvent: string): boolean {
return hasOwnProperty.call(this._activationMap, activationEvent);
}
@@ -61,7 +73,7 @@ export class ExtensionDescriptionRegistry {
return this._extensionsArr.slice(0);
}
public getExtensionDescription(extensionId: string): IExtensionDescription {
public getExtensionDescription(extensionId: string): IExtensionDescription | null {
if (!hasOwnProperty.call(this._extensionsMap, extensionId)) {
return null;
}

View File

@@ -3,28 +3,44 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionManagementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement';
import URI from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc';
import { IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IChannel } from 'vs/base/parts/ipc/node/ipc';
const localExtensionManagementServerAuthority: string = 'vscode-local';
export class ExtensionManagementServerService implements IExtensionManagementServerService {
_serviceBrand: any;
readonly extensionManagementServers: IExtensionManagementServer[];
readonly localExtensionManagementServer: IExtensionManagementServer;
readonly remoteExtensionManagementServer: IExtensionManagementServer | null = null;
constructor(
localExtensionManagementService: IExtensionManagementService
localExtensionManagementService: IExtensionManagementService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) {
this.extensionManagementServers = [{ extensionManagementService: localExtensionManagementService, location: URI.from({ scheme: Schemas.file }) }];
this.localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, authority: localExtensionManagementServerAuthority, label: localize('local', "Local") };
const remoteAgentConnection = remoteAgentService.getConnection();
if (remoteAgentConnection) {
const extensionManagementService = new ExtensionManagementChannelClient(remoteAgentConnection.getChannel<IChannel>('extensions'));
this.remoteExtensionManagementServer = { authority: remoteAgentConnection.remoteAuthority, extensionManagementService, label: remoteAgentConnection.remoteAuthority };
}
}
getExtensionManagementServer(location: URI): IExtensionManagementServer {
return this.extensionManagementServers[0];
}
getDefaultExtensionManagementServer(): IExtensionManagementServer {
return this.extensionManagementServers[0];
getExtensionManagementServer(location: URI): IExtensionManagementServer | null {
if (location.scheme === Schemas.file) {
return this.localExtensionManagementServer;
}
if (location.scheme === REMOTE_HOST_SCHEME) {
return this.remoteExtensionManagementServer;
}
return null;
}
}
@@ -32,20 +48,22 @@ export class SingleServerExtensionManagementServerService implements IExtensionM
_serviceBrand: any;
readonly extensionManagementServers: IExtensionManagementServer[];
constructor(
extensionManagementServer: IExtensionManagementServer
private readonly extensionManagementServer: IExtensionManagementServer
) {
this.extensionManagementServers = [extensionManagementServer];
}
getExtensionManagementServer(location: URI): IExtensionManagementServer {
location = location.scheme === Schemas.file ? URI.from({ scheme: Schemas.file }) : location;
return this.extensionManagementServers.filter(server => location.authority === server.location.authority)[0];
getExtensionManagementServer(location: URI): IExtensionManagementServer | null {
const authority = location.scheme === Schemas.file ? localExtensionManagementServerAuthority : location.authority;
return this.extensionManagementServer.authority === authority ? this.extensionManagementServer : null;
}
getDefaultExtensionManagementServer(): IExtensionManagementServer {
return this.extensionManagementServers[0];
get localExtensionManagementServer(): IExtensionManagementServer | null {
return this.extensionManagementServer.authority === localExtensionManagementServerAuthority ? this.extensionManagementServer : null;
}
get remoteExtensionManagementServer(): IExtensionManagementServer | null {
return this.extensionManagementServer.authority !== localExtensionManagementServerAuthority ? this.extensionManagementServer : null;
}
}

View File

@@ -3,21 +3,19 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import * as pfs from 'vs/base/node/pfs';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { TPromise } from 'vs/base/common/winjs.base';
import { join, normalize, extname } from 'path';
import * as json from 'vs/base/common/json';
import * as types from 'vs/base/common/types';
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
import * as path from 'path';
import * as semver from 'semver';
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import * as json from 'vs/base/common/json';
import * as arrays from 'vs/base/common/arrays';
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import { groupByExtension, getGalleryExtensionId, getLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import URI from 'vs/base/common/uri';
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 { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
const MANIFEST_FILE = 'package.json';
@@ -51,7 +49,7 @@ namespace Translations {
export interface NlsConfiguration {
readonly devMode: boolean;
readonly locale: string;
readonly locale: string | undefined;
readonly pseudo: boolean;
readonly translations: Translations;
}
@@ -77,13 +75,13 @@ abstract class ExtensionManifestHandler {
this._absoluteFolderPath = absoluteFolderPath;
this._isBuiltin = isBuiltin;
this._isUnderDevelopment = isUnderDevelopment;
this._absoluteManifestPath = join(absoluteFolderPath, MANIFEST_FILE);
this._absoluteManifestPath = path.join(absoluteFolderPath, MANIFEST_FILE);
}
}
class ExtensionManifestParser extends ExtensionManifestHandler {
public parse(): TPromise<IExtensionDescription> {
public parse(): Promise<IExtensionDescription> {
return pfs.readFile(this._absoluteManifestPath).then((manifestContents) => {
try {
const manifest = JSON.parse(manifestContents.toString());
@@ -120,7 +118,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
this._nlsConfig = nlsConfig;
}
public replaceNLS(extensionDescription: IExtensionDescription): TPromise<IExtensionDescription> {
public replaceNLS(extensionDescription: IExtensionDescription): Promise<IExtensionDescription> {
interface MessageBag {
[key: string]: string;
}
@@ -132,22 +130,22 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
}
interface LocalizedMessages {
values: MessageBag;
default: string;
values: MessageBag | undefined;
default: string | null;
}
const reportErrors = (localized: string, errors: json.ParseError[]): void => {
const reportErrors = (localized: string | null, errors: json.ParseError[]): void => {
errors.forEach((error) => {
this._log.error(this._absoluteFolderPath, nls.localize('jsonsParseReportErrors', "Failed to parse {0}: {1}.", localized, getParseErrorMessage(error.error)));
});
};
let extension = extname(this._absoluteManifestPath);
let extension = path.extname(this._absoluteManifestPath);
let basename = this._absoluteManifestPath.substr(0, this._absoluteManifestPath.length - extension.length);
const translationId = `${extensionDescription.publisher}.${extensionDescription.name}`;
let translationPath = this._nlsConfig.translations[translationId];
let localizedMessages: TPromise<LocalizedMessages>;
let localizedMessages: Promise<LocalizedMessages | undefined>;
if (translationPath) {
localizedMessages = pfs.readFile(translationPath, 'utf8').then<LocalizedMessages, LocalizedMessages>((content) => {
let errors: json.ParseError[] = [];
@@ -163,7 +161,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
return { values: undefined, default: `${basename}.nls.json` };
});
} else {
localizedMessages = pfs.fileExists(basename + '.nls' + extension).then<LocalizedMessages, undefined | LocalizedMessages>(exists => {
localizedMessages = pfs.fileExists(basename + '.nls' + extension).then<LocalizedMessages | undefined, LocalizedMessages | undefined>(exists => {
if (!exists) {
return undefined;
}
@@ -211,8 +209,8 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
/**
* Parses original message bundle, returns null if the original message bundle is null.
*/
private static resolveOriginalMessageBundle(originalMessageBundle: string, errors: json.ParseError[]) {
return new TPromise<{ [key: string]: string; }>((c, e) => {
private static resolveOriginalMessageBundle(originalMessageBundle: string | null, errors: json.ParseError[]) {
return new Promise<{ [key: string]: string; } | null>((c, e) => {
if (originalMessageBundle) {
pfs.readFile(originalMessageBundle).then(originalBundleContent => {
c(json.parse(originalBundleContent.toString(), errors));
@@ -229,8 +227,8 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
* Finds localized message bundle and the original (unlocalized) one.
* If the localized file is not present, returns null for the original and marks original as localized.
*/
private static findMessageBundles(nlsConfig: NlsConfiguration, basename: string): TPromise<{ localized: string, original: string }> {
return new TPromise<{ localized: string, original: string }>((c, e) => {
private static findMessageBundles(nlsConfig: NlsConfiguration, basename: string): Promise<{ localized: string; original: string | null; }> {
return new Promise<{ localized: string; original: string | null; }>((c, e) => {
function loop(basename: string, locale: string): void {
let toCheck = `${basename}.nls.${locale}.json`;
pfs.fileExists(toCheck).then(exists => {
@@ -258,7 +256,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
* This routine makes the following assumptions:
* The root element is an object literal
*/
private static _replaceNLStrings<T>(nlsConfig: NlsConfiguration, literal: T, messages: { [key: string]: string; }, originalMessages: { [key: string]: string }, log: ILog, messageScope: string): void {
private static _replaceNLStrings<T>(nlsConfig: NlsConfiguration, literal: T, messages: { [key: string]: string; }, originalMessages: { [key: string]: string } | null, log: ILog, messageScope: string): void {
function processEntry(obj: any, key: string | number, command?: boolean) {
let value = obj[key];
if (types.isString(value)) {
@@ -320,7 +318,7 @@ export interface IRelaxedExtensionDescription {
}
class ExtensionManifestValidator extends ExtensionManifestHandler {
validate(_extensionDescription: IExtensionDescription): IExtensionDescription {
validate(_extensionDescription: IExtensionDescription): IExtensionDescription | null {
let extensionDescription = <IRelaxedExtensionDescription>_extensionDescription;
extensionDescription.isBuiltin = this._isBuiltin;
extensionDescription.isUnderDevelopment = this._isUnderDevelopment;
@@ -338,12 +336,17 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
this._log.warn(this._absoluteFolderPath, error);
});
// allow publisher to be undefined to make the initial extension authoring experience smoother
if (!extensionDescription.publisher) {
extensionDescription.publisher = 'undefined_publisher';
}
// id := `publisher.name`
extensionDescription.id = `${extensionDescription.publisher}.${extensionDescription.name}`;
// main := absolutePath(`main`)
if (extensionDescription.main) {
extensionDescription.main = join(this._absoluteFolderPath, extensionDescription.main);
extensionDescription.main = path.join(this._absoluteFolderPath, extensionDescription.main);
}
extensionDescription.extensionLocation = URI.file(this._absoluteFolderPath);
@@ -370,8 +373,8 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
notices.push(nls.localize('extensionDescription.empty', "Got empty extension description"));
return false;
}
if (typeof extensionDescription.publisher !== 'string') {
notices.push(nls.localize('extensionDescription.publisher', "property `{0}` is mandatory and must be of type `string`", 'publisher'));
if (typeof extensionDescription.publisher !== 'undefined' && typeof extensionDescription.publisher !== 'string') {
notices.push(nls.localize('extensionDescription.publisher', "property publisher must be of type `string`."));
return false;
}
if (typeof extensionDescription.name !== 'string') {
@@ -411,7 +414,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
notices.push(nls.localize('extensionDescription.main1', "property `{0}` can be omitted or must be of type `string`", 'main'));
return false;
} else {
let normalizedAbsolutePath = join(extensionFolderPath, extensionDescription.main);
let normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.main);
if (normalizedAbsolutePath.indexOf(extensionFolderPath)) {
notices.push(nls.localize('extensionDescription.main2', "Expected `main` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", normalizedAbsolutePath, extensionFolderPath));
@@ -445,8 +448,8 @@ export class ExtensionScannerInput {
constructor(
public readonly ourVersion: string,
public readonly commit: string,
public readonly locale: string,
public readonly commit: string | undefined,
public readonly locale: string | undefined,
public readonly devMode: boolean,
public readonly absoluteFolderPath: string,
public readonly isBuiltin: boolean,
@@ -486,16 +489,16 @@ export interface IExtensionReference {
}
export interface IExtensionResolver {
resolveExtensions(): TPromise<IExtensionReference[]>;
resolveExtensions(): Promise<IExtensionReference[]>;
}
class DefaultExtensionResolver implements IExtensionResolver {
constructor(private root: string) { }
resolveExtensions(): TPromise<IExtensionReference[]> {
resolveExtensions(): Promise<IExtensionReference[]> {
return pfs.readDirsInDir(this.root)
.then(folders => folders.map(name => ({ name, path: join(this.root, name) })));
.then(folders => folders.map(name => ({ name, path: path.join(this.root, name) })));
}
}
@@ -504,11 +507,11 @@ export class ExtensionScanner {
/**
* Read the extension defined in `absoluteFolderPath`
*/
public static scanExtension(version: string, log: ILog, absoluteFolderPath: string, isBuiltin: boolean, isUnderDevelopment: boolean, nlsConfig: NlsConfiguration): TPromise<IExtensionDescription> {
absoluteFolderPath = normalize(absoluteFolderPath);
public 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);
return parser.parse().then((extensionDescription) => {
return parser.parse().then<IExtensionDescription | null>((extensionDescription) => {
if (extensionDescription === null) {
return null;
}
@@ -528,7 +531,7 @@ export class ExtensionScanner {
/**
* Scan a list of extensions defined in `absoluteFolderPath`
*/
public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver: IExtensionResolver = null): Promise<IExtensionDescription[]> {
public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver: IExtensionResolver | null = null): Promise<IExtensionDescription[]> {
const absoluteFolderPath = input.absoluteFolderPath;
const isBuiltin = input.isBuiltin;
const isUnderDevelopment = input.isUnderDevelopment;
@@ -541,7 +544,7 @@ export class ExtensionScanner {
let obsolete: { [folderName: string]: boolean; } = {};
if (!isBuiltin) {
try {
const obsoleteFileContents = await pfs.readFile(join(absoluteFolderPath, '.obsolete'), 'utf8');
const obsoleteFileContents = await pfs.readFile(path.join(absoluteFolderPath, '.obsolete'), 'utf8');
obsolete = JSON.parse(obsoleteFileContents);
} catch (err) {
// Don't care
@@ -572,7 +575,8 @@ export class ExtensionScanner {
}
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
let extensionDescriptions = await TPromise.join(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, isUnderDevelopment, nlsConfig)));
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)]);
if (!isBuiltin) {
@@ -598,12 +602,12 @@ export class ExtensionScanner {
* Combination of scanExtension and scanExtensions: If an extension manifest is found at root, we load just this extension,
* otherwise we assume the folder contains multiple extensions.
*/
public static scanOneOrMultipleExtensions(input: ExtensionScannerInput, log: ILog): TPromise<IExtensionDescription[]> {
public static scanOneOrMultipleExtensions(input: ExtensionScannerInput, log: ILog): Promise<IExtensionDescription[]> {
const absoluteFolderPath = input.absoluteFolderPath;
const isBuiltin = input.isBuiltin;
const isUnderDevelopment = input.isUnderDevelopment;
return pfs.fileExists(join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => {
return pfs.fileExists(path.join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => {
if (exists) {
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig).then((extensionDescription) => {
@@ -619,4 +623,31 @@ export class ExtensionScanner {
return [];
});
}
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];
}
// Overwrite with extensions found in extra
for (let i = 0, len = extraBuiltinExtensions.length; i < len; i++) {
resultMap[extraBuiltinExtensions[i].id] = extraBuiltinExtensions[i];
}
let resultArr = Object.keys(resultMap).map((id) => resultMap[id]);
resultArr.sort((a, b) => {
const aLastSegment = path.basename(a.extensionLocation.fsPath);
const bLastSegment = path.basename(b.extensionLocation.fsPath);
if (aLastSegment < bLastSegment) {
return -1;
}
if (aLastSegment > bLastSegment) {
return 1;
}
return 0;
});
return resultArr;
});
}
}

View File

@@ -2,18 +2,14 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise, ValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
import { onUnexpectedError } from 'vs/base/common/errors';
export class LazyPromise implements TPromise<any> {
export class LazyPromise implements Thenable<any> {
private _onCancel: () => void;
private _actual: TPromise<any>;
private _actualOk: ValueCallback;
private _actualErr: ErrorCallback;
private _actual: Promise<any> | null;
private _actualOk: ((value?: any) => any) | null;
private _actualErr: ((err?: any) => any) | null;
private _hasValue: boolean;
private _value: any;
@@ -21,10 +17,7 @@ export class LazyPromise implements TPromise<any> {
private _hasErr: boolean;
private _err: any;
private _isCanceled: boolean;
constructor(onCancel: () => void) {
this._onCancel = onCancel;
constructor() {
this._actual = null;
this._actualOk = null;
this._actualErr = null;
@@ -32,29 +25,28 @@ export class LazyPromise implements TPromise<any> {
this._value = null;
this._hasErr = false;
this._err = null;
this._isCanceled = false;
}
private _ensureActual(): TPromise<any> {
private _ensureActual(): Promise<any> {
if (!this._actual) {
this._actual = new TPromise<any>((c, e) => {
this._actual = new Promise<any>((c, e) => {
this._actualOk = c;
this._actualErr = e;
}, this._onCancel);
if (this._hasValue) {
this._actualOk(this._value);
}
if (this._hasValue) {
this._actualOk(this._value);
}
if (this._hasErr) {
this._actualErr(this._err);
}
if (this._hasErr) {
this._actualErr(this._err);
}
});
}
return this._actual;
}
public resolveOk(value: any): void {
if (this._isCanceled || this._hasErr) {
if (this._hasValue || this._hasErr) {
return;
}
@@ -62,12 +54,12 @@ export class LazyPromise implements TPromise<any> {
this._value = value;
if (this._actual) {
this._actualOk(value);
this._actualOk!(value);
}
}
public resolveErr(err: any): void {
if (this._isCanceled || this._hasValue) {
if (this._hasValue || this._hasErr) {
return;
}
@@ -75,7 +67,7 @@ export class LazyPromise implements TPromise<any> {
this._err = err;
if (this._actual) {
this._actualErr(err);
this._actualErr!(err);
} else {
// If nobody's listening at this point, it is safe to assume they never will,
// since resolving this promise is always "async"
@@ -84,32 +76,6 @@ export class LazyPromise implements TPromise<any> {
}
public then(success: any, error: any): any {
if (this._isCanceled) {
return;
}
return this._ensureActual().then(success, error);
}
public done(success: any, error: any): void {
if (this._isCanceled) {
return;
}
this._ensureActual().done(success, error);
}
public cancel(): void {
if (this._hasValue || this._hasErr) {
return;
}
this._isCanceled = true;
if (this._actual) {
this._actual.cancel();
} else {
this._onCancel();
}
}
}

View File

@@ -2,7 +2,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface IRPCProtocol {
/**
@@ -22,27 +21,35 @@ export interface IRPCProtocol {
}
export class ProxyIdentifier<T> {
public static count = 0;
_proxyIdentifierBrand: void;
_suppressCompilerUnusedWarning: T;
public readonly isMain: boolean;
public readonly id: string;
public readonly sid: string;
public readonly nid: number;
constructor(isMain: boolean, id: string) {
constructor(isMain: boolean, sid: string) {
this.isMain = isMain;
this.id = id;
this.sid = sid;
this.nid = (++ProxyIdentifier.count);
}
}
/**
* Using `isFancy` indicates that arguments or results of type `URI` or `RegExp`
* will be serialized/deserialized automatically, but this has a performance cost,
* as each argument/result must be visited.
*/
const identifiers: ProxyIdentifier<any>[] = [];
export function createMainContextProxyIdentifier<T>(identifier: string): ProxyIdentifier<T> {
return new ProxyIdentifier(true, 'm' + identifier);
const result = new ProxyIdentifier<T>(true, identifier);
identifiers[result.nid] = result;
return result;
}
export function createExtHostContextProxyIdentifier<T>(identifier: string): ProxyIdentifier<T> {
return new ProxyIdentifier(false, 'e' + identifier);
const result = new ProxyIdentifier<T>(false, identifier);
identifiers[result.nid] = result;
return result;
}
export function getStringIdentifierForProxy(nid: number): string {
return identifiers[nid].sid;
}

View File

@@ -2,19 +2,43 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { LazyPromise } from 'vs/workbench/services/extensions/node/lazyPromise';
import { ProxyIdentifier, IRPCProtocol } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { RunOnceScheduler } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { CharCode } from 'vs/base/common/charCode';
import URI from 'vs/base/common/uri';
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 { 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';
declare var Proxy: any; // TODO@TypeScript
export interface JSONStringifyReplacer {
(key: string, value: any): any;
}
function safeStringify(obj: any, replacer: JSONStringifyReplacer | null): string {
try {
return JSON.stringify(obj, <(key: string, value: any) => any>replacer);
} catch (err) {
return 'null';
}
}
function createURIReplacer(transformer: IURITransformer | null): JSONStringifyReplacer | null {
if (!transformer) {
return null;
}
return (key: string, value: any): any => {
if (value && value.$mid === 1) {
return transformer.transformOutgoing(value);
}
return value;
};
}
function _transformOutgoingURIs(obj: any, transformer: IURITransformer, depth: number): any {
@@ -41,7 +65,7 @@ function _transformOutgoingURIs(obj: any, transformer: IURITransformer, depth: n
return null;
}
export function transformOutgoingURIs(obj: any, transformer: IURITransformer): any {
export function transformOutgoingURIs<T>(obj: T, transformer: IURITransformer): T {
const result = _transformOutgoingURIs(obj, transformer, 0);
if (result === null) {
// no change
@@ -76,7 +100,7 @@ function _transformIncomingURIs(obj: any, transformer: IURITransformer, depth: n
return null;
}
function transformIncomingURIs(obj: any, transformer: IURITransformer): any {
function transformIncomingURIs<T>(obj: T, transformer: IURITransformer): T {
const result = _transformIncomingURIs(obj, transformer, 0);
if (result === null) {
// no change
@@ -85,26 +109,66 @@ function transformIncomingURIs(obj: any, transformer: IURITransformer): any {
return result;
}
export class RPCProtocol implements IRPCProtocol {
export const enum RequestInitiator {
LocalSide = 0,
OtherSide = 1
}
private readonly _uriTransformer: IURITransformer;
export const enum ResponsiveState {
Responsive = 0,
Unresponsive = 1
}
export interface IRPCProtocolLogger {
logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void;
logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void;
}
const noop = () => { };
export class RPCProtocol extends Disposable implements IRPCProtocol {
private static UNRESPONSIVE_TIME = 3 * 1000; // 3s
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
private readonly _protocol: IMessagePassingProtocol;
private readonly _logger: IRPCProtocolLogger | null;
private readonly _uriTransformer: IURITransformer | null;
private readonly _uriReplacer: JSONStringifyReplacer | null;
private _isDisposed: boolean;
private readonly _locals: { [id: string]: any; };
private readonly _proxies: { [id: string]: any; };
private readonly _locals: any[];
private readonly _proxies: any[];
private _lastMessageId: number;
private readonly _invokedHandlers: { [req: string]: TPromise<any>; };
private readonly _cancelInvokedHandlers: { [req: string]: () => void; };
private readonly _pendingRPCReplies: { [msgId: string]: LazyPromise; };
private readonly _multiplexor: RPCMultiplexer;
private _responsiveState: ResponsiveState;
private _unacknowledgedCount: number;
private _unresponsiveTime: number;
private _asyncCheckUresponsive: RunOnceScheduler;
constructor(protocol: IMessagePassingProtocol, transformer: IURITransformer = null) {
constructor(protocol: IMessagePassingProtocol, logger: IRPCProtocolLogger | null = null, transformer: IURITransformer | null = null) {
super();
this._protocol = protocol;
this._logger = logger;
this._uriTransformer = transformer;
this._uriReplacer = createURIReplacer(this._uriTransformer);
this._isDisposed = false;
this._locals = Object.create(null);
this._proxies = Object.create(null);
this._locals = [];
this._proxies = [];
for (let i = 0, len = ProxyIdentifier.count; i < len; i++) {
this._locals[i] = null;
this._proxies[i] = null;
}
this._lastMessageId = 0;
this._invokedHandlers = Object.create(null);
this._cancelInvokedHandlers = Object.create(null);
this._pendingRPCReplies = {};
this._multiplexor = new RPCMultiplexer(protocol, (msg) => this._receiveOneMessage(msg));
this._responsiveState = ResponsiveState.Responsive;
this._unacknowledgedCount = 0;
this._unresponsiveTime = 0;
this._asyncCheckUresponsive = this._register(new RunOnceScheduler(() => this._checkUnresponsive(), 1000));
this._protocol.onMessage((msg) => this._receiveOneMessage(msg));
}
public dispose(): void {
@@ -117,6 +181,58 @@ export class RPCProtocol implements IRPCProtocol {
});
}
private _onWillSendRequest(req: number): void {
if (this._unacknowledgedCount === 0) {
// Since this is the first request we are sending in a while,
// mark this moment as the start for the countdown to unresponsive time
this._unresponsiveTime = Date.now() + RPCProtocol.UNRESPONSIVE_TIME;
}
this._unacknowledgedCount++;
if (!this._asyncCheckUresponsive.isScheduled()) {
this._asyncCheckUresponsive.schedule();
}
}
private _onDidReceiveAcknowledge(req: number): void {
// The next possible unresponsive time is now + delta.
this._unresponsiveTime = Date.now() + RPCProtocol.UNRESPONSIVE_TIME;
this._unacknowledgedCount--;
if (this._unacknowledgedCount === 0) {
// No more need to check for unresponsive
this._asyncCheckUresponsive.cancel();
}
// The ext host is responsive!
this._setResponsiveState(ResponsiveState.Responsive);
}
private _checkUnresponsive(): void {
if (this._unacknowledgedCount === 0) {
// Not waiting for anything => cannot say if it is responsive or not
return;
}
if (Date.now() > this._unresponsiveTime) {
// Unresponsive!!
this._setResponsiveState(ResponsiveState.Unresponsive);
} else {
// Not (yet) unresponsive, be sure to check again soon
this._asyncCheckUresponsive.schedule();
}
}
private _setResponsiveState(newResponsiveState: ResponsiveState): void {
if (this._responsiveState === newResponsiveState) {
// no change
return;
}
this._responsiveState = newResponsiveState;
this._onDidChangeResponsiveState.fire(this._responsiveState);
}
public get responsiveState(): ResponsiveState {
return this._responsiveState;
}
public transformIncomingURIs<T>(obj: T): T {
if (!this._uriTransformer) {
return obj;
@@ -125,18 +241,19 @@ export class RPCProtocol implements IRPCProtocol {
}
public getProxy<T>(identifier: ProxyIdentifier<T>): T {
if (!this._proxies[identifier.id]) {
this._proxies[identifier.id] = this._createProxy(identifier.id);
const rpcId = identifier.nid;
if (!this._proxies[rpcId]) {
this._proxies[rpcId] = this._createProxy(rpcId);
}
return this._proxies[identifier.id];
return this._proxies[rpcId];
}
private _createProxy<T>(proxyId: string): T {
private _createProxy<T>(rpcId: number): T {
let handler = {
get: (target: any, name: string) => {
if (!target[name] && name.charCodeAt(0) === CharCode.DollarSign) {
target[name] = (...myArgs: any[]) => {
return this._remoteCall(proxyId, name, myArgs);
return this._remoteCall(rpcId, name, myArgs);
};
}
return target[name];
@@ -146,72 +263,151 @@ export class RPCProtocol implements IRPCProtocol {
}
public set<T, R extends T>(identifier: ProxyIdentifier<T>, value: R): R {
this._locals[identifier.id] = value;
this._locals[identifier.nid] = value;
return value;
}
public assertRegistered(identifiers: ProxyIdentifier<any>[]): void {
for (let i = 0, len = identifiers.length; i < len; i++) {
const identifier = identifiers[i];
if (!this._locals[identifier.id]) {
throw new Error(`Missing actor ${identifier.id} (isMain: ${identifier.isMain})`);
if (!this._locals[identifier.nid]) {
throw new Error(`Missing actor ${identifier.sid} (isMain: ${identifier.isMain})`);
}
}
}
private _receiveOneMessage(rawmsg: string): void {
private _receiveOneMessage(rawmsg: Buffer): void {
if (this._isDisposed) {
return;
}
let msg = <RPCMessage>JSON.parse(rawmsg);
if (this._uriTransformer) {
msg = transformIncomingURIs(msg, this._uriTransformer);
}
const msgLength = rawmsg.length;
const buff = MessageBuffer.read(rawmsg, 0);
const messageType = <MessageType>buff.readUInt8();
const req = buff.readUInt32();
switch (msg.type) {
case MessageType.Request:
this._receiveRequest(msg);
switch (messageType) {
case MessageType.RequestJSONArgs:
case MessageType.RequestJSONArgsWithCancellation: {
let { rpcId, method, args } = MessageIO.deserializeRequestJSONArgs(buff);
if (this._uriTransformer) {
args = transformIncomingURIs(args, this._uriTransformer);
}
this._receiveRequest(msgLength, req, rpcId, method, args, (messageType === MessageType.RequestJSONArgsWithCancellation));
break;
case MessageType.Cancel:
this._receiveCancel(msg);
}
case MessageType.RequestMixedArgs:
case MessageType.RequestMixedArgsWithCancellation: {
let { rpcId, method, args } = MessageIO.deserializeRequestMixedArgs(buff);
if (this._uriTransformer) {
args = transformIncomingURIs(args, this._uriTransformer);
}
this._receiveRequest(msgLength, req, rpcId, method, args, (messageType === MessageType.RequestMixedArgsWithCancellation));
break;
case MessageType.Reply:
this._receiveReply(msg);
}
case MessageType.Acknowledged: {
if (this._logger) {
this._logger.logIncoming(msgLength, req, RequestInitiator.LocalSide, `ack`);
}
this._onDidReceiveAcknowledge(req);
break;
case MessageType.ReplyErr:
this._receiveReplyErr(msg);
}
case MessageType.Cancel: {
this._receiveCancel(msgLength, req);
break;
}
case MessageType.ReplyOKEmpty: {
this._receiveReply(msgLength, req, undefined);
break;
}
case MessageType.ReplyOKJSON: {
let value = MessageIO.deserializeReplyOKJSON(buff);
if (this._uriTransformer) {
value = transformIncomingURIs(value, this._uriTransformer);
}
this._receiveReply(msgLength, req, value);
break;
}
case MessageType.ReplyOKBuffer: {
let value = MessageIO.deserializeReplyOKBuffer(buff);
this._receiveReply(msgLength, req, value);
break;
}
case MessageType.ReplyErrError: {
let err = MessageIO.deserializeReplyErrError(buff);
if (this._uriTransformer) {
err = transformIncomingURIs(err, this._uriTransformer);
}
this._receiveReplyErr(msgLength, req, err);
break;
}
case MessageType.ReplyErrEmpty: {
this._receiveReplyErr(msgLength, req, undefined);
break;
}
}
}
private _receiveRequest(msg: RequestMessage): void {
const callId = msg.id;
const proxyId = msg.proxyId;
private _receiveRequest(msgLength: number, req: number, rpcId: number, method: string, args: any[], usesCancellationToken: boolean): void {
if (this._logger) {
this._logger.logIncoming(msgLength, req, RequestInitiator.OtherSide, `receiveRequest ${getStringIdentifierForProxy(rpcId)}.${method}(`, args);
}
const callId = String(req);
this._invokedHandlers[callId] = this._invokeHandler(proxyId, msg.method, msg.args);
let promise: Thenable<any>;
let cancel: () => void;
if (usesCancellationToken) {
const cancellationTokenSource = new CancellationTokenSource();
args.push(cancellationTokenSource.token);
promise = this._invokeHandler(rpcId, method, args);
cancel = () => cancellationTokenSource.cancel();
} else {
// cannot be cancelled
promise = this._invokeHandler(rpcId, method, args);
cancel = noop;
}
this._invokedHandlers[callId].then((r) => {
delete this._invokedHandlers[callId];
if (this._uriTransformer) {
r = transformOutgoingURIs(r, this._uriTransformer);
this._cancelInvokedHandlers[callId] = cancel;
// Acknowledge the request
const msg = MessageIO.serializeAcknowledged(req);
if (this._logger) {
this._logger.logOutgoing(msg.byteLength, req, RequestInitiator.OtherSide, `ack`);
}
this._protocol.send(msg);
promise.then((r) => {
delete this._cancelInvokedHandlers[callId];
const msg = MessageIO.serializeReplyOK(req, r, this._uriReplacer);
if (this._logger) {
this._logger.logOutgoing(msg.byteLength, req, RequestInitiator.OtherSide, `reply:`, r);
}
this._multiplexor.send(MessageFactory.replyOK(callId, r));
this._protocol.send(msg);
}, (err) => {
delete this._invokedHandlers[callId];
this._multiplexor.send(MessageFactory.replyErr(callId, err));
delete this._cancelInvokedHandlers[callId];
const msg = MessageIO.serializeReplyErr(req, err);
if (this._logger) {
this._logger.logOutgoing(msg.byteLength, req, RequestInitiator.OtherSide, `replyErr:`, err);
}
this._protocol.send(msg);
});
}
private _receiveCancel(msg: CancelMessage): void {
const callId = msg.id;
if (this._invokedHandlers[callId]) {
this._invokedHandlers[callId].cancel();
private _receiveCancel(msgLength: number, req: number): void {
if (this._logger) {
this._logger.logIncoming(msgLength, req, RequestInitiator.OtherSide, `receiveCancel`);
}
const callId = String(req);
if (this._cancelInvokedHandlers[callId]) {
this._cancelInvokedHandlers[callId]();
}
}
private _receiveReply(msg: ReplyMessage): void {
const callId = msg.id;
private _receiveReply(msgLength: number, req: number, value: any): void {
if (this._logger) {
this._logger.logIncoming(msgLength, req, RequestInitiator.LocalSide, `receiveReply:`, value);
}
const callId = String(req);
if (!this._pendingRPCReplies.hasOwnProperty(callId)) {
return;
}
@@ -219,11 +415,15 @@ export class RPCProtocol implements IRPCProtocol {
const pendingReply = this._pendingRPCReplies[callId];
delete this._pendingRPCReplies[callId];
pendingReply.resolveOk(msg.res);
pendingReply.resolveOk(value);
}
private _receiveReplyErr(msg: ReplyErrMessage): void {
const callId = msg.id;
private _receiveReplyErr(msgLength: number, req: number, value: any): void {
if (this._logger) {
this._logger.logIncoming(msgLength, req, RequestInitiator.LocalSide, `receiveReplyErr:`, value);
}
const callId = String(req);
if (!this._pendingRPCReplies.hasOwnProperty(callId)) {
return;
}
@@ -231,145 +431,402 @@ export class RPCProtocol implements IRPCProtocol {
const pendingReply = this._pendingRPCReplies[callId];
delete this._pendingRPCReplies[callId];
let err: Error = null;
if (msg.err && msg.err.$isError) {
let err: Error | null = null;
if (value && value.$isError) {
err = new Error();
err.name = msg.err.name;
err.message = msg.err.message;
err.stack = msg.err.stack;
err.name = value.name;
err.message = value.message;
err.stack = value.stack;
}
pendingReply.resolveErr(err);
}
private _invokeHandler(proxyId: string, methodName: string, args: any[]): TPromise<any> {
private _invokeHandler(rpcId: number, methodName: string, args: any[]): Thenable<any> {
try {
return TPromise.as(this._doInvokeHandler(proxyId, methodName, args));
return Promise.resolve(this._doInvokeHandler(rpcId, methodName, args));
} catch (err) {
return TPromise.wrapError(err);
return Promise.reject(err);
}
}
private _doInvokeHandler(proxyId: string, methodName: string, args: any[]): any {
if (!this._locals[proxyId]) {
throw new Error('Unknown actor ' + proxyId);
private _doInvokeHandler(rpcId: number, methodName: string, args: any[]): any {
const actor = this._locals[rpcId];
if (!actor) {
throw new Error('Unknown actor ' + getStringIdentifierForProxy(rpcId));
}
let actor = this._locals[proxyId];
let method = actor[methodName];
if (typeof method !== 'function') {
throw new Error('Unknown method ' + methodName + ' on actor ' + proxyId);
throw new Error('Unknown method ' + methodName + ' on actor ' + getStringIdentifierForProxy(rpcId));
}
return method.apply(actor, args);
}
private _remoteCall(proxyId: string, methodName: string, args: any[]): TPromise<any> {
private _remoteCall(rpcId: number, methodName: string, args: any[]): Thenable<any> {
if (this._isDisposed) {
return TPromise.wrapError<any>(errors.canceled());
return Promise.reject<any>(errors.canceled());
}
let cancellationToken: CancellationToken | null = null;
if (args.length > 0 && CancellationToken.isCancellationToken(args[args.length - 1])) {
cancellationToken = args.pop();
}
const callId = String(++this._lastMessageId);
const result = new LazyPromise(() => {
this._multiplexor.send(MessageFactory.cancel(callId));
});
if (cancellationToken && cancellationToken.isCancellationRequested) {
// No need to do anything...
return Promise.reject<any>(errors.canceled());
}
const req = ++this._lastMessageId;
const callId = String(req);
const result = new LazyPromise();
if (cancellationToken) {
cancellationToken.onCancellationRequested(() => {
const msg = MessageIO.serializeCancel(req);
if (this._logger) {
this._logger.logOutgoing(msg.byteLength, req, RequestInitiator.LocalSide, `cancel`);
}
this._protocol.send(MessageIO.serializeCancel(req));
});
}
this._pendingRPCReplies[callId] = result;
if (this._uriTransformer) {
args = transformOutgoingURIs(args, this._uriTransformer);
this._onWillSendRequest(req);
const msg = MessageIO.serializeRequest(req, rpcId, methodName, args, !!cancellationToken, this._uriReplacer);
if (this._logger) {
this._logger.logOutgoing(msg.byteLength, req, RequestInitiator.LocalSide, `request: ${getStringIdentifierForProxy(rpcId)}.${methodName}(`, args);
}
this._multiplexor.send(MessageFactory.request(callId, proxyId, methodName, args));
this._protocol.send(msg);
return result;
}
}
/**
* Sends/Receives multiple messages in one go:
* - multiple messages to be sent from one stack get sent in bulk at `process.nextTick`.
* - each incoming message is handled in a separate `process.nextTick`.
*/
class RPCMultiplexer {
class MessageBuffer {
private readonly _protocol: IMessagePassingProtocol;
private readonly _sendAccumulatedBound: () => void;
public static alloc(type: MessageType, req: number, messageSize: number): MessageBuffer {
let result = new MessageBuffer(Buffer.allocUnsafe(messageSize + 1 /* type */ + 4 /* req */), 0);
result.writeUInt8(type);
result.writeUInt32(req);
return result;
}
private _messagesToSend: string[];
public static read(buff: Buffer, offset: number): MessageBuffer {
return new MessageBuffer(buff, offset);
}
constructor(protocol: IMessagePassingProtocol, onMessage: (msg: string) => void) {
this._protocol = protocol;
this._sendAccumulatedBound = this._sendAccumulated.bind(this);
private _buff: Buffer;
private _offset: number;
this._messagesToSend = [];
public get buffer(): Buffer {
return this._buff;
}
this._protocol.onMessage(data => {
for (let i = 0, len = data.length; i < len; i++) {
onMessage(data[i]);
private constructor(buff: Buffer, offset: number) {
this._buff = buff;
this._offset = offset;
}
public static sizeUInt8(): number {
return 1;
}
public writeUInt8(n: number): void {
this._buff.writeUInt8(n, this._offset, true); this._offset += 1;
}
public readUInt8(): number {
const n = this._buff.readUInt8(this._offset, true); this._offset += 1;
return n;
}
public writeUInt32(n: number): void {
this._buff.writeUInt32BE(n, this._offset, true); this._offset += 4;
}
public readUInt32(): number {
const n = this._buff.readUInt32BE(this._offset, true); this._offset += 4;
return n;
}
public static sizeShortString(str: string, strByteLength: number): number {
return 1 /* string length */ + strByteLength /* actual string */;
}
public writeShortString(str: string, strByteLength: number): void {
this._buff.writeUInt8(strByteLength, this._offset, true); this._offset += 1;
this._buff.write(str, this._offset, strByteLength, 'utf8'); this._offset += strByteLength;
}
public readShortString(): string {
const strLength = this._buff.readUInt8(this._offset, true); this._offset += 1;
const str = this._buff.toString('utf8', this._offset, this._offset + strLength); this._offset += strLength;
return str;
}
public static sizeLongString(str: string, strByteLength: number): number {
return 4 /* string length */ + strByteLength /* actual string */;
}
public writeLongString(str: string, strByteLength: number): void {
this._buff.writeUInt32LE(strByteLength, this._offset, true); this._offset += 4;
this._buff.write(str, this._offset, strByteLength, 'utf8'); this._offset += strByteLength;
}
public readLongString(): string {
const strLength = this._buff.readUInt32LE(this._offset, true); this._offset += 4;
const str = this._buff.toString('utf8', this._offset, this._offset + strLength); this._offset += strLength;
return str;
}
public static sizeBuffer(buff: Buffer, buffByteLength: number): number {
return 4 /* buffer length */ + buffByteLength /* actual buffer */;
}
public writeBuffer(buff: Buffer, buffByteLength: number): void {
this._buff.writeUInt32LE(buffByteLength, this._offset, true); this._offset += 4;
buff.copy(this._buff, this._offset); this._offset += buffByteLength;
}
public readBuffer(): Buffer {
const buffLength = this._buff.readUInt32LE(this._offset, true); this._offset += 4;
const buff = this._buff.slice(this._offset, this._offset + buffLength); this._offset += buffLength;
return buff;
}
public static sizeMixedArray(arr: (string | Buffer)[], arrLengths: number[]): number {
let size = 0;
size += 1; // arr length
for (let i = 0, len = arr.length; i < len; i++) {
const el = arr[i];
const elLength = arrLengths[i];
size += 1; // arg type
if (typeof el === 'string') {
size += this.sizeLongString(el, elLength);
} else {
size += this.sizeBuffer(el, elLength);
}
});
}
private _sendAccumulated(): void {
const tmp = this._messagesToSend;
this._messagesToSend = [];
this._protocol.send(tmp);
}
public send(msg: string): void {
if (this._messagesToSend.length === 0) {
process.nextTick(this._sendAccumulatedBound);
}
this._messagesToSend.push(msg);
return size;
}
public writeMixedArray(arr: (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];
const elLength = arrLengths[i];
if (typeof el === 'string') {
this.writeUInt8(ArgType.String);
this.writeLongString(el, elLength);
} else {
this.writeUInt8(ArgType.Buffer);
this.writeBuffer(el, elLength);
}
}
}
public readMixedArray(): (string | Buffer)[] {
const arrLen = this._buff.readUInt8(this._offset, true); this._offset += 1;
let arr: (string | Buffer)[] = new Array(arrLen);
for (let i = 0; i < arrLen; i++) {
const argType = <ArgType>this.readUInt8();
if (argType === ArgType.String) {
arr[i] = this.readLongString();
} else {
arr[i] = this.readBuffer();
}
}
return arr;
}
}
class MessageFactory {
public static cancel(req: string): string {
return `{"type":${MessageType.Cancel},"id":"${req}"}`;
class MessageIO {
private static _arrayContainsBuffer(arr: any[]): boolean {
for (let i = 0, len = arr.length; i < len; i++) {
if (Buffer.isBuffer(arr[i])) {
return true;
}
}
return false;
}
public static request(req: string, rpcId: string, method: string, args: any[]): string {
return `{"type":${MessageType.Request},"id":"${req}","proxyId":"${rpcId}","method":"${method}","args":${JSON.stringify(args)}}`;
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 argsLengths: number[] = new Array(args.length);
for (let i = 0, len = args.length; i < len; i++) {
const arg = args[i];
if (Buffer.isBuffer(arg)) {
massagedArgs[i] = arg;
argsLengths[i] = arg.byteLength;
} else {
massagedArgs[i] = safeStringify(arg, replacer);
argsLengths[i] = Buffer.byteLength(massagedArgs[i], 'utf8');
}
}
return this._requestMixedArgs(req, rpcId, method, massagedArgs, argsLengths, usesCancellationToken);
}
return this._requestJSONArgs(req, rpcId, method, safeStringify(args, replacer), usesCancellationToken);
}
public static replyOK(req: string, res: any): string {
private static _requestJSONArgs(req: number, rpcId: number, method: string, args: string, usesCancellationToken: boolean): Buffer {
const methodByteLength = Buffer.byteLength(method, 'utf8');
const argsByteLength = Buffer.byteLength(args, 'utf8');
let len = 0;
len += MessageBuffer.sizeUInt8();
len += MessageBuffer.sizeShortString(method, methodByteLength);
len += MessageBuffer.sizeLongString(args, argsByteLength);
let result = MessageBuffer.alloc(usesCancellationToken ? MessageType.RequestJSONArgsWithCancellation : MessageType.RequestJSONArgs, req, len);
result.writeUInt8(rpcId);
result.writeShortString(method, methodByteLength);
result.writeLongString(args, argsByteLength);
return result.buffer;
}
public static deserializeRequestJSONArgs(buff: MessageBuffer): { rpcId: number; method: string; args: any[]; } {
const rpcId = buff.readUInt8();
const method = buff.readShortString();
const args = buff.readLongString();
return {
rpcId: rpcId,
method: method,
args: JSON.parse(args)
};
}
private static _requestMixedArgs(req: number, rpcId: number, method: string, args: (string | Buffer)[], argsLengths: number[], usesCancellationToken: boolean): Buffer {
const methodByteLength = Buffer.byteLength(method, 'utf8');
let len = 0;
len += MessageBuffer.sizeUInt8();
len += MessageBuffer.sizeShortString(method, methodByteLength);
len += MessageBuffer.sizeMixedArray(args, argsLengths);
let result = MessageBuffer.alloc(usesCancellationToken ? MessageType.RequestMixedArgsWithCancellation : MessageType.RequestMixedArgs, req, len);
result.writeUInt8(rpcId);
result.writeShortString(method, methodByteLength);
result.writeMixedArray(args, argsLengths);
return result.buffer;
}
public static deserializeRequestMixedArgs(buff: MessageBuffer): { rpcId: number; method: string; args: any[]; } {
const rpcId = buff.readUInt8();
const method = buff.readShortString();
const rawargs = buff.readMixedArray();
const args: any[] = new Array(rawargs.length);
for (let i = 0, len = rawargs.length; i < len; i++) {
const rawarg = rawargs[i];
if (typeof rawarg === 'string') {
args[i] = JSON.parse(rawarg);
} else {
args[i] = rawarg;
}
}
return {
rpcId: rpcId,
method: method,
args: args
};
}
public static serializeAcknowledged(req: number): Buffer {
return MessageBuffer.alloc(MessageType.Acknowledged, req, 0).buffer;
}
public static serializeCancel(req: number): Buffer {
return MessageBuffer.alloc(MessageType.Cancel, req, 0).buffer;
}
public static serializeReplyOK(req: number, res: any, replacer: JSONStringifyReplacer | null): Buffer {
if (typeof res === 'undefined') {
return `{"type":${MessageType.Reply},"id":"${req}"}`;
return this._serializeReplyOKEmpty(req);
}
return `{"type":${MessageType.Reply},"id":"${req}","res":${JSON.stringify(res)}}`;
if (Buffer.isBuffer(res)) {
return this._serializeReplyOKBuffer(req, res);
}
return this._serializeReplyOKJSON(req, safeStringify(res, replacer));
}
public static replyErr(req: string, err: any): string {
private static _serializeReplyOKEmpty(req: number): Buffer {
return MessageBuffer.alloc(MessageType.ReplyOKEmpty, req, 0).buffer;
}
private static _serializeReplyOKBuffer(req: number, res: Buffer): Buffer {
const resByteLength = res.byteLength;
let len = 0;
len += MessageBuffer.sizeBuffer(res, resByteLength);
let result = MessageBuffer.alloc(MessageType.ReplyOKBuffer, req, len);
result.writeBuffer(res, resByteLength);
return result.buffer;
}
public static deserializeReplyOKBuffer(buff: MessageBuffer): Buffer {
return buff.readBuffer();
}
private static _serializeReplyOKJSON(req: number, res: string): Buffer {
const resByteLength = Buffer.byteLength(res, 'utf8');
let len = 0;
len += MessageBuffer.sizeLongString(res, resByteLength);
let result = MessageBuffer.alloc(MessageType.ReplyOKJSON, req, len);
result.writeLongString(res, resByteLength);
return result.buffer;
}
public static deserializeReplyOKJSON(buff: MessageBuffer): any {
const res = buff.readLongString();
return JSON.parse(res);
}
public static serializeReplyErr(req: number, err: any): Buffer {
if (err instanceof Error) {
return `{"type":${MessageType.ReplyErr},"id":"${req}","err":${JSON.stringify(errors.transformErrorForSerialization(err))}}`;
return this._serializeReplyErrEror(req, err);
}
return `{"type":${MessageType.ReplyErr},"id":"${req}","err":null}`;
return this._serializeReplyErrEmpty(req);
}
private static _serializeReplyErrEror(req: number, _err: Error): Buffer {
const err = safeStringify(errors.transformErrorForSerialization(_err), null);
const errByteLength = Buffer.byteLength(err, 'utf8');
let len = 0;
len += MessageBuffer.sizeLongString(err, errByteLength);
let result = MessageBuffer.alloc(MessageType.ReplyErrError, req, len);
result.writeLongString(err, errByteLength);
return result.buffer;
}
public static deserializeReplyErrError(buff: MessageBuffer): Error {
const err = buff.readLongString();
return JSON.parse(err);
}
private static _serializeReplyErrEmpty(req: number): Buffer {
return MessageBuffer.alloc(MessageType.ReplyErrEmpty, req, 0).buffer;
}
}
const enum MessageType {
Request = 1,
Cancel = 2,
Reply = 3,
ReplyErr = 4
RequestJSONArgs = 1,
RequestJSONArgsWithCancellation = 2,
RequestMixedArgs = 3,
RequestMixedArgsWithCancellation = 4,
Acknowledged = 5,
Cancel = 6,
ReplyOKEmpty = 7,
ReplyOKBuffer = 8,
ReplyOKJSON = 9,
ReplyErrError = 10,
ReplyErrEmpty = 11,
}
class RequestMessage {
type: MessageType.Request;
id: string;
proxyId: string;
method: string;
args: any[];
const enum ArgType {
String = 1,
Buffer = 2
}
class CancelMessage {
type: MessageType.Cancel;
id: string;
}
class ReplyMessage {
type: MessageType.Reply;
id: string;
res: any;
}
class ReplyErrMessage {
type: MessageType.ReplyErr;
id: string;
err: errors.SerializedError;
}
type RPCMessage = RequestMessage | CancelMessage | ReplyMessage | ReplyErrMessage;