UNPKG

@actyx/sdk

Version:
174 lines 7.37 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MultiplexedWebsocket = exports.ResponseMessageType = exports.RequestMessage = exports.RequestMessageType = exports.RequestId = void 0; const webSocket_1 = require("../../node_modules/rxjs/webSocket"); const rxjs_1 = require("../../node_modules/rxjs"); const t = require("io-ts"); const util_1 = require("../util"); const WebSocket = require("isomorphic-ws"); const log_1 = require("./log"); const error_1 = require("../util/error"); /** * Unique request id to be chosen by the client. 53 bit integer. Reusing existing request id will cancel the current * request with that id. */ exports.RequestId = t.number; var RequestMessageType; (function (RequestMessageType) { RequestMessageType["Request"] = "request"; RequestMessageType["Cancel"] = "cancel"; })(RequestMessageType = exports.RequestMessageType || (exports.RequestMessageType = {})); const DoRequestMsg = t.type({ type: t.literal("request" /* Request */), requestId: exports.RequestId, serviceId: t.string, payload: t.unknown, }); const CancelMsg = t.type({ type: t.literal("cancel" /* Cancel */), requestId: exports.RequestId, }); exports.RequestMessage = t.union([DoRequestMsg, CancelMsg]); var ResponseMessageType; (function (ResponseMessageType) { ResponseMessageType["Next"] = "next"; ResponseMessageType["Error"] = "error"; ResponseMessageType["Complete"] = "complete"; })(ResponseMessageType = exports.ResponseMessageType || (exports.ResponseMessageType = {})); const summariseEvent = (e, level = 0) => { if (Array.isArray(e)) { return ('[' + (level > 2 ? '...' : e.map((x, idx) => (idx === 0 ? summariseEvent(x, level + 1) : '')).join()) + ']'); } if (typeof e === 'object' && e !== null) { return ('{' + Object.entries(e) .map(([k, v]) => k === 'type' ? `type:${v}` : k === 'payload' ? `payload:${summariseEvent(v, level + 1)}` : k) .join() + '}'); } return `${e}`; }; class MultiplexedWebsocket { constructor(config, redialAfter) { this.config = config; this.redialAfter = redialAfter; this.requestId = 0; this.lastDial = 0; this.errors = new rxjs_1.Subject(); this.disconnected = true; this.queue = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any config.WebSocketCtor || (config.WebSocketCtor = WebSocket); this.subject = (0, webSocket_1.webSocket)(config); this.keepAlive(); } keepAlive() { var _a; log_1.default.ws.debug('dialling', this.config.url); this.disconnected = false; this.lastDial = Date.now(); (_a = this.subject) === null || _a === void 0 ? void 0 : _a.subscribe({ next: (msg) => log_1.default.ws.debug('received message:', summariseEvent(msg)), error: (err) => { log_1.default.ws.error('connection error:', (0, error_1.massageError)(err)); this.disconnected = true; const now = Date.now(); const delay = Math.max(this.lastDial + this.redialAfter, now) - now; log_1.default.ws.debug(`triggering reconnect in ${delay}ms`); setTimeout(() => this.subject && this.keepAlive(), delay); this.errors.next(err); }, complete: () => { log_1.default.ws.warn('WebSocket closed'); this.disconnected = true; const now = Date.now(); const delay = Math.max(this.lastDial + this.redialAfter, now) - now; setTimeout(() => this.subject && this.keepAlive(), delay); }, }); this.retryAll(); } close() { const s = this.subject; this.subject = null; s === null || s === void 0 ? void 0 : s.complete(); this.queue.forEach(([_d, _s, _p, subject]) => (0, rxjs_1.throwError)(() => new Error('disconnected from Actyx')).subscribe(subject)); this.queue.length = 0; } enqueue(serviceId, payload) { const s = new rxjs_1.Subject(); this.queue.push([new Date(), serviceId, payload, s]); setTimeout(() => this.prune(), this.redialAfter * 1.5); return s; } prune() { if (this.queue.length < 1 || this.queue[0][0].valueOf() + this.redialAfter * 1.5 < Date.now()) { return; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const [_date, _serviceId, _payload, subject] = this.queue.shift(); (0, rxjs_1.throwError)(() => new Error('currently disconnected from Actyx')).subscribe(subject); } retryAll() { this.queue.forEach(([_date, serviceId, payload, subject]) => this.request(serviceId, payload).subscribe(subject)); this.queue.length = 0; } request(serviceId, payload) { if (this.subject === null) { this.subject = (0, webSocket_1.webSocket)(this.config); this.keepAlive(); } if (serviceId === 'wake up') { // the purpose was just to start a new webSocket return rxjs_1.NEVER; } if (this.disconnected) { log_1.default.ws.debug('enqueueing request for', serviceId); return this.enqueue(serviceId, payload || null); } log_1.default.ws.debug('got request for service', serviceId); const requestId = this.requestId++; const reqMsg = { type: "request" /* Request */, requestId, serviceId, payload: payload || null, }; const cancelMsg = { type: "cancel" /* Cancel */, requestId, }; return this.subject .multiplex(() => reqMsg, () => cancelMsg, (msg) => msg.requestId === requestId) .pipe((0, rxjs_1.catchError)((err) => (0, rxjs_1.throwError)(() => (0, error_1.massageError)(err))), (0, rxjs_1.takeWhile)((msg) => msg.type !== "complete" /* Complete */), (0, rxjs_1.mergeMap)((msg) => { switch (msg.type) { case "next" /* Next */: return (0, rxjs_1.of)(msg); case "error" /* Error */: { const myMsg = msg; if (typeof myMsg.kind === 'object' && myMsg.kind !== null) { const { type, value } = myMsg.kind; if (type === 'serviceError' && value === 'Channel towards event store is overloaded.') { log_1.default.ws.info('retrying request for service', serviceId, 'due to rate limit'); return (0, rxjs_1.interval)(100).pipe((0, rxjs_1.take)(1), (0, rxjs_1.mergeMap)(() => this.request(serviceId, payload))); } } return (0, rxjs_1.throwError)(() => msg); } default: (0, util_1.unreachable)(); } })); } } exports.MultiplexedWebsocket = MultiplexedWebsocket; //# sourceMappingURL=multiplexedWebSocket.js.map