UNPKG

diffusion

Version:

Diffusion JavaScript client

323 lines (322 loc) 12.3 kB
"use strict"; /** * @module Transport */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.XHRTransport = void 0; var errors_1 = require("./../../errors/errors"); var emitter_1 = require("./../events/emitter"); var stream_1 = require("./../events/stream"); var buffer_input_stream_1 = require("./../io/buffer-input-stream"); var buffer_output_stream_1 = require("./../io/buffer-output-stream"); var response_code_1 = require("./../protocol/response-code"); var logger_1 = require("./../util/logger"); var queue_1 = require("./../util/queue"); var credential_tunnel_1 = require("./../v4-stack/credential-tunnel"); var uint8array_1 = require("../util/uint8array"); var log = logger_1.create('XHR transport'); /** * Construct a valid URI from the given options. * * @param req the connection request. * @param opts the connection options. * @returns an object containing the connection URI and the headers */ 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 }; // tslint:disable-next-line:strict-type-predicates if (req.token !== undefined) { 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 !== undefined) { headers.username = encodeURIComponent(opts.principal); headers.password = credential_tunnel_1.encodeAsString(opts.credentials); } return { uri: uri, headers: headers }; } /** * The implementation of the {@link Transport} interface for XHR polling */ var XHRTransport = /** @class */ (function (_super) { __extends(XHRTransport, _super); /** * Construct a new XHRTransport instance * * @param opts the connection options * @param xhr the transport constructor function */ function XHRTransport(opts, xhr) { var _this = this; var factory = emitter_1.Emitter.create(); _this = _super.call(this, factory) || this; _this.emitter = factory.emitter(_this); _this.opts = opts; _this.protocolVersion = null; _this.pollSequence = 0; _this.pingSequence = 0; _this.pollRequest = null; _this.aborted = false; _this.isSending = false; // message message-queue _this.queue = new queue_1.Queue(); _this.xhr = xhr; return _this; } /** * Establish a polling connection with the server. * * @param req the connection request * @param handshake a handshake deserialiser */ XHRTransport.prototype.connect = function (req, handshake) { var _this = this; try { var url = constructURI(req, this.opts); this.protocolVersion = url.headers.v; this.URI = url.uri; var request_1 = this.createRequest(url.headers, url.uri); log.debug('Created XHR', url.uri); request_1.onreadystatechange = function () { if (request_1.readyState === 4) { if (request_1.status === 200) { var handShakeData = request_1.responseText; // the initial connection is in plain-text no need for base64 encoding/decoding. var serverResponse = handshake(uint8array_1.uint8FromBinary(handShakeData)); 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; } _this.token = serverResponse.token; var responseCode = serverResponse.response; var isSuccesss = response_code_1.isSuccess(responseCode); if (isSuccesss) { _this.poll(); } else { log.debug(responseCode.id + ' - ' + responseCode.message); _this.emitter.close(); } } else { _this.emitter.close(); } } }; request_1.send(null); } catch (error) { throw new errors_1.RuntimeError("Unable to create polling transport " + error); } }; /** * Aborting any opening poll request. * * @return this instance */ XHRTransport.prototype.close = function () { log.trace('Closing XHR transport'); if (this.pollRequest) { this.aborted = true; this.pollRequest.abort(); } // clear any pending messages this.queue = new queue_1.Queue(); this.emitter.close(); return this; }; /** * This is the generic method to send data to the server * * @param headers the headers to send to the server * @param uri the URI to connect to */ XHRTransport.prototype.createRequest = function (headers, uri) { var e_1, _a; var request = new this.xhr(); request.open('POST', uri, true); request.overrideMimeType('text/plain; charset=x-user-defined'); try { for (var _b = __values(Object.getOwnPropertyNames(headers)), _c = _b.next(); !_c.done; _c = _b.next()) { var header = _c.value; try { request.setRequestHeader(header, headers[header]); } catch (e) { log.warn("Can't set header " + header + ":" + headers.join(':')); } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } return request; }; /** * Send a poll request to the server. */ XHRTransport.prototype.poll = function () { var _this = this; var request = this.createRequest({ m: '1', c: this.token, s: this.pollSequence++, v: this.protocolVersion }, this.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') { _this.emitter.close(); } else if (request.status === 200) { if (!_this.aborted) { _this.poll(); } var data = request.responseText; var bis = new buffer_input_stream_1.BufferInputStream(uint8array_1.uint8FromBase64(data)); while (bis.pos !== bis.count) { var length_1 = bis.readInt32(); if (length_1 > _this.opts.maxMessageSize) { _this.emitter.error(new Error("Received message of size: " + length_1 + ", " + ("exceeding max message size: " + _this.opts.maxMessageSize))); return; } var typeAndEncoding = bis.read(); var body = bis.readMany(length_1); var payload = new Uint8Array(body.length + 1); // +1 for message type and encoding byte. payload[0] = typeAndEncoding; payload.set(body, 1); _this.emitter.emit('data', payload); } } else { _this.emitter.close(); } } }; this.pollRequest = request; request.send(null); }; /** * Queue message to be sent to the message message-queue. * * Delegate the actual request sending to #flush(). * * @param message to be queued */ XHRTransport.prototype.dispatch = function (message) { if (this.aborted) { return false; } this.queue.add(message); if (this.isSending) { return true; } this.flush(); return undefined; }; /** * Flush all pending messages from the message-queue and send upstream. */ XHRTransport.prototype.flush = function () { var _this = this; if (this.queue.isEmpty()) { return; } var buffer = drainToBuffer(this.queue.drain()); var request = this.createRequest({ m: '2', c: this.token, s: this.pingSequence++, v: this.protocolVersion }, this.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') { _this.emitter.close(); } else if (request.status === 200) { _this.isSending = false; _this.flush(); } else { _this.emitter.close(); } } }; this.isSending = true; request.send(uint8array_1.uint8ToBase64(buffer)); }; return XHRTransport; }(stream_1.StreamImpl)); exports.XHRTransport = XHRTransport; /** * Create a single buffer containing multiple messages * * @param messages the messages to conflate * @return a single buffer containing all messages */ function drainToBuffer(messages) { var e_2, _a; var messagesBOS = new buffer_output_stream_1.BufferOutputStream(); try { for (var messages_1 = __values(messages), messages_1_1 = messages_1.next(); !messages_1_1.done; messages_1_1 = messages_1.next()) { var msg = messages_1_1.value; messagesBOS.writeInt32(msg.length - 1); // skip the message type byte. messagesBOS.writeMany(msg, 0, msg.length); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (messages_1_1 && !messages_1_1.done && (_a = messages_1.return)) _a.call(messages_1); } finally { if (e_2) throw e_2.error; } } return messagesBOS.getBuffer(); }