UNPKG

surrealdb-driver

Version:
258 lines (224 loc) 5.99 kB
import * as WebSocket from 'ws'; import {ConnectionProvider} from './connection-provider'; import {Record} from '../interface/record.interface'; import {Response} from '../interface/response.interface'; import {AuthenticationException} from '../exception/authentication.exception'; import {UnknownException} from '../exception/unknown.exception'; import {RequestQueue} from '../interface/request-queue.interface'; import {ClientOptionsLogger} from '../interface/client-options.interface'; /** * @class ConnectionHandler */ export class ConnectionHandler { /** * Store exceptions by error codes * * @type {{code: number, exception: Error}[]} */ private _exceptions: {code: number; exception: Error}[] = [ { code: -32000, exception: new AuthenticationException(), }, ]; /** * @type {string} */ private _id = '0'; /** * @type {boolean} */ private _isConnected = false; /** * Store results of sent objects * * @type {{[key: number]: string}} */ private _records: {[key: number]: string} = {}; /** * Contains requests that are queued up * * @type {RequestQueue} */ private _holdingQueue: RequestQueue = {}; /** * NOTICE we could use NodeJS.Timer here, but I guess it would prevent * to run in browser * * @type {any} */ private _pingInterval: any; /** * ConnectionHandler constructor * * @constructor * @param {ConnectionProvider} provider * @param {ClientOptionsLogger} logger */ constructor( public readonly provider: ConnectionProvider, private readonly logger?: ClientOptionsLogger ) { this.assignEvents(); } /** * @private */ private assignEvents(): void { this.provider.connection.addEventListener('open', () => this.onOpen()); this.provider.connection.addEventListener('error', (e: any) => this.onError(e) ); this.provider.connection.addEventListener('close', (e: any) => this.onClose(e) ); } /** * @private * @async * @returns {Promise<string>} */ private async generateID(): Promise<string> { return new Promise<string>((res) => res( (this._id = ( (Number(this._id) + 1) % Number.MAX_SAFE_INTEGER ).toString()) ) ); } /** * Send payload to Surreal * * @private * @async * @param record Payload * @returns {Promise<any>} */ private async sendPayload(record: Record): Promise<any> { return new Promise((resolve) => { const payload = JSON.stringify(record); this.provider.connection.send(payload); if (this.logger && this.logger.log) { this.logger.log(payload); } this.provider.connection.onmessage = async (e) => { await this.handleMessage(e); resolve(this._records[Number(record.id)] as any); }; }); } /** * Generates payload * * @public * @param {string} method * @param {string} params * @returns {Promise<K>} */ public async send<T, K = any>(method: string, params?: T): Promise<K> { const record: Record = { id: await this.generateID(), method, params: params ? (Array.isArray(params) ? params : [params]) : [], }; if (!this.provider.connection || !this._isConnected) { return new Promise( (resolve) => (this._holdingQueue[Number(record.id)] = {record, resolve}) ); } return (await this.sendPayload(record)) as K; } /** * Handle incoming message * * @public * @async * @param {WebSocket.MessageEvent} e */ public async handleMessage(e: WebSocket.MessageEvent): Promise<void> { const response = JSON.parse(e.data as string) as Response; if (this.logger && this.logger.log) { this.logger.log(e.data); } if ('error' in response) { const exception = this._exceptions.filter( (e) => response.error.code === e.code ); if (!exception || exception.length === 0) { throw new UnknownException(response.error.code, response.error.message); } throw exception[0].exception; } if (!('id' in response) && !('result' in response)) { return; } const requestId = Number(response.id); const result = (this._records[Number(response.id)] = response.result); const queuedRequest = this._holdingQueue[requestId]; if (queuedRequest) { queuedRequest.resolve(result); } } /** * Close connection * * @public * @async * @returns {Promise<void>} */ public async close(): Promise<void> { return new Promise<void>(() => { this.provider.connection.close(); }); } /** * Handle error * * @public * @param {any} e */ public onError(e: any): void { if (this.logger && this.logger.error) { this.logger.error(e); } } /** * Handle close * * @public * @param {any} e */ public onClose(e: any): void { if (this._pingInterval) { clearInterval(this._pingInterval); } if (this.logger && this.logger.log) { this.logger.log('Connection closed'); } } /** * Handle open * * @public */ public onOpen(): void { this._isConnected = true; if (this.logger && this.logger.log) { this.logger.log(`Successfully connected`); } if (!this._pingInterval) { this._pingInterval = setInterval( async () => await this.send('ping'), 30000 ); } if (Object.keys(this._holdingQueue).length > 0) { Object.keys(this._holdingQueue).forEach(async (e) => { await this.sendPayload(this._holdingQueue[e].record); delete this._holdingQueue[e]; }); } } }