@interopio/bridge 0.0.6-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
@@ -697,6 +819,9 @@ var ServerConfig = class {
697
819
  type: "none",
698
820
  basic: {
699
821
  realm: "io.Bridge"
822
+ },
823
+ oauth2: {
824
+ jwt: { issuerUri: "" }
700
825
  }
701
826
  };
702
827
  wsPingInterval = 3e4;
@@ -1209,7 +1334,7 @@ function parseVersion(version) {
1209
1334
  // package.json
1210
1335
  var package_default = {
1211
1336
  name: "@interopio/bridge",
1212
- version: "0.0.6-beta.0",
1337
+ version: "0.1.0-beta.0",
1213
1338
  license: "see license in license.md",
1214
1339
  author: "interop.io",
1215
1340
  homepage: "https://docs.interop.io/bridge",
@@ -1220,6 +1345,11 @@ var package_default = {
1220
1345
  "glue42",
1221
1346
  "interop.io"
1222
1347
  ],
1348
+ repository: {
1349
+ type: "git",
1350
+ url: "https://github.com/InteropIO/bridge.git",
1351
+ directory: "packages/bridge"
1352
+ },
1223
1353
  type: "module",
1224
1354
  exports: {
1225
1355
  "./package.json": "./package.json",
@@ -1244,12 +1374,13 @@ var package_default = {
1244
1374
  build: "npm run build:main && npm run build:index"
1245
1375
  },
1246
1376
  dependencies: {
1247
- "@interopio/gateway-server": "^0.10.0-beta.0",
1248
- dotenv: "^17.2.2",
1377
+ "@interopio/gateway-server": "^0.13.0-beta.1",
1378
+ dotenv: "^17.2.3",
1249
1379
  jsrsasign: "^11.1.0",
1250
- nanoid: "^5.0.9"
1380
+ nanoid: "^5.1.6"
1251
1381
  },
1252
1382
  devDependencies: {
1383
+ "@interopio/gateway": "^0.16.1-beta.0",
1253
1384
  "@types/jsrsasign": "^10.5.15",
1254
1385
  "@types/ws": "^8.18.1",
1255
1386
  "rand-seed": "^3.0.0"
@@ -1579,195 +1710,6 @@ var BridgeLicenseValidator = (logger) => new LicenseValidator({ validationKey, l
1579
1710
  // src/instance/BridgeNode.ts
1580
1711
  import { userInfo } from "node:os";
1581
1712
 
1582
- // ../bridge-mesh/src/mesh/connections.ts
1583
- import "@interopio/gateway";
1584
- var InMemoryNodeConnections = class {
1585
- #logger;
1586
- #nodes = /* @__PURE__ */ new Map();
1587
- #nodesByEndpoint = /* @__PURE__ */ new Map();
1588
- #memberIds = 0;
1589
- #timeout;
1590
- constructor(logger, timeout = 6e4) {
1591
- this.#logger = logger;
1592
- this.#timeout = timeout;
1593
- }
1594
- announce(nodes) {
1595
- for (const node of nodes) {
1596
- const { node: nodeId, users, endpoint } = node;
1597
- const foundId = this.#nodesByEndpoint.get(endpoint);
1598
- if (foundId) {
1599
- if (foundId !== nodeId) {
1600
- this.#logger.warn(`endpoint ${endpoint} clash. replacing node ${foundId} with ${nodeId}`);
1601
- this.#nodesByEndpoint.set(endpoint, nodeId);
1602
- this.#nodes.delete(foundId);
1603
- }
1604
- } else {
1605
- this.#logger.info(`endpoint ${endpoint} announced for ${nodeId}`);
1606
- this.#nodesByEndpoint.set(endpoint, nodeId);
1607
- }
1608
- this.#nodes.set(nodeId, this.updateNode(node, new Set(users ?? []), nodeId, this.#nodes.get(nodeId)));
1609
- }
1610
- this.cleanupOldNodes();
1611
- const sortedNodes = this.sortedNodeValues();
1612
- return nodes.map((e) => {
1613
- const { node } = e;
1614
- const connect = this.findConnections(sortedNodes, this.#nodes.get(node));
1615
- return { node, connect };
1616
- });
1617
- }
1618
- find(nodeId) {
1619
- const e = this.#nodes.get(nodeId);
1620
- if (e !== void 0) {
1621
- const sortedNodes = this.sortedNodeValues();
1622
- const { node } = e;
1623
- return this.findConnections(sortedNodes, this.#nodes.get(node));
1624
- }
1625
- return void 0;
1626
- }
1627
- remove(nodeId) {
1628
- const removed = this.#nodes.get(nodeId);
1629
- if (removed !== void 0) {
1630
- this.#nodes.delete(nodeId);
1631
- const endpoint = removed.endpoint;
1632
- this.#nodesByEndpoint.delete(endpoint);
1633
- this.#logger.info(`endpoint ${endpoint} removed for ${nodeId}`);
1634
- return true;
1635
- }
1636
- return false;
1637
- }
1638
- updateNode(newNode, users, _key, oldNode) {
1639
- const node = oldNode ?? { ...newNode, memberId: this.#memberIds++ };
1640
- return { ...node, users, lastAccess: Date.now() };
1641
- }
1642
- sortedNodeValues() {
1643
- return Array.from(this.#nodes.values()).sort((a, b) => a.memberId - b.memberId);
1644
- }
1645
- cleanupOldNodes() {
1646
- const threshold = Date.now() - this.#timeout;
1647
- for (const [nodeId, v] of this.#nodes) {
1648
- if (v.lastAccess < threshold) {
1649
- if (this.#logger.enabledFor("debug")) {
1650
- this.#logger.debug(`${nodeId} expired - no announcement since ${new Date(v.lastAccess).toISOString()}, timeout is ${this.#timeout} ms.`);
1651
- }
1652
- this.#nodes.delete(nodeId);
1653
- }
1654
- }
1655
- }
1656
- findConnections(sortedNodes, node) {
1657
- return sortedNodes.reduce((l, c) => {
1658
- if (node !== void 0 && c.memberId < node.memberId) {
1659
- const intersection = new Set(c.users);
1660
- node.users.forEach((user) => {
1661
- if (!c.users.has(user)) {
1662
- intersection.delete(user);
1663
- }
1664
- });
1665
- c.users.forEach((user) => {
1666
- if (!node.users.has(user)) {
1667
- intersection.delete(user);
1668
- }
1669
- });
1670
- if (intersection.size > 0) {
1671
- const e = { node: c.node, endpoint: c.endpoint };
1672
- return l.concat(e);
1673
- }
1674
- }
1675
- return l;
1676
- }, new Array());
1677
- }
1678
- };
1679
-
1680
- // ../bridge-mesh/src/mesh/relays.ts
1681
- import { IOGateway as IOGateway3 } from "@interopio/gateway";
1682
- var codec = IOGateway3.Encoding.transit({
1683
- keywordize: /* @__PURE__ */ new Map([
1684
- ["/type", "*"],
1685
- ["/message/body/type", "*"],
1686
- ["/message/origin", "*"],
1687
- ["/message/receiver/type", "*"],
1688
- ["/message/source/type", "*"],
1689
- ["/message/body/type", "*"]
1690
- ])
1691
- });
1692
- var InternalRelays = class {
1693
- #logger;
1694
- // key -> socket
1695
- #clients = /* @__PURE__ */ new Map();
1696
- // node -> key
1697
- #links = /* @__PURE__ */ new Map();
1698
- onMsg;
1699
- onErr;
1700
- constructor(logger) {
1701
- this.#logger = logger;
1702
- }
1703
- add(key, soc) {
1704
- this.#clients.set(key, soc);
1705
- }
1706
- remove(key) {
1707
- this.#clients.delete(key);
1708
- for (const [node, k] of this.#links) {
1709
- if (k === key) {
1710
- this.#links.delete(node);
1711
- }
1712
- }
1713
- }
1714
- receive(key, msg) {
1715
- const node = this.link(key, msg);
1716
- if (node && this.onMsg) {
1717
- this.onMsg(key, node, msg);
1718
- }
1719
- }
1720
- link(key, msg) {
1721
- try {
1722
- const decoded = codec.decode(msg);
1723
- const { type, from, to } = decoded;
1724
- if (to === "all") {
1725
- switch (type) {
1726
- case "hello": {
1727
- if (this.#logger.enabledFor("debug")) {
1728
- this.#logger.debug(`${key} registers node ${from}`);
1729
- }
1730
- this.#links.set(from, key);
1731
- break;
1732
- }
1733
- case "bye": {
1734
- if (this.#logger.enabledFor("debug")) {
1735
- this.#logger.debug(`${key} unregisters node ${from}`);
1736
- }
1737
- this.#links.delete(from);
1738
- break;
1739
- }
1740
- }
1741
- return;
1742
- }
1743
- return from;
1744
- } catch (e) {
1745
- if (this.onErr) {
1746
- this.onErr(key, e instanceof Error ? e : new Error(`link failed :${e}`));
1747
- } else {
1748
- this.#logger.warn(`${key} unable to process ${msg}`, e);
1749
- }
1750
- }
1751
- }
1752
- send(key, node, msg, cb) {
1753
- const decoded = codec.decode(msg);
1754
- if (this.#logger.enabledFor("debug")) {
1755
- this.#logger.debug(`${key} sending msg to ${node} ${JSON.stringify(decoded)}`);
1756
- }
1757
- const clientKey = this.#links.get(node);
1758
- if (clientKey) {
1759
- const client = this.#clients.get(clientKey);
1760
- if (client) {
1761
- client.send(msg, { binary: false }, (err) => {
1762
- cb(clientKey, err);
1763
- });
1764
- return;
1765
- }
1766
- }
1767
- throw new Error(`${key} no active link for ${decoded.to}`);
1768
- }
1769
- };
1770
-
1771
1713
  // ../bridge-mesh/src/mesh/rest-directory/routes.ts
1772
1714
  function buildCanonicalBaseURL(request) {
1773
1715
  const requestURL = request.URL;
@@ -1888,7 +1830,11 @@ async function create(log, internal, env) {
1888
1830
  const logPrefix = handshake.logPrefix;
1889
1831
  const key = `r.${id}.${++keyId}`;
1890
1832
  log.info(`${logPrefix}connected on /relays with assigned key ${key}`);
1891
- internal.add(key, socket);
1833
+ internal.add(key, (msg, c, cb) => {
1834
+ socket.send(msg, { binary: false }, (err) => {
1835
+ cb(key, err);
1836
+ });
1837
+ });
1892
1838
  socket.on("error", (err) => {
1893
1839
  log.error(`${logPrefix}websocket error: ${err.message}`, err);
1894
1840
  });
@@ -1908,9 +1854,9 @@ async function create(log, internal, env) {
1908
1854
  });
1909
1855
  };
1910
1856
  }
1911
- var meshRelays = ({ logger, internal }) => {
1857
+ var meshRelays = ({ logger, relays }) => {
1912
1858
  return async (env) => {
1913
- return await create(logger, internal, env);
1859
+ return await create(logger, relays, env);
1914
1860
  };
1915
1861
  };
1916
1862
 
@@ -1938,10 +1884,13 @@ function onMessage(relays, log, key, node, socketsByNodeId, msg) {
1938
1884
  var handlerId2 = 0;
1939
1885
  async function create2(log, relays, env) {
1940
1886
  const socketsByNodeId = /* @__PURE__ */ new Map();
1941
- relays.onMsg = (k, nodeId, msg) => {
1887
+ relays.on("message", (k, nodeId, msg) => {
1942
1888
  try {
1943
1889
  const sockets = socketsByNodeId.get(nodeId);
1944
1890
  if (sockets && sockets.size > 0) {
1891
+ if (log.enabledFor("trace")) {
1892
+ log.debug(`${k} sending message to ${[...sockets.keys()]}`);
1893
+ }
1945
1894
  for (const [key, socket] of sockets) {
1946
1895
  socket.send(msg, { binary: false }, (err) => {
1947
1896
  if (err) {
@@ -1959,7 +1908,16 @@ async function create2(log, relays, env) {
1959
1908
  } catch (ex) {
1960
1909
  log.error(`${k} unable to process message`, ex);
1961
1910
  }
1962
- };
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
+ });
1963
1921
  const id = ++handlerId2;
1964
1922
  let keyId = 0;
1965
1923
  return async ({ socket, handshake }) => {
@@ -2008,19 +1966,222 @@ var meshCluster = ({ logger, relays }) => {
2008
1966
 
2009
1967
  // ../bridge-mesh/src/index.ts
2010
1968
  import "@interopio/gateway-server";
2011
- 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
+ }
2012
2173
  var mesh = async (options, configurer, config) => {
2013
- const { enabled, logger } = options;
2174
+ const { enabled, logger, relays, connections } = options;
2014
2175
  if (enabled !== true) {
2015
- logger.debug(`no mesh`);
2176
+ if (logger.enabledFor("debug")) {
2177
+ logger.debug(`no mesh`);
2178
+ }
2016
2179
  return;
2017
2180
  }
2018
- let { socket, timeout } = options;
2181
+ let { socket } = options;
2019
2182
  const authorize = socket.authorize ?? { access: config.auth.type === "none" ? "permitted" : "authenticated" };
2020
2183
  socket = { ping: 3e4, authorize, ...socket };
2021
- timeout ??= 6e4;
2022
- const connections = new InMemoryNodeConnections(logger, timeout);
2023
- const relays = new InternalRelays(logger);
2184
+ connectNodeRelays(logger, relays, connections);
2024
2185
  routes_default({ connections, authorize }, configurer);
2025
2186
  configurer.socket(
2026
2187
  {
@@ -2031,11 +2192,258 @@ var mesh = async (options, configurer, config) => {
2031
2192
  {
2032
2193
  path: "/relays",
2033
2194
  options: socket,
2034
- factory: meshRelays({ logger, internal: relays })
2195
+ factory: meshRelays({ logger, relays })
2035
2196
  }
2036
2197
  );
2037
2198
  };
2038
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
+
2039
2447
  // src/instance/BridgeNode.ts
2040
2448
  function logStartingInfo(logger) {
2041
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()})`);
@@ -2061,12 +2469,13 @@ var BridgeNode = class {
2061
2469
  config;
2062
2470
  uuid;
2063
2471
  discoveryService;
2472
+ meshChannel;
2064
2473
  version;
2065
2474
  server;
2066
2475
  licenseValidator;
2067
2476
  constructor(config) {
2068
2477
  this.config = config;
2069
- this.licenseValidator = BridgeLicenseValidator(this.getLogger("license-validator"));
2478
+ this.licenseValidator = BridgeLicenseValidator(this.getLogger("license"));
2070
2479
  this.licenseValidator.validate(config.license);
2071
2480
  this.uuid = newUUID();
2072
2481
  this.version = parseVersion(package_default.version);
@@ -2082,7 +2491,10 @@ var BridgeNode = class {
2082
2491
  const joinConfig = config.network.join;
2083
2492
  const isAutoDetectionEnabled = joinConfig.autoDetectionEnabled;
2084
2493
  const discoveryConfig = joinConfig.discovery;
2085
- 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"));
2086
2498
  }
2087
2499
  getLogger(name) {
2088
2500
  return getLogger(name);
@@ -2108,8 +2520,8 @@ var BridgeNode = class {
2108
2520
  await mesh({
2109
2521
  logger: this.getLogger("mesh"),
2110
2522
  enabled: true,
2111
- timeout: this.config.mesh.timeout ?? 6e4,
2112
- // 60 seconds
2523
+ relays: this.meshChannel.relays,
2524
+ connections: this.meshChannel.connections,
2113
2525
  socket: {
2114
2526
  ping: this.config.server.wsPingInterval ?? 3e4
2115
2527
  // 30 seconds
@@ -2119,50 +2531,15 @@ var BridgeNode = class {
2119
2531
  auth: { type: "none", ...this.config.server.auth },
2120
2532
  cors: this.config.server.cors.disabled ? false : this.config.server.cors
2121
2533
  };
2122
- if (this.config.gateway.enabled) {
2123
- const { WebSocket: websocket } = await import("ws");
2124
- config.gateway = {
2125
- globals: { websocket },
2126
- clients: {
2127
- inactive_seconds: 0
2128
- },
2129
- ping: this.config.server.wsPingInterval ?? void 0,
2130
- mesh: {
2131
- cluster: {
2132
- endpoint: `http://localhost:${this.config.server.port}`,
2133
- directory: { interval: 1e3 * 30 }
2134
- // 30 seconds
2135
- }
2136
- },
2137
- contexts: {
2138
- lifetime: this.config.gateway.contexts.lifetime,
2139
- visibility: [
2140
- { context: /___channel___.+/, restrictions: "cluster" },
2141
- { context: /T42\..*/, restrictions: "local" },
2142
- { context: "___platform_prefs___", restrictions: "local" },
2143
- { restrictions: "cluster" }
2144
- ]
2145
- },
2146
- methods: {
2147
- visibility: [
2148
- { method: /T42\..*/, restrictions: "local" },
2149
- { restrictions: "cluster" }
2150
- ]
2151
- },
2152
- peers: {
2153
- visibility: [
2154
- { domain: "context", restrictions: "cluster" },
2155
- { domain: "interop", restrictions: "cluster" },
2156
- { domain: "bus", restrictions: "local" }
2157
- ]
2158
- }
2159
- };
2160
- }
2534
+ config.gateway = await serverGatewayConfig(this.config.gateway, { meshChannel: this.meshChannel, wsPingInterval: this.config.server.wsPingInterval });
2161
2535
  logStartingInfo(this.getLogger("node"));
2162
2536
  this.server = await gatewayServer(config);
2163
2537
  if (false) {
2164
2538
  await this.discoveryService.start();
2165
2539
  }
2540
+ if (config.gateway !== void 0) {
2541
+ onGatewayStarted(this.getLogger("gw"))(this.server.gateway);
2542
+ }
2166
2543
  await this.scheduleLicenseCheck();
2167
2544
  }
2168
2545
  async stop() {
@@ -2173,6 +2550,7 @@ var BridgeNode = class {
2173
2550
  await this.discoveryService.close();
2174
2551
  } catch (e) {
2175
2552
  }
2553
+ this.meshChannel.close();
2176
2554
  }
2177
2555
  };
2178
2556
 
@@ -2234,12 +2612,15 @@ try {
2234
2612
  await import("dotenv/config");
2235
2613
  const config = new Config();
2236
2614
  config.license ??= loadLicense();
2237
- config.server.port ??= loadConfig("server.port", NUMBER) ?? 8383;
2615
+ config.server.port ??= loadConfig("server.port", NUMBER) ?? 8084;
2238
2616
  config.server.host ??= loadConfig("server.host", STRING);
2239
2617
  config.server.wsPingInterval ??= loadConfig("server.ws-ping-interval", NUMBER);
2240
2618
  config.gateway.enabled ??= loadConfig("gateway.enabled", BOOLEAN);
2241
2619
  config.gateway.contexts.lifetime ??= loadConfig("gateway.contexts.lifetime", STRING);
2242
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;
2243
2624
  config.server.cors.allowOrigin = loadConfig("server.cors.allow-origin", STRING) ?? "*";
2244
2625
  config.server.cors.allowCredentials = loadConfig("server.cors.allow-credentials", BOOLEAN) ?? true;
2245
2626
  config.server.cors.disabled = loadConfig("server.cors.disabled", BOOLEAN) ?? false ? true : void 0;