UNPKG

@akala/json-rpc-ws

Version:

json-rpc websocket transport

124 lines (112 loc) 3.97 kB
import { Base } from './base.js'; import debug from 'debug'; const logger = debug('akala:json-rpc-ws'); import { type PayloadDataType, Connection, Payload } from './shared-connection.js'; import { type Error as MyError } from './errors.js' import { IsomorphicBuffer, SocketProtocolAdapter, type SocketAdapter } from '@akala/core'; /** * json-rpc-ws connection * * @constructor * @param {Socket} socket - web socket for this connection * @param {Object} parent - parent that controls this connection */ export class JsonNDRpcSocketAdapter<T> extends SocketProtocolAdapter<T> implements SocketAdapter<T> { constructor(socket: SocketAdapter) { super({ receive: (data: string | IsomorphicBuffer) => JSON.parse((data instanceof IsomorphicBuffer ? data.toString('utf8') : data)), send: (data: T) => JSON.stringify(data) + '\n', }, socket); } } export default abstract class Client<TStreamable, TConnectOptions> extends Base<TStreamable> { constructor(private socketConstructor: (address: string, options?: TConnectOptions) => SocketAdapter<Payload<TStreamable>>, private options?: TConnectOptions) { super('client'); logger('new Client'); } public socket?: SocketAdapter<Payload<TStreamable>>; /** * Connect to a json-rpc-ws server * * @param {String} address - url to connect to i.e. `ws://foo.com/`. * @param {function} callback - optional callback to call once socket is connected * @public */ public connect(address: string, callback: (err?: Event) => void): void { logger('Client connect %s', address); if (this.isConnected()) throw new Error('Already connected'); let opened = false; const socket = this.socket = this.socketConstructor(address, this.options); socket.once('open', () => { // The client connected handler runs scoped as the socket so we can pass // it into our connected method like thisk this.connected(socket); opened = true; if (callback) callback.call(this); }); if (callback) this.socket.once('error', function socketError(err) { if (!opened) { callback.call(self, err); } }); } /** * Test whether we have a connection or not * * @returns {Boolean} whether or not we have a connection * @public */ public isConnected(): boolean { return Object.keys(this.connections).length !== 0; } /** * Return the current connection (there can be only one) * * @returns {Object} current connection * @public */ public getConnection(): Connection<TStreamable> { const ids = Object.keys(this.connections); return this.connections[ids[0]]; } /** * Close the current connection */ public disconnect(): Promise<CloseEvent> { if (!this.isConnected()) throw new Error('Not connected'); const connection = this.getConnection(); return connection.hangup(); } /** * Send a method request * * @param {String} method - name of method * @param {Array} params - optional parameters for method * @param {function} callback - optional reply handler * @public * @todo allow for empty params aka arguments.length === 2 */ public send<TParamType extends PayloadDataType<TStreamable>, TReplyType extends PayloadDataType<TStreamable>>(method: string, params: TParamType, callback?: (error?: MyError, result?: TReplyType) => void): void { logger('send %s', method); if (!this.isConnected()) throw new Error('Not connected'); const connection = this.getConnection(); connection.sendMethod(method, params, callback); } }