Skip to content

Commit 5c40594

Browse files
Add websocket server launcher (#41)
Requires eclipse-glsp/glsp-client#215 Fixes eclipse-glsp/glsp#945 Co-authored-by: Martin Fleck <[email protected]>
1 parent 385269d commit 5c40594

File tree

14 files changed

+1089
-1267
lines changed

14 files changed

+1089
-1267
lines changed

.vscode/launch.json

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,30 @@
7777
"${workspaceRoot}/packages/**/*/lib/**/*.js"
7878
],
7979
"sourceMapPathOverrides": {
80-
"webpack:///./~/*": "${workspaceFolder}/node_modules/*",
81-
"webpack:////*": "/*",
82-
"webpack://?:*/*": "${workspaceFolder}/*",
83-
"webpack:///([a-z]):/(.+)": "$1:/$2",
84-
"webpack://@eclipse-glsp-examples/workflow-server/(.+)": "${workspaceFolder}/examples/workflow-server/$1",
85-
"meteor://💻app/*": "${workspaceFolder}/*"
80+
"webpack://@eclipse-glsp-examples/workflow-server/(.+)": "${workspaceFolder}/examples/workflow-server/$1"
81+
},
82+
"smartStep": true,
83+
"internalConsoleOptions": "openOnSessionStart",
84+
"outputCapture": "std"
85+
},
86+
{
87+
"type": "node",
88+
"request": "launch",
89+
"name": "Debug Workflow Example Server (Websocket)",
90+
"program": "${workspaceFolder}/examples/workflow-server/bundle/wf-glsp-server-node.js",
91+
"args": ["--webSocket", "--port", "8081"],
92+
"env": {
93+
"NODE_ENV": "development"
94+
},
95+
"sourceMaps": true,
96+
"outFiles": [
97+
"${workspaceRoot}/examples/workflow-server/bundle/**/*.js",
98+
"${workspaceRoot}/node_modules/@eclipse-glsp/*/lib/**/*.js",
99+
"${workspaceRoot}/examples/workflow-server/*/lib/**/*.js",
100+
"${workspaceRoot}/packages/**/*/lib/**/*.js"
101+
],
102+
"sourceMapPathOverrides": {
103+
"webpack://@eclipse-glsp-examples/workflow-server/(.+)": "${workspaceFolder}/examples/workflow-server/$1"
86104
},
87105
"smartStep": true,
88106
"internalConsoleOptions": "openOnSessionStart",

README.md

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,41 +12,6 @@ The server consists of three components:
1212
The main target environment is node, nevertheless, all components are implemented in an ismorphic fashion and also provide
1313
an entrypoint to target browser environments (e.g. running the server in a web worker)
1414

15-
## Integration & Features
16-
17-
Currently the Typescript-based API does not yet support the full feature set of the Java GLSP Server API.
18-
Below is a list of features that are currently supported.
19-
20-
| Integration | Node Server | Java Server |
21-
| ------------------------------------------------- | :---------: | :---------: |
22-
| JSON-RPC over Socket (Theia, VSCode) |||
23-
| JSON-RPC over Websocket (Standalone, Eclipse IDE) | ||
24-
25-
| Feature | Node Server | Java Server |
26-
| ----------------------------------------------------------------- | :-------------: | :-------------: |
27-
| Model Saving |||
28-
| Model Dirty State |||
29-
| Model SVG Export |||
30-
| Model Layout |||
31-
| Model Edit Modes<br>- Edit<br>- Read-only | <br>✓<br>&nbsp; | <br>✓<br>✓ |
32-
| Client View Port<br>- Center<br>- Fit to Screen | <br>✓<br>✓ | <br>✓<br>✓ |
33-
| Client Status Notification |||
34-
| Client Message Notification |||
35-
| Element Selection |||
36-
| Element Hover |||
37-
| Element Validation |||
38-
| Element Navigation |||
39-
| Element Type Hints |||
40-
| Element Creation and Deletion |||
41-
| Node Change Bounds<br>- Move<br>- Resize | <br>✓<br>✓ | <br>✓<br>✓ |
42-
| Node Change Container |||
43-
| Edge Reconnect |||
44-
| Edge Routing Points |||
45-
| Element Text Editing |||
46-
| Clipboard (Cut, Copy, Paste) |||
47-
| Undo / Redo |||
48-
| Contexts<br>- Context Menu<br>- Command Palette<br>- Tool Palette | <br><br>✓<br>✓ | <br>✓<br>✓<br>✓ |
49-
5015
## Build
5116

5217
Install dependencies and build via
@@ -88,17 +53,39 @@ Simply select a test file (`*.spec.ts`), then go to the `Run & Debug` View (`Ctr
8853

8954
## Start & Debug
9055

91-
The example server can be launched with:
56+
### Socket
57+
58+
To launch the server for TCP sockets use:
9259

9360
```console
9461
yarn start
9562
```
9663

64+
This starts a server that is listening on port 5007 for incoming client requests.
65+
9766
To debug you can use the `Debug workflow example GLSP Server` launch configuration.
98-
This starts the example server in a dedicated process. To test the server you have to connect a workflow GLSP client that supports JSON-RPC via socket.
67+
To test the server you have to connect a workflow GLSP client that supports JSON-RPC via socket.
9968
We recommend to use the client provided by the [`glsp-integration`](https://github.com/eclipse-glsp/glsp-theia-integration#how-to-start-the-workflow-diagram-example-server-from-the-sources).
10069

70+
### Websocket
71+
72+
To launch the server for WebSockets use:
73+
74+
```console
75+
yarn start:websocket
76+
```
77+
78+
This starts a server that is listening on the `ws://localhost:8081/workflow` endpoint for incoming client requests.
79+
80+
To debug you can use the `Debug workflow example GLSP Server (Websocket)` launch configuration.
81+
To test the server you have to connect a workflow GLSP client that supports JSON-RPC via WebSocket.
82+
We recommend to use the standalone example provided by [`glsp-client`](https://github.com/eclipse-glsp/glsp-client/blob/master/README.md#how-to-start-the-workflow-diagram-example).
83+
10184
## More information
10285

10386
For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/).
10487
If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/).
88+
89+
```
90+
91+
```

examples/workflow-server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"lint:ci": "yarn lint -o eslint.xml -f checkstyle",
5151
"prepare": "yarn clean && yarn build",
5252
"start": "node --enable-source-maps bundle/wf-glsp-server-node.js",
53+
"start:websocket": "node --enable-source-maps bundle/wf-glsp-server-node.js -w --port 8081",
5354
"watch": "tsc -w"
5455
},
5556
"dependencies": {

examples/workflow-server/src/browser/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export async function launch(argv?: string[]): Promise<void> {
3838

3939
launcher.configure(serverModule);
4040

41-
await launcher.start();
41+
await launcher.start({});
4242
} catch (error) {
4343
(logger ?? console).error('Error in workflow server launcher:', error);
4444
}

examples/workflow-server/src/node/app.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,41 @@
1616
import { configureELKLayoutModule } from '@eclipse-glsp/layout-elk';
1717
import {
1818
createAppModule,
19-
createSocketCliParser,
2019
GModelStorage,
2120
Logger,
2221
LoggerFactory,
23-
SocketServerLauncher
22+
SocketServerLauncher,
23+
WebSocketServerLauncher
2424
} from '@eclipse-glsp/server/node';
2525
import { Container } from 'inversify';
2626
import { WorkflowLayoutConfigurator } from '../common/layout/workflow-layout-configurator';
2727
import { WorkflowDiagramModule, WorkflowServerModule } from '../common/workflow-diagram-module';
28+
import { createWorkflowCliParser } from './workflow-cli-parser';
2829

2930
export async function launch(argv?: string[]): Promise<void> {
3031
let logger: Logger | undefined;
3132
try {
32-
const options = createSocketCliParser().parse(argv);
33+
const options = createWorkflowCliParser().parse(argv);
3334
const appContainer = new Container();
3435
appContainer.load(createAppModule(options));
3536

3637
logger = appContainer.get<LoggerFactory>(LoggerFactory)('WorkflowServerApp');
37-
const launcher = appContainer.resolve(SocketServerLauncher);
38+
3839
const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator });
3940
const serverModule = new WorkflowServerModule().configureDiagramModule(
4041
new WorkflowDiagramModule(() => GModelStorage),
4142
elkLayoutModule
4243
);
4344

44-
launcher.configure(serverModule);
45-
46-
launcher.start({ port: options.port, host: options.host });
45+
if (options.webSocket) {
46+
const launcher = appContainer.resolve(WebSocketServerLauncher);
47+
launcher.configure(serverModule);
48+
launcher.start({ port: options.port, host: options.host, path: 'workflow' });
49+
} else {
50+
const launcher = appContainer.resolve(SocketServerLauncher);
51+
launcher.configure(serverModule);
52+
launcher.start({ port: options.port, host: options.host });
53+
}
4754
} catch (error) {
4855
(logger ?? console).error('Error in workflow server launcher:', error);
4956
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/********************************************************************************
2+
* Copyright (c) 2023 EclipseSource 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+
17+
import { CliParser, createSocketCliParser, defaultSocketLaunchOptions, SocketLaunchOptions } from '@eclipse-glsp/server/node';
18+
19+
export interface WorkflowLaunchOptions extends SocketLaunchOptions {
20+
webSocket: boolean;
21+
}
22+
23+
export function createWorkflowCliParser<O extends WorkflowLaunchOptions = WorkflowLaunchOptions>(
24+
defaultOptions: WorkflowLaunchOptions = { webSocket: false, ...defaultSocketLaunchOptions }
25+
): CliParser<O> {
26+
const parser = createSocketCliParser<O>(defaultOptions);
27+
parser.command.option('-w , --webSocket', 'Flag to use websocket launcher instead of default launcher', false);
28+
return parser;
29+
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@
2020
"publish:next": "SHA=$(git rev-parse --short HEAD) && lerna publish preminor --exact --canary --preid next.${SHA} --dist-tag next --no-git-reset --no-git-tag-version --no-push --ignore-scripts --yes --no-verify-access",
2121
"publish:prepare": "lerna version --ignore-scripts --yes --no-push",
2222
"start": "yarn --cwd examples/workflow-server start",
23+
"start:websocket": "yarn --cwd examples/workflow-server start:websocket",
2324
"test": "lerna run test",
2425
"test:ci": "lerna run test:ci",
2526
"test:coverage": "lerna run test:coverage",
2627
"test:coverage:ci": "yarn glsp coverageReport .",
27-
"upgrade:next": "yarn upgrade -p \"@eclipse-glsp.*|sprotty.*\" --next ",
28+
"upgrade:next": "yarn upgrade @eclipse-glsp/protocol@next",
2829
"watch": "lerna run --parallel watch"
2930
},
3031
"devDependencies": {
31-
"@eclipse-glsp/dev": "1.1.0-next.164cf99.124",
32+
"@eclipse-glsp/dev": "1.1.0-next.7026c40.129",
3233
"@types/node": "16.x"
3334
},
3435
"engines": {

packages/server/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,12 @@
6565
"commander": "^8.3.0",
6666
"fast-json-patch": "^3.1.0",
6767
"inversify": "^5.1.1",
68-
"winston": "^3.3.3"
68+
"winston": "^3.3.3",
69+
"ws": "^8.12.1"
6970
},
7071
"devDependencies": {
71-
"@types/fs-extra": "^9.0.13"
72+
"@types/fs-extra": "^9.0.13",
73+
"@types/ws": "^8.5.4"
7274
},
7375
"publishConfig": {
7476
"access": "public"

packages/server/src/browser/launch/worker-server-launcher.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,30 @@
1616

1717
import { Container, injectable } from 'inversify';
1818
import * as jsonrpc from 'vscode-jsonrpc/browser';
19-
import { GLSPServer, GLSPServerLauncher, JsonRpcGLSPServer, MaybePromise, START_UP_COMPLETE_MSG } from '../../common/index';
19+
import { GLSPServer, GLSPServerLauncher, JsonRpcGLSPServer, MaybePromise } from '../../common/index';
20+
export interface WorkerLaunchOptions {
21+
context?: Worker;
22+
}
23+
24+
export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed.';
2025

2126
@injectable()
22-
export class WorkerServerLauncher extends GLSPServerLauncher {
23-
private connection?: jsonrpc.MessageConnection;
27+
export class WorkerServerLauncher extends GLSPServerLauncher<WorkerLaunchOptions> {
28+
protected connection?: jsonrpc.MessageConnection;
2429

25-
protected run(): MaybePromise<void> {
30+
protected run(options: WorkerLaunchOptions): MaybePromise<void> {
2631
if (this.connection) {
2732
throw new Error('Error during launch. Server already has an active client connection');
2833
}
2934
const container = this.createContainer();
30-
this.connection = this.createConnection();
35+
this.connection = this.createConnection(options);
3136
const glspServer = container.get<JsonRpcGLSPServer>(JsonRpcGLSPServer);
3237
glspServer.connect(this.connection);
3338
this.connection.onDispose(() => this.disposeClientConnection(container, glspServer));
3439

3540
this.logger.info('GLSP server worker connection established');
3641
this.connection.listen();
42+
this.toDispose.push(this.connection);
3743
postMessage(START_UP_COMPLETE_MSG);
3844
return new Promise((resolve, rejects) => {
3945
this.connection?.onClose(() => resolve(undefined));
@@ -47,12 +53,10 @@ export class WorkerServerLauncher extends GLSPServerLauncher {
4753
this.connection = undefined;
4854
}
4955

50-
protected stop(): MaybePromise<void> {
51-
this.logger.info('Shutdown WorkerServerLauncher');
52-
this.connection?.dispose();
53-
}
54-
55-
protected createConnection(): jsonrpc.MessageConnection {
56-
return jsonrpc.createMessageConnection(new jsonrpc.BrowserMessageReader(self), new jsonrpc.BrowserMessageWriter(self));
56+
protected createConnection(options: WorkerLaunchOptions): jsonrpc.MessageConnection {
57+
return jsonrpc.createMessageConnection(
58+
new jsonrpc.BrowserMessageReader(options.context ?? self),
59+
new jsonrpc.BrowserMessageWriter(options.context ?? self)
60+
);
5761
}
5862
}

packages/server/src/common/launch/glsp-server-launcher.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,19 @@
1313
*
1414
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
1515
********************************************************************************/
16-
import { MaybePromise } from '@eclipse-glsp/protocol';
16+
import { Disposable, DisposableCollection, MaybePromise } from '@eclipse-glsp/protocol';
1717
import { Container, ContainerModule, inject, injectable, optional } from 'inversify';
1818
import { ServerModule } from '../di/server-module';
1919
import { InjectionContainer } from '../di/service-identifiers';
2020
import { Logger } from '../utils/logger';
2121

22-
export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed';
23-
2422
@injectable()
25-
export abstract class GLSPServerLauncher<T = undefined> {
23+
export abstract class GLSPServerLauncher<T> implements Disposable {
2624
@inject(Logger) protected logger: Logger;
2725

2826
protected _modules: ContainerModule[] = [];
2927
protected running: boolean;
28+
protected toDispose = new DisposableCollection();
3029

3130
@inject(InjectionContainer) @optional() protected parentContainer?: Container;
3231

@@ -40,26 +39,33 @@ export abstract class GLSPServerLauncher<T = undefined> {
4039
return container;
4140
}
4241

43-
start(startParams?: T): MaybePromise<void> {
42+
start(startParams: T): MaybePromise<void> {
4443
if (!this.running) {
4544
this.running = true;
4645
return this.run(startParams);
4746
}
4847
this.logger.warn('Could not start launcher. Launcher is already running!');
4948
}
5049

51-
protected abstract run(startParams?: T): MaybePromise<void>;
50+
protected abstract run(startParams: T): MaybePromise<void>;
5251

5352
shutdown(): MaybePromise<void> {
5453
if (this.running) {
54+
this.logger.info('Shutdown GLSPServerLauncher');
5555
const result = this.stop();
5656
this.running = false;
5757
return result;
5858
}
5959
this.logger.warn('Could not stop launcher. Launcher is not running!');
6060
}
6161

62-
protected abstract stop(): MaybePromise<void>;
62+
protected stop(): MaybePromise<void> {
63+
this.dispose();
64+
}
65+
66+
dispose(): void {
67+
this.toDispose.dispose();
68+
}
6369

6470
get modules(): ContainerModule[] {
6571
if (!this._modules) {

0 commit comments

Comments
 (0)