sussudio
Version:
An unofficial VS Code Internal API
968 lines (967 loc) • 39.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { getRandomElement } from "../../../common/arrays.mjs";
import { createCancelablePromise, timeout } from "../../../common/async.mjs";
import { VSBuffer } from "../../../common/buffer.mjs";
import { CancellationToken, CancellationTokenSource } from "../../../common/cancellation.mjs";
import { memoize } from "../../../common/decorators.mjs";
import { CancellationError } from "../../../common/errors.mjs";
import { Emitter, Event, EventMultiplexer, Relay } from "../../../common/event.mjs";
import { combinedDisposable, DisposableStore, dispose, toDisposable } from "../../../common/lifecycle.mjs";
import { revive } from "../../../common/marshalling.mjs";
import * as strings from "../../../common/strings.mjs";
import { isFunction, isUndefinedOrNull } from "../../../common/types.mjs";
var RequestType;
(function (RequestType) {
RequestType[RequestType["Promise"] = 100] = "Promise";
RequestType[RequestType["PromiseCancel"] = 101] = "PromiseCancel";
RequestType[RequestType["EventListen"] = 102] = "EventListen";
RequestType[RequestType["EventDispose"] = 103] = "EventDispose";
})(RequestType || (RequestType = {}));
function requestTypeToStr(type) {
switch (type) {
case 100 /* RequestType.Promise */:
return 'req';
case 101 /* RequestType.PromiseCancel */:
return 'cancel';
case 102 /* RequestType.EventListen */:
return 'subscribe';
case 103 /* RequestType.EventDispose */:
return 'unsubscribe';
}
}
var ResponseType;
(function (ResponseType) {
ResponseType[ResponseType["Initialize"] = 200] = "Initialize";
ResponseType[ResponseType["PromiseSuccess"] = 201] = "PromiseSuccess";
ResponseType[ResponseType["PromiseError"] = 202] = "PromiseError";
ResponseType[ResponseType["PromiseErrorObj"] = 203] = "PromiseErrorObj";
ResponseType[ResponseType["EventFire"] = 204] = "EventFire";
})(ResponseType || (ResponseType = {}));
function responseTypeToStr(type) {
switch (type) {
case 200 /* ResponseType.Initialize */:
return `init`;
case 201 /* ResponseType.PromiseSuccess */:
return `reply:`;
case 202 /* ResponseType.PromiseError */:
case 203 /* ResponseType.PromiseErrorObj */:
return `replyErr:`;
case 204 /* ResponseType.EventFire */:
return `event:`;
}
}
var State;
(function (State) {
State[State["Uninitialized"] = 0] = "Uninitialized";
State[State["Idle"] = 1] = "Idle";
})(State || (State = {}));
/**
* @see https://en.wikipedia.org/wiki/Variable-length_quantity
*/
function readIntVQL(reader) {
let value = 0;
for (let n = 0;; n += 7) {
const next = reader.read(1);
value |= (next.buffer[0] & 0b01111111) << n;
if (!(next.buffer[0] & 0b10000000)) {
return value;
}
}
}
const vqlZero = createOneByteBuffer(0);
/**
* @see https://en.wikipedia.org/wiki/Variable-length_quantity
*/
function writeInt32VQL(writer, value) {
if (value === 0) {
writer.write(vqlZero);
return;
}
let len = 0;
for (let v2 = value; v2 !== 0; v2 = v2 >>> 7) {
len++;
}
const scratch = VSBuffer.alloc(len);
for (let i = 0; value !== 0; i++) {
scratch.buffer[i] = value & 0b01111111;
value = value >>> 7;
if (value > 0) {
scratch.buffer[i] |= 0b10000000;
}
}
writer.write(scratch);
}
export class BufferReader {
buffer;
pos = 0;
constructor(buffer) {
this.buffer = buffer;
}
read(bytes) {
const result = this.buffer.slice(this.pos, this.pos + bytes);
this.pos += result.byteLength;
return result;
}
}
export class BufferWriter {
buffers = [];
get buffer() {
return VSBuffer.concat(this.buffers);
}
write(buffer) {
this.buffers.push(buffer);
}
}
var DataType;
(function (DataType) {
DataType[DataType["Undefined"] = 0] = "Undefined";
DataType[DataType["String"] = 1] = "String";
DataType[DataType["Buffer"] = 2] = "Buffer";
DataType[DataType["VSBuffer"] = 3] = "VSBuffer";
DataType[DataType["Array"] = 4] = "Array";
DataType[DataType["Object"] = 5] = "Object";
DataType[DataType["Int"] = 6] = "Int";
})(DataType || (DataType = {}));
function createOneByteBuffer(value) {
const result = VSBuffer.alloc(1);
result.writeUInt8(value, 0);
return result;
}
const BufferPresets = {
Undefined: createOneByteBuffer(DataType.Undefined),
String: createOneByteBuffer(DataType.String),
Buffer: createOneByteBuffer(DataType.Buffer),
VSBuffer: createOneByteBuffer(DataType.VSBuffer),
Array: createOneByteBuffer(DataType.Array),
Object: createOneByteBuffer(DataType.Object),
Uint: createOneByteBuffer(DataType.Int),
};
const hasBuffer = (typeof Buffer !== 'undefined');
export function serialize(writer, data) {
if (typeof data === 'undefined') {
writer.write(BufferPresets.Undefined);
}
else if (typeof data === 'string') {
const buffer = VSBuffer.fromString(data);
writer.write(BufferPresets.String);
writeInt32VQL(writer, buffer.byteLength);
writer.write(buffer);
}
else if (hasBuffer && Buffer.isBuffer(data)) {
const buffer = VSBuffer.wrap(data);
writer.write(BufferPresets.Buffer);
writeInt32VQL(writer, buffer.byteLength);
writer.write(buffer);
}
else if (data instanceof VSBuffer) {
writer.write(BufferPresets.VSBuffer);
writeInt32VQL(writer, data.byteLength);
writer.write(data);
}
else if (Array.isArray(data)) {
writer.write(BufferPresets.Array);
writeInt32VQL(writer, data.length);
for (const el of data) {
serialize(writer, el);
}
}
else if (typeof data === 'number' && (data | 0) === data) {
// write a vql if it's a number that we can do bitwise operations on
writer.write(BufferPresets.Uint);
writeInt32VQL(writer, data);
}
else {
const buffer = VSBuffer.fromString(JSON.stringify(data));
writer.write(BufferPresets.Object);
writeInt32VQL(writer, buffer.byteLength);
writer.write(buffer);
}
}
export function deserialize(reader) {
const type = reader.read(1).readUInt8(0);
switch (type) {
case DataType.Undefined: return undefined;
case DataType.String: return reader.read(readIntVQL(reader)).toString();
case DataType.Buffer: return reader.read(readIntVQL(reader)).buffer;
case DataType.VSBuffer: return reader.read(readIntVQL(reader));
case DataType.Array: {
const length = readIntVQL(reader);
const result = [];
for (let i = 0; i < length; i++) {
result.push(deserialize(reader));
}
return result;
}
case DataType.Object: return JSON.parse(reader.read(readIntVQL(reader)).toString());
case DataType.Int: return readIntVQL(reader);
}
}
export class ChannelServer {
protocol;
ctx;
logger;
timeoutDelay;
channels = new Map();
activeRequests = new Map();
protocolListener;
// Requests might come in for channels which are not yet registered.
// They will timeout after `timeoutDelay`.
pendingRequests = new Map();
constructor(protocol, ctx, logger = null, timeoutDelay = 1000) {
this.protocol = protocol;
this.ctx = ctx;
this.logger = logger;
this.timeoutDelay = timeoutDelay;
this.protocolListener = this.protocol.onMessage(msg => this.onRawMessage(msg));
this.sendResponse({ type: 200 /* ResponseType.Initialize */ });
}
registerChannel(channelName, channel) {
this.channels.set(channelName, channel);
// https://github.com/microsoft/vscode/issues/72531
setTimeout(() => this.flushPendingRequests(channelName), 0);
}
sendResponse(response) {
switch (response.type) {
case 200 /* ResponseType.Initialize */: {
const msgLength = this.send([response.type]);
this.logger?.logOutgoing(msgLength, 0, 1 /* RequestInitiator.OtherSide */, responseTypeToStr(response.type));
return;
}
case 201 /* ResponseType.PromiseSuccess */:
case 202 /* ResponseType.PromiseError */:
case 204 /* ResponseType.EventFire */:
case 203 /* ResponseType.PromiseErrorObj */: {
const msgLength = this.send([response.type, response.id], response.data);
this.logger?.logOutgoing(msgLength, response.id, 1 /* RequestInitiator.OtherSide */, responseTypeToStr(response.type), response.data);
return;
}
}
}
send(header, body = undefined) {
const writer = new BufferWriter();
serialize(writer, header);
serialize(writer, body);
return this.sendBuffer(writer.buffer);
}
sendBuffer(message) {
try {
this.protocol.send(message);
return message.byteLength;
}
catch (err) {
// noop
return 0;
}
}
onRawMessage(message) {
const reader = new BufferReader(message);
const header = deserialize(reader);
const body = deserialize(reader);
const type = header[0];
switch (type) {
case 100 /* RequestType.Promise */:
this.logger?.logIncoming(message.byteLength, header[1], 1 /* RequestInitiator.OtherSide */, `${requestTypeToStr(type)}: ${header[2]}.${header[3]}`, body);
return this.onPromise({ type, id: header[1], channelName: header[2], name: header[3], arg: body });
case 102 /* RequestType.EventListen */:
this.logger?.logIncoming(message.byteLength, header[1], 1 /* RequestInitiator.OtherSide */, `${requestTypeToStr(type)}: ${header[2]}.${header[3]}`, body);
return this.onEventListen({ type, id: header[1], channelName: header[2], name: header[3], arg: body });
case 101 /* RequestType.PromiseCancel */:
this.logger?.logIncoming(message.byteLength, header[1], 1 /* RequestInitiator.OtherSide */, `${requestTypeToStr(type)}`);
return this.disposeActiveRequest({ type, id: header[1] });
case 103 /* RequestType.EventDispose */:
this.logger?.logIncoming(message.byteLength, header[1], 1 /* RequestInitiator.OtherSide */, `${requestTypeToStr(type)}`);
return this.disposeActiveRequest({ type, id: header[1] });
}
}
onPromise(request) {
const channel = this.channels.get(request.channelName);
if (!channel) {
this.collectPendingRequest(request);
return;
}
const cancellationTokenSource = new CancellationTokenSource();
let promise;
try {
promise = channel.call(this.ctx, request.name, request.arg, cancellationTokenSource.token);
}
catch (err) {
promise = Promise.reject(err);
}
const id = request.id;
promise.then(data => {
this.sendResponse({ id, data, type: 201 /* ResponseType.PromiseSuccess */ });
this.activeRequests.delete(request.id);
}, err => {
if (err instanceof Error) {
this.sendResponse({
id, data: {
message: err.message,
name: err.name,
stack: err.stack ? (err.stack.split ? err.stack.split('\n') : err.stack) : undefined
}, type: 202 /* ResponseType.PromiseError */
});
}
else {
this.sendResponse({ id, data: err, type: 203 /* ResponseType.PromiseErrorObj */ });
}
this.activeRequests.delete(request.id);
});
const disposable = toDisposable(() => cancellationTokenSource.cancel());
this.activeRequests.set(request.id, disposable);
}
onEventListen(request) {
const channel = this.channels.get(request.channelName);
if (!channel) {
this.collectPendingRequest(request);
return;
}
const id = request.id;
const event = channel.listen(this.ctx, request.name, request.arg);
const disposable = event(data => this.sendResponse({ id, data, type: 204 /* ResponseType.EventFire */ }));
this.activeRequests.set(request.id, disposable);
}
disposeActiveRequest(request) {
const disposable = this.activeRequests.get(request.id);
if (disposable) {
disposable.dispose();
this.activeRequests.delete(request.id);
}
}
collectPendingRequest(request) {
let pendingRequests = this.pendingRequests.get(request.channelName);
if (!pendingRequests) {
pendingRequests = [];
this.pendingRequests.set(request.channelName, pendingRequests);
}
const timer = setTimeout(() => {
console.error(`Unknown channel: ${request.channelName}`);
if (request.type === 100 /* RequestType.Promise */) {
this.sendResponse({
id: request.id,
data: { name: 'Unknown channel', message: `Channel name '${request.channelName}' timed out after ${this.timeoutDelay}ms`, stack: undefined },
type: 202 /* ResponseType.PromiseError */
});
}
}, this.timeoutDelay);
pendingRequests.push({ request, timeoutTimer: timer });
}
flushPendingRequests(channelName) {
const requests = this.pendingRequests.get(channelName);
if (requests) {
for (const request of requests) {
clearTimeout(request.timeoutTimer);
switch (request.request.type) {
case 100 /* RequestType.Promise */:
this.onPromise(request.request);
break;
case 102 /* RequestType.EventListen */:
this.onEventListen(request.request);
break;
}
}
this.pendingRequests.delete(channelName);
}
}
dispose() {
if (this.protocolListener) {
this.protocolListener.dispose();
this.protocolListener = null;
}
dispose(this.activeRequests.values());
this.activeRequests.clear();
}
}
export var RequestInitiator;
(function (RequestInitiator) {
RequestInitiator[RequestInitiator["LocalSide"] = 0] = "LocalSide";
RequestInitiator[RequestInitiator["OtherSide"] = 1] = "OtherSide";
})(RequestInitiator || (RequestInitiator = {}));
export class ChannelClient {
protocol;
isDisposed = false;
state = State.Uninitialized;
activeRequests = new Set();
handlers = new Map();
lastRequestId = 0;
protocolListener;
logger;
_onDidInitialize = new Emitter();
onDidInitialize = this._onDidInitialize.event;
constructor(protocol, logger = null) {
this.protocol = protocol;
this.protocolListener = this.protocol.onMessage(msg => this.onBuffer(msg));
this.logger = logger;
}
getChannel(channelName) {
const that = this;
return {
call(command, arg, cancellationToken) {
if (that.isDisposed) {
return Promise.reject(new CancellationError());
}
return that.requestPromise(channelName, command, arg, cancellationToken);
},
listen(event, arg) {
if (that.isDisposed) {
return Event.None;
}
return that.requestEvent(channelName, event, arg);
}
};
}
requestPromise(channelName, name, arg, cancellationToken = CancellationToken.None) {
const id = this.lastRequestId++;
const type = 100 /* RequestType.Promise */;
const request = { id, type, channelName, name, arg };
if (cancellationToken.isCancellationRequested) {
return Promise.reject(new CancellationError());
}
let disposable;
const result = new Promise((c, e) => {
if (cancellationToken.isCancellationRequested) {
return e(new CancellationError());
}
const doRequest = () => {
const handler = response => {
switch (response.type) {
case 201 /* ResponseType.PromiseSuccess */:
this.handlers.delete(id);
c(response.data);
break;
case 202 /* ResponseType.PromiseError */: {
this.handlers.delete(id);
const error = new Error(response.data.message);
error.stack = Array.isArray(response.data.stack) ? response.data.stack.join('\n') : response.data.stack;
error.name = response.data.name;
e(error);
break;
}
case 203 /* ResponseType.PromiseErrorObj */:
this.handlers.delete(id);
e(response.data);
break;
}
};
this.handlers.set(id, handler);
this.sendRequest(request);
};
let uninitializedPromise = null;
if (this.state === State.Idle) {
doRequest();
}
else {
uninitializedPromise = createCancelablePromise(_ => this.whenInitialized());
uninitializedPromise.then(() => {
uninitializedPromise = null;
doRequest();
});
}
const cancel = () => {
if (uninitializedPromise) {
uninitializedPromise.cancel();
uninitializedPromise = null;
}
else {
this.sendRequest({ id, type: 101 /* RequestType.PromiseCancel */ });
}
e(new CancellationError());
};
const cancellationTokenListener = cancellationToken.onCancellationRequested(cancel);
disposable = combinedDisposable(toDisposable(cancel), cancellationTokenListener);
this.activeRequests.add(disposable);
});
return result.finally(() => { this.activeRequests.delete(disposable); });
}
requestEvent(channelName, name, arg) {
const id = this.lastRequestId++;
const type = 102 /* RequestType.EventListen */;
const request = { id, type, channelName, name, arg };
let uninitializedPromise = null;
const emitter = new Emitter({
onWillAddFirstListener: () => {
uninitializedPromise = createCancelablePromise(_ => this.whenInitialized());
uninitializedPromise.then(() => {
uninitializedPromise = null;
this.activeRequests.add(emitter);
this.sendRequest(request);
});
},
onDidRemoveLastListener: () => {
if (uninitializedPromise) {
uninitializedPromise.cancel();
uninitializedPromise = null;
}
else {
this.activeRequests.delete(emitter);
this.sendRequest({ id, type: 103 /* RequestType.EventDispose */ });
}
}
});
const handler = (res) => emitter.fire(res.data);
this.handlers.set(id, handler);
return emitter.event;
}
sendRequest(request) {
switch (request.type) {
case 100 /* RequestType.Promise */:
case 102 /* RequestType.EventListen */: {
const msgLength = this.send([request.type, request.id, request.channelName, request.name], request.arg);
this.logger?.logOutgoing(msgLength, request.id, 0 /* RequestInitiator.LocalSide */, `${requestTypeToStr(request.type)}: ${request.channelName}.${request.name}`, request.arg);
return;
}
case 101 /* RequestType.PromiseCancel */:
case 103 /* RequestType.EventDispose */: {
const msgLength = this.send([request.type, request.id]);
this.logger?.logOutgoing(msgLength, request.id, 0 /* RequestInitiator.LocalSide */, requestTypeToStr(request.type));
return;
}
}
}
send(header, body = undefined) {
const writer = new BufferWriter();
serialize(writer, header);
serialize(writer, body);
return this.sendBuffer(writer.buffer);
}
sendBuffer(message) {
try {
this.protocol.send(message);
return message.byteLength;
}
catch (err) {
// noop
return 0;
}
}
onBuffer(message) {
const reader = new BufferReader(message);
const header = deserialize(reader);
const body = deserialize(reader);
const type = header[0];
switch (type) {
case 200 /* ResponseType.Initialize */:
this.logger?.logIncoming(message.byteLength, 0, 0 /* RequestInitiator.LocalSide */, responseTypeToStr(type));
return this.onResponse({ type: header[0] });
case 201 /* ResponseType.PromiseSuccess */:
case 202 /* ResponseType.PromiseError */:
case 204 /* ResponseType.EventFire */:
case 203 /* ResponseType.PromiseErrorObj */:
this.logger?.logIncoming(message.byteLength, header[1], 0 /* RequestInitiator.LocalSide */, responseTypeToStr(type), body);
return this.onResponse({ type: header[0], id: header[1], data: body });
}
}
onResponse(response) {
if (response.type === 200 /* ResponseType.Initialize */) {
this.state = State.Idle;
this._onDidInitialize.fire();
return;
}
const handler = this.handlers.get(response.id);
handler?.(response);
}
get onDidInitializePromise() {
return Event.toPromise(this.onDidInitialize);
}
whenInitialized() {
if (this.state === State.Idle) {
return Promise.resolve();
}
else {
return this.onDidInitializePromise;
}
}
dispose() {
this.isDisposed = true;
if (this.protocolListener) {
this.protocolListener.dispose();
this.protocolListener = null;
}
dispose(this.activeRequests.values());
this.activeRequests.clear();
}
}
__decorate([
memoize
], ChannelClient.prototype, "onDidInitializePromise", null);
/**
* An `IPCServer` is both a channel server and a routing channel
* client.
*
* As the owner of a protocol, you should extend both this
* and the `IPCClient` classes to get IPC implementations
* for your protocol.
*/
export class IPCServer {
channels = new Map();
_connections = new Set();
_onDidAddConnection = new Emitter();
onDidAddConnection = this._onDidAddConnection.event;
_onDidRemoveConnection = new Emitter();
onDidRemoveConnection = this._onDidRemoveConnection.event;
get connections() {
const result = [];
this._connections.forEach(ctx => result.push(ctx));
return result;
}
constructor(onDidClientConnect) {
onDidClientConnect(({ protocol, onDidClientDisconnect }) => {
const onFirstMessage = Event.once(protocol.onMessage);
onFirstMessage(msg => {
const reader = new BufferReader(msg);
const ctx = deserialize(reader);
const channelServer = new ChannelServer(protocol, ctx);
const channelClient = new ChannelClient(protocol);
this.channels.forEach((channel, name) => channelServer.registerChannel(name, channel));
const connection = { channelServer, channelClient, ctx };
this._connections.add(connection);
this._onDidAddConnection.fire(connection);
onDidClientDisconnect(() => {
channelServer.dispose();
channelClient.dispose();
this._connections.delete(connection);
this._onDidRemoveConnection.fire(connection);
});
});
});
}
getChannel(channelName, routerOrClientFilter) {
const that = this;
return {
call(command, arg, cancellationToken) {
let connectionPromise;
if (isFunction(routerOrClientFilter)) {
// when no router is provided, we go random client picking
const connection = getRandomElement(that.connections.filter(routerOrClientFilter));
connectionPromise = connection
// if we found a client, let's call on it
? Promise.resolve(connection)
// else, let's wait for a client to come along
: Event.toPromise(Event.filter(that.onDidAddConnection, routerOrClientFilter));
}
else {
connectionPromise = routerOrClientFilter.routeCall(that, command, arg);
}
const channelPromise = connectionPromise
.then(connection => connection.channelClient.getChannel(channelName));
return getDelayedChannel(channelPromise)
.call(command, arg, cancellationToken);
},
listen(event, arg) {
if (isFunction(routerOrClientFilter)) {
return that.getMulticastEvent(channelName, routerOrClientFilter, event, arg);
}
const channelPromise = routerOrClientFilter.routeEvent(that, event, arg)
.then(connection => connection.channelClient.getChannel(channelName));
return getDelayedChannel(channelPromise)
.listen(event, arg);
}
};
}
getMulticastEvent(channelName, clientFilter, eventName, arg) {
const that = this;
let disposables = new DisposableStore();
// Create an emitter which hooks up to all clients
// as soon as first listener is added. It also
// disconnects from all clients as soon as the last listener
// is removed.
const emitter = new Emitter({
onWillAddFirstListener: () => {
disposables = new DisposableStore();
// The event multiplexer is useful since the active
// client list is dynamic. We need to hook up and disconnection
// to/from clients as they come and go.
const eventMultiplexer = new EventMultiplexer();
const map = new Map();
const onDidAddConnection = (connection) => {
const channel = connection.channelClient.getChannel(channelName);
const event = channel.listen(eventName, arg);
const disposable = eventMultiplexer.add(event);
map.set(connection, disposable);
};
const onDidRemoveConnection = (connection) => {
const disposable = map.get(connection);
if (!disposable) {
return;
}
disposable.dispose();
map.delete(connection);
};
that.connections.filter(clientFilter).forEach(onDidAddConnection);
Event.filter(that.onDidAddConnection, clientFilter)(onDidAddConnection, undefined, disposables);
that.onDidRemoveConnection(onDidRemoveConnection, undefined, disposables);
eventMultiplexer.event(emitter.fire, emitter, disposables);
disposables.add(eventMultiplexer);
},
onDidRemoveLastListener: () => {
disposables.dispose();
}
});
return emitter.event;
}
registerChannel(channelName, channel) {
this.channels.set(channelName, channel);
this._connections.forEach(connection => {
connection.channelServer.registerChannel(channelName, channel);
});
}
dispose() {
this.channels.clear();
this._connections.clear();
this._onDidAddConnection.dispose();
this._onDidRemoveConnection.dispose();
}
}
/**
* An `IPCClient` is both a channel client and a channel server.
*
* As the owner of a protocol, you should extend both this
* and the `IPCClient` classes to get IPC implementations
* for your protocol.
*/
export class IPCClient {
channelClient;
channelServer;
constructor(protocol, ctx, ipcLogger = null) {
const writer = new BufferWriter();
serialize(writer, ctx);
protocol.send(writer.buffer);
this.channelClient = new ChannelClient(protocol, ipcLogger);
this.channelServer = new ChannelServer(protocol, ctx, ipcLogger);
}
getChannel(channelName) {
return this.channelClient.getChannel(channelName);
}
registerChannel(channelName, channel) {
this.channelServer.registerChannel(channelName, channel);
}
dispose() {
this.channelClient.dispose();
this.channelServer.dispose();
}
}
export function getDelayedChannel(promise) {
return {
call(command, arg, cancellationToken) {
return promise.then(c => c.call(command, arg, cancellationToken));
},
listen(event, arg) {
const relay = new Relay();
promise.then(c => relay.input = c.listen(event, arg));
return relay.event;
}
};
}
export function getNextTickChannel(channel) {
let didTick = false;
return {
call(command, arg, cancellationToken) {
if (didTick) {
return channel.call(command, arg, cancellationToken);
}
return timeout(0)
.then(() => didTick = true)
.then(() => channel.call(command, arg, cancellationToken));
},
listen(event, arg) {
if (didTick) {
return channel.listen(event, arg);
}
const relay = new Relay();
timeout(0)
.then(() => didTick = true)
.then(() => relay.input = channel.listen(event, arg));
return relay.event;
}
};
}
export class StaticRouter {
fn;
constructor(fn) {
this.fn = fn;
}
routeCall(hub) {
return this.route(hub);
}
routeEvent(hub) {
return this.route(hub);
}
async route(hub) {
for (const connection of hub.connections) {
if (await Promise.resolve(this.fn(connection.ctx))) {
return Promise.resolve(connection);
}
}
await Event.toPromise(hub.onDidAddConnection);
return await this.route(hub);
}
}
/**
* Use ProxyChannels to automatically wrapping and unwrapping
* services to/from IPC channels, instead of manually wrapping
* each service method and event.
*
* Restrictions:
* - If marshalling is enabled, only `URI` and `RegExp` is converted
* automatically for you
* - Events must follow the naming convention `onUpperCase`
* - `CancellationToken` is currently not supported
* - If a context is provided, you can use `AddFirstParameterToFunctions`
* utility to signal this in the receiving side type
*/
export var ProxyChannel;
(function (ProxyChannel) {
function fromService(service, options) {
const handler = service;
const disableMarshalling = options && options.disableMarshalling;
// Buffer any event that should be supported by
// iterating over all property keys and finding them
const mapEventNameToEvent = new Map();
for (const key in handler) {
if (propertyIsEvent(key)) {
mapEventNameToEvent.set(key, Event.buffer(handler[key], true));
}
}
return new class {
listen(_, event, arg) {
const eventImpl = mapEventNameToEvent.get(event);
if (eventImpl) {
return eventImpl;
}
if (propertyIsDynamicEvent(event)) {
const target = handler[event];
if (typeof target === 'function') {
return target.call(handler, arg);
}
}
throw new Error(`Event not found: ${event}`);
}
call(_, command, args) {
const target = handler[command];
if (typeof target === 'function') {
// Revive unless marshalling disabled
if (!disableMarshalling && Array.isArray(args)) {
for (let i = 0; i < args.length; i++) {
args[i] = revive(args[i]);
}
}
return target.apply(handler, args);
}
throw new Error(`Method not found: ${command}`);
}
};
}
ProxyChannel.fromService = fromService;
function toService(channel, options) {
const disableMarshalling = options && options.disableMarshalling;
return new Proxy({}, {
get(_target, propKey) {
if (typeof propKey === 'string') {
// Check for predefined values
if (options?.properties?.has(propKey)) {
return options.properties.get(propKey);
}
// Dynamic Event
if (propertyIsDynamicEvent(propKey)) {
return function (arg) {
return channel.listen(propKey, arg);
};
}
// Event
if (propertyIsEvent(propKey)) {
return channel.listen(propKey);
}
// Function
return async function (...args) {
// Add context if any
let methodArgs;
if (options && !isUndefinedOrNull(options.context)) {
methodArgs = [options.context, ...args];
}
else {
methodArgs = args;
}
const result = await channel.call(propKey, methodArgs);
// Revive unless marshalling disabled
if (!disableMarshalling) {
return revive(result);
}
return result;
};
}
throw new Error(`Property not found: ${String(propKey)}`);
}
});
}
ProxyChannel.toService = toService;
function propertyIsEvent(name) {
// Assume a property is an event if it has a form of "onSomething"
return name[0] === 'o' && name[1] === 'n' && strings.isUpperAsciiLetter(name.charCodeAt(2));
}
function propertyIsDynamicEvent(name) {
// Assume a property is a dynamic event (a method that returns an event) if it has a form of "onDynamicSomething"
return /^onDynamic/.test(name) && strings.isUpperAsciiLetter(name.charCodeAt(9));
}
})(ProxyChannel || (ProxyChannel = {}));
const colorTables = [
['#2977B1', '#FC802D', '#34A13A', '#D3282F', '#9366BA'],
['#8B564C', '#E177C0', '#7F7F7F', '#BBBE3D', '#2EBECD']
];
function prettyWithoutArrays(data) {
if (Array.isArray(data)) {
return data;
}
if (data && typeof data === 'object' && typeof data.toString === 'function') {
const result = data.toString();
if (result !== '[object Object]') {
return result;
}
}
return data;
}
function pretty(data) {
if (Array.isArray(data)) {
return data.map(prettyWithoutArrays);
}
return prettyWithoutArrays(data);
}
function logWithColors(direction, totalLength, msgLength, req, initiator, str, data) {
data = pretty(data);
const colorTable = colorTables[initiator];
const color = colorTable[req % colorTable.length];
let args = [`%c[${direction}]%c[${String(totalLength).padStart(7, ' ')}]%c[len: ${String(msgLength).padStart(5, ' ')}]%c${String(req).padStart(5, ' ')} - ${str}`, 'color: darkgreen', 'color: grey', 'color: grey', `color: ${color}`];
if (/\($/.test(str)) {
args = args.concat(data);
args.push(')');
}
else {
args.push(data);
}
console.log.apply(console, args);
}
export class IPCLogger {
_outgoingPrefix;
_incomingPrefix;
_totalIncoming = 0;
_totalOutgoing = 0;
constructor(_outgoingPrefix, _incomingPrefix) {
this._outgoingPrefix = _outgoingPrefix;
this._incomingPrefix = _incomingPrefix;
}
logOutgoing(msgLength, requestId, initiator, str, data) {
this._totalOutgoing += msgLength;
logWithColors(this._outgoingPrefix, this._totalOutgoing, msgLength, requestId, initiator, str, data);
}
logIncoming(msgLength, requestId, initiator, str, data) {
this._totalIncoming += msgLength;
logWithColors(this._incomingPrefix, this._totalIncoming, msgLength, requestId, initiator, str, data);
}
}