@hydranium/client-theia 1.0.0-next.183 → 1.0.0-next.187

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 (29) hide show
  1. package/lib/browser/session-aware-connection-source.d.ts +5 -2
  2. package/lib/browser/session-aware-connection-source.d.ts.map +1 -1
  3. package/lib/browser/session-aware-connection-source.js +21 -4
  4. package/lib/browser/session-aware-connection-source.js.map +1 -1
  5. package/lib/common/connection-resilience-options.d.ts +6 -0
  6. package/lib/common/connection-resilience-options.d.ts.map +1 -1
  7. package/lib/common/framed-socket-write-buffer.d.ts +55 -3
  8. package/lib/common/framed-socket-write-buffer.d.ts.map +1 -1
  9. package/lib/common/framed-socket-write-buffer.js +111 -8
  10. package/lib/common/framed-socket-write-buffer.js.map +1 -1
  11. package/lib/common/inbound-message-sequence.d.ts +56 -0
  12. package/lib/common/inbound-message-sequence.d.ts.map +1 -0
  13. package/lib/common/inbound-message-sequence.js +98 -0
  14. package/lib/common/inbound-message-sequence.js.map +1 -0
  15. package/lib/common/index.d.ts +1 -0
  16. package/lib/common/index.d.ts.map +1 -1
  17. package/lib/common/index.js +1 -0
  18. package/lib/common/index.js.map +1 -1
  19. package/lib/node/session-bound-frontend-connection-service.d.ts +41 -0
  20. package/lib/node/session-bound-frontend-connection-service.d.ts.map +1 -1
  21. package/lib/node/session-bound-frontend-connection-service.js +98 -2
  22. package/lib/node/session-bound-frontend-connection-service.js.map +1 -1
  23. package/package.json +3 -3
  24. package/src/browser/session-aware-connection-source.ts +21 -4
  25. package/src/common/connection-resilience-options.ts +6 -0
  26. package/src/common/framed-socket-write-buffer.ts +127 -9
  27. package/src/common/inbound-message-sequence.ts +102 -0
  28. package/src/common/index.ts +1 -0
  29. package/src/node/session-bound-frontend-connection-service.ts +106 -1
@@ -7,6 +7,10 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
+ import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
11
+ import { AbstractChannel } from '@theia/core/lib/common/message-rpc/channel';
12
+ import { type WriteBuffer } from '@theia/core/lib/common/message-rpc/message-buffer';
13
+ import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '@theia/core/lib/common/message-rpc/uint8-array-message-buffer';
10
14
  import { BackendApplicationConfigProvider } from '@theia/core/lib/node/backend-application-config-provider';
11
15
  import { SocketWriteBuffer } from '@theia/core/lib/common/messaging/socket-write-buffer';
12
16
  import { WebsocketFrontendConnectionService } from '@theia/core/lib/node/messaging/websocket-frontend-connection-service';
@@ -15,9 +19,11 @@ import { type ConnectionResilienceOptions } from '../common/connection-resilienc
15
19
  import {
16
20
  CONNECTION_LOG_PREFIX as PREFIX,
17
21
  createFramedSocketWriteBuffer,
22
+ FramedSocketWriteBuffer,
18
23
  supportsConnectionResilience,
19
24
  warnConnectionResilienceUnavailable
20
25
  } from '../common/framed-socket-write-buffer';
26
+ import { ACKNOWLEDGEMENT_EVENT, InboundMessageSequence } from '../common/inbound-message-sequence';
21
27
 
22
28
  /**
23
29
  * Socket Theia hands the disconnect handler, read off the base signature rather
@@ -38,6 +44,78 @@ export type FrontendSocket = Parameters<WebsocketFrontendConnectionService['hand
38
44
  */
39
45
  export type FrontendChannel = Parameters<WebsocketFrontendConnectionService['handleSocketDisconnect']>[1];
40
46
 
47
+ /**
48
+ * The server end of one frontend's session, carried across the sockets the
49
+ * frontend reconnects on.
50
+ *
51
+ * Theia's own channel sends straight into any socket that reports itself
52
+ * connected. A socket whose far end died unnoticed still does, so whatever is
53
+ * sent until the server notices is lost. This one numbers and keeps every
54
+ * message through {@link FramedSocketWriteBuffer}, resends what is still
55
+ * unacknowledged when the frontend reconnects, and drops what it has received
56
+ * twice. Otherwise it behaves as Theia's.
57
+ */
58
+ export class AcknowledgedSocketChannel extends AbstractChannel {
59
+ protected socket: FrontendSocket | undefined;
60
+ protected socketDisposables = new DisposableCollection();
61
+ protected readonly inbound = new InboundMessageSequence(sequence => this.socket?.emit(ACKNOWLEDGEMENT_EVENT, sequence));
62
+
63
+ constructor(protected readonly socketBuffer: FramedSocketWriteBuffer) {
64
+ super();
65
+ this.toDispose.push(this.inbound);
66
+ this.toDispose.push(Disposable.create(() => this.socketDisposables.dispose()));
67
+ }
68
+
69
+ /** Flushes in the same step that attaches the listeners, so nothing new can overtake the resend. */
70
+ connect(socket: FrontendSocket): void {
71
+ this.socketDisposables.dispose();
72
+ this.socketDisposables = new DisposableCollection();
73
+ this.socket = socket;
74
+ const errorHandler = (error: unknown): void => this.onErrorEmitter.fire(error);
75
+ const dataListener = (data: ArrayBuffer | Uint8Array, sequence?: unknown): void => {
76
+ if (typeof sequence === 'number') {
77
+ this.socketBuffer.markPeerDeduplicates();
78
+ }
79
+ if (!this.inbound.accept(sequence, data.byteLength)) {
80
+ return;
81
+ }
82
+ const buffer = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
83
+ this.onMessageEmitter.fire(() => new Uint8ArrayReadBuffer(buffer));
84
+ };
85
+ const acknowledgementListener = (sequence: unknown): void => {
86
+ if (typeof sequence === 'number') {
87
+ this.socketBuffer.acknowledge(sequence);
88
+ }
89
+ };
90
+ socket.on('error', errorHandler);
91
+ socket.on('message', dataListener);
92
+ socket.on(ACKNOWLEDGEMENT_EVENT, acknowledgementListener);
93
+ this.socketDisposables.push(
94
+ Disposable.create(() => {
95
+ socket.off('error', errorHandler);
96
+ socket.off('message', dataListener);
97
+ socket.off(ACKNOWLEDGEMENT_EVENT, acknowledgementListener);
98
+ })
99
+ );
100
+ this.socketBuffer.flush(socket);
101
+ }
102
+
103
+ disconnect(): void {
104
+ this.socketDisposables.dispose();
105
+ this.socket = undefined;
106
+ }
107
+
108
+ drainBuffer(): void {
109
+ this.socketBuffer.drain();
110
+ }
111
+
112
+ getWriteBuffer(): WriteBuffer {
113
+ const writeBuffer = new Uint8ArrayWriteBuffer();
114
+ writeBuffer.onCommit(data => this.socketBuffer.sendOrQueue(this.socket?.connected ? this.socket : undefined, data));
115
+ return writeBuffer;
116
+ }
117
+ }
118
+
41
119
  /**
42
120
  * Ignores a disconnect from a socket that no longer belongs to the frontend.
43
121
  *
@@ -106,6 +184,33 @@ export class SessionBoundFrontendConnectionService extends WebsocketFrontendConn
106
184
  });
107
185
  }
108
186
 
187
+ /**
188
+ * Theia's body, building an {@link AcknowledgedSocketChannel} in place of its own channel.
189
+ *
190
+ * Handed over through a cast, because Theia types the slot as its own channel class. The cast
191
+ * also hides from the compiler what Theia calls on the channel, so a release that calls a member
192
+ * this one lacks fails at runtime: re-diff with the disconnect handler.
193
+ */
194
+ protected override createConnection(socket: FrontendSocket, frontEndId: string): FrontendChannel {
195
+ const channel = new AcknowledgedSocketChannel(this.createWriteBuffer()) as unknown as FrontendChannel;
196
+ channel.connect(socket);
197
+ this.connectionsByFrontend.set(frontEndId, channel);
198
+ return channel;
199
+ }
200
+
201
+ /**
202
+ * One buffer per session, from the container Theia resolves its own channel's buffer from.
203
+ * Reached through a structural view because Theia 1.70 does not declare `container`.
204
+ */
205
+ protected createWriteBuffer(): FramedSocketWriteBuffer {
206
+ const container = (this as unknown as { readonly container: interfaces.Container }).container;
207
+ const buffer = container.get<SocketWriteBuffer>(SocketWriteBuffer);
208
+ if (!(buffer instanceof FramedSocketWriteBuffer)) {
209
+ throw new Error('SessionBoundFrontendConnectionService requires FramedSocketWriteBuffer to be bound');
210
+ }
211
+ return buffer;
212
+ }
213
+
109
214
  protected override closeConnection(frontEndId: string, reason: string): void {
110
215
  console.info(`${PREFIX} closing frontend ${frontEndId}: ${reason}`);
111
216
  this.bindings.delete(frontEndId);
@@ -150,6 +255,6 @@ export function bindConnectionResilience(
150
255
  }
151
256
  bind(SessionBoundFrontendConnectionService).toSelf().inSingletonScope();
152
257
  rebind(WebsocketFrontendConnectionService).toService(SessionBoundFrontendConnectionService);
153
- rebind(SocketWriteBuffer).toDynamicValue(() => createFramedSocketWriteBuffer(options.bufferBytes));
258
+ rebind(SocketWriteBuffer).toDynamicValue(() => createFramedSocketWriteBuffer(options));
154
259
  return true;
155
260
  }