@interopio/desktop
Version:
JavaScript library for io.Connect Desktop clients.
1,505 lines (1,476 loc) • 1.01 MB
JavaScript
(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?.