@interopio/bridge 0.0.5-beta.0 → 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -1,3 +1,125 @@
1
+ // ../bridge-mesh/src/mesh/relays.ts
2
+ import { IOGateway } from "@interopio/gateway";
3
+ import { EventEmitter } from "node:events";
4
+ var codec = IOGateway.Encoding.transit({
5
+ keywordize: /* @__PURE__ */ new Map([
6
+ ["/type", "*"],
7
+ ["/message/body/type", "*"],
8
+ ["/message/origin", "*"],
9
+ ["/message/receiver/type", "*"],
10
+ ["/message/source/type", "*"],
11
+ ["/message/body/type", "*"]
12
+ ])
13
+ });
14
+ function isEncoded(msg) {
15
+ return typeof msg === "string" || Buffer.isBuffer(msg);
16
+ }
17
+ var InternalRelays = class {
18
+ #logger;
19
+ // key -> handler
20
+ #handlersByKey = /* @__PURE__ */ new Map();
21
+ // node -> keyed link
22
+ #links = /* @__PURE__ */ new Map();
23
+ #eventEmitter = new EventEmitter();
24
+ constructor(logger) {
25
+ this.#logger = logger;
26
+ }
27
+ add(key, handler) {
28
+ this.#handlersByKey.set(key, handler);
29
+ }
30
+ remove(key) {
31
+ this.#handlersByKey.delete(key);
32
+ this.#disconnect(key);
33
+ }
34
+ #disconnect(key, from) {
35
+ for (const [node, k] of this.#links) {
36
+ if (k.key === key && (from === void 0 || k.node === from)) {
37
+ const links = k.linksByNode;
38
+ if (this.#logger.enabledFor("debug")) {
39
+ this.#logger.debug(`${key} unregisters node ${node}, linked to ${Array.from(links.keys()).join(", ")}`);
40
+ }
41
+ this.#links.delete(node);
42
+ this.#eventEmitter.emit("disconnect", key, node, links);
43
+ }
44
+ }
45
+ }
46
+ #connect(key, node) {
47
+ if (this.#logger.enabledFor("debug")) {
48
+ this.#logger.debug(`${key} registers node ${node}`);
49
+ }
50
+ this.#links.set(node, { key, node, linksByNode: /* @__PURE__ */ new Map() });
51
+ this.#eventEmitter.emit("connect", key, node);
52
+ }
53
+ receive(key, msg) {
54
+ const { node, decoded, encoded } = this.link(key, msg);
55
+ if (node) {
56
+ this.#eventEmitter.emit("message", key, node, encoded, decoded);
57
+ }
58
+ }
59
+ link(key, msg) {
60
+ try {
61
+ const decoded = isEncoded(msg) ? codec.decode(msg) : msg;
62
+ const { type, from, to } = decoded;
63
+ if (to === "all") {
64
+ switch (type) {
65
+ case "hello": {
66
+ this.#connect(key, from);
67
+ break;
68
+ }
69
+ case "bye": {
70
+ this.#disconnect(key, from);
71
+ break;
72
+ }
73
+ }
74
+ return { decoded };
75
+ }
76
+ const encoded = isEncoded(msg) ? msg : codec.encode(msg);
77
+ return { node: from, decoded, encoded };
78
+ } catch (e) {
79
+ if (!this.#eventEmitter.emit("error", key, e instanceof Error ? e : new Error(`link failed :${e}`))) {
80
+ this.#logger.warn(`${key} unable to process ${msg}`, e);
81
+ }
82
+ }
83
+ }
84
+ on(event, listener) {
85
+ this.#eventEmitter.on(event, listener);
86
+ return this;
87
+ }
88
+ send(key, node, msg, cb) {
89
+ if (this.#logger.enabledFor("debug")) {
90
+ this.#logger.debug(`${key} sending msg to ${node}`);
91
+ }
92
+ const encoded = isEncoded(msg) ? msg : codec.encode(msg);
93
+ const decoded = isEncoded(msg) ? codec.decode(msg) : msg;
94
+ {
95
+ const senderLinkData = this.#links.get(decoded.from);
96
+ switch (decoded.type) {
97
+ case "hello":
98
+ if (node === decoded.to) {
99
+ senderLinkData?.linksByNode.set(decoded.to, key);
100
+ }
101
+ break;
102
+ case "bye":
103
+ if (node === decoded.to) {
104
+ senderLinkData?.linksByNode.delete(decoded.to);
105
+ }
106
+ break;
107
+ }
108
+ }
109
+ {
110
+ const receiverLinkData = this.#links.get(node);
111
+ if (receiverLinkData?.key) {
112
+ const handler = this.#handlersByKey.get(receiverLinkData?.key);
113
+ if (handler) {
114
+ handler(encoded, decoded, cb);
115
+ return;
116
+ }
117
+ }
118
+ }
119
+ throw new Error(`${key} no active link for ${node}`);
120
+ }
121
+ };
122
+
1
123
  // src/utils/uuid.ts
2
124
  import { nanoid } from "nanoid";
3
125
  function newUUID() {
@@ -201,9 +323,9 @@ var DefaultDiscoveryConfig = class {
201
323
  };
202
324
 
203
325
  // src/logging.ts
204
- import { IOGateway } from "@interopio/gateway";
326
+ import { getLogger as getGatewayLogger } from "@interopio/gateway/logging/core";
205
327
  function getLogger(name) {
206
- return IOGateway.Logging.getLogger(`gateway.bridge.${name}`);
328
+ return getGatewayLogger(`gateway.bridge.${name}`);
207
329
  }
208
330
 
209
331
  // src/kubernetes/KubernetesDiscoveryStrategyFactory.ts
@@ -686,6 +808,7 @@ var CorsConfig = class {
686
808
  get allowOrigin() {
687
809
  return this.#allowOrigin;
688
810
  }
811
+ disabled;
689
812
  allowCredentials = false;
690
813
  };
691
814
  var ServerConfig = class {
@@ -696,6 +819,9 @@ var ServerConfig = class {
696
819
  type: "none",
697
820
  basic: {
698
821
  realm: "io.Bridge"
822
+ },
823
+ oauth2: {
824
+ jwt: { issuerUri: "" }
699
825
  }
700
826
  };
701
827
  wsPingInterval = 3e4;
@@ -1208,7 +1334,7 @@ function parseVersion(version) {
1208
1334
  // package.json
1209
1335
  var package_default = {
1210
1336
  name: "@interopio/bridge",
1211
- version: "0.0.5-beta.0",
1337
+ version: "0.1.0-beta.0",
1212
1338
  license: "see license in license.md",
1213
1339
  author: "interop.io",
1214
1340
  homepage: "https://docs.interop.io/bridge",
@@ -1219,6 +1345,11 @@ var package_default = {
1219
1345
  "glue42",
1220
1346
  "interop.io"
1221
1347
  ],
1348
+ repository: {
1349
+ type: "git",
1350
+ url: "https://github.com/InteropIO/bridge.git",
1351
+ directory: "packages/bridge"
1352
+ },
1222
1353
  type: "module",
1223
1354
  exports: {
1224
1355
  "./package.json": "./package.json",
@@ -1243,12 +1374,13 @@ var package_default = {
1243
1374
  build: "npm run build:main && npm run build:index"
1244
1375
  },
1245
1376
  dependencies: {
1246
- "@interopio/gateway-server": "^0.9.0-beta.0",
1247
- dotenv: "^17.2.1",
1377
+ "@interopio/gateway-server": "^0.13.0-beta.1",
1378
+ dotenv: "^17.2.3",
1248
1379
  jsrsasign: "^11.1.0",
1249
- nanoid: "^5.0.9"
1380
+ nanoid: "^5.1.6"
1250
1381
  },
1251
1382
  devDependencies: {
1383
+ "@interopio/gateway": "^0.16.1-beta.0",
1252
1384
  "@types/jsrsasign": "^10.5.15",
1253
1385
  "@types/ws": "^8.18.1",
1254
1386
  "rand-seed": "^3.0.0"
@@ -1578,195 +1710,6 @@ var BridgeLicenseValidator = (logger) => new LicenseValidator({ validationKey, l
1578
1710
  // src/instance/BridgeNode.ts
1579
1711
  import { userInfo } from "node:os";
1580
1712
 
1581
- // ../bridge-mesh/src/mesh/connections.ts
1582
- import "@interopio/gateway";
1583
- var InMemoryNodeConnections = class {
1584
- #logger;
1585
- #nodes = /* @__PURE__ */ new Map();
1586
- #nodesByEndpoint = /* @__PURE__ */ new Map();
1587
- #memberIds = 0;
1588
- #timeout;
1589
- constructor(logger, timeout = 6e4) {
1590
- this.#logger = logger;
1591
- this.#timeout = timeout;
1592
- }
1593
- announce(nodes) {
1594
- for (const node of nodes) {
1595
- const { node: nodeId, users, endpoint } = node;
1596
- const foundId = this.#nodesByEndpoint.get(endpoint);
1597
- if (foundId) {
1598
- if (foundId !== nodeId) {
1599
- this.#logger.warn(`endpoint ${endpoint} clash. replacing node ${foundId} with ${nodeId}`);
1600
- this.#nodesByEndpoint.set(endpoint, nodeId);
1601
- this.#nodes.delete(foundId);
1602
- }
1603
- } else {
1604
- this.#logger.info(`endpoint ${endpoint} announced for ${nodeId}`);
1605
- this.#nodesByEndpoint.set(endpoint, nodeId);
1606
- }
1607
- this.#nodes.set(nodeId, this.updateNode(node, new Set(users ?? []), nodeId, this.#nodes.get(nodeId)));
1608
- }
1609
- this.cleanupOldNodes();
1610
- const sortedNodes = this.sortedNodeValues();
1611
- return nodes.map((e) => {
1612
- const { node } = e;
1613
- const connect = this.findConnections(sortedNodes, this.#nodes.get(node));
1614
- return { node, connect };
1615
- });
1616
- }
1617
- find(nodeId) {
1618
- const e = this.#nodes.get(nodeId);
1619
- if (e !== void 0) {
1620
- const sortedNodes = this.sortedNodeValues();
1621
- const { node } = e;
1622
- return this.findConnections(sortedNodes, this.#nodes.get(node));
1623
- }
1624
- return void 0;
1625
- }
1626
- remove(nodeId) {
1627
- const removed = this.#nodes.get(nodeId);
1628
- if (removed !== void 0) {
1629
- this.#nodes.delete(nodeId);
1630
- const endpoint = removed.endpoint;
1631
- this.#nodesByEndpoint.delete(endpoint);
1632
- this.#logger.info(`endpoint ${endpoint} removed for ${nodeId}`);
1633
- return true;
1634
- }
1635
- return false;
1636
- }
1637
- updateNode(newNode, users, _key, oldNode) {
1638
- const node = oldNode ?? { ...newNode, memberId: this.#memberIds++ };
1639
- return { ...node, users, lastAccess: Date.now() };
1640
- }
1641
- sortedNodeValues() {
1642
- return Array.from(this.#nodes.values()).sort((a, b) => a.memberId - b.memberId);
1643
- }
1644
- cleanupOldNodes() {
1645
- const threshold = Date.now() - this.#timeout;
1646
- for (const [nodeId, v] of this.#nodes) {
1647
- if (v.lastAccess < threshold) {
1648
- if (this.#logger.enabledFor("debug")) {
1649
- this.#logger.debug(`${nodeId} expired - no announcement since ${new Date(v.lastAccess).toISOString()}, timeout is ${this.#timeout} ms.`);
1650
- }
1651
- this.#nodes.delete(nodeId);
1652
- }
1653
- }
1654
- }
1655
- findConnections(sortedNodes, node) {
1656
- return sortedNodes.reduce((l, c) => {
1657
- if (node !== void 0 && c.memberId < node.memberId) {
1658
- const intersection = new Set(c.users);
1659
- node.users.forEach((user) => {
1660
- if (!c.users.has(user)) {
1661
- intersection.delete(user);
1662
- }
1663
- });
1664
- c.users.forEach((user) => {
1665
- if (!node.users.has(user)) {
1666
- intersection.delete(user);
1667
- }
1668
- });
1669
- if (intersection.size > 0) {
1670
- const e = { node: c.node, endpoint: c.endpoint };
1671
- return l.concat(e);
1672
- }
1673
- }
1674
- return l;
1675
- }, new Array());
1676
- }
1677
- };
1678
-
1679
- // ../bridge-mesh/src/mesh/relays.ts
1680
- import { IOGateway as IOGateway3 } from "@interopio/gateway";
1681
- var codec = IOGateway3.Encoding.transit({
1682
- keywordize: /* @__PURE__ */ new Map([
1683
- ["/type", "*"],
1684
- ["/message/body/type", "*"],
1685
- ["/message/origin", "*"],
1686
- ["/message/receiver/type", "*"],
1687
- ["/message/source/type", "*"],
1688
- ["/message/body/type", "*"]
1689
- ])
1690
- });
1691
- var InternalRelays = class {
1692
- #logger;
1693
- // key -> socket
1694
- #clients = /* @__PURE__ */ new Map();
1695
- // node -> key
1696
- #links = /* @__PURE__ */ new Map();
1697
- onMsg;
1698
- onErr;
1699
- constructor(logger) {
1700
- this.#logger = logger;
1701
- }
1702
- add(key, soc) {
1703
- this.#clients.set(key, soc);
1704
- }
1705
- remove(key) {
1706
- this.#clients.delete(key);
1707
- for (const [node, k] of this.#links) {
1708
- if (k === key) {
1709
- this.#links.delete(node);
1710
- }
1711
- }
1712
- }
1713
- receive(key, msg) {
1714
- const node = this.link(key, msg);
1715
- if (node && this.onMsg) {
1716
- this.onMsg(key, node, msg);
1717
- }
1718
- }
1719
- link(key, msg) {
1720
- try {
1721
- const decoded = codec.decode(msg);
1722
- const { type, from, to } = decoded;
1723
- if (to === "all") {
1724
- switch (type) {
1725
- case "hello": {
1726
- if (this.#logger.enabledFor("debug")) {
1727
- this.#logger.debug(`${key} registers node ${from}`);
1728
- }
1729
- this.#links.set(from, key);
1730
- break;
1731
- }
1732
- case "bye": {
1733
- if (this.#logger.enabledFor("debug")) {
1734
- this.#logger.debug(`${key} unregisters node ${from}`);
1735
- }
1736
- this.#links.delete(from);
1737
- break;
1738
- }
1739
- }
1740
- return;
1741
- }
1742
- return from;
1743
- } catch (e) {
1744
- if (this.onErr) {
1745
- this.onErr(key, e instanceof Error ? e : new Error(`link failed :${e}`));
1746
- } else {
1747
- this.#logger.warn(`${key} unable to process ${msg}`, e);
1748
- }
1749
- }
1750
- }
1751
- send(key, node, msg, cb) {
1752
- const decoded = codec.decode(msg);
1753
- if (this.#logger.enabledFor("debug")) {
1754
- this.#logger.debug(`${key} sending msg to ${node} ${JSON.stringify(decoded)}`);
1755
- }
1756
- const clientKey = this.#links.get(node);
1757
- if (clientKey) {
1758
- const client = this.#clients.get(clientKey);
1759
- if (client) {
1760
- client.send(msg, { binary: false }, (err) => {
1761
- cb(clientKey, err);
1762
- });
1763
- return;
1764
- }
1765
- }
1766
- throw new Error(`${key} no active link for ${decoded.to}`);
1767
- }
1768
- };
1769
-
1770
1713
  // ../bridge-mesh/src/mesh/rest-directory/routes.ts
1771
1714
  function buildCanonicalBaseURL(request) {
1772
1715
  const requestURL = request.URL;
@@ -1887,7 +1830,11 @@ async function create(log, internal, env) {
1887
1830
  const logPrefix = handshake.logPrefix;
1888
1831
  const key = `r.${id}.${++keyId}`;
1889
1832
  log.info(`${logPrefix}connected on /relays with assigned key ${key}`);
1890
- internal.add(key, socket);
1833
+ internal.add(key, (msg, c, cb) => {
1834
+ socket.send(msg, { binary: false }, (err) => {
1835
+ cb(key, err);
1836
+ });
1837
+ });
1891
1838
  socket.on("error", (err) => {
1892
1839
  log.error(`${logPrefix}websocket error: ${err.message}`, err);
1893
1840
  });
@@ -1907,9 +1854,9 @@ async function create(log, internal, env) {
1907
1854
  });
1908
1855
  };
1909
1856
  }
1910
- var meshRelays = ({ logger, internal }) => {
1857
+ var meshRelays = ({ logger, relays }) => {
1911
1858
  return async (env) => {
1912
- return await create(logger, internal, env);
1859
+ return await create(logger, relays, env);
1913
1860
  };
1914
1861
  };
1915
1862
 
@@ -1937,10 +1884,13 @@ function onMessage(relays, log, key, node, socketsByNodeId, msg) {
1937
1884
  var handlerId2 = 0;
1938
1885
  async function create2(log, relays, env) {
1939
1886
  const socketsByNodeId = /* @__PURE__ */ new Map();
1940
- relays.onMsg = (k, nodeId, msg) => {
1887
+ relays.on("message", (k, nodeId, msg) => {
1941
1888
  try {
1942
1889
  const sockets = socketsByNodeId.get(nodeId);
1943
1890
  if (sockets && sockets.size > 0) {
1891
+ if (log.enabledFor("trace")) {
1892
+ log.debug(`${k} sending message to ${[...sockets.keys()]}`);
1893
+ }
1944
1894
  for (const [key, socket] of sockets) {
1945
1895
  socket.send(msg, { binary: false }, (err) => {
1946
1896
  if (err) {
@@ -1958,7 +1908,16 @@ async function create2(log, relays, env) {
1958
1908
  } catch (ex) {
1959
1909
  log.error(`${k} unable to process message`, ex);
1960
1910
  }
1961
- };
1911
+ });
1912
+ relays.on("disconnect", (k, nodeId) => {
1913
+ const sockets = socketsByNodeId.get(nodeId);
1914
+ if (sockets) {
1915
+ for (const [key, socket] of sockets) {
1916
+ socket.terminate();
1917
+ log.info(`${key} terminated because ${k} disconnected ${nodeId}`);
1918
+ }
1919
+ }
1920
+ });
1962
1921
  const id = ++handlerId2;
1963
1922
  let keyId = 0;
1964
1923
  return async ({ socket, handshake }) => {
@@ -2007,19 +1966,222 @@ var meshCluster = ({ logger, relays }) => {
2007
1966
 
2008
1967
  // ../bridge-mesh/src/index.ts
2009
1968
  import "@interopio/gateway-server";
2010
- import "@interopio/gateway";
1969
+
1970
+ // ../bridge-mesh/src/mesh/gateway/mesh.ts
1971
+ import { nanoid as nanoid2 } from "nanoid";
1972
+ var instanceId = 0;
1973
+ var BridgeMeshChannel = class {
1974
+ #logger;
1975
+ #relays;
1976
+ #connections;
1977
+ #state;
1978
+ #keyPrefix;
1979
+ constructor(relays, nodes, logger) {
1980
+ this.#relays = relays;
1981
+ this.#connections = nodes;
1982
+ this.#logger = logger;
1983
+ this.#state = /* @__PURE__ */ new Map();
1984
+ this.#keyPrefix = `g.${++instanceId}`;
1985
+ }
1986
+ get relays() {
1987
+ return this.#relays;
1988
+ }
1989
+ get connections() {
1990
+ return this.#connections;
1991
+ }
1992
+ #intervalId;
1993
+ subscribe(node, subscriber) {
1994
+ node ??= nanoid2();
1995
+ const key = `${this.#keyPrefix}-${node}`;
1996
+ if (this.#state.has(key)) {
1997
+ throw new Error(`already subscribed to node ${node}`);
1998
+ }
1999
+ this.#state.set(node, { subscriber, users: /* @__PURE__ */ new Set(), members: /* @__PURE__ */ new Set() });
2000
+ this.#relays.receive(key, { type: "hello", from: node, to: "all" });
2001
+ this.#relays.add(key, this.createRelayClient(key, node));
2002
+ this.#intervalId ??= setInterval(() => this.#announce(), 3e4);
2003
+ return node;
2004
+ }
2005
+ createRelayClient(key, node) {
2006
+ return (_msg, command, cb) => {
2007
+ switch (command.type) {
2008
+ case "hello":
2009
+ case "bye":
2010
+ break;
2011
+ case "data": {
2012
+ try {
2013
+ const event = {
2014
+ type: "message-received",
2015
+ message: command.data
2016
+ };
2017
+ this.#state.get(node)?.subscriber(event, node, this);
2018
+ } catch (err) {
2019
+ cb(key, err);
2020
+ }
2021
+ break;
2022
+ }
2023
+ }
2024
+ };
2025
+ }
2026
+ unsubscribe(node) {
2027
+ this.#delete(node);
2028
+ this.#relays.remove(`${this.#keyPrefix}-${node}`);
2029
+ }
2030
+ execute(node, action) {
2031
+ switch (action.type) {
2032
+ case "publish-message": {
2033
+ this.#onMessage(node, action.message);
2034
+ break;
2035
+ }
2036
+ case "add-users": {
2037
+ this.#onAddUsers(node, action.added);
2038
+ break;
2039
+ }
2040
+ case "remove-users": {
2041
+ this.#onRemoveUsers(node, action.removed);
2042
+ break;
2043
+ }
2044
+ }
2045
+ }
2046
+ #onMessage(node, data) {
2047
+ const nodes = this.#connections.find(node);
2048
+ if (nodes !== void 0) {
2049
+ const key = `${this.#keyPrefix}-${node}`;
2050
+ const command = { type: "data", from: node, data };
2051
+ nodes.forEach((connection) => {
2052
+ const to = connection.node;
2053
+ if (to === node) {
2054
+ return;
2055
+ }
2056
+ try {
2057
+ this.#relays.send(key, to, { ...command, to }, (k, err) => {
2058
+ if (err) {
2059
+ this.#logger.warn(`failed to send message from ${node} to ${to} via ${k}: ${err.message}`);
2060
+ }
2061
+ });
2062
+ } catch (err) {
2063
+ this.#logger.warn(`failed to send message from ${node} to ${to} via ${key}: ${err.message}`);
2064
+ }
2065
+ });
2066
+ }
2067
+ }
2068
+ #onAddUsers(node, usersToAdd) {
2069
+ let shouldAnnounce = false;
2070
+ const nodeState = this.#state.get(node);
2071
+ const nodeUsers = nodeState?.users;
2072
+ if (nodeUsers === void 0) {
2073
+ nodeState.users = new Set(usersToAdd);
2074
+ shouldAnnounce = true;
2075
+ } else {
2076
+ for (const u of usersToAdd) {
2077
+ if (nodeUsers.has(u)) {
2078
+ continue;
2079
+ }
2080
+ nodeUsers.add(u);
2081
+ shouldAnnounce = true;
2082
+ }
2083
+ }
2084
+ if (shouldAnnounce) {
2085
+ this.#announce(node);
2086
+ }
2087
+ }
2088
+ #onRemoveUsers(node, usersToRemove) {
2089
+ const nodeState = this.#state.get(node);
2090
+ const nodeUsers = nodeState?.users;
2091
+ if (nodeUsers === void 0) {
2092
+ return;
2093
+ }
2094
+ let shouldAnnounce = false;
2095
+ for (const u of usersToRemove) {
2096
+ if (!nodeUsers.has(u)) {
2097
+ continue;
2098
+ }
2099
+ nodeUsers.delete(u);
2100
+ shouldAnnounce = true;
2101
+ }
2102
+ if (nodeUsers.size === 0) {
2103
+ this.#delete(node);
2104
+ } else if (shouldAnnounce) {
2105
+ this.#announce(node);
2106
+ }
2107
+ }
2108
+ #announce(node) {
2109
+ const entries = node === void 0 ? this.#state.entries() : [[node, this.#state.get(node)]];
2110
+ const nodes = Array.from(entries, entryToNode);
2111
+ const nodeConnections = this.#connections.announce(nodes);
2112
+ for (const { node: n, connect } of nodeConnections) {
2113
+ const state = this.#state.get(n);
2114
+ if (state === void 0) {
2115
+ continue;
2116
+ }
2117
+ const toRemove = new Set(state.members);
2118
+ for (const c of connect) {
2119
+ if (c.node === n) {
2120
+ continue;
2121
+ }
2122
+ toRemove.delete(c.node);
2123
+ if (state.members.has(c.node)) {
2124
+ continue;
2125
+ }
2126
+ state.members.add(c.node);
2127
+ state.subscriber({ type: "member-added", node: c.node }, n, this);
2128
+ }
2129
+ for (const r of toRemove) {
2130
+ state.members.delete(r);
2131
+ state.subscriber({ type: "member-removed", node: r }, n, this);
2132
+ }
2133
+ }
2134
+ }
2135
+ #delete(node) {
2136
+ this.#state.delete(node);
2137
+ this.#connections.remove(node);
2138
+ }
2139
+ close() {
2140
+ for (const node of this.#state.keys()) {
2141
+ this.#delete(node);
2142
+ this.#relays.remove(`${this.#keyPrefix}-${node}`);
2143
+ }
2144
+ clearInterval(this.#intervalId);
2145
+ }
2146
+ };
2147
+ var entryToNode = ([node, state]) => {
2148
+ const users = Array.from(state?.users);
2149
+ const endpoint = `/cluster?node=${node}`;
2150
+ return { node, endpoint, users };
2151
+ };
2152
+
2153
+ // ../bridge-mesh/src/index.ts
2154
+ function connectNodeRelays(logger, relays, connections) {
2155
+ relays.on("disconnect", (key, node, links) => {
2156
+ for (const [linkNode, linkKey] of links) {
2157
+ try {
2158
+ relays.send(linkKey, linkNode, { type: "bye", from: node, to: linkNode }, (k, err) => {
2159
+ if (err) {
2160
+ logger.warn(`${k} error writing 'bye' msg to ${linkNode}: ${err}`);
2161
+ return;
2162
+ }
2163
+ if (logger.enabledFor("debug")) {
2164
+ logger.debug(`${k} sent 'bye' msg to ${linkNode}`);
2165
+ }
2166
+ });
2167
+ } catch (err) {
2168
+ logger.warn(`${linkKey} exception writing 'bye' msg to ${linkNode}: ${err}`);
2169
+ }
2170
+ }
2171
+ });
2172
+ }
2011
2173
  var mesh = async (options, configurer, config) => {
2012
- const { enabled, logger } = options;
2174
+ const { enabled, logger, relays, connections } = options;
2013
2175
  if (enabled !== true) {
2014
- logger.debug(`no mesh`);
2176
+ if (logger.enabledFor("debug")) {
2177
+ logger.debug(`no mesh`);
2178
+ }
2015
2179
  return;
2016
2180
  }
2017
- let { socket, timeout } = options;
2181
+ let { socket } = options;
2018
2182
  const authorize = socket.authorize ?? { access: config.auth.type === "none" ? "permitted" : "authenticated" };
2019
2183
  socket = { ping: 3e4, authorize, ...socket };
2020
- timeout ??= 6e4;
2021
- const connections = new InMemoryNodeConnections(logger, timeout);
2022
- const relays = new InternalRelays(logger);
2184
+ connectNodeRelays(logger, relays, connections);
2023
2185
  routes_default({ connections, authorize }, configurer);
2024
2186
  configurer.socket(
2025
2187
  {
@@ -2030,11 +2192,258 @@ var mesh = async (options, configurer, config) => {
2030
2192
  {
2031
2193
  path: "/relays",
2032
2194
  options: socket,
2033
- factory: meshRelays({ logger, internal: relays })
2195
+ factory: meshRelays({ logger, relays })
2034
2196
  }
2035
2197
  );
2036
2198
  };
2037
2199
 
2200
+ // ../bridge-mesh/src/mesh/connections.ts
2201
+ import "@interopio/gateway";
2202
+ import { EventEmitter as EventEmitter2 } from "node:events";
2203
+ var OrderedMap = class {
2204
+ #elements = [];
2205
+ #binarySearch(key) {
2206
+ let low = 0;
2207
+ let high = this.#elements.length - 1;
2208
+ while (low <= high) {
2209
+ const mid = low + high >> 1;
2210
+ const cmp = this.#elements[mid][0].localeCompare(key);
2211
+ if (cmp === 0) return mid;
2212
+ if (cmp < 0) low = mid + 1;
2213
+ else high = mid - 1;
2214
+ }
2215
+ return -low - 1;
2216
+ }
2217
+ set(key, value) {
2218
+ const idx = this.#binarySearch(key);
2219
+ if (idx >= 0) {
2220
+ this.#elements[idx][1] = value;
2221
+ } else {
2222
+ this.#elements.splice(~idx, 0, [key, value]);
2223
+ }
2224
+ }
2225
+ get(key) {
2226
+ const idx = this.#binarySearch(key);
2227
+ return idx >= 0 ? this.#elements[idx][1] : void 0;
2228
+ }
2229
+ has(key) {
2230
+ return this.#binarySearch(key) >= 0;
2231
+ }
2232
+ delete(key) {
2233
+ const idx = this.#binarySearch(key);
2234
+ if (idx >= 0) {
2235
+ this.#elements.splice(idx, 1);
2236
+ return true;
2237
+ }
2238
+ return false;
2239
+ }
2240
+ entries() {
2241
+ return this.#elements[Symbol.iterator]();
2242
+ }
2243
+ values() {
2244
+ return this.#elements.map((e) => e[1]);
2245
+ }
2246
+ };
2247
+ var InMemoryNodeConnections = class {
2248
+ #logger;
2249
+ #eventEmitter = new EventEmitter2();
2250
+ #nodes = new OrderedMap();
2251
+ #nodesByEndpoint = /* @__PURE__ */ new Map();
2252
+ #timeout;
2253
+ constructor(logger, timeout = 6e4) {
2254
+ this.#logger = logger;
2255
+ this.#timeout = timeout;
2256
+ }
2257
+ announce(nodes) {
2258
+ const now = Date.now();
2259
+ for (const node of nodes) {
2260
+ const { node: nodeId, users, endpoint } = node;
2261
+ const foundId = this.#nodesByEndpoint.get(endpoint);
2262
+ if (foundId) {
2263
+ if (foundId !== nodeId) {
2264
+ this.#logger.warn(`endpoint ${endpoint} clash. replacing node ${foundId} with ${nodeId}`);
2265
+ this.#nodesByEndpoint.set(endpoint, nodeId);
2266
+ this.#nodes.delete(foundId);
2267
+ this.#eventEmitter.emit("endpoint-deleted", foundId, endpoint, "clashed");
2268
+ }
2269
+ } else {
2270
+ this.#logger.info(`endpoint ${endpoint} announced for ${nodeId}`);
2271
+ this.#nodesByEndpoint.set(endpoint, nodeId);
2272
+ }
2273
+ const set = new Set(users ?? []);
2274
+ this.#nodes.set(nodeId, this.updateNode(node, set, nodeId, now, this.#nodes.get(nodeId)));
2275
+ this.#eventEmitter.emit("endpoint-announced", nodeId, endpoint, set);
2276
+ }
2277
+ this.cleanupOldNodes();
2278
+ return nodes.map((e) => {
2279
+ const { node } = e;
2280
+ const connect = this.findConnections(this.#nodes.get(node));
2281
+ return { node, connect };
2282
+ });
2283
+ }
2284
+ find(nodeId) {
2285
+ const e = this.#nodes.get(nodeId);
2286
+ if (e !== void 0) {
2287
+ return this.findConnections(e);
2288
+ }
2289
+ return void 0;
2290
+ }
2291
+ remove(nodeId) {
2292
+ const removed = this.#nodes.get(nodeId);
2293
+ if (removed !== void 0) {
2294
+ this.#nodes.delete(nodeId);
2295
+ const endpoint = removed.endpoint;
2296
+ this.#nodesByEndpoint.delete(endpoint);
2297
+ this.#logger.info(`endpoint ${endpoint} removed for ${nodeId}`);
2298
+ this.#eventEmitter.emit("endpoint-deleted", nodeId, endpoint, "removed");
2299
+ return true;
2300
+ }
2301
+ return false;
2302
+ }
2303
+ updateNode(newNode, users, _key, lastAccess, oldNode) {
2304
+ const node = oldNode ?? { ...newNode };
2305
+ return { ...node, users, lastAccess };
2306
+ }
2307
+ cleanupOldNodes() {
2308
+ const threshold = Date.now() - this.#timeout;
2309
+ for (const [nodeId, v] of this.#nodes.entries()) {
2310
+ if (v.lastAccess < threshold) {
2311
+ if (this.#logger.enabledFor("debug")) {
2312
+ this.#logger.debug(`${nodeId} expired - no announcement since ${new Date(v.lastAccess).toISOString()}, timeout is ${this.#timeout} ms.`);
2313
+ }
2314
+ this.#nodes.delete(nodeId);
2315
+ const endpoint = v.endpoint;
2316
+ this.#nodesByEndpoint.delete(endpoint);
2317
+ this.#eventEmitter.emit("endpoint-deleted", nodeId, endpoint, "expired");
2318
+ }
2319
+ }
2320
+ }
2321
+ on(event, listener) {
2322
+ this.#eventEmitter.on(event, listener);
2323
+ return this;
2324
+ }
2325
+ off(event, listener) {
2326
+ this.#eventEmitter.off(event, listener);
2327
+ return this;
2328
+ }
2329
+ findConnections(node) {
2330
+ const connections = new Array();
2331
+ for (const [key, value] of this.#nodes.entries()) {
2332
+ if (node !== void 0 && key < node.node) {
2333
+ const intersection = new Set(value.users);
2334
+ node.users.forEach((user) => {
2335
+ if (!value.users.has(user)) {
2336
+ intersection.delete(user);
2337
+ }
2338
+ });
2339
+ value.users.forEach((user) => {
2340
+ if (!node.users.has(user)) {
2341
+ intersection.delete(user);
2342
+ }
2343
+ });
2344
+ if (intersection.size > 0) {
2345
+ const e = { node: value.node, endpoint: value.endpoint };
2346
+ connections.push(e);
2347
+ }
2348
+ }
2349
+ }
2350
+ return connections;
2351
+ }
2352
+ };
2353
+
2354
+ // ../bridge-gateway/src/desktop.ts
2355
+ function isRunningInConnectDesktop(env = process.env) {
2356
+ return env._GD_STARTING_CONTEXT_ !== void 0;
2357
+ }
2358
+ function parseStartingContext(env = process.env) {
2359
+ if (!isRunningInConnectDesktop(env)) {
2360
+ throw new Error("Not running in io.Connect Desktop");
2361
+ }
2362
+ return JSON.parse(env._GD_STARTING_CONTEXT_).applicationConfig?.customProperties;
2363
+ }
2364
+
2365
+ // ../bridge-gateway/src/index.ts
2366
+ import { tmpdir } from "node:os";
2367
+ import { createServer as createServer2 } from "node:net";
2368
+ import { join } from "node:path";
2369
+ import "@interopio/gateway";
2370
+ import "@interopio/gateway-server";
2371
+ function onGatewayStarted(log) {
2372
+ return (gateway) => {
2373
+ const info = JSON.stringify(gateway.info());
2374
+ if (isRunningInConnectDesktop()) {
2375
+ const env = process.env["GLUE-ENV"] || "DEMO";
2376
+ const region = process.env["GLUE-REGION"] || "INTEROP.IO";
2377
+ const user = process.env.USERNAME ?? process.env.USER;
2378
+ const pipe = `glue42-${env}-${region}-${user}`;
2379
+ const pipeName = process.platform === "win32" ? `\\\\.\\pipe\\${pipe}` : `${join(tmpdir(), pipe + ".sock")}`;
2380
+ log.info(`gateway started: ${info}, opening ${pipeName}`);
2381
+ const server = createServer2((stream) => {
2382
+ log.info(`stream connected, sending info...`);
2383
+ stream.write(info);
2384
+ stream.end(() => {
2385
+ server.close();
2386
+ });
2387
+ });
2388
+ server.listen(pipeName);
2389
+ } else {
2390
+ log.info(`gateway started: ${info}`);
2391
+ }
2392
+ };
2393
+ }
2394
+ async function serverGatewayConfig(gateway, bridge, env = process.env) {
2395
+ let enabled = gateway.enabled;
2396
+ if (enabled === void 0) {
2397
+ if (isRunningInConnectDesktop(env)) {
2398
+ enabled = parseStartingContext(env)?.gatewayApp === true;
2399
+ }
2400
+ }
2401
+ let contextsLifetime = gateway.contexts.lifetime;
2402
+ if (contextsLifetime === void 0) {
2403
+ if (isRunningInConnectDesktop(env)) {
2404
+ contextsLifetime = "retained";
2405
+ }
2406
+ }
2407
+ if (enabled) {
2408
+ return {
2409
+ // globals: { websocket },
2410
+ clients: {
2411
+ inactive_seconds: 0
2412
+ },
2413
+ ping: bridge.wsPingInterval ?? void 0,
2414
+ mesh: {
2415
+ channel: bridge.meshChannel
2416
+ },
2417
+ contexts: {
2418
+ lifetime: contextsLifetime,
2419
+ visibility: [
2420
+ { context: /___channel___.+/, restrictions: "cluster" },
2421
+ { context: /T42\..*/, restrictions: "local" },
2422
+ { context: "___platform_prefs___", restrictions: "local" },
2423
+ { context: /___workspace___.+/, restrictions: "local" },
2424
+ { context: /___window-hibernation___.+/, restrictions: "local" },
2425
+ { context: /___instance___.+/, restrictions: "local" },
2426
+ { context: /___window___.+/, restrictions: "local" },
2427
+ { restrictions: "cluster" }
2428
+ ]
2429
+ },
2430
+ methods: {
2431
+ visibility: [
2432
+ { method: /T42\..*/, restrictions: "local" },
2433
+ { restrictions: "cluster" }
2434
+ ]
2435
+ },
2436
+ peers: {
2437
+ visibility: [
2438
+ { domain: "context", restrictions: "cluster" },
2439
+ { domain: "interop", restrictions: "cluster" },
2440
+ { domain: "bus", restrictions: "local" }
2441
+ ]
2442
+ }
2443
+ };
2444
+ }
2445
+ }
2446
+
2038
2447
  // src/instance/BridgeNode.ts
2039
2448
  function logStartingInfo(logger) {
2040
2449
  logger.info(`Starting io.Bridge v${package_default.version} using Node.js ${process.version} with PID ${process.pid} (started by ${userInfo().username} in ${process.cwd()})`);
@@ -2060,12 +2469,13 @@ var BridgeNode = class {
2060
2469
  config;
2061
2470
  uuid;
2062
2471
  discoveryService;
2472
+ meshChannel;
2063
2473
  version;
2064
2474
  server;
2065
2475
  licenseValidator;
2066
2476
  constructor(config) {
2067
2477
  this.config = config;
2068
- this.licenseValidator = BridgeLicenseValidator(this.getLogger("license-validator"));
2478
+ this.licenseValidator = BridgeLicenseValidator(this.getLogger("license"));
2069
2479
  this.licenseValidator.validate(config.license);
2070
2480
  this.uuid = newUUID();
2071
2481
  this.version = parseVersion(package_default.version);
@@ -2081,7 +2491,10 @@ var BridgeNode = class {
2081
2491
  const joinConfig = config.network.join;
2082
2492
  const isAutoDetectionEnabled = joinConfig.autoDetectionEnabled;
2083
2493
  const discoveryConfig = joinConfig.discovery;
2084
- this.discoveryService = createDiscoveryService(getLogger("discovery-service"), discoveryConfig, isAutoDetectionEnabled, localMemberPromise);
2494
+ this.discoveryService = createDiscoveryService(getLogger("discovery"), discoveryConfig, isAutoDetectionEnabled, localMemberPromise);
2495
+ const relays = new InternalRelays(this.getLogger("relays"));
2496
+ const connections = new InMemoryNodeConnections(this.getLogger("nodes"), this.config.mesh.timeout ?? 6e4);
2497
+ this.meshChannel = new BridgeMeshChannel(relays, connections, this.getLogger("channel"));
2085
2498
  }
2086
2499
  getLogger(name) {
2087
2500
  return getLogger(name);
@@ -2107,8 +2520,8 @@ var BridgeNode = class {
2107
2520
  await mesh({
2108
2521
  logger: this.getLogger("mesh"),
2109
2522
  enabled: true,
2110
- timeout: this.config.mesh.timeout ?? 6e4,
2111
- // 60 seconds
2523
+ relays: this.meshChannel.relays,
2524
+ connections: this.meshChannel.connections,
2112
2525
  socket: {
2113
2526
  ping: this.config.server.wsPingInterval ?? 3e4
2114
2527
  // 30 seconds
@@ -2116,50 +2529,17 @@ var BridgeNode = class {
2116
2529
  }, configurer, config2);
2117
2530
  },
2118
2531
  auth: { type: "none", ...this.config.server.auth },
2119
- cors: this.config.server.cors
2532
+ cors: this.config.server.cors.disabled ? false : this.config.server.cors
2120
2533
  };
2121
- if (this.config.gateway.enabled) {
2122
- config.gateway = {
2123
- clients: {
2124
- inactive_seconds: 0
2125
- },
2126
- ping: this.config.server.wsPingInterval ?? void 0,
2127
- mesh: {
2128
- cluster: {
2129
- endpoint: `http://localhost:${this.config.server.port}`,
2130
- directory: { interval: 1e3 * 30 }
2131
- // 30 seconds
2132
- }
2133
- },
2134
- contexts: {
2135
- lifetime: this.config.gateway.contexts.lifetime,
2136
- visibility: [
2137
- { context: /___channel___.+/, restrictions: "cluster" },
2138
- { context: /T42\..*/, restrictions: "local" },
2139
- { context: "___platform_prefs___", restrictions: "local" },
2140
- { restrictions: "cluster" }
2141
- ]
2142
- },
2143
- methods: {
2144
- visibility: [
2145
- { method: /T42\..*/, restrictions: "local" },
2146
- { restrictions: "cluster" }
2147
- ]
2148
- },
2149
- peers: {
2150
- visibility: [
2151
- { domain: "context", restrictions: "cluster" },
2152
- { domain: "interop", restrictions: "cluster" },
2153
- { domain: "bus", restrictions: "local" }
2154
- ]
2155
- }
2156
- };
2157
- }
2534
+ config.gateway = await serverGatewayConfig(this.config.gateway, { meshChannel: this.meshChannel, wsPingInterval: this.config.server.wsPingInterval });
2158
2535
  logStartingInfo(this.getLogger("node"));
2159
2536
  this.server = await gatewayServer(config);
2160
2537
  if (false) {
2161
2538
  await this.discoveryService.start();
2162
2539
  }
2540
+ if (config.gateway !== void 0) {
2541
+ onGatewayStarted(this.getLogger("gw"))(this.server.gateway);
2542
+ }
2163
2543
  await this.scheduleLicenseCheck();
2164
2544
  }
2165
2545
  async stop() {
@@ -2170,17 +2550,31 @@ var BridgeNode = class {
2170
2550
  await this.discoveryService.close();
2171
2551
  } catch (e) {
2172
2552
  }
2553
+ this.meshChannel.close();
2173
2554
  }
2174
2555
  };
2175
2556
 
2176
2557
  // src/main.mts
2177
2558
  import { readFileSync as readFileSync3 } from "node:fs";
2559
+ import { format } from "node:util";
2178
2560
  var hadFatalError = false;
2179
2561
  var reportedErrors = /* @__PURE__ */ new Set();
2562
+ function getErrorMessage(error) {
2563
+ if (typeof error !== "object" || error === null) {
2564
+ return String(error);
2565
+ }
2566
+ if (typeof error["stack"] === "string") {
2567
+ return error["stack"];
2568
+ }
2569
+ return format("%o", error);
2570
+ }
2180
2571
  function onFatalError(error) {
2181
2572
  process.exitCode = 2;
2182
2573
  hadFatalError = true;
2183
- const errorMessage = `io.Bridge: ${package_default.version}`;
2574
+ const errorMessage = `
2575
+ io.Bridge: ${package_default.version}
2576
+
2577
+ ${getErrorMessage(error)}`;
2184
2578
  if (!reportedErrors.has(errorMessage)) {
2185
2579
  console.error(error);
2186
2580
  reportedErrors.add(errorMessage);
@@ -2218,14 +2612,18 @@ try {
2218
2612
  await import("dotenv/config");
2219
2613
  const config = new Config();
2220
2614
  config.license ??= loadLicense();
2221
- config.server.port ??= loadConfig("server.port", NUMBER) ?? 8383;
2615
+ config.server.port ??= loadConfig("server.port", NUMBER) ?? 8084;
2222
2616
  config.server.host ??= loadConfig("server.host", STRING);
2223
2617
  config.server.wsPingInterval ??= loadConfig("server.ws-ping-interval", NUMBER);
2224
2618
  config.gateway.enabled ??= loadConfig("gateway.enabled", BOOLEAN);
2225
2619
  config.gateway.contexts.lifetime ??= loadConfig("gateway.contexts.lifetime", STRING);
2226
2620
  config.server.auth.type = loadConfig("server.auth.type", STRING) ?? "none";
2621
+ config.server.auth.oauth2.jwt.issuerUri = loadConfig("server.auth.oauth2.jwt.issuer-uri", STRING) ?? void 0;
2622
+ config.server.auth.oauth2.jwt.issuer = loadConfig("server.auth.oauth2.jwt.issuer", STRING) ?? void 0;
2623
+ config.server.auth.oauth2.jwt.audience = loadConfig("server.auth.oauth2.jwt.audience", STRING) ?? void 0;
2227
2624
  config.server.cors.allowOrigin = loadConfig("server.cors.allow-origin", STRING) ?? "*";
2228
2625
  config.server.cors.allowCredentials = loadConfig("server.cors.allow-credentials", BOOLEAN) ?? true;
2626
+ config.server.cors.disabled = loadConfig("server.cors.disabled", BOOLEAN) ?? false ? true : void 0;
2229
2627
  const bridge = new BridgeNode(config);
2230
2628
  await bridge.start();
2231
2629
  } catch (error) {