UNPKG

@eclipse-glsp/protocol

Version:

The protocol definition for client-server communication in GLSP

219 lines (190 loc) 8.22 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 { Deferred } from 'sprotty-protocol'; import { Disposable } from 'vscode-jsonrpc'; import { Action, ActionMessage } from '../action-protocol/base-protocol'; import { distinctAdd, remove } from '../utils/array-util'; import { Emitter, Event } from '../utils/event'; import { ActionMessageHandler, ClientState, GLSPClient } from './glsp-client'; import { GLSPClientProxy, GLSPServer } from './glsp-server'; import { DisposeClientSessionParameters, InitializeClientSessionParameters, InitializeParameters, InitializeResult } from './types'; export const GLOBAL_HANDLER_ID = '*'; /** * A simple {@link GLSPClient} implementation for use cases where the client & server are running * in the same context/process without a communication layer (like json-rpc) between. The client * directly communicates with a given {@link GLSPServer} instance. */ export class BaseGLSPClient implements GLSPClient { protected serverDeferred = new Deferred<GLSPServer>(); protected onStartDeferred = new Deferred<void>(); protected onStopDeferred = new Deferred<void>(); readonly proxy: GLSPClientProxy; protected startupTimeout = 1500; protected actionMessageHandlers: Map<string, ActionMessageHandler[]> = new Map([[GLOBAL_HANDLER_ID, []]]); protected pendingServerInitialize?: Promise<InitializeResult>; protected onServerInitializedEmitter = new Emitter<InitializeResult>(); get onServerInitialized(): Event<InitializeResult> { return this.onServerInitializedEmitter.event; } protected onCurrentStateChangedEmitter = new Emitter<ClientState>(); get onCurrentStateChanged(): Event<ClientState> { return this.onCurrentStateChangedEmitter.event; } protected _state: ClientState; protected set state(state: ClientState) { if (this._state !== state) { this._state = state; this.onCurrentStateChangedEmitter.fire(state); } } protected get state(): ClientState { return this._state; } protected _server?: GLSPServer; protected get checkedServer(): GLSPServer { this.checkState(); if (!this._server) { throw new Error(`No server is configured for GLSPClient with id '${this.id}'`); } return this._server; } protected _initializeResult?: InitializeResult; get initializeResult(): InitializeResult | undefined { return this._initializeResult; } constructor(protected options: GLSPClient.Options) { this.state = ClientState.Initial; this.proxy = this.createProxy(); } protected createProxy(): GLSPClientProxy { return { process: message => { const handlers = this.actionMessageHandlers.get(message.clientId) ?? this.actionMessageHandlers.get(GLOBAL_HANDLER_ID); if (!handlers) { console.warn('No ActionMessageHandler is configured- Cannot process server message', message); return; } handlers.forEach(handler => handler(message)); } }; } configureServer(server: GLSPServer): void { if (this.state === ClientState.Running) { throw new Error('Could not configure new server. The GLSPClient is already running'); } this.serverDeferred.resolve(server); } start(): Promise<void> { if (this.state === ClientState.Running || this.state === ClientState.Starting) { return this.onStartDeferred.promise; } this.state = ClientState.Starting; const timeOut = new Promise<GLSPServer>((_, reject) => setTimeout(() => { reject(new Error('Could not start client. No server is configured')); }, this.startupTimeout) ); Promise.race([this.serverDeferred.promise, timeOut]) .then(server => { this._server = server; this.state = ClientState.Running; this.onStartDeferred.resolve(); }) .catch(error => { this.state = ClientState.StartFailed; this.onStartDeferred.reject(error); }); return this.onStartDeferred.promise; } async initializeServer(params: InitializeParameters): Promise<InitializeResult> { if (this.initializeResult) { return this.initializeResult; } else if (this.pendingServerInitialize) { return this.pendingServerInitialize; } const initializeDeferred = new Deferred<InitializeResult>(); try { this.pendingServerInitialize = initializeDeferred.promise; this._initializeResult = await this.checkedServer.initialize(params); this.onServerInitializedEmitter.fire(this._initializeResult); initializeDeferred.resolve(this._initializeResult); this.pendingServerInitialize = undefined; } catch (error) { initializeDeferred.reject(error); this._initializeResult = undefined; this.pendingServerInitialize = undefined; } return initializeDeferred.promise; } initializeClientSession(params: InitializeClientSessionParameters): Promise<void> { return this.checkedServer.initializeClientSession(params); } disposeClientSession(params: DisposeClientSessionParameters): Promise<void> { return this.checkedServer.disposeClientSession(params); } shutdownServer(): void { this.checkedServer.shutdown(); } async stop(): Promise<void> { if (this.state === ClientState.Stopped || this.state === ClientState.Stopping) { return this.onStop(); } this.state = ClientState.Stopping; try { if (this._server) { this._server.shutdown(); } } finally { this.state = ClientState.Stopped; this.onStopDeferred.resolve(); } } sendActionMessage(message: ActionMessage<Action>): void { this.checkedServer.process(message); } onActionMessage(handler: ActionMessageHandler, clientId?: string): Disposable { if (!clientId) { distinctAdd(this.actionMessageHandlers.get(GLOBAL_HANDLER_ID)!, handler); return Disposable.create(() => remove(this.actionMessageHandlers.get(GLOBAL_HANDLER_ID)!, handler)); } if (!this.actionMessageHandlers.has(clientId)) { this.actionMessageHandlers.set(clientId, [handler]); } else { distinctAdd(this.actionMessageHandlers.get(clientId)!, handler); } return Disposable.create(() => remove(this.actionMessageHandlers.get(clientId)!, handler)); } get currentState(): ClientState { return this.state; } onStart(): Promise<void> { return this.onStartDeferred.promise; } onStop(): Promise<void> { return this.onStopDeferred.promise; } get id(): string { return this.options.id; } protected checkState(): void | never { if (this.state !== ClientState.Running) { throw new Error(`Client with id '${this.id}' is not in 'Running' state`); } } setStartupTimeout(ms: number): void { this.startupTimeout = ms; } }