Skip to content

Commit

Permalink
Provide own websocket vsode-jsonrpc connection (#215)
Browse files Browse the repository at this point in the history
Replace vscode-ws-jsonrpc with own implementation
Contributed on behalf of STMicroelectronics

Part of eclipse-glsp/glsp#944
  • Loading branch information
tortmayr authored Mar 6, 2023
1 parent 40987f7 commit 6efef44
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 22 deletions.
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@
- [layout] Improve Layouter to support more dynamic layouts and complex parent/children node structures [#187](https://github.com/eclipse-glsp/glsp-client/pull/187) - Contributed on behalf of STMicroelectronics
- [diagram] Fixed SVG export for nested root elements e.g. `GLSPProjectionView` [#196](https://github.com/eclipse-glsp/glsp-client/pull/196)
- [diagram] scope the styles to not break existing application layout [#209](https://github.com/eclipse-glsp/glsp-client/pull/209)
- [routing] Ensured that routes are properly re-calculated when moving a routing point [#198](https://github.com/eclipse-glsp/glsp-client/pull/198)
- [routing] Ensured that routes are properly re-calculated when moving a routing point [#198](https://github.com/eclipse-glsp/glsp-client/pull/198)
- [diagram] Fixed a bug in the `EditLabelUIExtension` where the diagram becomes dirt wihtout an acual change. [#766](https://github.com/eclipse-glsp/glsp/issues/766)
- [diagram] Extends `ComputedBoundsAction` definition with routing information. This enables proper forwarding of cliend-side computed routes to the server. [#201](https://github.com/eclipse-glsp/glsp-client/pull/201/)
- [diagram] Extends `ComputedBoundsAction` definition with routing information. This enables proper forwarding of client-side computed routes to the server. [#201](https://github.com/eclipse-glsp/glsp-client/pull/201/)

### Breaking Changes

- [DI] Injecting an `IButtonHandler` constructor is now deprecated. Please use `configureButtonHandler()` instead. [#195](https://github.com/eclipse-glsp/glsp-client/pull/195) - Contributed on behalf of STMicroelectronics
- [protocol] Update vscode-ws-jsonrpc to version 2.0.1 [#210](https://github.com/eclipse-glsp/glsp-client/pull/210)
- [node] Update minimum requirements for Node to >=16.11.0 [#210](https://github.com/eclipse-glsp/glsp-client/pull/210)
- [protocol] Renamed `UndoOperation` and `RedoOperation` to `UndoAction` and `RedoAction` to match operation specification [#216](https://github.com/eclipse-glsp/glsp-client/pull/216)
- [protocol] Remove dependency to `vscode-ws-jsonrpc`. The protocol package now directly offers functions to create a websocket rpc connections [#215](https://github.com/eclipse-glsp/glsp-client/pull/215)

## [v1.0.0 - 30/06/2022](https://github.com/eclipse-glsp/glsp-client/releases/tag/v1.0.0)

Expand Down
5 changes: 2 additions & 3 deletions examples/workflow-standalone/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
GLSPActionDispatcher,
GLSPClient,
GLSPDiagramServer,
listen,
RequestModelAction,
RequestTypeHintsAction,
TYPES
Expand All @@ -42,9 +43,7 @@ const container = createContainer();
const diagramServer = container.get<GLSPDiagramServer>(TYPES.ModelSource);
diagramServer.clientId = clientId;

import('vscode-ws-jsonrpc').then(jsonrpc => {
jsonrpc.listen({ webSocket: websocket, onConnection: connection => initialize(connection) });
});
listen(websocket, connection => initialize(connection));

async function initialize(connectionProvider: MessageConnection): Promise<void> {
const client = new BaseJsonrpcGLSPClient({ id, connectionProvider });
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"dependencies": {
"sprotty-protocol": "0.13.0-next.f4445dd.342",
"uuid": "7.0.3",
"vscode-ws-jsonrpc": "^2.0.1"
"vscode-jsonrpc": "^8.0.2"
},
"devDependencies": {
"@types/uuid": "3.4.5"
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export * from './action-protocol';
export * from './glsp-client';
export * from './jsonrpc/base-jsonrpc-glsp-client';
export * from './jsonrpc/glsp-jsonrpc-client';
export * from './jsonrpc/websocket-connection';
export * from './model/default-types';
export * from './model/model-schema';
export * from './utils/array-util';
Expand Down
180 changes: 180 additions & 0 deletions packages/protocol/src/jsonrpc/websocket-connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/********************************************************************************
* Copyright (c) 2023 STMicroelectronics and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

// based on https://github.com/TypeFox/monaco-languageclient/blob/vwj-2.0.1/packages/vscode-ws-jsonrpc/src/socket/reader.ts
/* --------------------------------------------------------------------------------------------
* Copyright (c) 2018-2022 TypeFox GmbH (http://www.typefox.io). All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {
AbstractMessageReader,
AbstractMessageWriter,
createMessageConnection,
DataCallback,
Disposable,
Logger,
Message,
MessageConnection
} from 'vscode-jsonrpc';

/**
* A wrapper interface that enables the reuse of the {@link WebSocketMessageReader} and {@link WebSocketMessageWriter}
* independent of the underlying WebSocket implementation/library. e.g. one could use Socket.io instead of plain WebSockets
*/
export interface WebSocketWrapper extends Disposable {
send(content: string): void;
onMessage(cb: (data: any) => void): void;
onError(cb: (reason: any) => void): void;
onClose(cb: (code: number, reason: string) => void): void;
}

/**
* Creates a {@link WebSocketWrapper} for the given plain WebSocket
* @param socket The socket to wrap
*/
export function wrap(socket: WebSocket): WebSocketWrapper {
return {
send: content => socket.send(content),
onMessage: cb => (socket.onmessage = event => cb(event.data)),
onClose: cb => (socket.onclose = event => cb(event.code, event.reason)),
onError: cb =>
(socket.onerror = event => {
if ('error' in event) {
cb(event.error);
}
}),
dispose: () => socket.close()
};
}

/**
* A `vscode-jsonrpc` {@link MessageReader} that reads messages from an underlying {@link WebSocketWrapper}.
*/
export class WebSocketMessageReader extends AbstractMessageReader {
protected state: 'initial' | 'listening' | 'closed' = 'initial';
protected callback?: DataCallback;
protected eventQueue: Array<{ message?: unknown; error?: any }> = [];

constructor(protected readonly socket: WebSocketWrapper) {
super();
this.socket.onMessage(message => this.handleMessage(message));
this.socket.onError(error => this.fireError(error));
this.socket.onClose(() => this.fireClose());
}

listen(callback: DataCallback): Disposable {
if (this.state === 'initial') {
this.state = 'listening';
this.callback = callback;
this.eventQueue.forEach(event => {
if (event.message) {
this.handleMessage(event.message);
} else if (event.error) {
this.fireError(event.error);
} else {
this.fireClose();
}
});
this.eventQueue = [];
}
return Disposable.create(() => {
this.callback = undefined;
this.eventQueue = [];
});
}

protected handleMessage(message: any): void {
if (this.state === 'initial') {
this.eventQueue.push({ message });
} else if (this.state === 'listening') {
const data = JSON.parse(message);
this.callback!(data);
}
}

protected override fireError(error: any): void {
if (this.state === 'initial') {
this.eventQueue.push({ error });
} else if (this.state === 'listening') {
super.fireError(error);
}
}

protected override fireClose(): void {
if (this.state === 'initial') {
this.eventQueue.push({});
} else if (this.state === 'listening') {
super.fireClose();
}
this.state = 'closed';
}
}

/**
* A `vscode-jsonrpc` {@link MessageReader} that writes messages to an underlying {@link WebSocketWrapper}.
*/
export class WebSocketMessageWriter extends AbstractMessageWriter {
protected errorCount = 0;

constructor(protected readonly socket: WebSocketWrapper) {
super();
}

end(): void {
/** no-op */
}

async write(msg: Message): Promise<void> {
try {
const content = JSON.stringify(msg);
this.socket.send(content);
} catch (e) {
this.errorCount++;
this.fireError(e, msg, this.errorCount);
}
}
}

/**
* Create a `vscode-jsonrpc` {@link MessageConnection} on top of a given {@link WebSocketWrapper}.
*/
export function createWebSocketConnection(socket: WebSocketWrapper, logger?: Logger): MessageConnection {
const reader = new WebSocketMessageReader(socket);
const writer = new WebSocketMessageWriter(socket);
return createMessageConnection(reader, writer, logger);
}

/**
* Creates a new {@link MessageConnection} on top of the given websocket on open.
* @param webSocket The target webSocket
* @param onConnection Optional callback that is invoked after the connection has been created
* @param logger Optional connection logger
* @returns A promise of the created connection
*/
export function listen(
webSocket: WebSocket,
onConnection?: (connection: MessageConnection) => void,
logger?: Logger
): Promise<MessageConnection> {
return new Promise(resolve => {
webSocket.onopen = () => {
const socket = wrap(webSocket);
const connection = createWebSocketConnection(socket, logger);
onConnection?.(connection);
resolve(connection);
};
});
}
14 changes: 7 additions & 7 deletions packages/protocol/src/utils/type-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ export type TypeGuard<T> = (element: any) => element is T;
/**
* Utility function that create a typeguard function for a given class constructor.
* Essentially this wraps an instance of check as typeguard function.
* @param constructor The constructor fo the class for which the typeguard should be created.
* @param constructor The constructor of the class for which the typeguard should be created.
* @returns The typeguard for this class.
*/
export function toTypeGuard<G>(constructor: Constructor<G>): TypeGuard<G> {
return (element: unknown): element is G => element instanceof constructor;
}

/**
* Validates whether the given object as a property of type `string` with the given key.
* Validates whether the given object has a property of type `string` with the given key.
* @param object The object that should be validated
* @param propertyKey The key of the property
* @param optional Flag to indicate wether the property can be optional i.e. also return true if the given key is undefined
Expand All @@ -80,7 +80,7 @@ export function hasStringProp(object: AnyObject, propertyKey: string, optional =
}

/**
* Validates whether the given object as a property of type `boolean` with the given key.
* Validates whether the given object has a property of type `boolean` with the given key.
* @param object The object that should be validated
* @param propertyKey The key of the property
* @param optional Flag to indicate wether the property can be optional i.e. also return true if the given key is undefined
Expand All @@ -91,7 +91,7 @@ export function hasBooleanProp(object: AnyObject, propertyKey: string, optional
}

/**
* Validates whether the given object as a property of type `number` with the given key.
* Validates whether the given object has a property of type `number` with the given key.
* @param object The object that should be validated
* @param propertyKey The key of the property
* @param optional Flag to indicate wether the property can be optional i.e. also return true if the given key is undefined
Expand All @@ -102,7 +102,7 @@ export function hasNumberProp(object: AnyObject, propertyKey: string, optional =
}

/**
* Validates whether the given object as a property of type `object` with the given key.
* Validates whether the given object has a property of type `object` with the given key.
* @param object The object that should be validated
* @param propertyKey The key of the property
* @param optional Flag to indicate wether the property can be optional i.e. also return true if the given key is undefined
Expand All @@ -113,7 +113,7 @@ export function hasObjectProp(object: AnyObject, propertyKey: string, optional =
}

/**
* Validates whether the given object as a property of type `function` with the given key.
* Validates whether the given object has a property of type `function` with the given key.
* @param object The object that should be validated
* @param propertyKey The key of the property
* @param optional Flag to indicate wether the property can be optional i.e. also return true if the given key is undefined
Expand All @@ -124,7 +124,7 @@ export function hasFunctionProp(object: AnyObject, propertyKey: string, optional
}

/**
* Validates whether the given object as a property of type `Array` with the given key.
* Validates whether the given object has a property of type `Array` with the given key.
* @param object The object that should be validated
* @param propertyKey The key of the property
* @param optional Flag to indicate wether the property can be optional i.e. also return true if the given key is undefined
Expand Down
9 changes: 1 addition & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6904,18 +6904,11 @@ validate-npm-package-name@^4.0.0:
dependencies:
builtins "^5.0.0"

[email protected]:
vscode-jsonrpc@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2.tgz#f239ed2cd6004021b6550af9fd9d3e47eee3cac9"
integrity sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==

vscode-ws-jsonrpc@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/vscode-ws-jsonrpc/-/vscode-ws-jsonrpc-2.0.1.tgz#e0ac448a4a1df57d982a5abc62fc7128f4972587"
integrity sha512-ne5DO8/qe8tHt1U4LafLiYS832Yd4OltkP4+YZVOQwqGEU5nwLwZowUBqqEWt8sOZ0eLdCLV9luotGC2aUQ+LA==
dependencies:
vscode-jsonrpc "8.0.2"

walk-up-path@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e"
Expand Down

0 comments on commit 6efef44

Please sign in to comment.