UNPKG

11.5 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2017 TypeFox 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// *****************************************************************************
17Object.defineProperty(exports, "__esModule", { value: true });
18exports.JsonRpcProxyFactory = exports.JsonRpcConnectionHandler = exports.RpcProxyFactory = exports.RpcConnectionHandler = void 0;
19/* eslint-disable @typescript-eslint/no-explicit-any */
20const rpc_message_encoder_1 = require("../message-rpc/rpc-message-encoder");
21const application_error_1 = require("../application-error");
22const event_1 = require("../event");
23const rpc_protocol_1 = require("../message-rpc/rpc-protocol");
24const promise_util_1 = require("../promise-util");
25const inversify_1 = require("../../../shared/inversify");
26class RpcConnectionHandler {
27 constructor(path, targetFactory, factoryConstructor = RpcProxyFactory) {
28 this.path = path;
29 this.targetFactory = targetFactory;
30 this.factoryConstructor = factoryConstructor;
31 }
32 onConnection(connection) {
33 const factory = new this.factoryConstructor();
34 const proxy = factory.createProxy();
35 factory.target = this.targetFactory(proxy);
36 factory.listen(connection);
37 }
38}
39exports.RpcConnectionHandler = RpcConnectionHandler;
40const defaultRpcProtocolFactory = (channel, requestHandler) => new rpc_protocol_1.RpcProtocol(channel, requestHandler);
41/**
42 * Factory for RPC proxy objects.
43 *
44 * A RPC proxy exposes the programmatic interface of an object through
45 * Theia's RPC protocol. This allows remote programs to call methods of this objects by
46 * sending RPC requests. This takes place over a bi-directional stream,
47 * where both ends can expose an object and both can call methods on each other'
48 * exposed object.
49 *
50 * For example, assuming we have an object of the following type on one end:
51 *
52 * class Foo {
53 * bar(baz: number): number { return baz + 1 }
54 * }
55 *
56 * which we want to expose through a RPC interface. We would do:
57 *
58 * let target = new Foo()
59 * let factory = new RpcProxyFactory<Foo>('/foo', target)
60 * factory.onConnection(connection)
61 *
62 * The party at the other end of the `connection`, in order to remotely call
63 * methods on this object would do:
64 *
65 * let factory = new RpcProxyFactory<Foo>('/foo')
66 * factory.onConnection(connection)
67 * let proxy = factory.createProxy();
68 * let result = proxy.bar(42)
69 * // result is equal to 43
70 *
71 * One the wire, it would look like this:
72 *
73 * --> { "type":"1", "id": 1, "method": "bar", "args": [42]}
74 * <-- { "type":"3", "id": 1, "res": 43}
75 *
76 * Note that in the code of the caller, we didn't pass a target object to
77 * RpcProxyFactory, because we don't want/need to expose an object.
78 * If we had passed a target object, the other side could've called methods on
79 * it.
80 *
81 * @param <T> - The type of the object to expose to RPC.
82 */
83class RpcProxyFactory {
84 /**
85 * Build a new RpcProxyFactory.
86 *
87 * @param target - The object to expose to RPC methods calls. If this
88 * is omitted, the proxy won't be able to handle requests, only send them.
89 */
90 constructor(target, rpcProtocolFactory = defaultRpcProtocolFactory) {
91 this.target = target;
92 this.rpcProtocolFactory = rpcProtocolFactory;
93 this.onDidOpenConnectionEmitter = new event_1.Emitter();
94 this.onDidCloseConnectionEmitter = new event_1.Emitter();
95 this.waitForConnection();
96 }
97 waitForConnection() {
98 this.rpcDeferred = new promise_util_1.Deferred();
99 this.rpcDeferred.promise.then(protocol => {
100 protocol.channel.onClose(() => {
101 this.onDidCloseConnectionEmitter.fire(undefined);
102 // Wait for connection in case the backend reconnects
103 this.waitForConnection();
104 });
105 this.onDidOpenConnectionEmitter.fire(undefined);
106 });
107 }
108 /**
109 * Connect a {@link Channel} to the factory by creating an {@link RpcProtocol} on top of it.
110 *
111 * This protocol will be used to send/receive RPC requests and
112 * responses.
113 */
114 listen(channel) {
115 const protocol = this.rpcProtocolFactory(channel, (meth, args) => this.onRequest(meth, ...args));
116 protocol.onNotification(event => this.onNotification(event.method, ...event.args));
117 this.rpcDeferred.resolve(protocol);
118 }
119 /**
120 * Process an incoming RPC method call.
121 *
122 * onRequest is called when the RPC connection received a method call
123 * request. It calls the corresponding method on [[target]].
124 *
125 * The return value is a Promise object that is resolved with the return
126 * value of the method call, if it is successful. The promise is rejected
127 * if the called method does not exist or if it throws.
128 *
129 * @returns A promise of the method call completion.
130 */
131 async onRequest(method, ...args) {
132 try {
133 if (this.target) {
134 return await this.target[method](...args);
135 }
136 else {
137 throw new Error(`no target was set to handle ${method}`);
138 }
139 }
140 catch (error) {
141 const e = this.serializeError(error);
142 if (e instanceof rpc_message_encoder_1.ResponseError) {
143 throw e;
144 }
145 const reason = e.message || '';
146 const stack = e.stack || '';
147 console.error(`Request ${method} failed with error: ${reason}`, stack);
148 throw e;
149 }
150 }
151 /**
152 * Process an incoming RPC notification.
153 *
154 * Same as [[onRequest]], but called on incoming notifications rather than
155 * methods calls.
156 */
157 onNotification(method, ...args) {
158 if (this.target) {
159 this.target[method](...args);
160 }
161 }
162 /**
163 * Create a Proxy exposing the interface of an object of type T. This Proxy
164 * can be used to do RPC method calls on the remote target object as
165 * if it was local.
166 *
167 * If `T` implements `RpcServer` then a client is used as a target object for a remote target object.
168 */
169 createProxy() {
170 const result = new Proxy(this, this);
171 return result;
172 }
173 /**
174 * Get a callable object that executes a RPC method call.
175 *
176 * Getting a property on the Proxy object returns a callable that, when
177 * called, executes a RPC call. The name of the property defines the
178 * method to be called. The callable takes a variable number of arguments,
179 * which are passed in the RPC method call.
180 *
181 * For example, if you have a Proxy object:
182 *
183 * let fooProxyFactory = RpcProxyFactory<Foo>('/foo')
184 * let fooProxy = fooProxyFactory.createProxy()
185 *
186 * accessing `fooProxy.bar` will return a callable that, when called,
187 * executes a RPC method call to method `bar`. Therefore, doing
188 * `fooProxy.bar()` will call the `bar` method on the remote Foo object.
189 *
190 * @param target - unused.
191 * @param p - The property accessed on the Proxy object.
192 * @param receiver - unused.
193 * @returns A callable that executes the RPC call.
194 */
195 get(target, p, receiver) {
196 if (p === 'setClient') {
197 return (client) => {
198 this.target = client;
199 };
200 }
201 if (p === 'getClient') {
202 return () => this.target;
203 }
204 if (p === 'onDidOpenConnection') {
205 return this.onDidOpenConnectionEmitter.event;
206 }
207 if (p === 'onDidCloseConnection') {
208 return this.onDidCloseConnectionEmitter.event;
209 }
210 if (p === 'then') {
211 // Prevent inversify from identifying this proxy as a promise object.
212 return undefined;
213 }
214 const isNotify = this.isNotification(p);
215 return (...args) => {
216 const method = p.toString();
217 const capturedError = new Error(`Request '${method}' failed`);
218 return this.rpcDeferred.promise.then(connection => new Promise((resolve, reject) => {
219 try {
220 if (isNotify) {
221 connection.sendNotification(method, args);
222 resolve(undefined);
223 }
224 else {
225 const resultPromise = connection.sendRequest(method, args);
226 resultPromise
227 .catch((err) => reject(this.deserializeError(capturedError, err)))
228 .then((result) => resolve(result));
229 }
230 }
231 catch (err) {
232 reject(err);
233 }
234 }));
235 };
236 }
237 /**
238 * Return whether the given property represents a notification.
239 *
240 * A property leads to a notification rather than a method call if its name
241 * begins with `notify` or `on`.
242 *
243 * @param p - The property being called on the proxy.
244 * @return Whether `p` represents a notification.
245 */
246 isNotification(p) {
247 return p.toString().startsWith('notify') || p.toString().startsWith('on');
248 }
249 serializeError(e) {
250 if (application_error_1.ApplicationError.is(e)) {
251 return new rpc_message_encoder_1.ResponseError(e.code, '', Object.assign({ kind: 'application' }, e.toJson()));
252 }
253 return e;
254 }
255 deserializeError(capturedError, e) {
256 if (e instanceof rpc_message_encoder_1.ResponseError) {
257 const capturedStack = capturedError.stack || '';
258 if (e.data && e.data.kind === 'application') {
259 const { stack, data, message } = e.data;
260 return application_error_1.ApplicationError.fromJson(e.code, {
261 message: message || capturedError.message,
262 data,
263 stack: `${capturedStack}\nCaused by: ${stack}`
264 });
265 }
266 e.stack = capturedStack;
267 }
268 return e;
269 }
270}
271exports.RpcProxyFactory = RpcProxyFactory;
272/**
273 * @deprecated since 1.39.0 use `RpcConnectionHandler` instead
274 */
275class JsonRpcConnectionHandler extends RpcConnectionHandler {
276}
277exports.JsonRpcConnectionHandler = JsonRpcConnectionHandler;
278/**
279 * @deprecated since 1.39.0 use `RpcProxyFactory` instead
280 */
281class JsonRpcProxyFactory extends RpcProxyFactory {
282}
283exports.JsonRpcProxyFactory = JsonRpcProxyFactory;
284// eslint-disable-next-line deprecation/deprecation
285(0, inversify_1.decorate)((0, inversify_1.injectable)(), JsonRpcProxyFactory);
286// eslint-disable-next-line deprecation/deprecation
287(0, inversify_1.decorate)((0, inversify_1.unmanaged)(), JsonRpcProxyFactory, 0);
288//# sourceMappingURL=proxy-factory.js.map
\No newline at end of file