@hydranium/client-theia 1.0.0-next.185 → 1.0.0-next.188
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/lib/browser/session-aware-connection-source.d.ts +5 -2
- package/lib/browser/session-aware-connection-source.d.ts.map +1 -1
- package/lib/browser/session-aware-connection-source.js +21 -4
- package/lib/browser/session-aware-connection-source.js.map +1 -1
- package/lib/common/connection-resilience-options.d.ts +6 -0
- package/lib/common/connection-resilience-options.d.ts.map +1 -1
- package/lib/common/framed-socket-write-buffer.d.ts +55 -3
- package/lib/common/framed-socket-write-buffer.d.ts.map +1 -1
- package/lib/common/framed-socket-write-buffer.js +111 -8
- package/lib/common/framed-socket-write-buffer.js.map +1 -1
- package/lib/common/inbound-message-sequence.d.ts +56 -0
- package/lib/common/inbound-message-sequence.d.ts.map +1 -0
- package/lib/common/inbound-message-sequence.js +98 -0
- package/lib/common/inbound-message-sequence.js.map +1 -0
- package/lib/common/index.d.ts +1 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +1 -0
- package/lib/common/index.js.map +1 -1
- package/lib/node/session-bound-frontend-connection-service.d.ts +41 -0
- package/lib/node/session-bound-frontend-connection-service.d.ts.map +1 -1
- package/lib/node/session-bound-frontend-connection-service.js +98 -2
- package/lib/node/session-bound-frontend-connection-service.js.map +1 -1
- package/package.json +3 -3
- package/src/browser/session-aware-connection-source.ts +21 -4
- package/src/common/connection-resilience-options.ts +6 -0
- package/src/common/framed-socket-write-buffer.ts +127 -9
- package/src/common/inbound-message-sequence.ts +102 -0
- package/src/common/index.ts +1 -0
- 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
|
|
258
|
+
rebind(SocketWriteBuffer).toDynamicValue(() => createFramedSocketWriteBuffer(options));
|
|
154
259
|
return true;
|
|
155
260
|
}
|