@onereach/webform
Version:
Content Builder includes several views for: - Content builder view itself; - Web Form view; - Slack block-kit builder;
214 lines (179 loc) • 6.84 kB
JavaScript
import _isObject from 'lodash/isObject';
import _get from 'lodash/get';
import _set from 'lodash/set';
import _compact from 'lodash/compact';
import _join from 'lodash/join';
import _assign from 'lodash/assign';
import { nanoid } from 'nanoid';
import WSClient from '@onereach/ws-client';
import config from '@/config';
export default class EventManager {
static DEFAULT_OUTBOUND_TIMEOUT = 30000;
static connecting = false;
static callbacks = {};
static partialResponses = {};
static previousCallback = null;
static debug = true
static wsClientDebug = true
static wsClient = null;
static initWSClient = ({ token, emBusURL, onConnect, onError, onClose, onOffline, onOnline }) => {
const handleConnect = () => {
if (this.previousCallback) {
this.previousCallback = null;
}
this.connecting = false;
onConnect();
};
config.debug && console.debug('[RWC::initWSClient] emBusURL:', emBusURL);
config.debug && console.debug('[RWC::initWSClient] token:', token);
this.wsClient = new WSClient({
debug: this.wsClientDebug,
wssUrl: emBusURL,
ping: 25000,
token,
onConnect: handleConnect,
onError,
onClose,
onOffline,
onOnline
});
}
static close = () => {
this.wsClient.close();
}
static initEventManagerWS = (onMessage) => {
if (this.wsClient.name) {
this.previousCallback = this.wsClient.name;
}
this.connecting = true;
this.wsClient.onMessage = this._getOnMessageHandler(onMessage);
this.wsClient.connect();
}
static getCallback = () => {
return this.wsClient ? this.wsClient.getCallback() : null;
}
static sendOutboundMessage = async (data, includeCallback = true, options) => {
console.log('sendOutboundMessage');
const timeout = data.timeout || this.DEFAULT_OUTBOUND_TIMEOUT;
const callbackId = nanoid();
if (!_isObject(data.message)) throw new Error('[WS::sendOutboundMessage] Message is required');
if (!this.wsClient) throw new Error('[WS::sendOutboundMessage] No active WS');
return new Promise((resolve, reject) => {
console.log("sendOutboundMessage Promise data -> ", data);
const message = data.message;
message.callback = { id: callbackId };
message.type = _get(data, ['type'], 'sync');
message.$v = 3;
this.callbacks[callbackId] = {
resolve,
reject,
timeout: setTimeout(() => {
console.warn(
`[WS::sendOutboundMessage] Request Timeout: ${_get(message, 'event.name')}`,
{ message: data.message }
);
reject(data.timeoutError || new Error('[WS::sendOutboundMessage] Timeout'));
}, timeout)
};
console.log('sendOutboundMessage this.callbacks -> ', this.callbacks);
this._sendOutboundMessage(message, includeCallback, options);
}).finally(() => {
clearTimeout(_get(this.callbacks, [callbackId, 'timeout']));
delete this.callbacks[callbackId];
});
}
static _getOnMessageHandler (onMessage) {
return (message = {}) => {
if (message.id && message.result && this.callbacks[message.id]) {
this._handleResponse(message);
onMessage({ ping: true });
} else if (message.id && Number(_get(message, ['part', 'n'])) > 0) {
this._handlePartialResponse(message);
onMessage({ ping: true });
} else {
onMessage(message);
}
};
}
static _handleResponse (message = {}) {
this.debug && console.debug(
`[WS::_processResponse] response: ${_get(this.callbacks[message.id].message, 'event.name')}`,
{
request: this.callbacks[message.id].message,
response: message,
time: Date.now() - this.callbacks[message.id].startTime
}
);
const callback = this.callbacks[message.id];
const result = message.result;
if (result.resolve) {
callback.resolve(result.resolve);
} else {
callback.reject(result.reject);
}
}
static _handlePartialResponse (message = {}) {
const { id, part, body } = message;
this.debug && console.debug('[WS::_processPartialResponse] API Partial response', message);
if (!this.partialResponses[id]) {
this.partialResponses[id] = {
parts: [],
size: part.n
};
}
const timer = _get(this.partialResponses, [id, 'timer']);
if (timer) {
clearTimeout(timer);
}
_set(this.partialResponses, [id, 'timer'], setTimeout(() => {
console.error(`[WS::_processPartialResponse] Failed to receive all parts of a multipart response ${id}`);
this._cleanupPartialResponse(id);
}, this.DEFAULT_OUTBOUND_TIMEOUT));
const parts = _get(this.partialResponses, [id, 'parts'], []);
parts[part.i] = body;
_set(this.partialResponses, [id, 'parts'], parts);
if (_compact(parts).length >= _get(this.partialResponses, [id, 'size'])) {
this._cleanupPartialResponse(id);
const joinedParts = _join(parts, '');
const message = JSON.parse(joinedParts);
this._handleResponse(message);
}
}
static _cleanupPartialResponse (id) {
const timer = _get(this.partialResponses, [id, 'timer']);
clearTimeout(timer);
delete this.partialResponses[id];
}
static _sendOutboundMessage (message, includeCallback = true, options = {}) {
const callbackId = _get(message, ['callback', 'id']);
if (!this.callbacks[callbackId]) return;
if (this.wsClient.connected && !this.connecting) {
const wsCallback = this.wsClient.getCallback();
message.callback = _assign({}, wsCallback, { id: callbackId });
console.log('_sendOutboundMessage wsCallback -> ', wsCallback);
console.log('_sendOutboundMessage message.callback -> ', message.callback);
// TODO: need step refactoring to separate callback data from event params
if (includeCallback) {
const paramsName = message.event.params.name;
message.event.params = _assign({}, message.event.params, message.callback);
if (paramsName) {
message.event.params.name = paramsName;
}
message.event.params.callback = message.callback;
}
if (options.token === 'UNAUTHORIZED') {
message.event.name = `ws/${message.event.name}`;
message.event.target = options.accountId;
message.event.source = options.accountId;
}
if (this.debug) {
this.callbacks[callbackId].message = message;
this.callbacks[callbackId].startTime = Date.now();
console.debug(`[WS::_sendOutboundMessage] request: ${_get(message, 'event.name')}`, message);
}
this.wsClient.socket.send(JSON.stringify(message));
} else {
setTimeout(() => this._sendOutboundMessage(message, includeCallback, options), 250);
}
}
}