UNPKG

sussudio

Version:

An unofficial VS Code Internal API

968 lines (967 loc) 39.1 kB
/*--------------------------------------------------------------------------------------------- * 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); } }