UNPKG

@akala/json-rpc-ws

Version:

json-rpc websocket transport

124 lines (113 loc) 4.47 kB
import type { SocketAdapter, SerializableObject } from '@akala/core'; import debug from 'debug'; import * as stream from 'stream'; import { Connection as BaseConnection, type PayloadDataType, type SerializedBuffer, type Parent, Payload } from './shared-connection.js' const logger = debug('akala:json-rpc-ws'); function isBuffer(obj: unknown): obj is Uint8Array { return obj && obj instanceof Uint8Array; } export class Connection extends BaseConnection<stream.Readable> { constructor(socket: SocketAdapter<Payload<stream.Readable>>, parent: Parent<stream.Readable, Connection>) { super(socket, parent as Parent<stream.Readable, BaseConnection<stream.Readable>>); } protected isStream(result?: PayloadDataType<stream.Readable>): result is stream.Readable { return result instanceof stream.Readable; } protected sendStream(id: string | number, params: stream.Readable): void { const pt = new stream.PassThrough({ highWaterMark: 128 }); params.pipe(pt); pt.on('data', (chunk: Uint8Array | string) => { if (this.socket && this.socket.open) if (isBuffer(chunk)) { if (Buffer.isBuffer(chunk)) this.sendRaw({ id: id, result: { event: 'data', isBuffer: true, data: chunk.toJSON() } }); else this.sendRaw({ id: id, result: { event: 'data', isBuffer: true, data: { type: 'Buffer', data: chunk } } }); } else this.sendRaw({ id: id, result: { event: 'data', isBuffer: false, data: chunk.toString() } }); else { logger('socket was closed before endof stream') params.unpipe(pt); } }); pt.on('end', () => { if (this.socket.open) this.sendRaw({ id: id, result: { event: 'end' }, stream: true }); else logger('socket was closed before end of stream') }); } protected buildStream(this: Connection, id: string | number, result: PayloadDataType<stream.Readable>): SerializableObject & stream.Readable { const data: (string | Uint8Array | null)[] = []; let canPush = true; class temp extends stream.Readable { constructor() { super({ read: () => { if (data.length) { while (canPush) canPush = s.push(data.shift()); } canPush = true; } }); const src = result as SerializableObject; Object.getOwnPropertyNames(src).forEach((p) => { if (Object.getOwnPropertyDescriptor(this, p) == null) { if (src && src[p]) this[p] = src[p]; } }); } } const s = result = <SerializableObject & stream.Readable>new temp(); const f = this.responseHandlers[id] = (error, result: { event: string, isBuffer?: boolean, data?: SerializedBuffer | string }) => { if (error) s.emit('error', error); else switch (result.event) { case 'data': if (result.data) { let d: Uint8Array | string | undefined = undefined; if (typeof (result.data) == 'string') d = result.data; else d = Uint8Array.from(result.data.data); if (canPush) s.push(d); else data.push(d); } this.responseHandlers[id as string] = f; break; case 'end': if (canPush) s.push(null); else data.push(null); break; } } return s; } }