UNPKG

@interopio/desktop

Version:

JavaScript library for io.Connect Desktop clients.

1,505 lines (1,476 loc) 1.01 MB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (global.desktop = global.desktop || {}, global.desktop.browser = factory())); })(this, (function () { 'use strict'; var MetricTypes = { STRING: 1, NUMBER: 2, TIMESTAMP: 3, OBJECT: 4 }; function getMetricTypeByValue(metric) { if (metric.type === MetricTypes.TIMESTAMP) { return "timestamp"; } else if (metric.type === MetricTypes.NUMBER) { return "number"; } else if (metric.type === MetricTypes.STRING) { return "string"; } else if (metric.type === MetricTypes.OBJECT) { return "object"; } return "unknown"; } function getTypeByValue(value) { if (value.constructor === Date) { return "timestamp"; } else if (typeof value === "number") { return "number"; } else if (typeof value === "string") { return "string"; } else if (typeof value === "object") { return "object"; } else { return "string"; } } function serializeMetric(metric) { const serializedMetrics = {}; const type = getMetricTypeByValue(metric); if (type === "object") { const values = Object.keys(metric.value).reduce((memo, key) => { const innerType = getTypeByValue(metric.value[key]); if (innerType === "object") { const composite = defineNestedComposite(metric.value[key]); memo[key] = { type: "object", description: "", context: {}, composite, }; } else { memo[key] = { type: innerType, description: "", context: {}, }; } return memo; }, {}); serializedMetrics.composite = values; } serializedMetrics.name = normalizeMetricName(metric.path.join("/") + "/" + metric.name); serializedMetrics.type = type; serializedMetrics.description = metric.description; serializedMetrics.context = {}; return serializedMetrics; } function defineNestedComposite(values) { return Object.keys(values).reduce((memo, key) => { const type = getTypeByValue(values[key]); if (type === "object") { memo[key] = { type: "object", description: "", context: {}, composite: defineNestedComposite(values[key]), }; } else { memo[key] = { type, description: "", context: {}, }; } return memo; }, {}); } function normalizeMetricName(name) { if (typeof name !== "undefined" && name.length > 0 && name[0] !== "/") { return "/" + name; } else { return name; } } function getMetricValueByType(metric) { const type = getMetricTypeByValue(metric); if (type === "timestamp") { return Date.now(); } else { return publishNestedComposite(metric.value); } } function publishNestedComposite(values) { if (typeof values !== "object") { return values; } return Object.keys(values).reduce((memo, key) => { const value = values[key]; if (typeof value === "object" && value.constructor !== Date) { memo[key] = publishNestedComposite(value); } else if (value.constructor === Date) { memo[key] = new Date(value).getTime(); } else if (value.constructor === Boolean) { memo[key] = value.toString(); } else { memo[key] = value; } return memo; }, {}); } function flatten(arr) { return arr.reduce((flat, toFlatten) => { return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten); }, []); } function getHighestState(arr) { return arr.sort((a, b) => { if (!a.state) { return 1; } if (!b.state) { return -1; } return b.state - a.state; })[0]; } function aggregateDescription(arr) { let msg = ""; arr.forEach((m, idx, a) => { const path = m.path.join("."); if (idx === a.length - 1) { msg += path + "." + m.name + ": " + m.description; } else { msg += path + "." + m.name + ": " + m.description + ","; } }); if (msg.length > 100) { return msg.slice(0, 100) + "..."; } else { return msg; } } function composeMsgForRootStateMetric(system) { const aggregatedState = system.root.getAggregateState(); const merged = flatten(aggregatedState); const highestState = getHighestState(merged); const aggregateDesc = aggregateDescription(merged); return { description: aggregateDesc, value: highestState.state, }; } function gw3 (connection, config) { if (!connection || typeof connection !== "object") { throw new Error("Connection is required parameter"); } let joinPromise; let session; const init = (repo) => { let resolveReadyPromise; joinPromise = new Promise((resolve) => { resolveReadyPromise = resolve; }); session = connection.domain("metrics"); session.onJoined((reconnect) => { if (!reconnect && resolveReadyPromise) { resolveReadyPromise(); resolveReadyPromise = undefined; } const rootStateMetric = { name: "/State", type: "object", composite: { Description: { type: "string", description: "", }, Value: { type: "number", description: "", }, }, description: "System state", context: {}, }; const defineRootMetricsMsg = { type: "define", metrics: [rootStateMetric], }; session.sendFireAndForget(defineRootMetricsMsg) .catch((err) => { config.logger.warn(`Failed to send define for root state metric: ${JSON.stringify(err)}`); }); if (reconnect) { replayRepo(repo); } }); session.join({ system: config.system, service: config.service, instance: config.instance }); }; const replayRepo = (repo) => { replaySystem(repo.root); }; const replaySystem = (system) => { createSystem(system); system.metrics.forEach((m) => { createMetric(m); }); system.subSystems.forEach((ss) => { replaySystem(ss); }); }; const createSystem = async (system) => { if (system.parent === undefined) { return; } await joinPromise; const metric = { name: normalizeMetricName(system.path.join("/") + "/" + system.name + "/State"), type: "object", composite: { Description: { type: "string", description: "", }, Value: { type: "number", description: "", }, }, description: "System state", context: {}, }; const createMetricsMsg = { type: "define", metrics: [metric], }; session.sendFireAndForget(createMetricsMsg) .catch((err) => { config.logger.warn(`Failed to send define for system state metric of ${system.name}: ${JSON.stringify(err)}`); }); }; const updateSystem = async (system, state) => { await joinPromise; const shadowedUpdateMetric = { type: "publish", values: [{ name: normalizeMetricName(system.path.join("/") + "/" + system.name + "/State"), value: { Description: state.description, Value: state.state, }, timestamp: Date.now(), }], }; session.sendFireAndForget(shadowedUpdateMetric) .catch((err) => { config.logger.warn(`Failed to send update for system state metric of ${system.name}: ${JSON.stringify(err)}`); }); const stateObj = composeMsgForRootStateMetric(system); const rootMetric = { type: "publish", peer_id: connection.peerId, values: [{ name: "/State", value: { Description: stateObj.description, Value: stateObj.value, }, timestamp: Date.now(), }], }; session.sendFireAndForget(rootMetric) .catch((err) => { config.logger.warn(`Failed to send update for root state metric of ${system.name}: ${JSON.stringify(err)}`); }); }; const createMetric = async (metric) => { const metricClone = cloneMetric(metric); await joinPromise; const m = serializeMetric(metricClone); const createMetricsMsg = { type: "define", metrics: [m], }; session.sendFireAndForget(createMetricsMsg) .catch((err) => { config.logger.warn(`Failed to send define for metric ${metric.name}: ${JSON.stringify(err)}`); }); if (typeof metricClone.value !== "undefined") { updateMetricCore(metricClone); } }; const updateMetric = async (metric) => { const metricClone = cloneMetric(metric); await joinPromise; updateMetricCore(metricClone); }; const updateMetricCore = (metric) => { if (canUpdate()) { const value = getMetricValueByType(metric); const publishMetricsMsg = { type: "publish", values: [{ name: normalizeMetricName(metric.path.join("/") + "/" + metric.name), value, timestamp: Date.now(), }], }; return session.sendFireAndForget(publishMetricsMsg) .catch((err) => { config.logger.warn(`Failed to publish metric ${metric.name}: ${JSON.stringify(err)}`); }); } return Promise.resolve(); }; const cloneMetric = (metric) => { const metricClone = { ...metric }; if (typeof metric.value === "object" && metric.value !== null) { metricClone.value = { ...metric.value }; } return metricClone; }; const canUpdate = () => { try { const func = config.canUpdateMetric ?? (() => true); return func(); } catch { return true; } }; return { init, createSystem, updateSystem, createMetric, updateMetric, }; } var Helpers = { validate: (definition, parent, transport) => { if (definition === null || typeof definition !== "object") { throw new Error("Missing definition"); } if (parent === null || typeof parent !== "object") { throw new Error("Missing parent"); } if (transport === null || typeof transport !== "object") { throw new Error("Missing transport"); } }, }; class BaseMetric { definition; system; transport; value; type; path = []; name; description; get repo() { return this.system?.repo; } get id() { return `${this.system.path}/${name}`; } constructor(definition, system, transport, value, type) { this.definition = definition; this.system = system; this.transport = transport; this.value = value; this.type = type; Helpers.validate(definition, system, transport); this.path = system.path.slice(0); this.path.push(system.name); this.name = definition.name; this.description = definition.description; transport.createMetric(this); } update(newValue) { this.value = newValue; return this.transport.updateMetric(this); } } class NumberMetric extends BaseMetric { constructor(definition, system, transport, value) { super(definition, system, transport, value, MetricTypes.NUMBER); } incrementBy(num) { this.update(this.value + num); } increment() { this.incrementBy(1); } decrement() { this.incrementBy(-1); } decrementBy(num) { this.incrementBy(num * -1); } } class ObjectMetric extends BaseMetric { constructor(definition, system, transport, value) { super(definition, system, transport, value, MetricTypes.OBJECT); } update(newValue) { this.mergeValues(newValue); return this.transport.updateMetric(this); } mergeValues(values) { return Object.keys(this.value).forEach((k) => { if (typeof values[k] !== "undefined") { this.value[k] = values[k]; } }); } } class StringMetric extends BaseMetric { constructor(definition, system, transport, value) { super(definition, system, transport, value, MetricTypes.STRING); } } class TimestampMetric extends BaseMetric { constructor(definition, system, transport, value) { super(definition, system, transport, value, MetricTypes.TIMESTAMP); } now() { this.update(new Date()); } } function system$1(name, repo, protocol, parent, description) { if (!repo) { throw new Error("Repository is required"); } if (!protocol) { throw new Error("Transport is required"); } const _transport = protocol; const _name = name; const _description = description || ""; const _repo = repo; const _parent = parent; const _path = _buildPath(parent); let _state = {}; const id = _arrayToString(_path, "/") + name; const root = repo.root; const _subSystems = []; const _metrics = []; function subSystem(nameSystem, descriptionSystem) { if (!nameSystem || nameSystem.length === 0) { throw new Error("name is required"); } const match = _subSystems.filter((s) => s.name === nameSystem); if (match.length > 0) { return match[0]; } const _system = system$1(nameSystem, _repo, _transport, me, descriptionSystem); _subSystems.push(_system); return _system; } function setState(state, stateDescription) { _state = { state, description: stateDescription }; _transport.updateSystem(me, _state); } function stringMetric(definition, value) { return _getOrCreateMetric(definition, MetricTypes.STRING, value, (metricDef) => new StringMetric(metricDef, me, _transport, value)); } function numberMetric(definition, value) { return _getOrCreateMetric(definition, MetricTypes.NUMBER, value, (metricDef) => new NumberMetric(metricDef, me, _transport, value)); } function objectMetric(definition, value) { return _getOrCreateMetric(definition, MetricTypes.OBJECT, value, (metricDef) => new ObjectMetric(metricDef, me, _transport, value)); } function timestampMetric(definition, value) { return _getOrCreateMetric(definition, MetricTypes.TIMESTAMP, value, (metricDef) => new TimestampMetric(metricDef, me, _transport, value)); } function _getOrCreateMetric(metricObject, expectedType, value, createMetric) { let metricDef = { name: "" }; if (typeof metricObject === "string") { metricDef = { name: metricObject }; } else { metricDef = metricObject; } const matching = _metrics.filter((shadowedMetric) => shadowedMetric.name === metricDef.name); if (matching.length > 0) { const existing = matching[0]; if (existing.type !== expectedType) { throw new Error(`A metric named ${metricDef.name} is already defined with different type.`); } if (typeof value !== "undefined") { existing .update(value) .catch(() => { }); } return existing; } const metric = createMetric(metricDef); _metrics.push(metric); return metric; } function _buildPath(shadowedSystem) { if (!shadowedSystem || !shadowedSystem.parent) { return []; } const path = _buildPath(shadowedSystem.parent); path.push(shadowedSystem.name); return path; } function _arrayToString(path, separator) { return ((path && path.length > 0) ? path.join(separator) : ""); } function getAggregateState() { const aggState = []; if (Object.keys(_state).length > 0) { aggState.push({ name: _name, path: _path, state: _state.state, description: _state.description, }); } _subSystems.forEach((shadowedSubSystem) => { const result = shadowedSubSystem.getAggregateState(); if (result.length > 0) { aggState.push(...result); } }); return aggState; } const me = { get name() { return _name; }, get description() { return _description; }, get repo() { return _repo; }, get parent() { return _parent; }, path: _path, id, root, get subSystems() { return _subSystems; }, get metrics() { return _metrics; }, subSystem, getState: () => { return _state; }, setState, stringMetric, timestampMetric, objectMetric, numberMetric, getAggregateState, }; _transport.createSystem(me); return me; } class Repository { root; constructor(options, protocol) { protocol.init(this); this.root = system$1("", this, protocol); this.addSystemMetrics(this.root, options.clickStream || options.clickStream === undefined); } addSystemMetrics(rootSystem, useClickStream) { if (typeof navigator !== "undefined") { rootSystem.stringMetric("UserAgent", navigator.userAgent); } if (useClickStream && typeof document !== "undefined") { const clickStream = rootSystem.subSystem("ClickStream"); const documentClickHandler = (e) => { if (!e.target) { return; } const target = e.target; const className = target ? target.getAttribute("class") ?? "" : ""; clickStream.objectMetric("LastBrowserEvent", { type: "click", timestamp: new Date(), target: { className, id: target.id, type: "<" + target.tagName.toLowerCase() + ">", href: target.href || "", }, }); }; clickStream.objectMetric("Page", { title: document.title, page: window.location.href, }); if (document.addEventListener) { document.addEventListener("click", documentClickHandler); } else { document.attachEvent("onclick", documentClickHandler); } } rootSystem.stringMetric("StartTime", (new Date()).toString()); const urlMetric = rootSystem.stringMetric("StartURL", ""); const appNameMetric = rootSystem.stringMetric("AppName", ""); if (typeof window !== "undefined") { if (typeof window.location !== "undefined") { const startUrl = window.location.href; urlMetric.update(startUrl); } if (typeof window.glue42gd !== "undefined") { appNameMetric.update(window.glue42gd.appName); } } } } class NullProtocol { init(repo) { } createSystem(system) { return Promise.resolve(); } updateSystem(metric, state) { return Promise.resolve(); } createMetric(metric) { return Promise.resolve(); } updateMetric(metric) { return Promise.resolve(); } } class PerfTracker { api; lastCount = 0; initialPublishTimeout = 10 * 1000; publishInterval = 60 * 1000; system; constructor(api, initialPublishTimeout, publishInterval) { this.api = api; this.initialPublishTimeout = initialPublishTimeout ?? this.initialPublishTimeout; this.publishInterval = publishInterval ?? this.publishInterval; this.scheduleCollection(); this.system = this.api.subSystem("performance", "Performance data published by the web application"); } scheduleCollection() { setTimeout(() => { this.collect(); setInterval(() => { this.collect(); }, this.publishInterval); }, this.initialPublishTimeout); } collect() { try { this.collectMemory(); this.collectEntries(); } catch { } } collectMemory() { const memory = window.performance.memory; this.system.stringMetric("memory", JSON.stringify({ totalJSHeapSize: memory.totalJSHeapSize, usedJSHeapSize: memory.usedJSHeapSize })); } collectEntries() { const allEntries = window.performance.getEntries(); if (allEntries.length <= this.lastCount) { return; } this.lastCount = allEntries.length; const jsonfiedEntries = allEntries.map((i) => i.toJSON()); this.system.stringMetric("entries", JSON.stringify(jsonfiedEntries)); } } var metrics = (options) => { let protocol; if (!options.connection || typeof options.connection !== "object") { protocol = new NullProtocol(); } else { protocol = gw3(options.connection, options); } const repo = new Repository(options, protocol); let rootSystem = repo.root; if (!options.disableAutoAppSystem) { rootSystem = rootSystem.subSystem("App"); } const api = addFAVSupport(rootSystem); initPerf(api, options.pagePerformanceMetrics); return api; }; function initPerf(api, config) { if (typeof window === "undefined") { return; } const perfConfig = window?.glue42gd?.metrics?.pagePerformanceMetrics; if (perfConfig) { config = perfConfig; } if (config?.enabled) { new PerfTracker(api, config.initialPublishTimeout, config.publishInterval); } } function addFAVSupport(system) { const reportingSystem = system.subSystem("reporting"); const def = { name: "features" }; let featureMetric; const featureMetricFunc = (name, action, payload) => { if (typeof name === "undefined" || name === "") { throw new Error("name is mandatory"); } else if (typeof action === "undefined" || action === "") { throw new Error("action is mandatory"); } else if (typeof payload === "undefined" || payload === "") { throw new Error("payload is mandatory"); } if (!featureMetric) { featureMetric = reportingSystem.objectMetric(def, { name, action, payload }); } else { featureMetric.update({ name, action, payload }); } }; system.featureMetric = featureMetricFunc; return system; } var commonjsGlobal$1 = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function getDefaultExportFromCjs$1 (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function createRegistry$1(options) { if (options && options.errorHandling && typeof options.errorHandling !== "function" && options.errorHandling !== "log" && options.errorHandling !== "silent" && options.errorHandling !== "throw") { throw new Error("Invalid options passed to createRegistry. Prop errorHandling should be [\"log\" | \"silent\" | \"throw\" | (err) => void], but " + typeof options.errorHandling + " was passed"); } var _userErrorHandler = options && typeof options.errorHandling === "function" && options.errorHandling; var callbacks = {}; function add(key, callback, replayArgumentsArr) { var callbacksForKey = callbacks[key]; if (!callbacksForKey) { callbacksForKey = []; callbacks[key] = callbacksForKey; } callbacksForKey.push(callback); if (replayArgumentsArr) { setTimeout(function () { replayArgumentsArr.forEach(function (replayArgument) { var _a; if ((_a = callbacks[key]) === null || _a === void 0 ? void 0 : _a.includes(callback)) { try { if (Array.isArray(replayArgument)) { callback.apply(undefined, replayArgument); } else { callback.apply(undefined, [replayArgument]); } } catch (err) { _handleError(err, key); } } }); }, 0); } return function () { var allForKey = callbacks[key]; if (!allForKey) { return; } allForKey = allForKey.reduce(function (acc, element, index) { if (!(element === callback && acc.length === index)) { acc.push(element); } return acc; }, []); if (allForKey.length === 0) { delete callbacks[key]; } else { callbacks[key] = allForKey; } }; } function execute(key) { var argumentsArr = []; for (var _i = 1; _i < arguments.length; _i++) { argumentsArr[_i - 1] = arguments[_i]; } var callbacksForKey = callbacks[key]; if (!callbacksForKey || callbacksForKey.length === 0) { return []; } var results = []; callbacksForKey.forEach(function (callback) { try { var result = callback.apply(undefined, argumentsArr); results.push(result); } catch (err) { results.push(undefined); _handleError(err, key); } }); return results; } function _handleError(exceptionArtifact, key) { var errParam = exceptionArtifact instanceof Error ? exceptionArtifact : new Error(exceptionArtifact); if (_userErrorHandler) { _userErrorHandler(errParam); return; } var msg = "[ERROR] callback-registry: User callback for key \"" + key + "\" failed: " + errParam.stack; if (options) { switch (options.errorHandling) { case "log": return console.error(msg); case "silent": return; case "throw": throw new Error(msg); } } console.error(msg); } function clear() { callbacks = {}; } function clearKey(key) { var callbacksForKey = callbacks[key]; if (!callbacksForKey) { return; } delete callbacks[key]; } return { add: add, execute: execute, clear: clear, clearKey: clearKey }; } createRegistry$1.default = createRegistry$1; var lib$1 = createRegistry$1; var CallbackRegistryFactory$1 = /*@__PURE__*/getDefaultExportFromCjs$1(lib$1); class InProcTransport { gw; registry = CallbackRegistryFactory$1(); client; constructor(settings, logger) { this.gw = settings.facade; this.gw.connect((_client, message) => { this.messageHandler(message); }).then((client) => { this.client = client; }); } get isObjectBasedTransport() { return true; } sendObject(msg) { if (this.client) { this.client.send(msg); return Promise.resolve(undefined); } else { return Promise.reject(`not connected`); } } send(_msg) { return Promise.reject("not supported"); } onMessage(callback) { return this.registry.add("onMessage", callback); } onConnectedChanged(callback) { callback(true); return () => { }; } close() { return Promise.resolve(); } open() { return Promise.resolve(); } name() { return "in-memory"; } reconnect() { return Promise.resolve(); } messageHandler(msg) { this.registry.execute("onMessage", msg); } } class SharedWorkerTransport { logger; worker; registry = CallbackRegistryFactory$1(); constructor(workerFile, logger) { this.logger = logger; this.worker = new SharedWorker(workerFile); this.worker.port.onmessage = (e) => { this.messageHandler(e.data); }; } get isObjectBasedTransport() { return true; } sendObject(msg) { this.worker.port.postMessage(msg); return Promise.resolve(); } send(_msg) { return Promise.reject("not supported"); } onMessage(callback) { return this.registry.add("onMessage", callback); } onConnectedChanged(callback) { callback(true); return () => { }; } close() { return Promise.resolve(); } open() { return Promise.resolve(); } name() { return "shared-worker"; } reconnect() { return Promise.resolve(); } messageHandler(msg) { this.registry.execute("onMessage", msg); } } let Utils$1 = class Utils { static isNode() { if (typeof Utils._isNode !== "undefined") { return Utils._isNode; } if (typeof window !== "undefined") { Utils._isNode = false; return false; } try { Utils._isNode = Object.prototype.toString.call(global.process) === "[object process]"; } catch (e) { Utils._isNode = false; } return Utils._isNode; } static _isNode; }; let PromiseWrapper$1 = class PromiseWrapper { static delay(time) { return new Promise((resolve) => setTimeout(resolve, time)); } resolve; reject; promise; rejected = false; resolved = false; get ended() { return this.rejected || this.resolved; } constructor() { this.promise = new Promise((resolve, reject) => { this.resolve = (t) => { this.resolved = true; resolve(t); }; this.reject = (err) => { this.rejected = true; reject(err); }; }); } }; const timers = {}; function getAllTimers() { return timers; } function timer (timerName) { const existing = timers[timerName]; if (existing) { return existing; } const marks = []; function now() { return new Date().getTime(); } const startTime = now(); mark("start", startTime); let endTime; let period; function stop() { endTime = now(); mark("end", endTime); period = endTime - startTime; return period; } function mark(name, time) { const currentTime = time ?? now(); let diff = 0; if (marks.length > 0) { diff = currentTime - marks[marks.length - 1].time; } marks.push({ name, time: currentTime, diff }); } const timerObj = { get startTime() { return startTime; }, get endTime() { return endTime; }, get period() { return period; }, stop, mark, marks }; timers[timerName] = timerObj; return timerObj; } const WebSocketConstructor = Utils$1.isNode() ? null : window.WebSocket; class WS { ws; logger; settings; startupTimer = timer("connection"); _running = true; _registry = CallbackRegistryFactory$1(); wsRequests = []; constructor(settings, logger) { this.settings = settings; this.logger = logger; if (!this.settings.ws) { throw new Error("ws is missing"); } } onMessage(callback) { return this._registry.add("onMessage", callback); } send(msg, options) { return new Promise((resolve, reject) => { this.waitForSocketConnection(() => { try { this.ws?.send(msg); resolve(); } catch (e) { reject(e); } }, reject); }); } open() { this.logger.info("opening ws..."); this._running = true; return new Promise((resolve, reject) => { this.waitForSocketConnection(resolve, reject); }); } close() { this._running = false; if (this.ws) { this.ws.close(); } return Promise.resolve(); } onConnectedChanged(callback) { return this._registry.add("onConnectedChanged", callback); } name() { return this.settings.ws; } reconnect() { this.ws?.close(); const pw = new PromiseWrapper$1(); this.waitForSocketConnection(() => { pw.resolve(); }); return pw.promise; } waitForSocketConnection(callback, failed) { failed = failed ?? (() => { }); if (!this._running) { failed(`wait for socket on ${this.settings.ws} failed - socket closed by user`); return; } if (this.ws?.readyState === 1) { callback(); return; } this.wsRequests.push({ callback, failed }); if (this.wsRequests.length > 1) { return; } this.openSocket(); } async openSocket(retryInterval, retriesLeft) { this.logger.info(`opening ws to ${this.settings.ws}, retryInterval: ${retryInterval}, retriesLeft: ${retriesLeft}...`); this.startupTimer.mark("opening-socket"); if (retryInterval === undefined) { retryInterval = this.settings.reconnectInterval; } if (typeof retriesLeft === "undefined") { retriesLeft = this.settings.reconnectAttempts; } if (retriesLeft !== undefined) { if (retriesLeft === 0) { this.notifyForSocketState(`wait for socket on ${this.settings.ws} failed - no more retries left`); return; } this.logger.debug(`will retry ${retriesLeft} more times (every ${retryInterval} ms)`); } try { await this.initiateSocket(); this.startupTimer.mark("socket-initiated"); this.notifyForSocketState(); } catch { setTimeout(() => { const retries = retriesLeft === undefined ? undefined : retriesLeft - 1; this.openSocket(retryInterval, retries); }, retryInterval); } } initiateSocket() { const pw = new PromiseWrapper$1(); this.logger.debug(`initiating ws to ${this.settings.ws}...`); this.ws = new WebSocketConstructor(this.settings.ws ?? ""); let wasOpen = false; this.ws.onerror = (err) => { let reason; try { reason = JSON.stringify(err); } catch (error) { const seen = new WeakSet(); const replacer = (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); } return value; }; reason = JSON.stringify(err, replacer); } this.logger.info(`ws error - reason: ${reason}`); pw.reject("error"); if (wasOpen) { wasOpen = false; this.notifyForSocketState("error"); } this.notifyStatusChanged(false, reason); }; this.ws.onclose = (err) => { this.logger.info(`ws closed - code: ${err?.code} reason: ${err?.reason}`); pw.reject("closed"); if (wasOpen) { wasOpen = false; this.notifyForSocketState("closed"); } this.notifyStatusChanged(false); }; this.ws.onopen = () => { this.startupTimer.mark("ws-opened"); this.logger.info(`ws opened ${this.settings.identity?.application}`); pw.resolve(); wasOpen = true; this.notifyStatusChanged(true); }; this.ws.onmessage = (message) => { this._registry.execute("onMessage", message.data); }; return pw.promise; } notifyForSocketState(error) { this.wsRequests.forEach((wsRequest) => { if (error) { if (wsRequest.failed) { wsRequest.failed(error); } } else { wsRequest.callback(); } }); this.wsRequests = []; } notifyStatusChanged(status, reason) { this._registry.execute("onConnectedChanged", status, reason); } } class MessageReplayerImpl { specs; specsNames = []; messages = {}; isDone; subs = {}; subsRefCount = {}; connection; constructor(specs) { this.specs = {}; for (const spec of specs) { this.specs[spec.name] = spec; this.specsNames.push(spec.name); } } init(connection) { this.connection = connection; for (const name of this.specsNames) { for (const type of this.specs[name].types) { let refCount = this.subsRefCount[type]; if (!refCount) { refCount = 0; } refCount += 1; this.subsRefCount[type] = refCount; if (refCount > 1) { continue; } const sub = connection.on(type, (msg) => this.processMessage(type, msg)); this.subs[type] = sub; } } } processMessage(type, msg) { if (this.isDone || !msg) { return; } for (const name of this.specsNames) { if (this.specs[name].types.indexOf(type) !== -1) { const messages = this.messages[name] || []; this.messages[name] = messages; messages.push(msg); } } } drain(name, callback) { if (callback) { (this.messages[name] || []).forEach(callback); } delete this.messages[name]; for (const type of this.specs[name].types) { this.subsRefCount[type] -= 1; if (this.subsRefCount[type] <= 0) { this.connection?.off(this.subs[type]); delete this.subs[type]; delete this.subsRefCount[type]; } } delete this.specs[name]; if (!this.specs.length) { this.isDone = true; } } } /* @ts-self-types="./index.d.ts" */ let urlAlphabet$1 = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'; let nanoid$1 = (size = 21) => { let id = ''; let i = size | 0; while (i--) { id += urlAlphabet$1[(Math.random() * 64) | 0]; } return id }; const PromisePlus$1 = (executor, timeoutMilliseconds, timeoutMessage) => { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { const message = timeoutMessage || `Promise timeout hit: ${timeoutMilliseconds}`; reject(message); }, timeoutMilliseconds); const providedPromise = new Promise(executor); providedPromise .then((result) => { clearTimeout(timeout); resolve(result); }) .catch((error) => { clearTimeout(timeout); reject(error); }); }); }; class WebPlatformTransport { settings; logger; identity; isPreferredActivated; _connectionProtocolVersion; _communicationId; publicWindowId; selfAssignedWindowId; iAmConnected = false; parentReady = false; rejected = false; parentPingResolve; parentPingInterval; connectionResolve; extConnectionResolve; extConnectionReject; connectionReject; port; myClientId; extContentAvailable = false; extContentConnecting = false; extContentConnected = false; parentWindowId; parentInExtMode = false; webNamespace = "g42_core_web"; parent; parentType; parentPingTimeout = 5000; connectionRequestTimeout = 7000; defaultTargetString = "*"; registry = CallbackRegistryFactory$1(); messages = { connectionAccepted: { name: "connectionAccepted", handle: this.handleConnectionAccepted.bind(this) }, connectionRejected: { name: "connectionRejected", handle: this.handleConnectionRejected.bind(this) }, connectionRequest: { name: "connectionRequest", handle: this.handleConnectionRequest.bind(this) }, parentReady: { name: "parentReady", handle: () => { } }, parentPing: { name: "parentPing", handle: this.handleParentPing.bind(this) }, platformPing: { name: "platformPing", handle: this.handlePlatformPing.bind(this) }, platformReady: { name: "platformReady", handle: this.handlePlatformReady.bind(this) }, clientUnload: { name: "clientUnload", handle: () => { } }, manualUnload: { name: "manualUnload", handle: this.handleManualUnload.bind(this) }, extConnectionResponse: { name: "extConnectionResponse", handle: this.handleExtConnectionResponse.bind(this) }, extSetupRequest: { name: "extSetupRequest", handle: this.handleExtSetupRequest.bind(this) }, gatewayDisconnect: { name: "gatewayDisconnect", handle: this.handleGatewayDisconnect.bind(this) }, gatewayInternalConnect: { name: "gatewayInternalConnect", handle: this.handleGatewayInternalConnect.bind(this) } }; constructor(settings, logger, identity) { this.settings = settings; this.logger = logger; this.identity = identity; this.extContentAvailable = !!window.glue42ext; this.setUpMessageListener(); this.setUpUnload(); this.setupPlatformUnloadListener(); this.parentType = window.name.includes("#wsp") ? "workspace" : undefined; } manualSetReadyState() { this.iAmConnected = true; this.parentReady = true; } get transportWindowId() { return this.publicWindowId; } get communicationId() { return this._communicationId; } async sendObject(msg) { if (this.extContentConnected) { return window.postMessage({ glue42ExtOut: msg }, window.origin); } if (!this.port) { throw new Error("Cannot send message, because the port was not opened yet"); } this.port.postMessage(msg); } get isObjectBasedTransport() { return true; } onMessage(callback) { return this.registry.add("onMessage", callback); } send() { return Promise.reject("not supported"); } onConnectedChanged(callback) { return this.registry.add("onConnectedChanged", callback); } async open() { this.logger.debug("opening a connection to the web platform gateway."); await this.connect(); this.notifyStatusChanged(true); } close() { const message = { glue42core: { type: this.messages.gatewayDisconnect.name, data: { clientId: this.myClientId, ownWindowId: this.identity?.windowId } } }; this.port?.postMessage(message); this.parentReady = false; this.notifyStatusChanged(false, "manual reconnection"); return Promise.resolve(); } name() { return "web-platform"; } async reconnect() { await this.close(); return Promise.resolve(); } initiateInternalConnection() { return new Promise((resolve, reject) => { this.logger.debug("opening an internal web platform connection"); this.port = this.settings.port; if (this.iAmConnected) { this.logger.warn("cannot open a new connection, because this client is currently connected"); return; } this.port.onmessage = (event) => { if (this.iAmConnected && !event.data?.glue42core) { this.registry.execute("onMessage", event.data); return; } const data = event.data?.