UNPKG

@eclipse-emfcloud/modelserver-theia

Version:
202 lines (183 loc) 8.49 kB
/******************************************************************************** * Copyright (c) 2019-2022 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 * https://www.eclipse.org/legal/epl-2.0, or the MIT License which is * available at https://opensource.org/licenses/MIT. * * SPDX-License-Identifier: EPL-2.0 OR MIT *******************************************************************************/ import { AnyObject, CloseNotification, CommandExecutionResult, Diagnostic, DirtyStateNotification, ErrorNotification, FullUpdateNotification, IncrementalUpdateNotification, IncrementalUpdateNotificationV2, MessageDataMapper, MessageType, ModelServerMessage, ModelServerNotification, Operations, UnknownNotification, ValidationNotification } from '@eclipse-emfcloud/modelserver-client'; import { Emitter, Event } from '@theia/core'; import { injectable } from '@theia/core/shared/inversify'; import { applyPatch, deepClone } from 'fast-json-patch'; import WebSocket from 'isomorphic-ws'; import URI from 'urijs'; import { ModelServerFrontendClient } from '../common'; export const ModelServerSubscriptionService = Symbol('ModelServerSubscriptionService'); export interface ModelServerSubscriptionService { readonly onOpenListener: Event<ModelServerNotification>; readonly onClosedListener: Event<CloseNotification>; readonly onErrorListener: Event<ErrorNotification>; readonly onDirtyStateListener: Event<DirtyStateNotification>; readonly onIncrementalUpdateListener: Event<IncrementalUpdateNotification>; readonly onFullUpdateListener: Event<FullUpdateNotification>; readonly onSuccessListener: Event<ModelServerNotification>; readonly onUnknownMessageListener: Event<UnknownNotification>; readonly onValidationResultListener: Event<ValidationNotification>; } export const ModelServerSubscriptionServiceV2 = Symbol('ModelServerSubscriptionServiceV2'); export interface ModelServerSubscriptionServiceV2 extends ModelServerSubscriptionService { readonly onIncrementalUpdateListenerV2: Event<IncrementalUpdateNotificationV2>; } @injectable() export class ModelServerSubscriptionClient implements ModelServerFrontendClient, ModelServerSubscriptionService { onOpen(modeluri: URI, _event: WebSocket.Event): void { this.onOpenEmitter.fire({ modeluri, type: MessageType.open }); } onClose(modeluri: URI, event: WebSocket.CloseEvent): void { this.onClosedEmitter.fire({ modeluri, code: event.code, reason: event.reason, type: MessageType.close }); } onError(modeluri: URI, event: WebSocket.ErrorEvent): void { this.onErrorEmitter.fire({ modeluri, error: event.error, type: MessageType.error }); } onMessage(modeluri: URI, event: WebSocket.MessageEvent): void { const message = JSON.parse(event.data.toString()); if (ModelServerMessage.is(message)) { const type = MessageType.asMessageType(message.type); switch (type) { case MessageType.dirtyState: { this.onDirtyStateEmitter.fire({ modeluri, isDirty: MessageDataMapper.asBoolean(message), type }); break; } case MessageType.keepAlive: case MessageType.success: { this.onSuccessEmitter.fire({ modeluri, type }); break; } case MessageType.error: { this.onErrorEmitter.fire({ modeluri, error: MessageDataMapper.asString(message), type }); break; } case MessageType.incrementalUpdate: { let result: CommandExecutionResult | string; try { result = MessageDataMapper.as(message, CommandExecutionResult.is); } catch (error) { result = MessageDataMapper.asString(message); } this.onIncrementalUpdateEmitter.fire({ modeluri, result, type }); break; } case MessageType.fullUpdate: { let model: AnyObject | string; try { model = MessageDataMapper.asObject(message); } catch (error) { model = MessageDataMapper.asString(message); } this.onFullUpdateEmitter.fire({ modeluri, model, type }); break; } case MessageType.validationResult: { this.onValidationResultEmitter.fire({ modeluri, diagnostic: MessageDataMapper.as(message, Diagnostic.is), type }); break; } default: { this.onUnknownMessageEmitter.fire({ ...message, modeluri }); } } } } protected onOpenEmitter = new Emitter<Readonly<ModelServerNotification>>(); get onOpenListener(): Event<ModelServerNotification> { return this.onOpenEmitter.event; } protected onClosedEmitter = new Emitter<Readonly<CloseNotification>>(); get onClosedListener(): Event<CloseNotification> { return this.onClosedEmitter.event; } protected onErrorEmitter = new Emitter<Readonly<ErrorNotification>>(); get onErrorListener(): Event<ErrorNotification> { return this.onErrorEmitter.event; } protected onDirtyStateEmitter = new Emitter<Readonly<DirtyStateNotification>>(); get onDirtyStateListener(): Event<DirtyStateNotification> { return this.onDirtyStateEmitter.event; } protected onIncrementalUpdateEmitter = new Emitter<Readonly<IncrementalUpdateNotification>>(); get onIncrementalUpdateListener(): Event<IncrementalUpdateNotification> { return this.onIncrementalUpdateEmitter.event; } protected onFullUpdateEmitter = new Emitter<Readonly<FullUpdateNotification>>(); get onFullUpdateListener(): Event<FullUpdateNotification> { return this.onFullUpdateEmitter.event; } protected onSuccessEmitter = new Emitter<Readonly<ModelServerNotification>>(); get onSuccessListener(): Event<ModelServerNotification> { return this.onSuccessEmitter.event; } protected onUnknownMessageEmitter = new Emitter<Readonly<UnknownNotification>>(); get onUnknownMessageListener(): Event<UnknownNotification> { return this.onUnknownMessageEmitter.event; } protected onValidationResultEmitter = new Emitter<Readonly<ValidationNotification>>(); get onValidationResultListener(): Event<ValidationNotification> { return this.onValidationResultEmitter.event; } } /** * Implementation of {@link ModelServerSubscriptionClient} compatible with API V2 Notifications, * supporting Json Patch for incremental updates. */ @injectable() export class ModelServerSubscriptionClientV2 extends ModelServerSubscriptionClient implements ModelServerSubscriptionServiceV2 { protected onIncrementalUpdateEmitterV2 = new Emitter<Readonly<IncrementalUpdateNotificationV2>>(); get onIncrementalUpdateListenerV2(): Event<IncrementalUpdateNotificationV2> { return this.onIncrementalUpdateEmitterV2.event; } onMessage(modeluri: URI, event: WebSocket.MessageEvent): void { const message = JSON.parse(event.data.toString()); if (ModelServerMessage.is(message)) { const type = MessageType.asMessageType(message.type); switch (type) { case MessageType.incrementalUpdate: { const patch = MessageDataMapper.as(message, Operations.isPatch); this.onIncrementalUpdateEmitterV2.fire({ type, modeluri, patch, patchModel: (model, copy) => { const modelToPatch = copy ? deepClone(model) : model; return applyPatch(modelToPatch, patch).newDocument; } }); break; } } } super.onMessage(modeluri, event); } }