@actyx/sdk
Version:
Actyx SDK
174 lines • 7.37 kB
JavaScript
;
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