diffusion
Version:
Diffusion JavaScript client
323 lines (322 loc) • 12.3 kB
JavaScript
"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();
}