UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

191 lines (162 loc) • 6.57 kB
// ***************************************************************************** // Copyright (C) 2022 Red Hat, Inc. and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** /* eslint-disable @typescript-eslint/no-explicit-any */ import { Packr as MsgPack } from 'msgpackr'; import { ReadBuffer, WriteBuffer } from './message-buffer'; import { MsgPackExtensionManager } from './msg-pack-extension-manager'; /** * This code lets you encode rpc protocol messages (request/reply/notification/error/cancel) * into a channel write buffer and decode the same messages from a read buffer. * Custom encoders/decoders can be registered to specially handling certain types of values * to be encoded. Clients are responsible for ensuring that the set of tags for encoders * is distinct and the same at both ends of a channel. */ export type RpcMessage = RequestMessage | ReplyMessage | ReplyErrMessage | CancelMessage | NotificationMessage; export const enum RpcMessageType { Request = 1, Notification = 2, Reply = 3, ReplyErr = 4, Cancel = 5, } export interface CancelMessage { type: RpcMessageType.Cancel; id: number; } export interface RequestMessage { type: RpcMessageType.Request; id: number; method: string; args: any[]; } export interface NotificationMessage { type: RpcMessageType.Notification; id: number; method: string; args: any[]; } export interface ReplyMessage { type: RpcMessageType.Reply; id: number; res: any; } export interface ReplyErrMessage { type: RpcMessageType.ReplyErr; id: number; err: any; } export interface SerializedError { readonly $isError: true; readonly name: string; readonly message: string; readonly stack: string; } /** * A special error that can be returned in case a request * has failed. Provides additional information i.e. an error code * and additional error data. */ export class ResponseError extends Error { constructor(readonly code: number, message: string, readonly data: any) { super(message); } } /** * Custom error thrown by the {@link RpcMessageEncoder} if an error occurred during the encoding and the * object could not be written to the given {@link WriteBuffer} */ export class EncodingError extends Error { constructor(msg: string, public cause?: Error) { super(msg); } } /** * A `RpcMessageDecoder` parses a a binary message received via {@link ReadBuffer} into a {@link RpcMessage} */ export interface RpcMessageDecoder { parse(buffer: ReadBuffer): RpcMessage; } /** * A `RpcMessageEncoder` writes {@link RpcMessage} objects to a {@link WriteBuffer}. Note that it is * up to clients to commit the message. This allows for multiple messages being * encoded before sending. */ export interface RpcMessageEncoder { cancel(buf: WriteBuffer, requestId: number): void; notification(buf: WriteBuffer, requestId: number, method: string, args: any[]): void request(buf: WriteBuffer, requestId: number, method: string, args: any[]): void replyOK(buf: WriteBuffer, requestId: number, res: any): void replyErr(buf: WriteBuffer, requestId: number, err: any): void } export const defaultMsgPack = new MsgPack({ moreTypes: true, encodeUndefinedAsNil: false, bundleStrings: false }); export class MsgPackMessageEncoder implements RpcMessageEncoder { constructor(protected readonly msgPack: MsgPack = defaultMsgPack) { } cancel(buf: WriteBuffer, requestId: number): void { this.encode<CancelMessage>(buf, { type: RpcMessageType.Cancel, id: requestId }); } notification(buf: WriteBuffer, requestId: number, method: string, args: any[]): void { this.encode<NotificationMessage>(buf, { type: RpcMessageType.Notification, id: requestId, method, args }); } request(buf: WriteBuffer, requestId: number, method: string, args: any[]): void { this.encode<RequestMessage>(buf, { type: RpcMessageType.Request, id: requestId, method, args }); } replyOK(buf: WriteBuffer, requestId: number, res: any): void { this.encode<ReplyMessage>(buf, { type: RpcMessageType.Reply, id: requestId, res }); } replyErr(buf: WriteBuffer, requestId: number, err: any): void { this.encode<ReplyErrMessage>(buf, { type: RpcMessageType.ReplyErr, id: requestId, err }); } encode<T = unknown>(buf: WriteBuffer, value: T): void { try { buf.writeBytes(this.msgPack.encode(value)); } catch (err) { if (err instanceof Error) { throw new EncodingError(`Error during encoding: '${err.message}'`, err); } throw err; } } } export class MsgPackMessageDecoder implements RpcMessageDecoder { constructor(protected readonly msgPack: MsgPack = defaultMsgPack) { } decode<T = any>(buf: ReadBuffer): T { const bytes = buf.readBytes(); return this.msgPack.decode(bytes); } parse(buffer: ReadBuffer): RpcMessage { return this.decode(buffer); } } export function registerMsgPackExtensions(): void { // Register custom msgPack extension for Errors. MsgPackExtensionManager.getInstance().registerExtensions({ class: Error, tag: 1, // eslint-disable-next-line @typescript-eslint/no-explicit-any serialize: (error: any) => { const { code, data, message, name } = error; const stack = error.stacktrace ?? error.stack; const isResponseError = error instanceof ResponseError; return { code, data, message, name, stack, isResponseError }; }, deserialize: data => { const error = data.isResponseError ? new ResponseError(data.code, data.message, data.data) : new Error(data.message); error.name = data.name; error.stack = data.stack; return error; } }); }