UNPKG

@eclipse-glsp/theia-integration

Version:

Glue code to integrate GLSP clients into Eclipse Theia

101 lines (90 loc) 4.06 kB
/******************************************************************************** * Copyright (c) 2023-2024 EclipseSource 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 ********************************************************************************/ import { WebSocketWrapper, createWebSocketConnection } from '@eclipse-glsp/protocol'; import { Channel, Disposable, DisposableCollection } from '@theia/core'; import { MessageConnection } from '@theia/core/shared/vscode-languageserver-protocol'; import { WebSocket } from 'ws'; /** * Creates a new vscode-jsonrpc {@link MessageConnection} on top of a given WebSocket. * It handles messages between then service client channel and the given WebSocket. */ export class WebSocketConnectionForwarder implements Disposable { protected toDispose = new DisposableCollection(); protected initialChannelListener: Disposable; protected initialBufferStore: Uint8Array[] = []; constructor( protected readonly clientChannel: Channel, protected readonly webSocket: WebSocket ) { /** * The webSocket connection is successfully established `onOpen` * The service client channel however, might send message (i.e. the InitializeRequest) once the backend contribution is resolved. * In this case, the listener could not yet be successfully registered, hence we buffer such messages and process them, * once the webSocket connection was successfully established. */ this.initialChannelListener = this.clientChannel.onMessage(msgProvider => { const buffer = msgProvider().readBytes(); this.initialBufferStore.push(buffer); }); webSocket.onopen = () => { this.initialize(webSocket); }; } protected initialize(webSocket: WebSocket): void { const wrappedWebSocket = wrapWebSocket(webSocket); const connection: MessageConnection = createWebSocketConnection(wrappedWebSocket); connection.listen(); this.toDispose.pushAll([ connection.onClose(() => webSocket.close()), this.clientChannel.onMessage(msgProvider => { const buffer = msgProvider().readBytes(); wrappedWebSocket.send(this.decodeMessage(buffer)); }), connection.onClose(() => this.clientChannel.close()), Disposable.create(() => { this.clientChannel.close(); connection.dispose(); }) ]); webSocket.on('message', msg => { this.clientChannel .getWriteBuffer() .writeBytes(msg as Buffer) .commit(); }); // process initially received buffer messages this.initialChannelListener.dispose(); this.initialBufferStore.forEach(msg => { wrappedWebSocket.send(this.decodeMessage(msg)); }); this.initialBufferStore = []; } protected decodeMessage(buffer: Uint8Array): string { return new TextDecoder().decode(buffer); } dispose(): void { this.toDispose.dispose(); } } export function wrapWebSocket(socket: WebSocket): WebSocketWrapper { return { send: content => socket.send(content), onMessage: cb => socket.on('message', cb), onClose: cb => socket.on('close', cb), onError: cb => socket.on('error', cb), dispose: () => socket.close() }; }