UNPKG

@savantly/gremlin-js

Version:
199 lines 7.45 kB
import { Graphson } from './graphson'; import { GremlinQueryOperation } from './gremlin.query.operation'; import { GremlinQueryResponse } from './gremlin.query.response'; import { assertNotEmpty } from './utils'; export class GremlinWebSocket { constructor(options) { this.options = options; this._queries = {}; this._queue = new Array(); this.maxReconnects = 30; this.reconnectAttempts = 0; this.open(); } isOpen() { return this._ws && this._ws.OPEN === this._ws.readyState; } execute(gremlinQuery) { assertNotEmpty(gremlinQuery.id); this._queries[gremlinQuery.id] = gremlinQuery; const graphson = this.queryToGraphson(gremlinQuery, GremlinQueryOperation.eval); this._queue.push(graphson); if (this.isOpen()) { this.executeQueue(); } } sendBinaryMessage(binaryData) { this._ws.send(binaryData); } /** * Process the current command queue, sending commands to Gremlin Server * (First In, First Out). */ executeQueue() { while (this._queue.length > 0) { const graphson = this._queue.shift(); const binaryData = this.graphsonToBinary(graphson); this.sendBinaryMessage(binaryData); } } cancelPendingCommands({ message, details }) { this._queue.length = 0; this._queries = {}; } arrayBufferToString(buffer) { console.log('converting buffer to string'); const bufView = new Uint8Array(buffer); const length = bufView.length; let result = ''; let addition = Math.pow(2, 8) - 1; for (let i = 0; i < length; i += addition) { if (i + addition > length) { addition = length - i; } result += String.fromCharCode.apply(null, bufView.subarray(i, i + addition)); } console.log('extracted string from buffer: ' + result); return result; } /* * Process all incoming raw message events sent by Gremlin Server, and dispatch * to the appropriate command. * */ onMessage(message) { let rawMessage; let requestId; let statusCode; let statusMessage; console.log('web socket received message'); try { const { data } = message; const rawMessageString = this.arrayBufferToString(data); rawMessage = JSON.parse(rawMessageString); requestId = rawMessage.requestId; statusCode = rawMessage.status.code; statusMessage = rawMessage.status.message; } catch (e) { console.warn('MalformedResponse', 'Received malformed response message'); console.log(message); return; } const gremlinResponse = new GremlinQueryResponse(); gremlinResponse.rawMessage = rawMessage; gremlinResponse.requestId = requestId; gremlinResponse.statusCode = statusCode; gremlinResponse.statusMessage = statusMessage; console.log('preparing to excecute callback for request'); // If we didn't find a waiting query for this response, emit a warning if (!this._queries[requestId]) { console.warn('OrphanedResponse', `Received response for missing or closed request: ${requestId}, status: ${statusCode}, ${statusMessage}`); return; } const query = this._queries[requestId]; switch (statusCode) { case 200:// SUCCESS delete this._queries[requestId]; // TODO: optimize performance query.onMessage(gremlinResponse); query.onMessage(null); break; case 204:// NO_CONTENT delete this._queries[requestId]; query.onMessage(null); break; case 206:// PARTIAL_CONTENT query.onMessage(rawMessage); break; case 407:// AUTHENTICATE CHALLANGE const challengeResponse = this.buildChallengeResponse(query); this._queue.push(challengeResponse); this.executeQueue(); break; default: delete this._queries[requestId]; console.error(statusMessage + ' (Error ' + statusCode + ')'); break; } } onClose(evt) { console.log('connection closed'); if (this._queue.length > 0) { console.warn(`${this._queue.length} queries are queued`); } if (this.reconnectAttempts < this.maxReconnects && !this.open()) { this.reconnect(); } else if (!this.open()) { console.warn('giving up reconnecting'); } else { this.reconnectAttempts = 0; } } reconnect() { setTimeout(() => { console.log('reconnecting'); this.onClose(null); }, 1000); } onOpen(evt) { console.log('opened connection'); this.executeQueue(); } onError(err) { console.log('an error occured'); console.error(err); } buildChallengeResponse(query) { const { processor, accept, language } = this.options; const saslbase64 = btoa('\0' + this.options.user + '\0' + this.options.password); const args = { sasl: saslbase64 }; const message = { requestId: query.id, processor, op: GremlinQueryOperation.authentication, args, }; const graphson = new Graphson(message); return graphson; } queryToGraphson(query, operation) { console.log(`queryToGraphson: ${query.id}`); return query.getGraphson(operation); } /* * returns a binary format ready for web-socket transfer */ graphsonToBinary(query) { const serializedMessage = this.options.accept + JSON.stringify(query); // Let's start packing the message into binary // mimeLength(1) + mimeType Length + serializedMessage Length const binaryMessage = new Uint8Array(1 + serializedMessage.length); binaryMessage[0] = this.options.accept.length; for (let i = 0; i < serializedMessage.length; i++) { binaryMessage[i + 1] = serializedMessage.charCodeAt(i); } return binaryMessage; } isConnecting() { return this._ws && this._ws.readyState === this._ws.CONNECTING; } /* * return true if open or connecting */ open() { if (this.isOpen() || this.isConnecting()) { return true; } const address = `ws${this.options.ssl ? 's' : ''}://${this.options.host}:${this.options.port}${this.options.path}`; this._ws = new WebSocket(address); this._ws.binaryType = 'arraybuffer'; this._ws.onopen = (evt) => { this.onOpen(evt); }; this._ws.onerror = (evt) => { this.onError(evt); }; this._ws.onmessage = (evt) => { this.onMessage(evt); }; this._ws.onclose = (evt) => { this.onClose(evt); }; return false; } } //# sourceMappingURL=gremlin.web.socket.js.map