@eclipse-glsp/server 2.8.0-next.7 → 2.8.0

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 (31) hide show
  1. package/lib/common/features/layout/computed-bounds-action-handler.d.ts +16 -1
  2. package/lib/common/features/layout/computed-bounds-action-handler.d.ts.map +1 -1
  3. package/lib/common/features/layout/computed-bounds-action-handler.js +41 -3
  4. package/lib/common/features/layout/computed-bounds-action-handler.js.map +1 -1
  5. package/lib/common/launch/glsp-server-launcher.d.ts +8 -1
  6. package/lib/common/launch/glsp-server-launcher.d.ts.map +1 -1
  7. package/lib/common/launch/glsp-server-launcher.js +11 -1
  8. package/lib/common/launch/glsp-server-launcher.js.map +1 -1
  9. package/lib/common/launch/jsonrpc-server-launcher.d.ts +51 -2
  10. package/lib/common/launch/jsonrpc-server-launcher.d.ts.map +1 -1
  11. package/lib/common/launch/jsonrpc-server-launcher.js +82 -3
  12. package/lib/common/launch/jsonrpc-server-launcher.js.map +1 -1
  13. package/lib/common/utils/layout-util.d.ts +5 -5
  14. package/lib/common/utils/layout-util.d.ts.map +1 -1
  15. package/lib/common/utils/layout-util.js +12 -11
  16. package/lib/common/utils/layout-util.js.map +1 -1
  17. package/lib/node/launch/socket-server-launcher.d.ts +39 -2
  18. package/lib/node/launch/socket-server-launcher.d.ts.map +1 -1
  19. package/lib/node/launch/socket-server-launcher.js +50 -31
  20. package/lib/node/launch/socket-server-launcher.js.map +1 -1
  21. package/lib/node/launch/websocket-server-launcher.d.ts +36 -5
  22. package/lib/node/launch/websocket-server-launcher.d.ts.map +1 -1
  23. package/lib/node/launch/websocket-server-launcher.js +53 -12
  24. package/lib/node/launch/websocket-server-launcher.js.map +1 -1
  25. package/package.json +3 -3
  26. package/src/common/features/layout/computed-bounds-action-handler.ts +52 -4
  27. package/src/common/launch/glsp-server-launcher.ts +12 -1
  28. package/src/common/launch/jsonrpc-server-launcher.ts +89 -2
  29. package/src/common/utils/layout-util.ts +13 -12
  30. package/src/node/launch/socket-server-launcher.ts +54 -31
  31. package/src/node/launch/websocket-server-launcher.ts +72 -14
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2022-2024 STMicroelectronics and others.
2
+ * Copyright (c) 2022-2026 STMicroelectronics and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
- import { Disposable } from '@eclipse-glsp/protocol';
16
+ import { Disposable, Emitter } from '@eclipse-glsp/protocol';
17
17
  import { inject, injectable } from 'inversify';
18
18
  import * as net from 'net';
19
19
  import * as jsonrpc from 'vscode-jsonrpc/node';
@@ -24,48 +24,71 @@ import { Logger } from '../../common/utils/logger';
24
24
  export class SocketServerLauncher extends JsonRpcGLSPServerLauncher<net.TcpSocketConnectOpts> {
25
25
  @inject(Logger) protected override logger: Logger;
26
26
 
27
- protected netServer: net.Server;
27
+ protected netServer?: net.Server;
28
28
 
29
- constructor() {
30
- super();
29
+ protected onConnectionEmitter = new Emitter<net.Socket>();
30
+
31
+ /**
32
+ * Fires for every socket the server accepts, before the JSON-RPC connection is built on it.
33
+ *
34
+ * A listener must not consume the socket. Subscribe before {@link start} to observe the first connection; the
35
+ * subscription outlives an individual launch and is disposed by the caller.
36
+ */
37
+ readonly onConnection = this.onConnectionEmitter.event;
38
+
39
+ protected override getServerSocket(): net.Server | undefined {
40
+ return this.netServer;
41
+ }
42
+
43
+ protected override registerDisposables(): void {
44
+ super.registerDisposables();
31
45
  this.toDispose.push(
32
46
  Disposable.create(() => {
33
- this.netServer.close();
47
+ this.netServer?.close();
34
48
  })
35
49
  );
36
50
  }
37
51
 
38
52
  protected run(opts: net.TcpSocketConnectOpts): Promise<void> {
39
- this.netServer = net.createServer(socket => {
40
- const connection = this.createConnection(socket);
41
- this.createServerInstance(connection);
42
- });
53
+ this.resetListening();
54
+ const netServer = net.createServer(socket => this.acceptConnection(socket));
55
+ this.netServer = netServer;
43
56
 
44
- this.netServer.listen(opts.port, opts.host);
45
- this.netServer.on('listening', () => {
46
- const addressInfo = this.netServer.address();
47
- if (!addressInfo) {
48
- this.logger.error('Could not resolve GLSP Server address info. Shutting down.');
49
- this.shutdown();
50
- return;
51
- } else if (typeof addressInfo === 'string') {
52
- this.logger.error(`GLSP Server is unexpectedly listening to pipe or domain socket "${addressInfo}". Shutting down.`);
53
- this.shutdown();
54
- return;
55
- }
56
- const currentPort = addressInfo.port;
57
- this.logger.info(`The GLSP server is ready to accept new client requests on port: ${currentPort}`);
58
- // Print a message to the output stream that indicates that the start is completed.
59
- // This indicates to the client that the server process is ready (in an embedded scenario).
60
- console.log(this.startupCompleteMessage.concat(currentPort.toString()));
61
- });
62
- this.netServer.on('error', () => this.shutdown());
57
+ netServer.listen(opts.port, opts.host);
58
+ netServer.on('listening', () => this.handleListening(netServer));
59
+ netServer.on('error', error => this.handleError(error));
63
60
  return new Promise((resolve, reject) => {
64
- this.netServer.on('close', () => resolve(undefined));
65
- this.netServer.on('error', error => reject(error));
61
+ netServer.on('close', () => resolve(undefined));
62
+ netServer.on('error', error => reject(error));
66
63
  });
67
64
  }
68
65
 
66
+ /**
67
+ * Takes up a socket the server accepted. Observers are notified before the socket is read from.
68
+ *
69
+ * @param socket The accepted socket.
70
+ */
71
+ protected acceptConnection(socket: net.Socket): void {
72
+ this.onConnectionEmitter.fire(socket);
73
+ this.createServerInstance(this.createConnection(socket));
74
+ }
75
+
76
+ /**
77
+ * Reports the server as ready and resolves {@link listening}.
78
+ *
79
+ * @param server The server that reported itself as listening.
80
+ */
81
+ protected handleListening(server: net.Server): void {
82
+ const addressInfo = this.settleListening(server);
83
+ if (!addressInfo) {
84
+ return;
85
+ }
86
+ this.logger.info(`The GLSP server is ready to accept new client requests on port: ${addressInfo.port}`);
87
+ // Print a message to the output stream that indicates that the start is completed.
88
+ // This indicates to the client that the server process is ready (in an embedded scenario).
89
+ console.log(this.startupCompleteMessage.concat(addressInfo.port.toString()));
90
+ }
91
+
69
92
  protected createConnection(socket: net.Socket): jsonrpc.MessageConnection {
70
93
  return jsonrpc.createMessageConnection(new jsonrpc.SocketMessageReader(socket), new jsonrpc.SocketMessageWriter(socket), console);
71
94
  }
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2023 EclipseSource and others.
2
+ * Copyright (c) 2023-2026 EclipseSource and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -14,12 +14,12 @@
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
16
 
17
- import { createWebSocketConnection, Disposable, MaybePromise, WebSocketWrapper } from '@eclipse-glsp/protocol';
17
+ import { createWebSocketConnection, Disposable, Emitter, MaybePromise, WebSocketWrapper } from '@eclipse-glsp/protocol';
18
18
  import * as http from 'http';
19
19
  import { inject, injectable } from 'inversify';
20
20
  import * as net from 'net';
21
21
  import * as jsonrpc from 'vscode-jsonrpc';
22
- import { Server, WebSocket } from 'ws';
22
+ import { WebSocket, WebSocketServer } from 'ws';
23
23
  import { JsonRpcGLSPServerLauncher } from '../../common/launch/jsonrpc-server-launcher';
24
24
  import { Logger } from '../../common/utils/logger';
25
25
 
@@ -41,6 +41,13 @@ export interface WebsocketConnectionData {
41
41
  connection: jsonrpc.MessageConnection;
42
42
  }
43
43
 
44
+ /** An accepted web socket together with the upgrade request it was negotiated with. */
45
+ export interface AcceptedWebSocket {
46
+ socket: WebSocket;
47
+ /** The handshake request, the only place where its headers and query are still available. */
48
+ request: http.IncomingMessage;
49
+ }
50
+
44
51
  const STATUS_UPGRADE_REQUIRED = 426;
45
52
 
46
53
  @injectable()
@@ -48,35 +55,86 @@ export class WebSocketServerLauncher extends JsonRpcGLSPServerLauncher<WebSocket
48
55
  @inject(Logger)
49
56
  protected override logger: Logger;
50
57
 
51
- protected server: Server;
58
+ protected server?: WebSocketServer;
59
+
60
+ /**
61
+ * The HTTP server the web socket server is mounted on.
62
+ *
63
+ * Held separately because `ws` only closes an HTTP server it created itself, so releasing the socket is up to us.
64
+ */
65
+ protected httpServer?: http.Server;
52
66
 
53
- constructor() {
54
- super();
67
+ protected onConnectionEmitter = new Emitter<AcceptedWebSocket>();
68
+
69
+ /**
70
+ * Fires for every web socket the server accepts, before the JSON-RPC connection is built on it.
71
+ *
72
+ * A listener must not consume the socket. Subscribe before {@link start} to observe the first connection; the
73
+ * subscription outlives an individual launch and is disposed by the caller.
74
+ */
75
+ readonly onConnection = this.onConnectionEmitter.event;
76
+
77
+ protected override getServerSocket(): net.Server | undefined {
78
+ return this.httpServer;
79
+ }
80
+
81
+ protected override registerDisposables(): void {
82
+ super.registerDisposables();
55
83
  this.toDispose.push(
56
84
  Disposable.create(() => {
57
- this.server.close();
85
+ this.server?.close();
86
+ // `ws` leaves an HTTP server it did not create itself listening, so close ours explicitly.
87
+ this.httpServer?.close();
88
+ this.httpServer = undefined;
58
89
  })
59
90
  );
60
91
  }
61
92
 
62
93
  protected async run(options: WebSocketServerOptions): Promise<void> {
94
+ this.resetListening();
63
95
  const resolvedOptions = await this.resolveOptions(options);
64
- this.server = new Server({ server: resolvedOptions.server, path: resolvedOptions.path });
96
+ if (!this.running) {
97
+ // Shut down while the options were being resolved, so release the socket they already bound.
98
+ resolvedOptions.server.close();
99
+ return;
100
+ }
101
+ this.httpServer = resolvedOptions.server;
102
+ this.server = new WebSocketServer({ server: resolvedOptions.server, path: resolvedOptions.path });
65
103
  const endpoint = `ws://${resolvedOptions.host}:${resolvedOptions.port}:${resolvedOptions.path}`;
66
104
  this.logger.info(`The GLSP Websocket launcher is ready to accept new client requests on endpoint '${endpoint}'`);
67
105
  console.log(this.startupCompleteMessage.concat(resolvedOptions.port.toString()));
68
106
 
69
- this.server.on('connection', (ws, req) => {
70
- const connection = this.createConnection(ws);
71
- this.createServerInstance(connection);
72
- });
107
+ this.observeListening(resolvedOptions.server);
108
+ this.server.on('connection', (ws, request) => this.acceptConnection(ws, request));
73
109
 
110
+ const server = this.server;
74
111
  return new Promise((resolve, reject) => {
75
- this.server.on('close', () => resolve(undefined));
76
- this.server.on('error', error => reject(error));
112
+ server.on('close', () => resolve(undefined));
113
+ server.on('error', error => reject(error));
77
114
  });
78
115
  }
79
116
 
117
+ /** Settles {@link listening} from the HTTP server, which may already be bound by the time we get here. */
118
+ protected observeListening(server: http.Server): void {
119
+ if (server.listening) {
120
+ this.settleListening(server);
121
+ return;
122
+ }
123
+ server.on('listening', () => this.settleListening(server));
124
+ server.on('error', error => this.handleError(error));
125
+ }
126
+
127
+ /**
128
+ * Takes up a web socket the server accepted. Observers are notified before the socket is read from.
129
+ *
130
+ * @param socket The accepted web socket.
131
+ * @param request The upgrade request the socket was negotiated with.
132
+ */
133
+ protected acceptConnection(socket: WebSocket, request: http.IncomingMessage): void {
134
+ this.onConnectionEmitter.fire({ socket, request });
135
+ this.createServerInstance(this.createConnection(socket));
136
+ }
137
+
80
138
  protected createConnection(socket: WebSocket): jsonrpc.MessageConnection {
81
139
  return createWebSocketConnection(wrapWebSocket(socket));
82
140
  }