UNPKG

9.21 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2021 Red Hat, Inc. and others.
4//
5// This program and the accompanying materials are made available under the
6// terms of the Eclipse Public License v. 2.0 which is available at
7// http://www.eclipse.org/legal/epl-2.0.
8//
9// This Source Code may also be made available under the following Secondary
10// Licenses when the conditions for such availability set forth in the Eclipse
11// Public License v. 2.0 are satisfied: GNU General Public License, version 2
12// with the GNU Classpath Exception which is available at
13// https://www.gnu.org/software/classpath/license.html.
14//
15// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16// *****************************************************************************
17/* eslint-disable @typescript-eslint/no-explicit-any */
18Object.defineProperty(exports, "__esModule", { value: true });
19exports.RpcProtocol = void 0;
20const cancellation_1 = require("../cancellation");
21const disposable_1 = require("../disposable");
22const event_1 = require("../event");
23const promise_util_1 = require("../promise-util");
24const rpc_message_encoder_1 = require("./rpc-message-encoder");
25/**
26 * Establish a RPC protocol on top of a given channel. By default the rpc protocol is bi-directional, meaning it is possible to send
27 * requests and notifications to the remote side (i.e. acts as client) as well as receiving requests and notifications from the remote side (i.e. acts as a server).
28 * Clients can get a promise for a remote request result that will be either resolved or
29 * rejected depending on the success of the request. Keeps track of outstanding requests and matches replies to the appropriate request
30 * Currently, there is no timeout handling for long running requests implemented.
31 * The bi-directional mode can be reconfigured using the {@link RpcProtocolOptions} to construct an RPC protocol instance that acts only as client or server instead.
32 */
33class RpcProtocol {
34 constructor(channel, requestHandler, options = {}) {
35 var _a, _b, _c;
36 this.channel = channel;
37 this.requestHandler = requestHandler;
38 this.pendingRequests = new Map();
39 this.nextMessageId = 0;
40 this.onNotificationEmitter = new event_1.Emitter();
41 this.cancellationTokenSources = new Map();
42 this.toDispose = new disposable_1.DisposableCollection();
43 this.encoder = (_a = options.encoder) !== null && _a !== void 0 ? _a : new rpc_message_encoder_1.MsgPackMessageEncoder();
44 this.decoder = (_b = options.decoder) !== null && _b !== void 0 ? _b : new rpc_message_encoder_1.MsgPackMessageDecoder();
45 this.toDispose.push(this.onNotificationEmitter);
46 channel.onClose(event => {
47 this.pendingRequests.forEach(pending => pending.reject(new Error(event.reason)));
48 this.pendingRequests.clear();
49 this.toDispose.dispose();
50 });
51 this.toDispose.push(channel.onMessage(readBuffer => this.handleMessage(this.decoder.parse(readBuffer()))));
52 this.mode = (_c = options.mode) !== null && _c !== void 0 ? _c : 'default';
53 if (this.mode !== 'clientOnly' && requestHandler === undefined) {
54 console.error('RPCProtocol was initialized without a request handler but was not set to clientOnly mode.');
55 }
56 }
57 get onNotification() {
58 return this.onNotificationEmitter.event;
59 }
60 handleMessage(message) {
61 if (this.mode !== 'clientOnly') {
62 switch (message.type) {
63 case 5 /* Cancel */: {
64 this.handleCancel(message.id);
65 return;
66 }
67 case 1 /* Request */: {
68 this.handleRequest(message.id, message.method, message.args);
69 return;
70 }
71 case 2 /* Notification */: {
72 this.handleNotify(message.method, message.args, message.id);
73 return;
74 }
75 }
76 }
77 if (this.mode !== 'serverOnly') {
78 switch (message.type) {
79 case 3 /* Reply */: {
80 this.handleReply(message.id, message.res);
81 return;
82 }
83 case 4 /* ReplyErr */: {
84 this.handleReplyErr(message.id, message.err);
85 return;
86 }
87 }
88 }
89 // If the message was not handled until here, it is incompatible with the mode.
90 console.warn(`Received message incompatible with this RPCProtocol's mode '${this.mode}'. Type: ${message.type}. ID: ${message.id}.`);
91 }
92 handleReply(id, value) {
93 const replyHandler = this.pendingRequests.get(id);
94 if (replyHandler) {
95 this.pendingRequests.delete(id);
96 replyHandler.resolve(value);
97 }
98 else {
99 throw new Error(`No reply handler for reply with id: ${id}`);
100 }
101 }
102 handleReplyErr(id, error) {
103 const replyHandler = this.pendingRequests.get(id);
104 if (replyHandler) {
105 this.pendingRequests.delete(id);
106 replyHandler.reject(error);
107 }
108 else {
109 throw new Error(`No reply handler for error reply with id: ${id}`);
110 }
111 }
112 sendRequest(method, args) {
113 // The last element of the request args might be a cancellation token. As these tokens are not serializable we have to remove it from the
114 // args array and the `CANCELLATION_TOKEN_KEY` string instead.
115 const cancellationToken = args.length && cancellation_1.CancellationToken.is(args[args.length - 1]) ? args.pop() : undefined;
116 const id = this.nextMessageId++;
117 const reply = new promise_util_1.Deferred();
118 if (cancellationToken) {
119 args.push(RpcProtocol.CANCELLATION_TOKEN_KEY);
120 }
121 this.pendingRequests.set(id, reply);
122 const output = this.channel.getWriteBuffer();
123 this.encoder.request(output, id, method, args);
124 output.commit();
125 if (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested) {
126 this.sendCancel(id);
127 }
128 else {
129 cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.onCancellationRequested(() => this.sendCancel(id));
130 }
131 return reply.promise;
132 }
133 sendNotification(method, args) {
134 // If the notification supports a CancellationToken, it needs to be treated like a request
135 // because cancellation does not work with the simplified "fire and forget" approach of simple notifications.
136 if (args.length && cancellation_1.CancellationToken.is(args[args.length - 1])) {
137 this.sendRequest(method, args);
138 return;
139 }
140 const output = this.channel.getWriteBuffer();
141 this.encoder.notification(output, method, args, this.nextMessageId++);
142 output.commit();
143 }
144 sendCancel(requestId) {
145 const output = this.channel.getWriteBuffer();
146 this.encoder.cancel(output, requestId);
147 output.commit();
148 }
149 handleCancel(id) {
150 const cancellationTokenSource = this.cancellationTokenSources.get(id);
151 if (cancellationTokenSource) {
152 cancellationTokenSource.cancel();
153 }
154 }
155 async handleRequest(id, method, args) {
156 const output = this.channel.getWriteBuffer();
157 // Check if the last argument of the received args is the key for indicating that a cancellation token should be used
158 // If so remove the key from the args and create a new cancellation token.
159 const addToken = args.length && args[args.length - 1] === RpcProtocol.CANCELLATION_TOKEN_KEY ? args.pop() : false;
160 if (addToken) {
161 const tokenSource = new cancellation_1.CancellationTokenSource();
162 this.cancellationTokenSources.set(id, tokenSource);
163 args.push(tokenSource.token);
164 }
165 try {
166 const result = await this.requestHandler(method, args);
167 this.cancellationTokenSources.delete(id);
168 this.encoder.replyOK(output, id, result);
169 output.commit();
170 }
171 catch (err) {
172 // In case of an error the output buffer might already contains parts of an message.
173 // => Dispose the current buffer and retrieve a new, clean one for writing the response error.
174 if (disposable_1.Disposable.is(output)) {
175 output.dispose();
176 }
177 const errorOutput = this.channel.getWriteBuffer();
178 this.cancellationTokenSources.delete(id);
179 this.encoder.replyErr(errorOutput, id, err);
180 errorOutput.commit();
181 }
182 }
183 async handleNotify(method, args, id) {
184 if (this.toDispose.disposed) {
185 return;
186 }
187 this.onNotificationEmitter.fire({ method, args });
188 }
189}
190exports.RpcProtocol = RpcProtocol;
191RpcProtocol.CANCELLATION_TOKEN_KEY = 'add.cancellation.token';
192//# sourceMappingURL=rpc-protocol.js.map
\No newline at end of file