/********************************************************************************
 * Copyright (C) 2017 TypeFox 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 { MessageConnection , ResponseError } from 'vscode-ws-jsonrpc' ; import { ApplicationError } from '../application-error' ; import { Event , Emitter } from '../event' ; import { Disposable } from '../disposable' ; import { ConnectionHandler } from './handler' ; export type JsonRpcServer < Client > = Disposable & { /**
     * If this server is a proxy to a remote server then
     * a client is used as a local object
     * to handle JSON-RPC messages from the remote server.
     */ setClient ( client ) : void ; getClient ? ( ) : Client | undefined ; } ; export interface JsonRpcConnectionEventEmitter { readonly onDidOpenConnection : Event < void > ; readonly onDidCloseConnection : Event < void > ; } export type JsonRpcProxy < T > = T & JsonRpcConnectionEventEmitter ; export class JsonRpcConnectionHandler < T extends object > implements ConnectionHandler { constructor ( readonly path , readonly targetFactory , readonly factoryConstructor = JsonRpcProxyFactory ) { } onConnection ( connection ) { const factory = new this . factoryConstructor ( ) ; const proxy = factory . createProxy ( ) ; factory . target = this . targetFactory ( proxy ) ; factory . listen ( connection ) ; } } param export class JsonRpcProxyFactory < T extends object > implements ProxyHandler < T > { protected readonly onDidOpenConnectionEmitter = new Emitter < void > ( ) ; protected readonly onDidCloseConnectionEmitter = new Emitter < void > ( ) ; protected connectionPromiseResolve : ( connection : MessageConnection ) => void ; protected connectionPromise : Promise < MessageConnection > ; param target constructor ( public target ? ) { this . waitForConnection ( ) ; } protected waitForConnection ( ) { this . connectionPromise = new Promise ( resolve => this . connectionPromiseResolve = resolve ) ; this . connectionPromise . then ( connection => { connection . onClose ( ( ) => this . onDidCloseConnectionEmitter . fire ( undefined ) ) ; this . onDidOpenConnectionEmitter . fire ( undefined ) ; } ) ; } /**
     * Connect a MessageConnection to the factory.
     *
     * This connection will be used to send/receive JSON-RPC requests and
     * response.
     */ listen ( connection ) { connection . onRequest ( ( prop , ... args ) => this . onRequest ( prop , ... args ) ) ; connection . onNotification ( ( prop , ... args ) => this . onNotification ( prop , ... args ) ) ; connection . onDispose ( ( ) => this . waitForConnection ( ) ) ; connection . listen ( ) ; this . connectionPromiseResolve ( connection ) ; } returns protected async onRequest ( method , ... args ) { try { if ( this . target ) { return await this . target [ method ] ( ... args ) ; } else { throw new Error ( `no target was set to handle ${ method }` ) ; } } catch ( error ) { const e = this . serializeError ( error ) ; if ( e instanceof ResponseError ) { throw e ; } const reason = e . message || '' ; const stack = e . stack || '' ; console . error ( `Request ${ method } failed with error: ${ reason }` , stack ) ; throw e ; } } /**
     * Process an incoming JSON-RPC notification.
     *
     * Same as [[onRequest]], but called on incoming notifications rather than
     * methods calls.
     */ protected onNotification ( method , ... args ) { if ( this . target ) { this . target [ method ] ( ... args ) ; } } /**
     * Create a Proxy exposing the interface of an object of type T.  This Proxy
     * can be used to do JSON-RPC method calls on the remote target object as
     * if it was local.
     *
     * If `T` implements `JsonRpcServer` then a client is used as a target object for a remote target object.
     */ createProxy ( ) { const result = new Proxy < T > ( this as any , this ) ; return result as any ; } param target param p param receiver returns get ( target , p , receiver ) { if ( p === 'setClient' ) { return ( client ) => { this . target = client ; } ; } if ( p === 'getClient' ) { return ( ) => this . target ; } if ( p === 'onDidOpenConnection' ) { return this . onDidOpenConnectionEmitter . event ; } if ( p === 'onDidCloseConnection' ) { return this . onDidCloseConnectionEmitter . event ; } const isNotify = this . isNotification ( p ) ; return ( ... args ) => { const method = p . toString ( ) ; const capturedError = new Error ( `Request '${ method }' failed` ) ; return this . connectionPromise . then ( connection => new Promise ( ( resolve , reject ) => { try { if ( isNotify ) { connection . sendNotification ( method , ... args ) ; resolve ( undefined ) ; } else { const resultPromise = connection . sendRequest ( method , ... args ) as Promise < any > ; resultPromise . catch ( ( err ) => reject ( this . deserializeError ( capturedError , err ) ) ) . then ( ( result ) => resolve ( result ) ) ; } } catch ( err ) { reject ( err ) ; } } ) ) ; } ; } param p return protected isNotification ( p ) { return p . toString ( ) . startsWith ( 'notify' ) || p . toString ( ) . startsWith ( 'on' ) ; } protected serializeError ( e ) { if ( ApplicationError . is ( e ) ) { return new ResponseError ( e . code , '' , Object . assign ( { kind : 'application' } , e . toJson ( ) ) ) ; } return e ; } protected deserializeError ( capturedError , e ) { if ( e instanceof ResponseError ) { const capturedStack = capturedError . stack || '' ; if ( e . data && e . data . kind === 'application' ) { const { stack , data , message } = e . data ; return ApplicationError . fromJson ( e . code , { message : message || capturedError . message , data , stack : `${ capturedStack }\nCaused by: ${ stack }` } ) ; } e . stack = capturedStack ; } return e ; } }