@theia/core 1.26.0 → 1.27.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/lib/browser/messaging/ws-connection-provider.d.ts +5 -4
- package/lib/browser/messaging/ws-connection-provider.d.ts.map +1 -1
- package/lib/browser/messaging/ws-connection-provider.js +30 -23
- package/lib/browser/messaging/ws-connection-provider.js.map +1 -1
- package/lib/browser/progress-status-bar-item.d.ts +1 -1
- package/lib/browser/progress-status-bar-item.d.ts.map +1 -1
- package/lib/browser/tree/tree-compression/compressed-tree-widget.js +2 -2
- package/lib/browser/tree/tree-compression/compressed-tree-widget.js.map +1 -1
- package/lib/browser/widgets/select-component.d.ts +4 -1
- package/lib/browser/widgets/select-component.d.ts.map +1 -1
- package/lib/browser/widgets/select-component.js +30 -16
- package/lib/browser/widgets/select-component.js.map +1 -1
- package/lib/common/cancellation.d.ts +1 -0
- package/lib/common/cancellation.d.ts.map +1 -1
- package/lib/common/cancellation.js +8 -0
- package/lib/common/cancellation.js.map +1 -1
- package/lib/common/index.d.ts +2 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +2 -0
- package/lib/common/index.js.map +1 -1
- package/lib/common/message-rpc/channel.d.ts +106 -0
- package/lib/common/message-rpc/channel.d.ts.map +1 -0
- package/lib/common/message-rpc/channel.js +195 -0
- package/lib/common/message-rpc/channel.js.map +1 -0
- package/lib/common/message-rpc/channel.spec.d.ts +9 -0
- package/lib/common/message-rpc/channel.spec.d.ts.map +1 -0
- package/lib/common/message-rpc/channel.spec.js +80 -0
- package/lib/common/message-rpc/channel.spec.js.map +1 -0
- package/lib/common/message-rpc/index.d.ts +4 -0
- package/lib/common/message-rpc/index.d.ts.map +1 -0
- package/lib/{node/messaging/logger.js → common/message-rpc/index.js} +6 -19
- package/lib/common/message-rpc/index.js.map +1 -0
- package/lib/common/message-rpc/message-buffer.d.ts +50 -0
- package/lib/common/message-rpc/message-buffer.d.ts.map +1 -0
- package/lib/common/message-rpc/message-buffer.js +56 -0
- package/lib/common/message-rpc/message-buffer.js.map +1 -0
- package/lib/common/message-rpc/rpc-message-encoder.d.ts +159 -0
- package/lib/common/message-rpc/rpc-message-encoder.d.ts.map +1 -0
- package/lib/common/message-rpc/rpc-message-encoder.js +362 -0
- package/lib/common/message-rpc/rpc-message-encoder.js.map +1 -0
- package/lib/common/message-rpc/rpc-message-encoder.spec.d.ts +2 -0
- package/lib/common/message-rpc/rpc-message-encoder.spec.d.ts.map +1 -0
- package/lib/common/message-rpc/rpc-message-encoder.spec.js +37 -0
- package/lib/common/message-rpc/rpc-message-encoder.spec.js.map +1 -0
- package/lib/common/message-rpc/rpc-protocol.d.ts +61 -0
- package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -0
- package/lib/common/message-rpc/rpc-protocol.js +183 -0
- package/lib/common/message-rpc/rpc-protocol.js.map +1 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.d.ts +52 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.d.ts.map +1 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.js +169 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.js.map +1 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.spec.d.ts +2 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.spec.d.ts.map +1 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.spec.js +39 -0
- package/lib/common/message-rpc/uint8-array-message-buffer.spec.js.map +1 -0
- package/lib/common/messaging/abstract-connection-provider.d.ts +9 -8
- package/lib/common/messaging/abstract-connection-provider.d.ts.map +1 -1
- package/lib/common/messaging/abstract-connection-provider.js +20 -35
- package/lib/common/messaging/abstract-connection-provider.js.map +1 -1
- package/lib/common/messaging/connection-error-handler.d.ts +1 -2
- package/lib/common/messaging/connection-error-handler.d.ts.map +1 -1
- package/lib/common/messaging/connection-error-handler.js +1 -1
- package/lib/common/messaging/connection-error-handler.js.map +1 -1
- package/lib/common/messaging/handler.d.ts +2 -2
- package/lib/common/messaging/handler.d.ts.map +1 -1
- package/lib/common/messaging/proxy-factory.d.ts +13 -7
- package/lib/common/messaging/proxy-factory.d.ts.map +1 -1
- package/lib/common/messaging/proxy-factory.js +18 -13
- package/lib/common/messaging/proxy-factory.js.map +1 -1
- package/lib/common/messaging/proxy-factory.spec.js +4 -15
- package/lib/common/messaging/proxy-factory.spec.js.map +1 -1
- package/lib/common/messaging/web-socket-channel.d.ts +54 -48
- package/lib/common/messaging/web-socket-channel.d.ts.map +1 -1
- package/lib/common/messaging/web-socket-channel.js +41 -105
- package/lib/common/messaging/web-socket-channel.js.map +1 -1
- package/lib/electron-browser/messaging/electron-ipc-connection-provider.d.ts +2 -2
- package/lib/electron-browser/messaging/electron-ipc-connection-provider.d.ts.map +1 -1
- package/lib/electron-browser/messaging/electron-ipc-connection-provider.js +18 -7
- package/lib/electron-browser/messaging/electron-ipc-connection-provider.js.map +1 -1
- package/lib/electron-browser/messaging/electron-ws-connection-provider.d.ts +2 -2
- package/lib/electron-browser/messaging/electron-ws-connection-provider.d.ts.map +1 -1
- package/lib/electron-browser/messaging/electron-ws-connection-provider.js +5 -7
- package/lib/electron-browser/messaging/electron-ws-connection-provider.js.map +1 -1
- package/lib/electron-main/messaging/electron-messaging-contribution.d.ts +37 -9
- package/lib/electron-main/messaging/electron-messaging-contribution.d.ts.map +1 -1
- package/lib/electron-main/messaging/electron-messaging-contribution.js +83 -68
- package/lib/electron-main/messaging/electron-messaging-contribution.js.map +1 -1
- package/lib/electron-main/messaging/electron-messaging-service.d.ts +2 -8
- package/lib/electron-main/messaging/electron-messaging-service.d.ts.map +1 -1
- package/lib/electron-main/messaging/electron-messaging-service.js.map +1 -1
- package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
- package/lib/electron-main/theia-electron-window.js +11 -8
- package/lib/electron-main/theia-electron-window.js.map +1 -1
- package/lib/node/messaging/binary-message-pipe.d.ts +45 -0
- package/lib/node/messaging/binary-message-pipe.d.ts.map +1 -0
- package/lib/node/messaging/binary-message-pipe.js +152 -0
- package/lib/node/messaging/binary-message-pipe.js.map +1 -0
- package/lib/node/messaging/ipc-bootstrap.js +2 -11
- package/lib/node/messaging/ipc-bootstrap.js.map +1 -1
- package/lib/node/messaging/ipc-channel.d.ts +26 -0
- package/lib/node/messaging/ipc-channel.d.ts.map +1 -0
- package/lib/node/messaging/ipc-channel.js +86 -0
- package/lib/node/messaging/ipc-channel.js.map +1 -0
- package/lib/node/messaging/ipc-connection-provider.d.ts +3 -5
- package/lib/node/messaging/ipc-connection-provider.d.ts.map +1 -1
- package/lib/node/messaging/ipc-connection-provider.js +14 -31
- package/lib/node/messaging/ipc-connection-provider.js.map +1 -1
- package/lib/node/messaging/ipc-protocol.d.ts +2 -2
- package/lib/node/messaging/ipc-protocol.d.ts.map +1 -1
- package/lib/node/messaging/messaging-contribution.d.ts +6 -9
- package/lib/node/messaging/messaging-contribution.d.ts.map +1 -1
- package/lib/node/messaging/messaging-contribution.js +23 -68
- package/lib/node/messaging/messaging-contribution.js.map +1 -1
- package/lib/node/messaging/messaging-service.d.ts +4 -23
- package/lib/node/messaging/messaging-service.d.ts.map +1 -1
- package/lib/node/messaging/messaging-service.js +1 -15
- package/lib/node/messaging/messaging-service.js.map +1 -1
- package/lib/node/messaging/test/test-web-socket-channel.d.ts +4 -2
- package/lib/node/messaging/test/test-web-socket-channel.d.ts.map +1 -1
- package/lib/node/messaging/test/test-web-socket-channel.js +25 -12
- package/lib/node/messaging/test/test-web-socket-channel.js.map +1 -1
- package/package.json +5 -8
- package/src/browser/messaging/ws-connection-provider.ts +34 -25
- package/src/browser/progress-status-bar-item.ts +1 -1
- package/src/browser/style/menus.css +1 -0
- package/src/browser/tree/tree-compression/compressed-tree-widget.tsx +2 -2
- package/src/browser/widgets/select-component.tsx +37 -17
- package/src/common/cancellation.ts +8 -0
- package/src/common/index.ts +2 -0
- package/src/common/message-rpc/channel.spec.ts +88 -0
- package/src/common/message-rpc/channel.ts +260 -0
- package/src/{node/messaging/logger.ts → common/message-rpc/index.ts} +4 -23
- package/src/common/message-rpc/message-buffer.ts +99 -0
- package/src/common/message-rpc/rpc-message-encoder.spec.ts +42 -0
- package/src/common/message-rpc/rpc-message-encoder.ts +497 -0
- package/src/common/message-rpc/rpc-protocol.ts +217 -0
- package/src/common/message-rpc/uint8-array-message-buffer.spec.ts +41 -0
- package/src/common/message-rpc/uint8-array-message-buffer.ts +206 -0
- package/src/common/messaging/abstract-connection-provider.ts +28 -37
- package/src/common/messaging/connection-error-handler.ts +1 -2
- package/src/common/messaging/handler.ts +2 -2
- package/src/common/messaging/proxy-factory.spec.ts +4 -17
- package/src/common/messaging/proxy-factory.ts +27 -16
- package/src/common/messaging/web-socket-channel.ts +79 -135
- package/src/electron-browser/messaging/electron-ipc-connection-provider.ts +21 -7
- package/src/electron-browser/messaging/electron-ws-connection-provider.ts +5 -8
- package/src/electron-main/messaging/electron-messaging-contribution.ts +87 -65
- package/src/electron-main/messaging/electron-messaging-service.ts +2 -8
- package/src/electron-main/theia-electron-window.ts +12 -9
- package/src/node/messaging/binary-message-pipe.ts +168 -0
- package/src/node/messaging/ipc-bootstrap.ts +3 -11
- package/src/node/messaging/ipc-channel.ts +97 -0
- package/src/node/messaging/ipc-connection-provider.ts +18 -35
- package/src/node/messaging/ipc-protocol.ts +2 -2
- package/src/node/messaging/messaging-contribution.ts +29 -74
- package/src/node/messaging/messaging-service.ts +4 -31
- package/src/node/messaging/test/test-web-socket-channel.ts +26 -17
- package/lib/node/messaging/logger.d.ts +0 -8
- package/lib/node/messaging/logger.d.ts.map +0 -1
- package/lib/node/messaging/logger.js.map +0 -1
- package/shared/vscode-ws-jsonrpc/index.d.ts +0 -1
- package/shared/vscode-ws-jsonrpc/index.js +0 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2021 Red Hat, Inc. and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
17
|
+
|
|
18
|
+
import { CancellationToken, CancellationTokenSource } from '../cancellation';
|
|
19
|
+
import { DisposableCollection } from '../disposable';
|
|
20
|
+
import { Emitter, Event } from '../event';
|
|
21
|
+
import { Deferred } from '../promise-util';
|
|
22
|
+
import { Channel } from './channel';
|
|
23
|
+
import { RpcMessage, RpcMessageDecoder, RpcMessageEncoder, RpcMessageType } from './rpc-message-encoder';
|
|
24
|
+
import { Uint8ArrayWriteBuffer } from './uint8-array-message-buffer';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Handles request messages received by the {@link RpcServer}.
|
|
28
|
+
*/
|
|
29
|
+
export type RequestHandler = (method: string, args: any[]) => Promise<any>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initialization options for a {@link RpcProtocol}.
|
|
33
|
+
*/
|
|
34
|
+
export interface RpcProtocolOptions {
|
|
35
|
+
/**
|
|
36
|
+
* The message encoder that should be used. If `undefined` the default {@link RpcMessageEncoder} will be used.
|
|
37
|
+
*/
|
|
38
|
+
encoder?: RpcMessageEncoder,
|
|
39
|
+
/**
|
|
40
|
+
* The message decoder that should be used. If `undefined` the default {@link RpcMessageDecoder} will be used.
|
|
41
|
+
*/
|
|
42
|
+
decoder?: RpcMessageDecoder
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Establish a bi-directional RPC protocol on top of a given channel. Bi-directional means to send
|
|
47
|
+
* sends requests and notifications to the remote side as well as receiving requests and notifications from the remote side.
|
|
48
|
+
* Clients can get a promise for a remote request result that will be either resolved or
|
|
49
|
+
* rejected depending on the success of the request. Keeps track of outstanding requests and matches replies to the appropriate request
|
|
50
|
+
* Currently, there is no timeout handling for long running requests implemented.
|
|
51
|
+
*/
|
|
52
|
+
export class RpcProtocol {
|
|
53
|
+
static readonly CANCELLATION_TOKEN_KEY = 'add.cancellation.token';
|
|
54
|
+
|
|
55
|
+
protected readonly pendingRequests: Map<number, Deferred<any>> = new Map();
|
|
56
|
+
|
|
57
|
+
protected nextMessageId: number = 0;
|
|
58
|
+
|
|
59
|
+
protected readonly encoder: RpcMessageEncoder;
|
|
60
|
+
protected readonly decoder: RpcMessageDecoder;
|
|
61
|
+
|
|
62
|
+
protected readonly onNotificationEmitter: Emitter<{ method: string; args: any[]; }> = new Emitter();
|
|
63
|
+
protected readonly cancellationTokenSources = new Map<number, CancellationTokenSource>();
|
|
64
|
+
|
|
65
|
+
get onNotification(): Event<{ method: string; args: any[]; }> {
|
|
66
|
+
return this.onNotificationEmitter.event;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected toDispose = new DisposableCollection();
|
|
70
|
+
|
|
71
|
+
constructor(public readonly channel: Channel, public readonly requestHandler: RequestHandler, options: RpcProtocolOptions = {}) {
|
|
72
|
+
this.encoder = options.encoder ?? new RpcMessageEncoder();
|
|
73
|
+
this.decoder = options.decoder ?? new RpcMessageDecoder();
|
|
74
|
+
this.toDispose.push(this.onNotificationEmitter);
|
|
75
|
+
this.toDispose.push(channel.onMessage(readBuffer => this.handleMessage(this.decoder.parse(readBuffer()))));
|
|
76
|
+
channel.onClose(() => this.toDispose.dispose());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
handleMessage(message: RpcMessage): void {
|
|
80
|
+
switch (message.type) {
|
|
81
|
+
case RpcMessageType.Cancel: {
|
|
82
|
+
this.handleCancel(message.id);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case RpcMessageType.Request: {
|
|
86
|
+
this.handleRequest(message.id, message.method, message.args);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case RpcMessageType.Notification: {
|
|
90
|
+
this.handleNotify(message.id, message.method, message.args);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case RpcMessageType.Reply: {
|
|
94
|
+
this.handleReply(message.id, message.res);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case RpcMessageType.ReplyErr: {
|
|
98
|
+
this.handleReplyErr(message.id, message.err);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
protected handleReply(id: number, value: any): void {
|
|
105
|
+
const replyHandler = this.pendingRequests.get(id);
|
|
106
|
+
if (replyHandler) {
|
|
107
|
+
this.pendingRequests.delete(id);
|
|
108
|
+
replyHandler.resolve(value);
|
|
109
|
+
} else {
|
|
110
|
+
throw new Error(`No reply handler for reply with id: ${id}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
protected handleReplyErr(id: number, error: any): void {
|
|
115
|
+
try {
|
|
116
|
+
const replyHandler = this.pendingRequests.get(id);
|
|
117
|
+
if (replyHandler) {
|
|
118
|
+
this.pendingRequests.delete(id);
|
|
119
|
+
replyHandler.reject(error);
|
|
120
|
+
} else {
|
|
121
|
+
throw new Error(`No reply handler for error reply with id: ${id}`);
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
sendRequest<T>(method: string, args: any[]): Promise<T> {
|
|
129
|
+
const id = this.nextMessageId++;
|
|
130
|
+
const reply = new Deferred<T>();
|
|
131
|
+
|
|
132
|
+
// 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
|
|
133
|
+
// args array and the `CANCELLATION_TOKEN_KEY` string instead.
|
|
134
|
+
const cancellationToken: CancellationToken | undefined = args.length && CancellationToken.is(args[args.length - 1]) ? args.pop() : undefined;
|
|
135
|
+
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
|
136
|
+
return Promise.reject(this.cancelError());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (cancellationToken) {
|
|
140
|
+
args.push(RpcProtocol.CANCELLATION_TOKEN_KEY);
|
|
141
|
+
cancellationToken.onCancellationRequested(() => {
|
|
142
|
+
this.sendCancel(id);
|
|
143
|
+
this.pendingRequests.get(id)?.reject(this.cancelError());
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
this.pendingRequests.set(id, reply);
|
|
148
|
+
|
|
149
|
+
const output = this.channel.getWriteBuffer();
|
|
150
|
+
this.encoder.request(output, id, method, args);
|
|
151
|
+
output.commit();
|
|
152
|
+
return reply.promise;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
sendNotification(method: string, args: any[]): void {
|
|
156
|
+
const output = this.channel.getWriteBuffer();
|
|
157
|
+
this.encoder.notification(output, this.nextMessageId++, method, args);
|
|
158
|
+
output.commit();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
sendCancel(requestId: number): void {
|
|
162
|
+
const output = this.channel.getWriteBuffer();
|
|
163
|
+
this.encoder.cancel(output, requestId);
|
|
164
|
+
output.commit();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
cancelError(): Error {
|
|
168
|
+
const error = new Error('"Request has already been canceled by the sender"');
|
|
169
|
+
error.name = 'Cancel';
|
|
170
|
+
return error;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected handleCancel(id: number): void {
|
|
174
|
+
const cancellationTokenSource = this.cancellationTokenSources.get(id);
|
|
175
|
+
if (cancellationTokenSource) {
|
|
176
|
+
this.cancellationTokenSources.delete(id);
|
|
177
|
+
cancellationTokenSource.cancel();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
protected async handleRequest(id: number, method: string, args: any[]): Promise<void> {
|
|
182
|
+
const output = this.channel.getWriteBuffer();
|
|
183
|
+
|
|
184
|
+
// Check if the last argument of the received args is the key for indicating that a cancellation token should be used
|
|
185
|
+
// If so remove the key from the args and create a new cancellation token.
|
|
186
|
+
const addToken = args.length && args[args.length - 1] === RpcProtocol.CANCELLATION_TOKEN_KEY ? args.pop() : false;
|
|
187
|
+
if (addToken) {
|
|
188
|
+
const tokenSource = new CancellationTokenSource();
|
|
189
|
+
this.cancellationTokenSources.set(id, tokenSource);
|
|
190
|
+
args.push(tokenSource.token);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const result = await this.requestHandler(method, args);
|
|
195
|
+
this.cancellationTokenSources.delete(id);
|
|
196
|
+
this.encoder.replyOK(output, id, result);
|
|
197
|
+
output.commit();
|
|
198
|
+
} catch (err) {
|
|
199
|
+
// In case of an error the output buffer might already contains parts of an message.
|
|
200
|
+
// => Dispose the current buffer and retrieve a new, clean one for writing the response error.
|
|
201
|
+
if (output instanceof Uint8ArrayWriteBuffer) {
|
|
202
|
+
output.dispose();
|
|
203
|
+
}
|
|
204
|
+
const errorOutput = this.channel.getWriteBuffer();
|
|
205
|
+
this.cancellationTokenSources.delete(id);
|
|
206
|
+
this.encoder.replyErr(errorOutput, id, err);
|
|
207
|
+
errorOutput.commit();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
protected async handleNotify(id: number, method: string, args: any[]): Promise<void> {
|
|
212
|
+
if (this.toDispose.disposed) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
this.onNotificationEmitter.fire({ method, args });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2021 Red Hat, Inc. and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
import { expect } from 'chai';
|
|
17
|
+
import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from './uint8-array-message-buffer';
|
|
18
|
+
|
|
19
|
+
describe('array message buffer tests', () => {
|
|
20
|
+
it('basic read write test', () => {
|
|
21
|
+
const buffer = new Uint8Array(1024);
|
|
22
|
+
const writer = new Uint8ArrayWriteBuffer(buffer);
|
|
23
|
+
|
|
24
|
+
writer.writeUint8(8);
|
|
25
|
+
writer.writeUint32(10000);
|
|
26
|
+
writer.writeBytes(new Uint8Array([1, 2, 3, 4]));
|
|
27
|
+
writer.writeString('this is a string');
|
|
28
|
+
writer.writeString('another string');
|
|
29
|
+
writer.commit();
|
|
30
|
+
|
|
31
|
+
const written = writer.getCurrentContents();
|
|
32
|
+
|
|
33
|
+
const reader = new Uint8ArrayReadBuffer(written);
|
|
34
|
+
|
|
35
|
+
expect(reader.readUint8()).equal(8);
|
|
36
|
+
expect(reader.readUint32()).equal(10000);
|
|
37
|
+
expect(reader.readBytes()).deep.equal(new Uint8Array([1, 2, 3, 4]));
|
|
38
|
+
expect(reader.readString()).equal('this is a string');
|
|
39
|
+
expect(reader.readString()).equal('another string');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2021 Red Hat, Inc. and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
import { Disposable } from '../disposable';
|
|
17
|
+
import { Emitter, Event } from '../event';
|
|
18
|
+
import { ReadBuffer, WriteBuffer } from './message-buffer';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The default {@link WriteBuffer} implementation. Uses a {@link Uint8Array} for buffering.
|
|
22
|
+
* The {@link Uint8ArrayWriteBuffer.onCommit} hook can be used to rect to on-commit events.
|
|
23
|
+
* After the {@link Uint8ArrayWriteBuffer.commit} method has been called the buffer is disposed
|
|
24
|
+
* and can no longer be used for writing data. If the writer buffer is no longer needed but the message
|
|
25
|
+
* has not been committed yet it has to be disposed manually.
|
|
26
|
+
*/
|
|
27
|
+
export class Uint8ArrayWriteBuffer implements WriteBuffer, Disposable {
|
|
28
|
+
|
|
29
|
+
private encoder = new TextEncoder();
|
|
30
|
+
private msg: DataView;
|
|
31
|
+
private isDisposed = false;
|
|
32
|
+
private offset: number;
|
|
33
|
+
|
|
34
|
+
constructor(private buffer: Uint8Array = new Uint8Array(1024), writePosition: number = 0) {
|
|
35
|
+
this.offset = buffer.byteOffset + writePosition;
|
|
36
|
+
this.msg = new DataView(buffer.buffer);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ensureCapacity(value: number): this {
|
|
40
|
+
let newLength = this.buffer.byteLength;
|
|
41
|
+
while (newLength < this.offset + value) {
|
|
42
|
+
newLength *= 2;
|
|
43
|
+
}
|
|
44
|
+
if (newLength !== this.buffer.byteLength) {
|
|
45
|
+
const newBuffer = new Uint8Array(newLength);
|
|
46
|
+
newBuffer.set(this.buffer);
|
|
47
|
+
this.buffer = newBuffer;
|
|
48
|
+
this.msg = new DataView(this.buffer.buffer);
|
|
49
|
+
}
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
writeLength(length: number): this {
|
|
54
|
+
if (length < 0 || (length % 1) !== 0) {
|
|
55
|
+
throw new Error(`Could not write the given length value. '${length}' is not an integer >= 0`);
|
|
56
|
+
}
|
|
57
|
+
if (length < 127) {
|
|
58
|
+
this.writeUint8(length);
|
|
59
|
+
} else {
|
|
60
|
+
this.writeUint8(128 + (length & 127));
|
|
61
|
+
this.writeLength(length >> 7);
|
|
62
|
+
}
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
writeNumber(value: number): this {
|
|
67
|
+
this.ensureCapacity(8);
|
|
68
|
+
this.msg.setFloat64(this.offset, value);
|
|
69
|
+
this.offset += 8;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
writeUint8(value: number): this {
|
|
74
|
+
this.ensureCapacity(1);
|
|
75
|
+
this.buffer[this.offset++] = value;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
writeUint16(value: number): this {
|
|
80
|
+
this.ensureCapacity(2);
|
|
81
|
+
this.msg.setUint16(this.offset, value);
|
|
82
|
+
this.offset += 2;
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
writeUint32(value: number): this {
|
|
87
|
+
this.ensureCapacity(4);
|
|
88
|
+
this.msg.setUint32(this.offset, value);
|
|
89
|
+
this.offset += 4;
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
writeString(value: string): this {
|
|
94
|
+
this.ensureCapacity(4 * value.length);
|
|
95
|
+
const result = this.encoder.encodeInto(value, this.buffer.subarray(this.offset + 4));
|
|
96
|
+
this.msg.setUint32(this.offset, result.written!);
|
|
97
|
+
this.offset += 4 + result.written!;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
writeBytes(value: Uint8Array): this {
|
|
102
|
+
this.writeLength(value.byteLength);
|
|
103
|
+
this.ensureCapacity(value.length);
|
|
104
|
+
this.buffer.set(value, this.offset);
|
|
105
|
+
this.offset += value.length;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private onCommitEmitter = new Emitter<Uint8Array>();
|
|
110
|
+
get onCommit(): Event<Uint8Array> {
|
|
111
|
+
return this.onCommitEmitter.event;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
commit(): void {
|
|
115
|
+
if (this.isDisposed) {
|
|
116
|
+
throw new Error("Could not invoke 'commit'. The WriteBuffer is already disposed.");
|
|
117
|
+
}
|
|
118
|
+
this.onCommitEmitter.fire(this.getCurrentContents());
|
|
119
|
+
this.dispose();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getCurrentContents(): Uint8Array {
|
|
123
|
+
return this.buffer.slice(this.buffer.byteOffset, this.offset);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
dispose(): void {
|
|
127
|
+
if (!this.isDisposed) {
|
|
128
|
+
this.onCommitEmitter.dispose();
|
|
129
|
+
this.isDisposed = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The default {@link ReadBuffer} implementation. Uses a {@link Uint8Array} for buffering.
|
|
136
|
+
* Is for single message read. A message can only be read once.
|
|
137
|
+
*/
|
|
138
|
+
export class Uint8ArrayReadBuffer implements ReadBuffer {
|
|
139
|
+
private offset: number = 0;
|
|
140
|
+
private msg: DataView;
|
|
141
|
+
private decoder = new TextDecoder();
|
|
142
|
+
|
|
143
|
+
constructor(private readonly buffer: Uint8Array, readPosition = 0) {
|
|
144
|
+
this.offset = buffer.byteOffset + readPosition;
|
|
145
|
+
this.msg = new DataView(buffer.buffer);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
readUint8(): number {
|
|
149
|
+
return this.msg.getUint8(this.offset++);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
readUint16(): number {
|
|
153
|
+
const result = this.msg.getUint16(this.offset);
|
|
154
|
+
this.offset += 2;
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
readUint32(): number {
|
|
159
|
+
const result = this.msg.getUint32(this.offset);
|
|
160
|
+
this.offset += 4;
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
readLength(): number {
|
|
165
|
+
let shift = 0;
|
|
166
|
+
let byte = this.readUint8();
|
|
167
|
+
let value = (byte & 127) << shift;
|
|
168
|
+
while (byte > 127) {
|
|
169
|
+
shift += 7;
|
|
170
|
+
byte = this.readUint8();
|
|
171
|
+
value = value + ((byte & 127) << shift);
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
readNumber(): number {
|
|
177
|
+
const result = this.msg.getFloat64(this.offset);
|
|
178
|
+
this.offset += 8;
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
readString(): string {
|
|
183
|
+
const len = this.readUint32();
|
|
184
|
+
const sliceOffset = this.offset - this.buffer.byteOffset;
|
|
185
|
+
const result = this.decodeString(this.buffer.slice(sliceOffset, sliceOffset + len));
|
|
186
|
+
this.offset += len;
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private decodeString(buf: Uint8Array): string {
|
|
191
|
+
return this.decoder.decode(buf);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
readBytes(): Uint8Array {
|
|
195
|
+
const length = this.readLength();
|
|
196
|
+
const sliceOffset = this.offset - this.buffer.byteOffset;
|
|
197
|
+
const result = this.buffer.slice(sliceOffset, sliceOffset + length);
|
|
198
|
+
this.offset += length;
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
sliceAtReadPosition(): ReadBuffer {
|
|
203
|
+
const sliceOffset = this.offset - this.buffer.byteOffset;
|
|
204
|
+
return new Uint8ArrayReadBuffer(this.buffer, sliceOffset);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -15,11 +15,10 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { injectable, interfaces } from 'inversify';
|
|
18
|
-
import { ConsoleLogger, createWebSocketConnection, Logger } from 'vscode-ws-jsonrpc';
|
|
19
18
|
import { Emitter, Event } from '../event';
|
|
20
19
|
import { ConnectionHandler } from './handler';
|
|
21
20
|
import { JsonRpcProxy, JsonRpcProxyFactory } from './proxy-factory';
|
|
22
|
-
import {
|
|
21
|
+
import { Channel, ChannelMultiplexer } from '../message-rpc/channel';
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* Factor common logic according to `ElectronIpcConnectionProvider` and
|
|
@@ -45,9 +44,6 @@ export abstract class AbstractConnectionProvider<AbstractOptions extends object>
|
|
|
45
44
|
throw new Error('abstract');
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
protected channelIdSeq = 0;
|
|
49
|
-
protected readonly channels = new Map<number, WebSocketChannel>();
|
|
50
|
-
|
|
51
47
|
protected readonly onIncomingMessageActivityEmitter: Emitter<void> = new Emitter();
|
|
52
48
|
get onIncomingMessageActivity(): Event<void> {
|
|
53
49
|
return this.onIncomingMessageActivityEmitter.event;
|
|
@@ -75,50 +71,45 @@ export abstract class AbstractConnectionProvider<AbstractOptions extends object>
|
|
|
75
71
|
return factory.createProxy();
|
|
76
72
|
}
|
|
77
73
|
|
|
74
|
+
protected channelMultiPlexer?: ChannelMultiplexer;
|
|
75
|
+
|
|
76
|
+
// A set of channel opening functions that are executed if the backend reconnects to restore the
|
|
77
|
+
// the channels that were open before the disconnect occurred.
|
|
78
|
+
protected reconnectChannelOpeners: Array<() => Promise<void>> = [];
|
|
79
|
+
|
|
80
|
+
protected initializeMultiplexer(): void {
|
|
81
|
+
const mainChannel = this.createMainChannel();
|
|
82
|
+
mainChannel.onMessage(() => this.onIncomingMessageActivityEmitter.fire());
|
|
83
|
+
this.channelMultiPlexer = new ChannelMultiplexer(mainChannel);
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
/**
|
|
79
87
|
* Install a connection handler for the given path.
|
|
80
88
|
*/
|
|
81
89
|
listen(handler: ConnectionHandler, options?: AbstractOptions): void {
|
|
82
90
|
this.openChannel(handler.path, channel => {
|
|
83
|
-
|
|
84
|
-
connection.onDispose(() => channel.close());
|
|
85
|
-
handler.onConnection(connection);
|
|
91
|
+
handler.onConnection(channel);
|
|
86
92
|
}, options);
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
openChannel(path: string, handler: (channel:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
console.error('The ws channel does not exist', channel.id);
|
|
95
|
+
async openChannel(path: string, handler: (channel: Channel) => void, options?: AbstractOptions): Promise<void> {
|
|
96
|
+
if (!this.channelMultiPlexer) {
|
|
97
|
+
throw new Error('The channel multiplexer has not been initialized yet!');
|
|
98
|
+
}
|
|
99
|
+
const newChannel = await this.channelMultiPlexer.open(path);
|
|
100
|
+
newChannel.onClose(() => {
|
|
101
|
+
const { reconnecting } = { reconnecting: true, ...options };
|
|
102
|
+
if (reconnecting) {
|
|
103
|
+
this.reconnectChannelOpeners.push(() => this.openChannel(path, handler, options));
|
|
101
104
|
}
|
|
102
105
|
});
|
|
103
|
-
channel.onOpen(() => handler(channel));
|
|
104
|
-
channel.open(path);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
protected abstract createChannel(id: number): WebSocketChannel;
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
const message: WebSocketChannel.Message = JSON.parse(data);
|
|
111
|
-
const channel = this.channels.get(message.id);
|
|
112
|
-
if (channel) {
|
|
113
|
-
channel.handleMessage(message);
|
|
114
|
-
} else {
|
|
115
|
-
console.error('The ws channel does not exist', message.id);
|
|
116
|
-
}
|
|
117
|
-
this.onIncomingMessageActivityEmitter.fire(undefined);
|
|
107
|
+
handler(newChannel);
|
|
118
108
|
}
|
|
119
109
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Create the main connection that is used for multiplexing all service channels.
|
|
112
|
+
*/
|
|
113
|
+
protected abstract createMainChannel(): Channel;
|
|
123
114
|
|
|
124
115
|
}
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { Message } from 'vscode-ws-jsonrpc';
|
|
18
17
|
import { ILogger } from '../../common';
|
|
19
18
|
|
|
20
19
|
export interface ResolvedConnectionErrorHandlerOptions {
|
|
@@ -51,7 +50,7 @@ export class ConnectionErrorHandler {
|
|
|
51
50
|
};
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
shouldStop(error: Error,
|
|
53
|
+
shouldStop(error: Error, count?: number): boolean {
|
|
55
54
|
return !count || count > this.options.maxErrors;
|
|
56
55
|
}
|
|
57
56
|
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { Channel } from '../message-rpc/channel';
|
|
18
18
|
|
|
19
19
|
export const ConnectionHandler = Symbol('ConnectionHandler');
|
|
20
20
|
|
|
21
21
|
export interface ConnectionHandler {
|
|
22
22
|
readonly path: string;
|
|
23
|
-
onConnection(connection:
|
|
23
|
+
onConnection(connection: Channel): void;
|
|
24
24
|
}
|
|
@@ -15,21 +15,11 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import * as chai from 'chai';
|
|
18
|
-
import { ConsoleLogger } from '../../node/messaging/logger';
|
|
19
18
|
import { JsonRpcProxyFactory, JsonRpcProxy } from './proxy-factory';
|
|
20
|
-
import {
|
|
21
|
-
import * as stream from 'stream';
|
|
19
|
+
import { ChannelPipe } from '../message-rpc/channel.spec';
|
|
22
20
|
|
|
23
21
|
const expect = chai.expect;
|
|
24
22
|
|
|
25
|
-
class NoTransform extends stream.Transform {
|
|
26
|
-
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
-
override _transform(chunk: any, encoding: string, callback: Function): void {
|
|
29
|
-
callback(undefined, chunk);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
23
|
class TestServer {
|
|
34
24
|
requests: string[] = [];
|
|
35
25
|
doStuff(arg: string): Promise<string> {
|
|
@@ -102,15 +92,12 @@ function getSetup(): {
|
|
|
102
92
|
const server = new TestServer();
|
|
103
93
|
|
|
104
94
|
const serverProxyFactory = new JsonRpcProxyFactory<TestServer>(client);
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const serverConnection = createMessageConnection(server2client, client2server, new ConsoleLogger());
|
|
108
|
-
serverProxyFactory.listen(serverConnection);
|
|
95
|
+
const pipe = new ChannelPipe();
|
|
96
|
+
serverProxyFactory.listen(pipe.right);
|
|
109
97
|
const serverProxy = serverProxyFactory.createProxy();
|
|
110
98
|
|
|
111
99
|
const clientProxyFactory = new JsonRpcProxyFactory<TestClient>(server);
|
|
112
|
-
|
|
113
|
-
clientProxyFactory.listen(clientConnection);
|
|
100
|
+
clientProxyFactory.listen(pipe.left);
|
|
114
101
|
const clientProxy = clientProxyFactory.createProxy();
|
|
115
102
|
return {
|
|
116
103
|
client,
|