due-client
Version:
a client written in typescript of the due game framework
411 lines (399 loc) • 13 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _typeof(obj) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
}, _typeof(obj);
}
function _toPrimitive(input, hint) {
if (_typeof(input) !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (_typeof(res) !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return _typeof(key) === "symbol" ? key : String(key);
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", {
writable: false
});
return Constructor;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var BIG_ENDIAN = 'big';
var LITTLE_ENDIAN = 'little';
var DEFAULT_BYTE_ORDER = BIG_ENDIAN;
var DEFAULT_SIZE_BYTES = 4;
var DEFAULT_HEADER_BYTES = 1;
var DEFAULT_ROUTE_BYTES = 2;
var DEFAULT_SEQ_BYTES = 2;
var DEFAULT_TIMESTAMP_BYTES = 8;
var DEFAULT_BUFFER_BYTES = 5000;
var Packer = /*#__PURE__*/function () {
function Packer(opts) {
_classCallCheck(this, Packer);
_defineProperty(this, "byteOrder", void 0);
_defineProperty(this, "seqBytes", void 0);
_defineProperty(this, "routeBytes", void 0);
_defineProperty(this, "bufferBytes", void 0);
_defineProperty(this, "heartbeat", void 0);
this.byteOrder = opts ? opts.byteOrder ? opts.byteOrder : DEFAULT_BYTE_ORDER : DEFAULT_BYTE_ORDER;
this.routeBytes = opts ? opts.routeBytes ? opts.routeBytes : DEFAULT_ROUTE_BYTES : DEFAULT_ROUTE_BYTES;
this.seqBytes = opts ? opts.seqBytes !== undefined ? opts.seqBytes : DEFAULT_SEQ_BYTES : DEFAULT_SEQ_BYTES;
this.bufferBytes = opts ? opts.bufferBytes !== undefined ? opts.bufferBytes : DEFAULT_BUFFER_BYTES : DEFAULT_BUFFER_BYTES;
this.heartbeat = this.doPackHeartbeat();
}
_createClass(Packer, [{
key: "packHeartbeat",
value: function packHeartbeat() {
return this.heartbeat;
}
}, {
key: "doPackHeartbeat",
value: function doPackHeartbeat() {
var offset = 0;
var arrayBuffer = new ArrayBuffer(DEFAULT_SIZE_BYTES + DEFAULT_HEADER_BYTES);
var dataView = new DataView(arrayBuffer);
dataView.setUint32(offset, DEFAULT_HEADER_BYTES);
offset += DEFAULT_SIZE_BYTES;
dataView.setUint8(offset, 1 << 7);
offset += DEFAULT_HEADER_BYTES;
return arrayBuffer;
}
}, {
key: "packMessage",
value: function packMessage(message) {
var seq = message.seq || 0;
var route = message.route;
var offset = 0;
var size = DEFAULT_HEADER_BYTES + this.routeBytes + this.seqBytes + (message.buffer ? message.buffer.length : 0);
var isLittleEndian = this.byteOrder == LITTLE_ENDIAN;
if (route > Math.pow(2, 8 * this.routeBytes) / 2 - 1 || route < Math.pow(2, 8 * this.routeBytes) / -2) {
throw 'route overflow';
}
if (this.seqBytes > 0 && (seq > Math.pow(2, 8 * this.seqBytes) / 2 - 1 || seq < Math.pow(2, 8 * this.seqBytes) / -2)) {
throw 'seq overflow';
}
if (message.buffer && message.buffer.length > this.bufferBytes) {
throw 'message too large';
}
var arrayBuffer = new ArrayBuffer(DEFAULT_SIZE_BYTES + size);
var dataView = new DataView(arrayBuffer);
dataView.setUint32(offset, size, isLittleEndian);
offset += DEFAULT_SIZE_BYTES;
dataView.setUint8(offset, 0);
offset += DEFAULT_HEADER_BYTES;
switch (this.routeBytes) {
case 1:
dataView.setInt8(offset, route);
break;
case 2:
dataView.setInt16(offset, route, isLittleEndian);
break;
case 4:
dataView.setInt32(offset, route, isLittleEndian);
break;
}
offset += this.routeBytes;
switch (this.seqBytes) {
case 1:
dataView.setInt8(offset, seq);
break;
case 2:
dataView.setInt16(offset, seq, isLittleEndian);
break;
case 4:
dataView.setInt32(offset, seq, isLittleEndian);
break;
}
offset += this.seqBytes;
if (message.buffer) {
for (var i = 0; i < message.buffer.length; i++) {
dataView.setUint8(offset, message.buffer[i]);
offset += 1;
}
}
return arrayBuffer;
}
}, {
key: "unpack",
value: function unpack(data) {
var offset = 0;
var dataView = new DataView(data);
var isLittleEndian = this.byteOrder == LITTLE_ENDIAN;
var size = dataView.getUint32(offset, isLittleEndian);
offset += DEFAULT_SIZE_BYTES;
var header = dataView.getUint8(offset);
offset += DEFAULT_HEADER_BYTES;
var isHeartbeat = header >> 7 == 1;
if (isHeartbeat) {
if (size + DEFAULT_SIZE_BYTES > offset) {
var millisecond = Number(dataView.getBigUint64(offset, isLittleEndian).toString());
offset += DEFAULT_TIMESTAMP_BYTES;
return {
isHeartbeat: isHeartbeat,
millisecond: millisecond
};
} else {
return {
isHeartbeat: isHeartbeat
};
}
} else {
var message = {
seq: 0,
route: 0
};
if (this.routeBytes) {
switch (this.routeBytes) {
case 1:
message.route = dataView.getInt8(offset);
break;
case 2:
message.route = dataView.getInt16(offset, isLittleEndian);
break;
case 4:
message.route = dataView.getInt32(offset, isLittleEndian);
break;
}
}
offset += this.routeBytes;
switch (this.seqBytes) {
case 1:
message.seq = dataView.getInt8(offset);
break;
case 2:
message.seq = dataView.getInt16(offset, isLittleEndian);
break;
case 4:
message.seq = dataView.getInt32(offset, isLittleEndian);
break;
}
offset += this.seqBytes;
if (size + DEFAULT_SIZE_BYTES > offset) {
message.buffer = new Uint8Array(data, offset);
offset += message.buffer.length;
}
return {
isHeartbeat: isHeartbeat,
message: message
};
}
}
}]);
return Packer;
}();
var Client = /*#__PURE__*/function () {
function Client(opts) {
_classCallCheck(this, Client);
_defineProperty(this, "connectHandler", void 0);
_defineProperty(this, "disconnectHandler", void 0);
_defineProperty(this, "receiveHandler", void 0);
_defineProperty(this, "errorHandler", void 0);
_defineProperty(this, "heartbeatHandler", void 0);
_defineProperty(this, "opts", void 0);
_defineProperty(this, "websocket", void 0);
_defineProperty(this, "intervalId", void 0);
_defineProperty(this, "packer", void 0);
_defineProperty(this, "buffer", void 0);
_defineProperty(this, "waitgroup", void 0);
this.opts = opts;
this.websocket = undefined;
this.packer = opts.packer || new Packer(opts.packer);
this.waitgroup = new Map();
}
_createClass(Client, [{
key: "connect",
value: function connect() {
var _this = this;
try {
this.disconnect();
this.websocket = new WebSocket(this.opts.url);
this.websocket.binaryType = 'arraybuffer';
this.websocket.onopen = function (ev) {
_this.heartbeat();
_this.connectHandler && _this.connectHandler(_this);
};
this.websocket.onclose = function (ev) {
_this.disconnectHandler && _this.disconnectHandler(_this);
};
this.websocket.onerror = function (ev) {
_this.errorHandler && _this.errorHandler(_this);
};
this.websocket.onmessage = function (e) {
if (e.data.byteLength == 0) {
return;
}
var packet = _this.packer.unpack(e.data);
if (packet.isHeartbeat) {
_this.heartbeatHandler && _this.heartbeatHandler(_this, packet.millisecond);
} else {
packet.message && (_this.invoke(packet.message) || _this.receiveHandler && _this.receiveHandler(_this, packet.message));
}
};
return true;
} catch (error) {
console.log(error);
return false;
}
}
}, {
key: "disconnect",
value: function disconnect() {
if (!this.websocket) {
return;
}
this.intervalId && clearInterval(this.intervalId);
this.intervalId = null;
this.websocket.close();
}
}, {
key: "heartbeat",
value: function heartbeat() {
var _this2 = this;
if (!this.opts.heartbeat || this.opts.heartbeat <= 0) {
return;
}
var data = this.packer.packHeartbeat();
this.intervalId = setInterval(function () {
_this2.isConnected() && _this2.websocket && _this2.websocket.send(data);
}, this.opts.heartbeat);
}
}, {
key: "onConnect",
value: function onConnect(handler) {
this.connectHandler = handler;
}
}, {
key: "onDisconnect",
value: function onDisconnect(handler) {
this.disconnectHandler = handler;
}
}, {
key: "onReceive",
value: function onReceive(handler) {
this.receiveHandler = handler;
}
}, {
key: "onError",
value: function onError(handler) {
this.errorHandler = handler;
}
}, {
key: "onHeartbeat",
value: function onHeartbeat(handler) {
this.heartbeatHandler = handler;
}
}, {
key: "isConnected",
value: function isConnected() {
return this.websocket !== undefined && this.websocket.readyState == WebSocket.OPEN;
}
}, {
key: "isConnecting",
value: function isConnecting() {
return this.websocket !== undefined && this.websocket.readyState === WebSocket.CONNECTING;
}
}, {
key: "send",
value: function send(message) {
if (this.isConnected()) {
var data = this.packer.packMessage(message);
this.websocket && this.websocket.send(data);
return true;
}
return false;
}
}, {
key: "request",
value: function request(route, buffer, timeout) {
var _this3 = this;
return new Promise(function (resolve, reject) {
if (_this3.isConnected()) {
var group = _this3.waitgroup.get(route);
if (group === undefined) {
group = {
seq: 0,
callback: new Map()
};
_this3.waitgroup.set(route, group);
}
var seq = ++group.seq;
var timeoutId;
if (timeout && timeout > 0) {
timeoutId = setTimeout(function () {
reject();
}, timeout);
}
group.callback.set(seq, function (message) {
timeoutId && clearTimeout(timeoutId);
resolve(message);
});
_this3.send({
seq: seq,
route: route,
buffer: buffer
});
} else {
reject();
}
});
}
}, {
key: "invoke",
value: function invoke(message) {
if (message.seq === undefined || message.seq === 0) {
return false;
}
var group = this.waitgroup.get(message.route);
if (group === undefined) {
return false;
}
var callback = group.callback.get(message.seq || 0);
group.callback["delete"](message.seq || 0);
callback && callback(message);
return true;
}
}]);
return Client;
}();
exports.Client = Client;
exports.Packer = Packer;