@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.
Files changed (164) hide show
  1. package/README.md +6 -7
  2. package/lib/browser/messaging/ws-connection-provider.d.ts +5 -4
  3. package/lib/browser/messaging/ws-connection-provider.d.ts.map +1 -1
  4. package/lib/browser/messaging/ws-connection-provider.js +30 -23
  5. package/lib/browser/messaging/ws-connection-provider.js.map +1 -1
  6. package/lib/browser/progress-status-bar-item.d.ts +1 -1
  7. package/lib/browser/progress-status-bar-item.d.ts.map +1 -1
  8. package/lib/browser/tree/tree-compression/compressed-tree-widget.js +2 -2
  9. package/lib/browser/tree/tree-compression/compressed-tree-widget.js.map +1 -1
  10. package/lib/browser/widgets/select-component.d.ts +4 -1
  11. package/lib/browser/widgets/select-component.d.ts.map +1 -1
  12. package/lib/browser/widgets/select-component.js +30 -16
  13. package/lib/browser/widgets/select-component.js.map +1 -1
  14. package/lib/common/cancellation.d.ts +1 -0
  15. package/lib/common/cancellation.d.ts.map +1 -1
  16. package/lib/common/cancellation.js +8 -0
  17. package/lib/common/cancellation.js.map +1 -1
  18. package/lib/common/index.d.ts +2 -0
  19. package/lib/common/index.d.ts.map +1 -1
  20. package/lib/common/index.js +2 -0
  21. package/lib/common/index.js.map +1 -1
  22. package/lib/common/message-rpc/channel.d.ts +106 -0
  23. package/lib/common/message-rpc/channel.d.ts.map +1 -0
  24. package/lib/common/message-rpc/channel.js +195 -0
  25. package/lib/common/message-rpc/channel.js.map +1 -0
  26. package/lib/common/message-rpc/channel.spec.d.ts +9 -0
  27. package/lib/common/message-rpc/channel.spec.d.ts.map +1 -0
  28. package/lib/common/message-rpc/channel.spec.js +80 -0
  29. package/lib/common/message-rpc/channel.spec.js.map +1 -0
  30. package/lib/common/message-rpc/index.d.ts +4 -0
  31. package/lib/common/message-rpc/index.d.ts.map +1 -0
  32. package/lib/{node/messaging/logger.js → common/message-rpc/index.js} +6 -19
  33. package/lib/common/message-rpc/index.js.map +1 -0
  34. package/lib/common/message-rpc/message-buffer.d.ts +50 -0
  35. package/lib/common/message-rpc/message-buffer.d.ts.map +1 -0
  36. package/lib/common/message-rpc/message-buffer.js +56 -0
  37. package/lib/common/message-rpc/message-buffer.js.map +1 -0
  38. package/lib/common/message-rpc/rpc-message-encoder.d.ts +159 -0
  39. package/lib/common/message-rpc/rpc-message-encoder.d.ts.map +1 -0
  40. package/lib/common/message-rpc/rpc-message-encoder.js +362 -0
  41. package/lib/common/message-rpc/rpc-message-encoder.js.map +1 -0
  42. package/lib/common/message-rpc/rpc-message-encoder.spec.d.ts +2 -0
  43. package/lib/common/message-rpc/rpc-message-encoder.spec.d.ts.map +1 -0
  44. package/lib/common/message-rpc/rpc-message-encoder.spec.js +37 -0
  45. package/lib/common/message-rpc/rpc-message-encoder.spec.js.map +1 -0
  46. package/lib/common/message-rpc/rpc-protocol.d.ts +61 -0
  47. package/lib/common/message-rpc/rpc-protocol.d.ts.map +1 -0
  48. package/lib/common/message-rpc/rpc-protocol.js +183 -0
  49. package/lib/common/message-rpc/rpc-protocol.js.map +1 -0
  50. package/lib/common/message-rpc/uint8-array-message-buffer.d.ts +52 -0
  51. package/lib/common/message-rpc/uint8-array-message-buffer.d.ts.map +1 -0
  52. package/lib/common/message-rpc/uint8-array-message-buffer.js +169 -0
  53. package/lib/common/message-rpc/uint8-array-message-buffer.js.map +1 -0
  54. package/lib/common/message-rpc/uint8-array-message-buffer.spec.d.ts +2 -0
  55. package/lib/common/message-rpc/uint8-array-message-buffer.spec.d.ts.map +1 -0
  56. package/lib/common/message-rpc/uint8-array-message-buffer.spec.js +39 -0
  57. package/lib/common/message-rpc/uint8-array-message-buffer.spec.js.map +1 -0
  58. package/lib/common/messaging/abstract-connection-provider.d.ts +9 -8
  59. package/lib/common/messaging/abstract-connection-provider.d.ts.map +1 -1
  60. package/lib/common/messaging/abstract-connection-provider.js +20 -35
  61. package/lib/common/messaging/abstract-connection-provider.js.map +1 -1
  62. package/lib/common/messaging/connection-error-handler.d.ts +1 -2
  63. package/lib/common/messaging/connection-error-handler.d.ts.map +1 -1
  64. package/lib/common/messaging/connection-error-handler.js +1 -1
  65. package/lib/common/messaging/connection-error-handler.js.map +1 -1
  66. package/lib/common/messaging/handler.d.ts +2 -2
  67. package/lib/common/messaging/handler.d.ts.map +1 -1
  68. package/lib/common/messaging/proxy-factory.d.ts +13 -7
  69. package/lib/common/messaging/proxy-factory.d.ts.map +1 -1
  70. package/lib/common/messaging/proxy-factory.js +18 -13
  71. package/lib/common/messaging/proxy-factory.js.map +1 -1
  72. package/lib/common/messaging/proxy-factory.spec.js +4 -15
  73. package/lib/common/messaging/proxy-factory.spec.js.map +1 -1
  74. package/lib/common/messaging/web-socket-channel.d.ts +54 -48
  75. package/lib/common/messaging/web-socket-channel.d.ts.map +1 -1
  76. package/lib/common/messaging/web-socket-channel.js +41 -105
  77. package/lib/common/messaging/web-socket-channel.js.map +1 -1
  78. package/lib/electron-browser/messaging/electron-ipc-connection-provider.d.ts +2 -2
  79. package/lib/electron-browser/messaging/electron-ipc-connection-provider.d.ts.map +1 -1
  80. package/lib/electron-browser/messaging/electron-ipc-connection-provider.js +18 -7
  81. package/lib/electron-browser/messaging/electron-ipc-connection-provider.js.map +1 -1
  82. package/lib/electron-browser/messaging/electron-ws-connection-provider.d.ts +2 -2
  83. package/lib/electron-browser/messaging/electron-ws-connection-provider.d.ts.map +1 -1
  84. package/lib/electron-browser/messaging/electron-ws-connection-provider.js +5 -7
  85. package/lib/electron-browser/messaging/electron-ws-connection-provider.js.map +1 -1
  86. package/lib/electron-main/messaging/electron-messaging-contribution.d.ts +37 -9
  87. package/lib/electron-main/messaging/electron-messaging-contribution.d.ts.map +1 -1
  88. package/lib/electron-main/messaging/electron-messaging-contribution.js +83 -68
  89. package/lib/electron-main/messaging/electron-messaging-contribution.js.map +1 -1
  90. package/lib/electron-main/messaging/electron-messaging-service.d.ts +2 -8
  91. package/lib/electron-main/messaging/electron-messaging-service.d.ts.map +1 -1
  92. package/lib/electron-main/messaging/electron-messaging-service.js.map +1 -1
  93. package/lib/electron-main/theia-electron-window.d.ts.map +1 -1
  94. package/lib/electron-main/theia-electron-window.js +11 -8
  95. package/lib/electron-main/theia-electron-window.js.map +1 -1
  96. package/lib/node/messaging/binary-message-pipe.d.ts +45 -0
  97. package/lib/node/messaging/binary-message-pipe.d.ts.map +1 -0
  98. package/lib/node/messaging/binary-message-pipe.js +152 -0
  99. package/lib/node/messaging/binary-message-pipe.js.map +1 -0
  100. package/lib/node/messaging/ipc-bootstrap.js +2 -11
  101. package/lib/node/messaging/ipc-bootstrap.js.map +1 -1
  102. package/lib/node/messaging/ipc-channel.d.ts +26 -0
  103. package/lib/node/messaging/ipc-channel.d.ts.map +1 -0
  104. package/lib/node/messaging/ipc-channel.js +86 -0
  105. package/lib/node/messaging/ipc-channel.js.map +1 -0
  106. package/lib/node/messaging/ipc-connection-provider.d.ts +3 -5
  107. package/lib/node/messaging/ipc-connection-provider.d.ts.map +1 -1
  108. package/lib/node/messaging/ipc-connection-provider.js +14 -31
  109. package/lib/node/messaging/ipc-connection-provider.js.map +1 -1
  110. package/lib/node/messaging/ipc-protocol.d.ts +2 -2
  111. package/lib/node/messaging/ipc-protocol.d.ts.map +1 -1
  112. package/lib/node/messaging/messaging-contribution.d.ts +6 -9
  113. package/lib/node/messaging/messaging-contribution.d.ts.map +1 -1
  114. package/lib/node/messaging/messaging-contribution.js +23 -68
  115. package/lib/node/messaging/messaging-contribution.js.map +1 -1
  116. package/lib/node/messaging/messaging-service.d.ts +4 -23
  117. package/lib/node/messaging/messaging-service.d.ts.map +1 -1
  118. package/lib/node/messaging/messaging-service.js +1 -15
  119. package/lib/node/messaging/messaging-service.js.map +1 -1
  120. package/lib/node/messaging/test/test-web-socket-channel.d.ts +4 -2
  121. package/lib/node/messaging/test/test-web-socket-channel.d.ts.map +1 -1
  122. package/lib/node/messaging/test/test-web-socket-channel.js +25 -12
  123. package/lib/node/messaging/test/test-web-socket-channel.js.map +1 -1
  124. package/package.json +5 -8
  125. package/src/browser/messaging/ws-connection-provider.ts +34 -25
  126. package/src/browser/progress-status-bar-item.ts +1 -1
  127. package/src/browser/style/menus.css +1 -0
  128. package/src/browser/tree/tree-compression/compressed-tree-widget.tsx +2 -2
  129. package/src/browser/widgets/select-component.tsx +37 -17
  130. package/src/common/cancellation.ts +8 -0
  131. package/src/common/index.ts +2 -0
  132. package/src/common/message-rpc/channel.spec.ts +88 -0
  133. package/src/common/message-rpc/channel.ts +260 -0
  134. package/src/{node/messaging/logger.ts → common/message-rpc/index.ts} +4 -23
  135. package/src/common/message-rpc/message-buffer.ts +99 -0
  136. package/src/common/message-rpc/rpc-message-encoder.spec.ts +42 -0
  137. package/src/common/message-rpc/rpc-message-encoder.ts +497 -0
  138. package/src/common/message-rpc/rpc-protocol.ts +217 -0
  139. package/src/common/message-rpc/uint8-array-message-buffer.spec.ts +41 -0
  140. package/src/common/message-rpc/uint8-array-message-buffer.ts +206 -0
  141. package/src/common/messaging/abstract-connection-provider.ts +28 -37
  142. package/src/common/messaging/connection-error-handler.ts +1 -2
  143. package/src/common/messaging/handler.ts +2 -2
  144. package/src/common/messaging/proxy-factory.spec.ts +4 -17
  145. package/src/common/messaging/proxy-factory.ts +27 -16
  146. package/src/common/messaging/web-socket-channel.ts +79 -135
  147. package/src/electron-browser/messaging/electron-ipc-connection-provider.ts +21 -7
  148. package/src/electron-browser/messaging/electron-ws-connection-provider.ts +5 -8
  149. package/src/electron-main/messaging/electron-messaging-contribution.ts +87 -65
  150. package/src/electron-main/messaging/electron-messaging-service.ts +2 -8
  151. package/src/electron-main/theia-electron-window.ts +12 -9
  152. package/src/node/messaging/binary-message-pipe.ts +168 -0
  153. package/src/node/messaging/ipc-bootstrap.ts +3 -11
  154. package/src/node/messaging/ipc-channel.ts +97 -0
  155. package/src/node/messaging/ipc-connection-provider.ts +18 -35
  156. package/src/node/messaging/ipc-protocol.ts +2 -2
  157. package/src/node/messaging/messaging-contribution.ts +29 -74
  158. package/src/node/messaging/messaging-service.ts +4 -31
  159. package/src/node/messaging/test/test-web-socket-channel.ts +26 -17
  160. package/lib/node/messaging/logger.d.ts +0 -8
  161. package/lib/node/messaging/logger.d.ts.map +0 -1
  162. package/lib/node/messaging/logger.js.map +0 -1
  163. package/shared/vscode-ws-jsonrpc/index.d.ts +0 -1
  164. package/shared/vscode-ws-jsonrpc/index.js +0 -1
@@ -16,10 +16,12 @@
16
16
 
17
17
  /* eslint-disable @typescript-eslint/no-explicit-any */
18
18
 
19
- import { MessageConnection, ResponseError } from 'vscode-ws-jsonrpc';
19
+ import { ResponseError } from '../message-rpc/rpc-message-encoder';
20
20
  import { ApplicationError } from '../application-error';
21
- import { Event, Emitter } from '../event';
22
21
  import { Disposable } from '../disposable';
22
+ import { Emitter, Event } from '../event';
23
+ import { Channel } from '../message-rpc/channel';
24
+ import { RequestHandler, RpcProtocol } from '../message-rpc/rpc-protocol';
23
25
  import { ConnectionHandler } from './handler';
24
26
 
25
27
  export type JsonRpcServer<Client> = Disposable & {
@@ -45,13 +47,19 @@ export class JsonRpcConnectionHandler<T extends object> implements ConnectionHan
45
47
  readonly factoryConstructor: new () => JsonRpcProxyFactory<T> = JsonRpcProxyFactory
46
48
  ) { }
47
49
 
48
- onConnection(connection: MessageConnection): void {
50
+ onConnection(connection: Channel): void {
49
51
  const factory = new this.factoryConstructor();
50
52
  const proxy = factory.createProxy();
51
53
  factory.target = this.targetFactory(proxy);
52
54
  factory.listen(connection);
53
55
  }
54
56
  }
57
+ /**
58
+ * Factory for creating a new {@link RpcConnection} for a given chanel and {@link RequestHandler}.
59
+ */
60
+ export type RpcConnectionFactory = (channel: Channel, requestHandler: RequestHandler) => RpcProtocol;
61
+
62
+ const defaultRPCConnectionFactory: RpcConnectionFactory = (channel, requestHandler) => new RpcProtocol(channel, requestHandler);
55
63
 
56
64
  /**
57
65
  * Factory for JSON-RPC proxy objects.
@@ -95,13 +103,14 @@ export class JsonRpcConnectionHandler<T extends object> implements ConnectionHan
95
103
  *
96
104
  * @param <T> - The type of the object to expose to JSON-RPC.
97
105
  */
106
+
98
107
  export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
99
108
 
100
109
  protected readonly onDidOpenConnectionEmitter = new Emitter<void>();
101
110
  protected readonly onDidCloseConnectionEmitter = new Emitter<void>();
102
111
 
103
- protected connectionPromiseResolve: (connection: MessageConnection) => void;
104
- protected connectionPromise: Promise<MessageConnection>;
112
+ protected connectionPromiseResolve: (connection: RpcProtocol) => void;
113
+ protected connectionPromise: Promise<RpcProtocol>;
105
114
 
106
115
  /**
107
116
  * Build a new JsonRpcProxyFactory.
@@ -109,7 +118,7 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
109
118
  * @param target - The object to expose to JSON-RPC methods calls. If this
110
119
  * is omitted, the proxy won't be able to handle requests, only send them.
111
120
  */
112
- constructor(public target?: any) {
121
+ constructor(public target?: any, protected rpcConnectionFactory = defaultRPCConnectionFactory) {
113
122
  this.waitForConnection();
114
123
  }
115
124
 
@@ -118,9 +127,11 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
118
127
  this.connectionPromiseResolve = resolve
119
128
  );
120
129
  this.connectionPromise.then(connection => {
121
- connection.onClose(() =>
122
- this.onDidCloseConnectionEmitter.fire(undefined)
123
- );
130
+ connection.channel.onClose(() => {
131
+ this.onDidCloseConnectionEmitter.fire(undefined);
132
+ // Wait for connection in case the backend reconnects
133
+ this.waitForConnection();
134
+ });
124
135
  this.onDidOpenConnectionEmitter.fire(undefined);
125
136
  });
126
137
  }
@@ -131,11 +142,10 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
131
142
  * This connection will be used to send/receive JSON-RPC requests and
132
143
  * response.
133
144
  */
134
- listen(connection: MessageConnection): void {
135
- connection.onRequest((prop, ...args) => this.onRequest(prop, ...args));
136
- connection.onNotification((prop, ...args) => this.onNotification(prop, ...args));
137
- connection.onDispose(() => this.waitForConnection());
138
- connection.listen();
145
+ listen(channel: Channel): void {
146
+ const connection = this.rpcConnectionFactory(channel, (meth, args) => this.onRequest(meth, ...args));
147
+ connection.onNotification(event => this.onNotification(event.method, ...event.args));
148
+
139
149
  this.connectionPromiseResolve(connection);
140
150
  }
141
151
 
@@ -239,10 +249,10 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
239
249
  new Promise<void>((resolve, reject) => {
240
250
  try {
241
251
  if (isNotify) {
242
- connection.sendNotification(method, ...args);
252
+ connection.sendNotification(method, args);
243
253
  resolve(undefined);
244
254
  } else {
245
- const resultPromise = connection.sendRequest(method, ...args) as Promise<any>;
255
+ const resultPromise = connection.sendRequest(method, args) as Promise<any>;
246
256
  resultPromise
247
257
  .catch((err: any) => reject(this.deserializeError(capturedError, err)))
248
258
  .then((result: any) => resolve(result));
@@ -293,3 +303,4 @@ export class JsonRpcProxyFactory<T extends object> implements ProxyHandler<T> {
293
303
  }
294
304
 
295
305
  }
306
+
@@ -16,157 +16,101 @@
16
16
 
17
17
  /* eslint-disable @typescript-eslint/no-explicit-any */
18
18
 
19
- import { IWebSocket } from 'vscode-ws-jsonrpc/lib/socket/socket';
20
- import { Disposable, DisposableCollection } from '../disposable';
21
- import { Emitter } from '../event';
22
-
23
- export class WebSocketChannel implements IWebSocket {
24
-
19
+ import { Emitter, Event } from '../event';
20
+ import { WriteBuffer } from '../message-rpc';
21
+ import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '../message-rpc/uint8-array-message-buffer';
22
+ import { Channel, MessageProvider, ChannelCloseEvent } from '../message-rpc/channel';
23
+ import { DisposableCollection } from '../disposable';
24
+
25
+ /**
26
+ * A channel that manages the main websocket connection between frontend and backend. All service channels
27
+ * are reusing this main channel. (multiplexing). An {@link IWebSocket} abstraction is used to keep the implementation
28
+ * independent of the actual websocket implementation and its execution context (backend vs. frontend).
29
+ */
30
+ export class WebSocketChannel implements Channel {
25
31
  static wsPath = '/services';
26
32
 
27
- protected readonly closeEmitter = new Emitter<[number, string]>();
28
- protected readonly toDispose = new DisposableCollection(this.closeEmitter);
29
-
30
- constructor(
31
- readonly id: number,
32
- protected readonly doSend: (content: string) => void
33
- ) { }
34
-
35
- dispose(): void {
36
- this.toDispose.dispose();
37
- }
38
-
39
- protected checkNotDisposed(): void {
40
- if (this.toDispose.disposed) {
41
- throw new Error('The channel has been disposed.');
42
- }
33
+ protected readonly onCloseEmitter: Emitter<ChannelCloseEvent> = new Emitter();
34
+ get onClose(): Event<ChannelCloseEvent> {
35
+ return this.onCloseEmitter.event;
43
36
  }
44
37
 
45
- handleMessage(message: WebSocketChannel.Message): void {
46
- if (message.kind === 'ready') {
47
- this.fireOpen();
48
- } else if (message.kind === 'data') {
49
- this.fireMessage(message.content);
50
- } else if (message.kind === 'close') {
51
- this.fireClose(message.code, message.reason);
52
- }
38
+ protected readonly onMessageEmitter: Emitter<MessageProvider> = new Emitter();
39
+ get onMessage(): Event<MessageProvider> {
40
+ return this.onMessageEmitter.event;
53
41
  }
54
42
 
55
- open(path: string): void {
56
- this.checkNotDisposed();
57
- this.doSend(JSON.stringify(<WebSocketChannel.OpenMessage>{
58
- kind: 'open',
59
- id: this.id,
60
- path
61
- }));
43
+ protected readonly onErrorEmitter: Emitter<unknown> = new Emitter();
44
+ get onError(): Event<unknown> {
45
+ return this.onErrorEmitter.event;
62
46
  }
63
47
 
64
- ready(): void {
65
- this.checkNotDisposed();
66
- this.doSend(JSON.stringify(<WebSocketChannel.ReadyMessage>{
67
- kind: 'ready',
68
- id: this.id
69
- }));
70
- }
48
+ protected toDispose = new DisposableCollection();
71
49
 
72
- send(content: string): void {
73
- this.checkNotDisposed();
74
- this.doSend(JSON.stringify(<WebSocketChannel.DataMessage>{
75
- kind: 'data',
76
- id: this.id,
77
- content
50
+ constructor(protected readonly socket: IWebSocket) {
51
+ this.toDispose.pushAll([this.onCloseEmitter, this.onMessageEmitter, this.onErrorEmitter]);
52
+ socket.onClose((reason, code) => this.onCloseEmitter.fire({ reason, code }));
53
+ socket.onClose(() => this.close());
54
+ socket.onError(error => this.onErrorEmitter.fire(error));
55
+ // eslint-disable-next-line arrow-body-style
56
+ socket.onMessage(data => this.onMessageEmitter.fire(() => {
57
+ // In the browser context socketIO receives binary messages as ArrayBuffers.
58
+ // So we have to convert them to a Uint8Array before delegating the message to the read buffer.
59
+ const buffer = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
60
+ return new Uint8ArrayReadBuffer(buffer);
78
61
  }));
79
62
  }
80
63
 
81
- close(code: number = 1000, reason: string = ''): void {
82
- if (this.closing) {
83
- // Do not try to close the channel if it is already closing.
84
- return;
85
- }
86
- this.checkNotDisposed();
87
- this.doSend(JSON.stringify(<WebSocketChannel.CloseMessage>{
88
- kind: 'close',
89
- id: this.id,
90
- code,
91
- reason
92
- }));
93
- this.fireClose(code, reason);
94
- }
64
+ getWriteBuffer(): WriteBuffer {
65
+ const result = new Uint8ArrayWriteBuffer();
95
66
 
96
- tryClose(code: number = 1000, reason: string = ''): void {
97
- if (this.closing || this.toDispose.disposed) {
98
- // Do not try to close the channel if it is already closing or disposed.
99
- return;
100
- }
101
- this.doSend(JSON.stringify(<WebSocketChannel.CloseMessage>{
102
- kind: 'close',
103
- id: this.id,
104
- code,
105
- reason
106
- }));
107
- this.fireClose(code, reason);
108
- }
67
+ result.onCommit(buffer => {
68
+ if (this.socket.isConnected()) {
69
+ this.socket.send(buffer);
70
+ }
71
+ });
109
72
 
110
- protected fireOpen: () => void = () => { };
111
- onOpen(cb: () => void): void {
112
- this.checkNotDisposed();
113
- this.fireOpen = cb;
114
- this.toDispose.push(Disposable.create(() => this.fireOpen = () => { }));
73
+ return result;
115
74
  }
116
75
 
117
- protected fireMessage: (data: any) => void = () => { };
118
- onMessage(cb: (data: any) => void): void {
119
- this.checkNotDisposed();
120
- this.fireMessage = cb;
121
- this.toDispose.push(Disposable.create(() => this.fireMessage = () => { }));
122
- }
123
-
124
- fireError: (reason: any) => void = () => { };
125
- onError(cb: (reason: any) => void): void {
126
- this.checkNotDisposed();
127
- this.fireError = cb;
128
- this.toDispose.push(Disposable.create(() => this.fireError = () => { }));
129
- }
130
-
131
- protected closing = false;
132
- protected fireClose(code: number, reason: string): void {
133
- if (this.closing) {
134
- return;
135
- }
136
- this.closing = true;
137
- try {
138
- this.closeEmitter.fire([code, reason]);
139
- } finally {
140
- this.closing = false;
141
- }
142
- this.dispose();
143
- }
144
- onClose(cb: (code: number, reason: string) => void): Disposable {
145
- this.checkNotDisposed();
146
- return this.closeEmitter.event(([code, reason]) => cb(code, reason));
76
+ close(): void {
77
+ this.toDispose.dispose();
78
+ this.socket.close();
147
79
  }
148
-
149
80
  }
150
- export namespace WebSocketChannel {
151
- export interface OpenMessage {
152
- kind: 'open'
153
- id: number
154
- path: string
155
- }
156
- export interface ReadyMessage {
157
- kind: 'ready'
158
- id: number
159
- }
160
- export interface DataMessage {
161
- kind: 'data'
162
- id: number
163
- content: string
164
- }
165
- export interface CloseMessage {
166
- kind: 'close'
167
- id: number
168
- code: number
169
- reason: string
170
- }
171
- export type Message = OpenMessage | ReadyMessage | DataMessage | CloseMessage;
81
+
82
+ /**
83
+ * An abstraction that enables reuse of the `{@link WebSocketChannel} class in the frontend and backend
84
+ * independent of the actual underlying socket implementation.
85
+ */
86
+ export interface IWebSocket {
87
+ /**
88
+ * Sends the given message over the web socket in binary format.
89
+ * @param message The binary message.
90
+ */
91
+ send(message: Uint8Array): void;
92
+ /**
93
+ * Closes the websocket from the local side.
94
+ */
95
+ close(): void;
96
+ /**
97
+ * The connection state of the web socket.
98
+ */
99
+ isConnected(): boolean;
100
+ /**
101
+ * Listener callback to handle incoming messages.
102
+ * @param cb The callback.
103
+ */
104
+ onMessage(cb: (message: Uint8Array) => void): void;
105
+ /**
106
+ * Listener callback to handle socket errors.
107
+ * @param cb The callback.
108
+ */
109
+ onError(cb: (reason: any) => void): void;
110
+ /**
111
+ * Listener callback to handle close events (Remote side).
112
+ * @param cb The callback.
113
+ */
114
+ onClose(cb: (reason: string, code?: number) => void): void;
172
115
  }
116
+
@@ -17,9 +17,11 @@
17
17
  import { Event as ElectronEvent, ipcRenderer } from '@theia/electron/shared/electron';
18
18
  import { injectable, interfaces } from 'inversify';
19
19
  import { JsonRpcProxy } from '../../common/messaging';
20
- import { WebSocketChannel } from '../../common/messaging/web-socket-channel';
21
20
  import { AbstractConnectionProvider } from '../../common/messaging/abstract-connection-provider';
22
21
  import { THEIA_ELECTRON_IPC_CHANNEL_NAME } from '../../electron-common/messaging/electron-connection-handler';
22
+ import { Emitter, Event } from '../../common';
23
+ import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '../../common/message-rpc/uint8-array-message-buffer';
24
+ import { Channel, MessageProvider } from '../../common/message-rpc/channel';
23
25
 
24
26
  export interface ElectronIpcOptions {
25
27
  }
@@ -36,15 +38,27 @@ export class ElectronIpcConnectionProvider extends AbstractConnectionProvider<El
36
38
 
37
39
  constructor() {
38
40
  super();
39
- ipcRenderer.on(THEIA_ELECTRON_IPC_CHANNEL_NAME, (event: ElectronEvent, data: string) => {
40
- this.handleIncomingRawMessage(data);
41
- });
41
+ this.initializeMultiplexer();
42
42
  }
43
43
 
44
- protected createChannel(id: number): WebSocketChannel {
45
- return new WebSocketChannel(id, content => {
46
- ipcRenderer.send(THEIA_ELECTRON_IPC_CHANNEL_NAME, content);
44
+ protected createMainChannel(): Channel {
45
+ const onMessageEmitter = new Emitter<MessageProvider>();
46
+ ipcRenderer.on(THEIA_ELECTRON_IPC_CHANNEL_NAME, (_event: ElectronEvent, data: Uint8Array) => {
47
+ onMessageEmitter.fire(() => new Uint8ArrayReadBuffer(data));
47
48
  });
49
+ return {
50
+ close: () => Event.None,
51
+ getWriteBuffer: () => {
52
+ const writer = new Uint8ArrayWriteBuffer();
53
+ writer.onCommit(buffer =>
54
+ ipcRenderer.send(THEIA_ELECTRON_IPC_CHANNEL_NAME, buffer)
55
+ );
56
+ return writer;
57
+ },
58
+ onClose: Event.None,
59
+ onError: Event.None,
60
+ onMessage: onMessageEmitter.event
61
+ };
48
62
  }
49
63
 
50
64
  }
@@ -15,9 +15,9 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { injectable } from 'inversify';
18
- import { WebSocketChannel } from '../../common/messaging/web-socket-channel';
19
18
  import { WebSocketConnectionProvider, WebSocketOptions } from '../../browser/messaging/ws-connection-provider';
20
19
  import { FrontendApplicationContribution } from '../../browser/frontend-application';
20
+ import { Channel } from '../../common';
21
21
 
22
22
  /**
23
23
  * Customized connection provider between the frontend and the backend in electron environment.
@@ -34,16 +34,13 @@ export class ElectronWebSocketConnectionProvider extends WebSocketConnectionProv
34
34
 
35
35
  onStop(): void {
36
36
  this.stopping = true;
37
- // Close the websocket connection `onStop`. Otherwise, the channels will be closed with 30 sec (`MessagingContribution#checkAliveTimeout`) delay.
37
+ // Manually close the websocket connections `onStop`. Otherwise, the channels will be closed with 30 sec (`MessagingContribution#checkAliveTimeout`) delay.
38
38
  // https://github.com/eclipse-theia/theia/issues/6499
39
- for (const channel of [...this.channels.values()]) {
40
- // `1001` indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page.
41
- // But we cannot use `1001`: https://github.com/TypeFox/vscode-ws-jsonrpc/issues/15
42
- channel.close(1000, 'The frontend is "going away"...');
43
- }
39
+ // `1001` indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page.
40
+ this.channelMultiPlexer?.onUnderlyingChannelClose({ reason: 'The frontend is "going away"', code: 1001 });
44
41
  }
45
42
 
46
- override openChannel(path: string, handler: (channel: WebSocketChannel) => void, options?: WebSocketOptions): void {
43
+ override async openChannel(path: string, handler: (channel: Channel) => void, options?: WebSocketOptions): Promise<void> {
47
44
  if (!this.stopping) {
48
45
  super.openChannel(path, handler, options);
49
46
  }
@@ -16,24 +16,23 @@
16
16
 
17
17
  import { IpcMainEvent, ipcMain, WebContents } from '@theia/electron/shared/electron';
18
18
  import { inject, injectable, named, postConstruct } from 'inversify';
19
- import { MessageConnection } from 'vscode-ws-jsonrpc';
20
- import { createWebSocketConnection } from 'vscode-ws-jsonrpc/lib/socket/connection';
21
19
  import { ContributionProvider } from '../../common/contribution-provider';
22
- import { WebSocketChannel } from '../../common/messaging/web-socket-channel';
23
20
  import { MessagingContribution } from '../../node/messaging/messaging-contribution';
24
- import { ConsoleLogger } from '../../node/messaging/logger';
25
21
  import { ElectronConnectionHandler, THEIA_ELECTRON_IPC_CHANNEL_NAME } from '../../electron-common/messaging/electron-connection-handler';
26
22
  import { ElectronMainApplicationContribution } from '../electron-main-application';
27
23
  import { ElectronMessagingService } from './electron-messaging-service';
24
+ import { Channel, ChannelCloseEvent, ChannelMultiplexer, MessageProvider } from '../../common/message-rpc/channel';
25
+ import { Emitter, Event, WriteBuffer } from '../../common';
26
+ import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '../../common/message-rpc/uint8-array-message-buffer';
28
27
 
29
28
  /**
30
29
  * This component replicates the role filled by `MessagingContribution` but for Electron.
31
30
  * Unlike the WebSocket based implementation, we do not expect to receive
32
31
  * connection events. Instead, we'll create channels based on incoming `open`
33
32
  * events on the `ipcMain` channel.
34
- *
35
33
  * This component allows communication between renderer process (frontend) and electron main process.
36
34
  */
35
+
37
36
  @injectable()
38
37
  export class ElectronMessagingContribution implements ElectronMainApplicationContribution, ElectronMessagingService {
39
38
 
@@ -43,89 +42,112 @@ export class ElectronMessagingContribution implements ElectronMainApplicationCon
43
42
  @inject(ContributionProvider) @named(ElectronConnectionHandler)
44
43
  protected readonly connectionHandlers: ContributionProvider<ElectronConnectionHandler>;
45
44
 
46
- protected readonly channelHandlers = new MessagingContribution.ConnectionHandlers<WebSocketChannel>();
47
- protected readonly windowChannels = new Map<number, Map<number, WebSocketChannel>>();
45
+ protected readonly channelHandlers = new MessagingContribution.ConnectionHandlers<Channel>();
46
+ /**
47
+ * Each electron window has a main chanel and its own multiplexer to route multiple client messages the same IPC connection.
48
+ */
49
+ protected readonly windowChannelMultiplexer = new Map<number, { channel: ElectronWebContentChannel, multiPlexer: ChannelMultiplexer }>();
48
50
 
49
51
  @postConstruct()
50
52
  protected init(): void {
51
- ipcMain.on(THEIA_ELECTRON_IPC_CHANNEL_NAME, (event: IpcMainEvent, data: string) => {
52
- this.handleIpcMessage(event, data);
53
+ ipcMain.on(THEIA_ELECTRON_IPC_CHANNEL_NAME, (event: IpcMainEvent, data: Uint8Array) => {
54
+ this.handleIpcEvent(event, data);
53
55
  });
54
56
  }
55
57
 
58
+ protected handleIpcEvent(event: IpcMainEvent, data: Uint8Array): void {
59
+ const sender = event.sender;
60
+ // Get the multiplexer for a given window id
61
+ try {
62
+ const windowChannelData = this.windowChannelMultiplexer.get(sender.id) ?? this.createWindowChannelData(sender);
63
+ windowChannelData!.channel.onMessageEmitter.fire(() => new Uint8ArrayReadBuffer(data));
64
+ } catch (error) {
65
+ console.error('IPC: Failed to handle message', { error, data });
66
+ }
67
+ }
68
+
69
+ // Creates a new multiplexer for a given sender/window
70
+ protected createWindowChannelData(sender: Electron.WebContents): { channel: ElectronWebContentChannel, multiPlexer: ChannelMultiplexer } {
71
+ const mainChannel = this.createWindowMainChannel(sender);
72
+ const multiPlexer = new ChannelMultiplexer(mainChannel);
73
+ multiPlexer.onDidOpenChannel(openEvent => {
74
+ const { channel, id } = openEvent;
75
+ if (this.channelHandlers.route(id, channel)) {
76
+ console.debug(`Opening channel for service path '${id}'.`);
77
+ channel.onClose(() => console.debug(`Closing channel on service path '${id}'.`));
78
+ }
79
+ });
80
+
81
+ sender.once('did-navigate', () => multiPlexer.onUnderlyingChannelClose({ reason: 'Window was refreshed' })); // When refreshing the browser window.
82
+ sender.once('destroyed', () => multiPlexer.onUnderlyingChannelClose({ reason: 'Window was closed' })); // When closing the browser window.
83
+ const data = { channel: mainChannel, multiPlexer };
84
+ this.windowChannelMultiplexer.set(sender.id, data);
85
+ return data;
86
+ }
87
+
88
+ /**
89
+ * Creates the main channel to a window.
90
+ * @param sender The window that the channel should be established to.
91
+ */
92
+ protected createWindowMainChannel(sender: WebContents): ElectronWebContentChannel {
93
+ return new ElectronWebContentChannel(sender);
94
+ }
95
+
56
96
  onStart(): void {
57
97
  for (const contribution of this.messagingContributions.getContributions()) {
58
98
  contribution.configure(this);
59
99
  }
60
100
  for (const connectionHandler of this.connectionHandlers.getContributions()) {
61
101
  this.channelHandlers.push(connectionHandler.path, (params, channel) => {
62
- const connection = createWebSocketConnection(channel, new ConsoleLogger());
63
- connectionHandler.onConnection(connection);
102
+ connectionHandler.onConnection(channel);
64
103
  });
65
104
  }
66
105
  }
67
106
 
68
- listen(spec: string, callback: (params: ElectronMessagingService.PathParams, connection: MessageConnection) => void): void {
69
- this.ipcChannel(spec, (params, channel) => {
70
- const connection = createWebSocketConnection(channel, new ConsoleLogger());
71
- callback(params, connection);
72
- });
73
- }
74
-
75
107
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
- ipcChannel(spec: string, callback: (params: any, channel: WebSocketChannel) => void): void {
108
+ ipcChannel(spec: string, callback: (params: any, channel: Channel) => void): void {
77
109
  this.channelHandlers.push(spec, callback);
78
110
  }
111
+ }
112
+
113
+ /**
114
+ * Used to establish a connection between the ipcMain and the Electron frontend (window).
115
+ * Messages a transferred via electron IPC.
116
+ */
117
+ export class ElectronWebContentChannel implements Channel {
118
+ protected readonly onCloseEmitter: Emitter<ChannelCloseEvent> = new Emitter();
119
+ get onClose(): Event<ChannelCloseEvent> {
120
+ return this.onCloseEmitter.event;
121
+ }
79
122
 
80
- protected handleIpcMessage(event: IpcMainEvent, data: string): void {
81
- const sender = event.sender;
82
- try {
83
- // Get the channel map for a given window id
84
- let channels = this.windowChannels.get(sender.id)!;
85
- if (!channels) {
86
- this.windowChannels.set(sender.id, channels = new Map<number, WebSocketChannel>());
87
- }
88
- // Start parsing the message to extract the channel id and route
89
- const message: WebSocketChannel.Message = JSON.parse(data.toString());
90
- // Someone wants to open a logical channel
91
- if (message.kind === 'open') {
92
- const { id, path } = message;
93
- const channel = this.createChannel(id, sender);
94
- if (this.channelHandlers.route(path, channel)) {
95
- channel.ready();
96
- channels.set(id, channel);
97
- channel.onClose(() => channels.delete(id));
98
- } else {
99
- console.error('Cannot find a service for the path: ' + path);
100
- }
101
- } else {
102
- const { id } = message;
103
- const channel = channels.get(id);
104
- if (channel) {
105
- channel.handleMessage(message);
106
- } else {
107
- console.error('The ipc channel does not exist', id);
108
- }
109
- }
110
- const close = () => {
111
- for (const channel of Array.from(channels.values())) {
112
- channel.close(undefined, 'webContent destroyed');
113
- }
114
- channels.clear();
115
- };
116
- sender.once('did-navigate', close); // When refreshing the browser window.
117
- sender.once('destroyed', close); // When closing the browser window.
118
- } catch (error) {
119
- console.error('IPC: Failed to handle message', { error, data });
120
- }
123
+ // Make the message emitter public so that we can easily forward messages received from the ipcMain.
124
+ readonly onMessageEmitter: Emitter<MessageProvider> = new Emitter();
125
+ get onMessage(): Event<MessageProvider> {
126
+ return this.onMessageEmitter.event;
127
+ }
128
+
129
+ protected readonly onErrorEmitter: Emitter<unknown> = new Emitter();
130
+ get onError(): Event<unknown> {
131
+ return this.onErrorEmitter.event;
132
+ }
133
+
134
+ constructor(protected readonly sender: Electron.WebContents) {
121
135
  }
122
136
 
123
- protected createChannel(id: number, sender: WebContents): WebSocketChannel {
124
- return new WebSocketChannel(id, content => {
125
- if (!sender.isDestroyed()) {
126
- sender.send(THEIA_ELECTRON_IPC_CHANNEL_NAME, content);
137
+ getWriteBuffer(): WriteBuffer {
138
+ const writer = new Uint8ArrayWriteBuffer();
139
+
140
+ writer.onCommit(buffer => {
141
+ if (!this.sender.isDestroyed()) {
142
+ this.sender.send(THEIA_ELECTRON_IPC_CHANNEL_NAME, buffer);
127
143
  }
128
144
  });
129
- }
130
145
 
146
+ return writer;
147
+ }
148
+ close(): void {
149
+ this.onCloseEmitter.dispose();
150
+ this.onMessageEmitter.dispose();
151
+ this.onErrorEmitter.dispose();
152
+ }
131
153
  }
@@ -14,20 +14,14 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import type { MessageConnection } from 'vscode-jsonrpc';
18
- import type { WebSocketChannel } from '../../common/messaging/web-socket-channel';
17
+ import { Channel } from '../../common/message-rpc/channel';
19
18
 
20
19
  export interface ElectronMessagingService {
21
- /**
22
- * Accept a JSON-RPC connection on the given path.
23
- * A path supports the route syntax: https://github.com/rcs/route-parser#what-can-i-use-in-my-routes.
24
- */
25
- listen(path: string, callback: (params: ElectronMessagingService.PathParams, connection: MessageConnection) => void): void;
26
20
  /**
27
21
  * Accept an ipc channel on the given path.
28
22
  * A path supports the route syntax: https://github.com/rcs/route-parser#what-can-i-use-in-my-routes.
29
23
  */
30
- ipcChannel(path: string, callback: (params: ElectronMessagingService.PathParams, socket: WebSocketChannel) => void): void;
24
+ ipcChannel(path: string, callback: (params: ElectronMessagingService.PathParams, socket: Channel) => void): void;
31
25
  }
32
26
  export namespace ElectronMessagingService {
33
27
  export interface PathParams {