UNPKG

diffusion

Version:

Diffusion JavaScript client

275 lines (222 loc) 8.35 kB
/*eslint valid-jsdoc: "off"*/ var encodeAsString = require('v4-stack/credential-tunnel').encodeAsString; var Emitter = require('events/emitter'); var Queue = require('util/queue'); var ResponseCode = require('protocol/response-code'); var BufferInputStream = require('io/buffer-input-stream'); var BufferOutputStream = require('io/buffer-output-stream'); var log = require('util/logger').create('XHR transport'); /** * Construct a valid URI from the given options. * * @param {Options} opts - The connection options. * @returns {String} The connection URI */ function constructURI(req, opts) { var scheme = opts.secure ? 'https' : 'http'; var uri = scheme + "://" + opts.host + ":" + opts.port + opts.path; var headers = { m : "0", ty : "B", ca : req.capabilities, r : opts.reconnect.timeout, v : req.version }; if (req.token) { headers.c = req.token; headers.cs = req.availableClientSequence; headers.ss =req.lastServerSequence; } if (opts.properties) { headers.sp = encodeURIComponent(JSON.stringify(opts.properties)); } if (opts.principal) { headers.username = encodeURIComponent(opts.principal); headers.password = encodeAsString(opts.credentials); } return { 'uri' : uri , 'headers' : headers }; } function XHRTransport(opts, xhr) { var emitter = Emitter.assign(this); var token; var pollSequence = 0; var pingSequence = 0; var URI; var pollRequest = null; var aborted = false; var isSending = false; var protocolVersion = null; var self = this; // Message message-queue var queue = Queue.create(); var constructor = xhr; /** * Establish a polling connection with the server. */ this.connect = function connect(req, handshake) { try { var url = constructURI(req, opts); protocolVersion = url.headers.v; URI = url.uri; var request = this.createRequest(url.headers, url.uri); log.debug('Created XHR', url.uri); request.onreadystatechange = function() { if (request.readyState === 4 ) { if (request.status === 200) { var handShakeData = request.responseText; // The initial connection is in plain-text no need for base64 encoding/decoding. var serverResponse = handshake(new Buffer(handShakeData, 'binary')); if (!serverResponse) { // If the handshake could not be parsed or there was some sort of error return // The transport will be closed by the error handler return; } token = serverResponse.token; var responseCode = serverResponse.response; var isSuccesss = ResponseCode.isSuccess(responseCode); if (isSuccesss) { self.poll(); } else { log.debug(responseCode.id + ' - ' + responseCode.message); emitter.close(); } } else { emitter.close(); } } }; request.send(null); } catch (error) { throw new Error('Unable to create polling transport', error); } }; /** * Aborting any opening poll request. */ this.close = function() { log.trace("Closing XHR transport"); if (pollRequest) { aborted = true; pollRequest.abort(); } // clear any pending messages queue = Queue.create(); emitter.close(); }; /** * This is the generic method to send data to the server * * @param headers the headers to send to the server */ this.createRequest = function createRequest(headers, uri) { var request = new constructor(); request.open("POST", uri, true); request.overrideMimeType('text\/plain; charset=x-user-defined'); for (var header in headers) { try { request.setRequestHeader(header,headers[header]); } catch(e) { log.warn("Can't set header " + header + ":" + headers.join(":")); } } return request; }; /** * Send a poll request to the server. */ this.poll = function poll() { var request = this.createRequest( { "m" : "1", "c" : token, "s" : pollSequence++, "v" : protocolVersion }, URI); request.onreadystatechange = function() { if (request.readyState === 4) { // From protocol 9+, the client should send a reconnect request when instructed by the server if (request.getResponseHeader('diffusion-connection') === 'reconnect') { emitter.close(); } else if (request.status === 200) { var data = request.responseText; var bis = new BufferInputStream(new Buffer(data, 'base64')); while (bis.pos !== bis.count) { var length = bis.readInt32(); if (length > opts.maxMessageSize) { emitter.error(new Error( "Received message of size: " + length + ", exceeding max message size: " + opts.maxMessageSize)); return; } var typeAndEncoding = bis.read(); var body = bis.readMany(length); var payload = new Buffer(body.length + 1); // +1 for message type and encoding byte. payload.writeUInt8(typeAndEncoding); body.copy(payload, 1); emitter.emit('data', payload); } self.poll(); } else { emitter.close(); } } }; pollRequest = request; request.send(null); }; /** * Queue message to be sent to the message message-queue. * * Delegate the actual request sending to #flush(). * * @param {Message} message to be queued */ this.dispatch = function dispatch(message) { if (aborted) { return false; } queue.add(message); if (isSending) { return true; } self.flush(); }; /** * Flush all pending messages from the message-queue and send upstream. */ this.flush = function flush() { if (queue.isEmpty()) { return; } var buffer = drainToBuffer(queue.drain()); var request = this.createRequest( { "m" : "2", "c" : token, "s" : pingSequence++, "v" : protocolVersion }, URI); request.onreadystatechange = function() { if (request.readyState === 4) { // From protocol 9+, the client should send a reconnect request when instructed by the server if (request.getResponseHeader('diffusion-connection') === 'reconnect') { emitter.close(); } else if (request.status === 200) { isSending = false; self.flush(); } else { emitter.close(); } } }; isSending = true; request.send(buffer.toString('base64')); }; } function drainToBuffer (messages) { var messagesBOS = new BufferOutputStream(); for (var i = 0; i < messages.length; i++) { var msg = messages[i]; messagesBOS.writeInt32(msg.length - 1); // Skip the message type byte. messagesBOS.writeMany(msg, 0, msg.length); } return messagesBOS.getBuffer(); } module.exports = XHRTransport;