@interopio/bridge 0.0.6-beta.0 → 1.0.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/changelog.md +29 -0
- package/dist/main.js +746 -260
- package/dist/main.js.map +4 -4
- package/package.json +11 -5
package/dist/main.js
CHANGED
|
@@ -1,3 +1,152 @@
|
|
|
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("info")) {
|
|
39
|
+
this.#logger.info(`${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("info")) {
|
|
48
|
+
this.#logger.info(`${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
|
+
} else {
|
|
76
|
+
const node = decoded.to;
|
|
77
|
+
const link = decoded.from;
|
|
78
|
+
const type2 = decoded.type;
|
|
79
|
+
const senderLinkData = this.#links.get(link);
|
|
80
|
+
switch (type2) {
|
|
81
|
+
case "hello":
|
|
82
|
+
if (this.#logger.enabledFor("debug")) {
|
|
83
|
+
this.#logger.debug(`${key} connecting ${link} to ${node}`);
|
|
84
|
+
}
|
|
85
|
+
senderLinkData?.linksByNode.set(decoded.to, key);
|
|
86
|
+
break;
|
|
87
|
+
case "bye":
|
|
88
|
+
if (this.#logger.enabledFor("debug")) {
|
|
89
|
+
this.#logger.debug(`${key} disconnecting ${link} from ${node}`);
|
|
90
|
+
}
|
|
91
|
+
senderLinkData?.linksByNode.delete(decoded.to);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const encoded = isEncoded(msg) ? msg : codec.encode(msg);
|
|
96
|
+
return { node: from, decoded, encoded };
|
|
97
|
+
} catch (e) {
|
|
98
|
+
if (!this.#eventEmitter.emit("error", key, e instanceof Error ? e : new Error(`link failed :${e}`))) {
|
|
99
|
+
this.#logger.warn(`${key} unable to process ${msg}`, e);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
on(event, listener) {
|
|
104
|
+
this.#eventEmitter.on(event, listener);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
send(key, node, msg, cb) {
|
|
108
|
+
if (this.#logger.enabledFor("trace")) {
|
|
109
|
+
this.#logger.debug(`${key} sending msg to ${node}`);
|
|
110
|
+
}
|
|
111
|
+
const encoded = isEncoded(msg) ? msg : codec.encode(msg);
|
|
112
|
+
const decoded = isEncoded(msg) ? codec.decode(msg) : msg;
|
|
113
|
+
{
|
|
114
|
+
node ??= decoded.to;
|
|
115
|
+
const link = decoded.from;
|
|
116
|
+
const type = decoded.type;
|
|
117
|
+
const senderLinkData = this.#links.get(link);
|
|
118
|
+
switch (type) {
|
|
119
|
+
case "hello":
|
|
120
|
+
if (this.#logger.enabledFor("debug")) {
|
|
121
|
+
this.#logger.debug(`${key} connecting ${link} to ${node}`);
|
|
122
|
+
}
|
|
123
|
+
senderLinkData?.linksByNode.set(node, key);
|
|
124
|
+
break;
|
|
125
|
+
case "bye":
|
|
126
|
+
if (this.#logger.enabledFor("debug")) {
|
|
127
|
+
this.#logger.debug(`${key} disconnecting ${link} from ${node}`);
|
|
128
|
+
}
|
|
129
|
+
senderLinkData?.linksByNode.delete(node);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
{
|
|
134
|
+
const receiverLinkData = this.#links.get(node);
|
|
135
|
+
if (receiverLinkData?.key) {
|
|
136
|
+
const handler = this.#handlersByKey.get(receiverLinkData?.key);
|
|
137
|
+
if (handler) {
|
|
138
|
+
handler(encoded, decoded, cb);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (decoded.type === "bye") {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`${key} no active link for ${node}`);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
1
150
|
// src/utils/uuid.ts
|
|
2
151
|
import { nanoid } from "nanoid";
|
|
3
152
|
function newUUID() {
|
|
@@ -201,9 +350,9 @@ var DefaultDiscoveryConfig = class {
|
|
|
201
350
|
};
|
|
202
351
|
|
|
203
352
|
// src/logging.ts
|
|
204
|
-
import {
|
|
353
|
+
import { getLogger as getGatewayLogger } from "@interopio/gateway/logging/core";
|
|
205
354
|
function getLogger(name) {
|
|
206
|
-
return
|
|
355
|
+
return getGatewayLogger(`gateway.bridge.${name}`);
|
|
207
356
|
}
|
|
208
357
|
|
|
209
358
|
// src/kubernetes/KubernetesDiscoveryStrategyFactory.ts
|
|
@@ -697,6 +846,9 @@ var ServerConfig = class {
|
|
|
697
846
|
type: "none",
|
|
698
847
|
basic: {
|
|
699
848
|
realm: "io.Bridge"
|
|
849
|
+
},
|
|
850
|
+
oauth2: {
|
|
851
|
+
jwt: { issuerUri: "" }
|
|
700
852
|
}
|
|
701
853
|
};
|
|
702
854
|
wsPingInterval = 3e4;
|
|
@@ -1209,7 +1361,7 @@ function parseVersion(version) {
|
|
|
1209
1361
|
// package.json
|
|
1210
1362
|
var package_default = {
|
|
1211
1363
|
name: "@interopio/bridge",
|
|
1212
|
-
version: "
|
|
1364
|
+
version: "1.0.0",
|
|
1213
1365
|
license: "see license in license.md",
|
|
1214
1366
|
author: "interop.io",
|
|
1215
1367
|
homepage: "https://docs.interop.io/bridge",
|
|
@@ -1220,6 +1372,11 @@ var package_default = {
|
|
|
1220
1372
|
"glue42",
|
|
1221
1373
|
"interop.io"
|
|
1222
1374
|
],
|
|
1375
|
+
repository: {
|
|
1376
|
+
type: "git",
|
|
1377
|
+
url: "git+https://github.com/InteropIO/bridge.git",
|
|
1378
|
+
directory: "packages/bridge"
|
|
1379
|
+
},
|
|
1223
1380
|
type: "module",
|
|
1224
1381
|
exports: {
|
|
1225
1382
|
"./package.json": "./package.json",
|
|
@@ -1234,7 +1391,7 @@ var package_default = {
|
|
|
1234
1391
|
}
|
|
1235
1392
|
},
|
|
1236
1393
|
bin: {
|
|
1237
|
-
bridge: "
|
|
1394
|
+
bridge: "bin/bridge.js"
|
|
1238
1395
|
},
|
|
1239
1396
|
scripts: {
|
|
1240
1397
|
test: "mocha test --recursive",
|
|
@@ -1244,12 +1401,13 @@ var package_default = {
|
|
|
1244
1401
|
build: "npm run build:main && npm run build:index"
|
|
1245
1402
|
},
|
|
1246
1403
|
dependencies: {
|
|
1247
|
-
"@interopio/gateway-server": "^0.
|
|
1248
|
-
dotenv: "^17.2.
|
|
1404
|
+
"@interopio/gateway-server": "^0.17.0",
|
|
1405
|
+
dotenv: "^17.2.3",
|
|
1249
1406
|
jsrsasign: "^11.1.0",
|
|
1250
|
-
nanoid: "^5.
|
|
1407
|
+
nanoid: "^5.1.6"
|
|
1251
1408
|
},
|
|
1252
1409
|
devDependencies: {
|
|
1410
|
+
"@interopio/gateway": "^0.20.0",
|
|
1253
1411
|
"@types/jsrsasign": "^10.5.15",
|
|
1254
1412
|
"@types/ws": "^8.18.1",
|
|
1255
1413
|
"rand-seed": "^3.0.0"
|
|
@@ -1579,195 +1737,6 @@ var BridgeLicenseValidator = (logger) => new LicenseValidator({ validationKey, l
|
|
|
1579
1737
|
// src/instance/BridgeNode.ts
|
|
1580
1738
|
import { userInfo } from "node:os";
|
|
1581
1739
|
|
|
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
1740
|
// ../bridge-mesh/src/mesh/rest-directory/routes.ts
|
|
1772
1741
|
function buildCanonicalBaseURL(request) {
|
|
1773
1742
|
const requestURL = request.URL;
|
|
@@ -1817,9 +1786,13 @@ function routes(config, { handle }) {
|
|
|
1817
1786
|
);
|
|
1818
1787
|
await response.end();
|
|
1819
1788
|
} else {
|
|
1820
|
-
const nodes = json.map((node) =>
|
|
1789
|
+
const nodes = json.map((node) => {
|
|
1790
|
+
return fromRequestNode(node, request);
|
|
1791
|
+
});
|
|
1821
1792
|
const result = connections.announce(nodes).map((connection) => {
|
|
1822
|
-
const connect = connection.connect?.map((node) =>
|
|
1793
|
+
const connect = connection.connect?.map((node) => {
|
|
1794
|
+
return toRequestNode(node, request);
|
|
1795
|
+
});
|
|
1823
1796
|
return { ...connection, connect };
|
|
1824
1797
|
});
|
|
1825
1798
|
const buffer = Buffer.from(JSON.stringify(result), "utf8");
|
|
@@ -1885,10 +1858,15 @@ async function create(log, internal, env) {
|
|
|
1885
1858
|
log.info(`relays-${id} server is listening on ${env.endpoint}`);
|
|
1886
1859
|
let keyId = 0;
|
|
1887
1860
|
return async ({ socket, handshake }) => {
|
|
1888
|
-
const logPrefix = handshake.logPrefix;
|
|
1861
|
+
const logPrefix = handshake.logPrefix ?? "";
|
|
1862
|
+
const query = handshake.url.searchParams;
|
|
1889
1863
|
const key = `r.${id}.${++keyId}`;
|
|
1890
|
-
log.info(`${logPrefix}connected on /relays with assigned key ${key}`);
|
|
1891
|
-
internal.add(key,
|
|
1864
|
+
log.info(`${logPrefix}connected on /relays with ${query} and assigned key ${key}`);
|
|
1865
|
+
internal.add(key, (msg, c, cb) => {
|
|
1866
|
+
socket.send(msg, { binary: false }, (err) => {
|
|
1867
|
+
cb(key, err);
|
|
1868
|
+
});
|
|
1869
|
+
});
|
|
1892
1870
|
socket.on("error", (err) => {
|
|
1893
1871
|
log.error(`${logPrefix}websocket error: ${err.message}`, err);
|
|
1894
1872
|
});
|
|
@@ -1908,9 +1886,9 @@ async function create(log, internal, env) {
|
|
|
1908
1886
|
});
|
|
1909
1887
|
};
|
|
1910
1888
|
}
|
|
1911
|
-
var meshRelays = ({ logger,
|
|
1889
|
+
var meshRelays = ({ logger, relays }) => {
|
|
1912
1890
|
return async (env) => {
|
|
1913
|
-
return await create(logger,
|
|
1891
|
+
return await create(logger, relays, env);
|
|
1914
1892
|
};
|
|
1915
1893
|
};
|
|
1916
1894
|
|
|
@@ -1923,7 +1901,7 @@ function onMessage(relays, log, key, node, socketsByNodeId, msg) {
|
|
|
1923
1901
|
log.warn(`${k} error writing msg ${msg}: ${err}`);
|
|
1924
1902
|
return;
|
|
1925
1903
|
}
|
|
1926
|
-
if (log.enabledFor("
|
|
1904
|
+
if (log.enabledFor("trace")) {
|
|
1927
1905
|
log.debug(`${k} sent msg ${msg}`);
|
|
1928
1906
|
}
|
|
1929
1907
|
});
|
|
@@ -1938,17 +1916,20 @@ function onMessage(relays, log, key, node, socketsByNodeId, msg) {
|
|
|
1938
1916
|
var handlerId2 = 0;
|
|
1939
1917
|
async function create2(log, relays, env) {
|
|
1940
1918
|
const socketsByNodeId = /* @__PURE__ */ new Map();
|
|
1941
|
-
relays.
|
|
1919
|
+
relays.on("message", (k, nodeId, msg) => {
|
|
1942
1920
|
try {
|
|
1943
1921
|
const sockets = socketsByNodeId.get(nodeId);
|
|
1944
1922
|
if (sockets && sockets.size > 0) {
|
|
1923
|
+
if (log.enabledFor("trace")) {
|
|
1924
|
+
log.debug(`${k} sending message to ${[...sockets.keys()]}`);
|
|
1925
|
+
}
|
|
1945
1926
|
for (const [key, socket] of sockets) {
|
|
1946
1927
|
socket.send(msg, { binary: false }, (err) => {
|
|
1947
1928
|
if (err) {
|
|
1948
1929
|
log.warn(`${key} error writing from ${k} msg ${msg}: ${err}`);
|
|
1949
1930
|
return;
|
|
1950
1931
|
}
|
|
1951
|
-
if (log.enabledFor("
|
|
1932
|
+
if (log.enabledFor("trace")) {
|
|
1952
1933
|
log.debug(`${key} sent from ${k} msg ${msg}`);
|
|
1953
1934
|
}
|
|
1954
1935
|
});
|
|
@@ -1959,14 +1940,23 @@ async function create2(log, relays, env) {
|
|
|
1959
1940
|
} catch (ex) {
|
|
1960
1941
|
log.error(`${k} unable to process message`, ex);
|
|
1961
1942
|
}
|
|
1962
|
-
};
|
|
1943
|
+
});
|
|
1944
|
+
relays.on("disconnect", (k, nodeId) => {
|
|
1945
|
+
const sockets = socketsByNodeId.get(nodeId);
|
|
1946
|
+
if (sockets) {
|
|
1947
|
+
for (const [key, socket] of sockets) {
|
|
1948
|
+
socket.terminate();
|
|
1949
|
+
log.info(`${key} terminated because ${k} disconnected ${nodeId}`);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1963
1953
|
const id = ++handlerId2;
|
|
1964
1954
|
let keyId = 0;
|
|
1965
1955
|
return async ({ socket, handshake }) => {
|
|
1966
1956
|
const logPrefix = handshake.logPrefix ?? "";
|
|
1967
1957
|
const query = handshake.url.searchParams;
|
|
1968
1958
|
const key = `c.${id}.${++keyId}`;
|
|
1969
|
-
log.info(`${logPrefix}connected on /cluster with ${query}
|
|
1959
|
+
log.info(`${logPrefix}connected on /cluster with ${query} and assigned key ${key}`);
|
|
1970
1960
|
const node = query.get("node");
|
|
1971
1961
|
if (node) {
|
|
1972
1962
|
let sockets = socketsByNodeId.get(node);
|
|
@@ -2008,19 +1998,231 @@ var meshCluster = ({ logger, relays }) => {
|
|
|
2008
1998
|
|
|
2009
1999
|
// ../bridge-mesh/src/index.ts
|
|
2010
2000
|
import "@interopio/gateway-server";
|
|
2011
|
-
|
|
2001
|
+
|
|
2002
|
+
// ../bridge-mesh/src/mesh/gateway/mesh.ts
|
|
2003
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
2004
|
+
import gateway from "@interopio/gateway/package.json" with { type: "json" };
|
|
2005
|
+
var instanceId = 0;
|
|
2006
|
+
var BridgeMeshChannel = class {
|
|
2007
|
+
#logger;
|
|
2008
|
+
#relays;
|
|
2009
|
+
#connections;
|
|
2010
|
+
#state;
|
|
2011
|
+
#keyPrefix;
|
|
2012
|
+
constructor(relays, nodes, logger) {
|
|
2013
|
+
this.#relays = relays;
|
|
2014
|
+
this.#connections = nodes;
|
|
2015
|
+
this.#logger = logger;
|
|
2016
|
+
this.#state = /* @__PURE__ */ new Map();
|
|
2017
|
+
this.#keyPrefix = `g.${++instanceId}`;
|
|
2018
|
+
}
|
|
2019
|
+
get relays() {
|
|
2020
|
+
return this.#relays;
|
|
2021
|
+
}
|
|
2022
|
+
get connections() {
|
|
2023
|
+
return this.#connections;
|
|
2024
|
+
}
|
|
2025
|
+
#intervalId;
|
|
2026
|
+
subscribe(node, subscriber) {
|
|
2027
|
+
node ??= nanoid2();
|
|
2028
|
+
const key = `${this.#keyPrefix}-${node}`;
|
|
2029
|
+
if (this.#state.has(key)) {
|
|
2030
|
+
throw new Error(`already subscribed to node ${node}`);
|
|
2031
|
+
}
|
|
2032
|
+
this.#state.set(node, { subscriber, users: /* @__PURE__ */ new Set(), members: /* @__PURE__ */ new Set() });
|
|
2033
|
+
this.#relays.receive(key, { type: "hello", from: node, to: "all" });
|
|
2034
|
+
this.#relays.add(key, this.createRelayClient(key, node));
|
|
2035
|
+
this.#intervalId ??= setInterval(() => this.#announce(), 3e4);
|
|
2036
|
+
return node;
|
|
2037
|
+
}
|
|
2038
|
+
createRelayClient(key, node) {
|
|
2039
|
+
return (_msg, command, cb) => {
|
|
2040
|
+
switch (command.type) {
|
|
2041
|
+
case "hello":
|
|
2042
|
+
case "bye":
|
|
2043
|
+
break;
|
|
2044
|
+
case "data": {
|
|
2045
|
+
try {
|
|
2046
|
+
const event = {
|
|
2047
|
+
type: "message-received",
|
|
2048
|
+
message: command.data
|
|
2049
|
+
};
|
|
2050
|
+
this.#state.get(node)?.subscriber(event, node, this);
|
|
2051
|
+
} catch (err) {
|
|
2052
|
+
cb(key, err);
|
|
2053
|
+
}
|
|
2054
|
+
break;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
unsubscribe(node) {
|
|
2060
|
+
this.#delete(node);
|
|
2061
|
+
this.#relays.remove(`${this.#keyPrefix}-${node}`);
|
|
2062
|
+
}
|
|
2063
|
+
execute(node, action) {
|
|
2064
|
+
switch (action.type) {
|
|
2065
|
+
case "publish-message": {
|
|
2066
|
+
this.#onMessage(node, action.message);
|
|
2067
|
+
break;
|
|
2068
|
+
}
|
|
2069
|
+
case "add-users": {
|
|
2070
|
+
this.#onAddUsers(node, action.added);
|
|
2071
|
+
break;
|
|
2072
|
+
}
|
|
2073
|
+
case "remove-users": {
|
|
2074
|
+
this.#onRemoveUsers(node, action.removed);
|
|
2075
|
+
break;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
#onMessage(node, data) {
|
|
2080
|
+
const nodes = this.#connections.find(node);
|
|
2081
|
+
if (nodes !== void 0) {
|
|
2082
|
+
const key = `${this.#keyPrefix}-${node}`;
|
|
2083
|
+
const command = { type: "data", from: node, data };
|
|
2084
|
+
nodes.forEach((connection) => {
|
|
2085
|
+
const to = connection.node;
|
|
2086
|
+
if (to === node) {
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
try {
|
|
2090
|
+
this.#relays.send(key, to, { ...command, to }, (k, err) => {
|
|
2091
|
+
if (err) {
|
|
2092
|
+
this.#logger.warn(`failed to send message from ${node} to ${to} via ${k}: ${err.message}`);
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
2095
|
+
} catch (err) {
|
|
2096
|
+
this.#logger.warn(`failed to send message from ${node} to ${to} via ${key}: ${err.message}`);
|
|
2097
|
+
}
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
#onAddUsers(node, usersToAdd) {
|
|
2102
|
+
let shouldAnnounce = false;
|
|
2103
|
+
const nodeState = this.#state.get(node);
|
|
2104
|
+
const nodeUsers = nodeState?.users;
|
|
2105
|
+
if (nodeUsers === void 0) {
|
|
2106
|
+
nodeState.users = new Set(usersToAdd);
|
|
2107
|
+
shouldAnnounce = true;
|
|
2108
|
+
} else {
|
|
2109
|
+
for (const u of usersToAdd) {
|
|
2110
|
+
if (nodeUsers.has(u)) {
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
nodeUsers.add(u);
|
|
2114
|
+
shouldAnnounce = true;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
if (shouldAnnounce) {
|
|
2118
|
+
this.#announce(node);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
#onRemoveUsers(node, usersToRemove) {
|
|
2122
|
+
const nodeState = this.#state.get(node);
|
|
2123
|
+
const nodeUsers = nodeState?.users;
|
|
2124
|
+
if (nodeUsers === void 0) {
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
let shouldAnnounce = false;
|
|
2128
|
+
for (const u of usersToRemove) {
|
|
2129
|
+
if (!nodeUsers.has(u)) {
|
|
2130
|
+
continue;
|
|
2131
|
+
}
|
|
2132
|
+
nodeUsers.delete(u);
|
|
2133
|
+
shouldAnnounce = true;
|
|
2134
|
+
}
|
|
2135
|
+
if (nodeUsers.size === 0) {
|
|
2136
|
+
this.#delete(node);
|
|
2137
|
+
} else if (shouldAnnounce) {
|
|
2138
|
+
this.#announce(node);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
#announce(node) {
|
|
2142
|
+
const entries = node === void 0 ? this.#state.entries() : [[node, this.#state.get(node)]];
|
|
2143
|
+
const nodes = Array.from(entries, entryToNode);
|
|
2144
|
+
const nodeConnections = this.#connections.announce(nodes);
|
|
2145
|
+
for (const { node: n, connect } of nodeConnections) {
|
|
2146
|
+
const state = this.#state.get(n);
|
|
2147
|
+
if (state === void 0) {
|
|
2148
|
+
continue;
|
|
2149
|
+
}
|
|
2150
|
+
const toRemove = new Set(state.members);
|
|
2151
|
+
for (const c of connect) {
|
|
2152
|
+
if (c.node === n) {
|
|
2153
|
+
continue;
|
|
2154
|
+
}
|
|
2155
|
+
toRemove.delete(c.node);
|
|
2156
|
+
if (state.members.has(c.node)) {
|
|
2157
|
+
continue;
|
|
2158
|
+
}
|
|
2159
|
+
state.members.add(c.node);
|
|
2160
|
+
state.subscriber({ type: "member-added", node: c.node }, n, this);
|
|
2161
|
+
}
|
|
2162
|
+
for (const r of toRemove) {
|
|
2163
|
+
state.members.delete(r);
|
|
2164
|
+
state.subscriber({ type: "member-removed", node: r }, n, this);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
#delete(node) {
|
|
2169
|
+
this.#state.delete(node);
|
|
2170
|
+
this.#connections.remove(node);
|
|
2171
|
+
}
|
|
2172
|
+
async init(gossipTimestamp = 0) {
|
|
2173
|
+
await this.#connections.init(gossipTimestamp);
|
|
2174
|
+
}
|
|
2175
|
+
async close() {
|
|
2176
|
+
for (const node of this.#state.keys()) {
|
|
2177
|
+
this.#delete(node);
|
|
2178
|
+
this.#relays.remove(`${this.#keyPrefix}-${node}`);
|
|
2179
|
+
}
|
|
2180
|
+
clearInterval(this.#intervalId);
|
|
2181
|
+
await this.#connections.close();
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
var entryToNode = ([node, state]) => {
|
|
2185
|
+
const users = Array.from(state?.users);
|
|
2186
|
+
return { node, users, metadata: { version: gateway.version, type: "bridge" } };
|
|
2187
|
+
};
|
|
2188
|
+
|
|
2189
|
+
// ../bridge-mesh/src/index.ts
|
|
2190
|
+
function connectNodeRelays(logger, relays, connections) {
|
|
2191
|
+
relays.on("disconnect", (key, node, links) => {
|
|
2192
|
+
try {
|
|
2193
|
+
connections.remove(node);
|
|
2194
|
+
} catch (e) {
|
|
2195
|
+
logger.warn(`${key} error removing node ${node} from connections: ${e}`);
|
|
2196
|
+
}
|
|
2197
|
+
for (const [linkNode, linkKey] of links) {
|
|
2198
|
+
try {
|
|
2199
|
+
relays.send(linkKey, linkNode, { type: "bye", from: node, to: linkNode }, (k, err) => {
|
|
2200
|
+
if (err) {
|
|
2201
|
+
logger.warn(`${k} error writing 'bye' msg to ${linkNode}: ${err}`);
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
if (logger.enabledFor("debug")) {
|
|
2205
|
+
logger.debug(`${k} sent 'bye' msg to ${linkNode} from ${node}`);
|
|
2206
|
+
}
|
|
2207
|
+
});
|
|
2208
|
+
} catch (err) {
|
|
2209
|
+
logger.warn(`${linkKey} exception writing 'bye' msg to ${linkNode} from ${node}: ${err}`);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2012
2214
|
var mesh = async (options, configurer, config) => {
|
|
2013
|
-
const { enabled, logger } = options;
|
|
2215
|
+
const { enabled, logger, relays, connections } = options;
|
|
2014
2216
|
if (enabled !== true) {
|
|
2015
|
-
logger.debug
|
|
2217
|
+
if (logger.enabledFor("debug")) {
|
|
2218
|
+
logger.debug(`no mesh`);
|
|
2219
|
+
}
|
|
2016
2220
|
return;
|
|
2017
2221
|
}
|
|
2018
|
-
let { socket
|
|
2222
|
+
let { socket } = options;
|
|
2019
2223
|
const authorize = socket.authorize ?? { access: config.auth.type === "none" ? "permitted" : "authenticated" };
|
|
2020
2224
|
socket = { ping: 3e4, authorize, ...socket };
|
|
2021
|
-
|
|
2022
|
-
const connections = new InMemoryNodeConnections(logger, timeout);
|
|
2023
|
-
const relays = new InternalRelays(logger);
|
|
2225
|
+
connectNodeRelays(logger, relays, connections);
|
|
2024
2226
|
routes_default({ connections, authorize }, configurer);
|
|
2025
2227
|
configurer.socket(
|
|
2026
2228
|
{
|
|
@@ -2031,11 +2233,321 @@ var mesh = async (options, configurer, config) => {
|
|
|
2031
2233
|
{
|
|
2032
2234
|
path: "/relays",
|
|
2033
2235
|
options: socket,
|
|
2034
|
-
factory: meshRelays({ logger,
|
|
2236
|
+
factory: meshRelays({ logger, relays })
|
|
2035
2237
|
}
|
|
2036
2238
|
);
|
|
2037
2239
|
};
|
|
2038
2240
|
|
|
2241
|
+
// ../bridge-mesh/src/mesh/connections.ts
|
|
2242
|
+
import { hrwHash } from "hrw-hash";
|
|
2243
|
+
|
|
2244
|
+
// ../bridge-mesh/src/mesh/lamport.ts
|
|
2245
|
+
import { appendFile, readFile, rename, writeFile } from "node:fs/promises";
|
|
2246
|
+
var LamportClock = class _LamportClock {
|
|
2247
|
+
static #FLUSH_INTERVAL = 5e3;
|
|
2248
|
+
// 5 seconds
|
|
2249
|
+
static #CLOCK_FILE = "./lamport.clock";
|
|
2250
|
+
static #WAL_FILE = "./lamport.wal";
|
|
2251
|
+
#timestamp;
|
|
2252
|
+
#instanceId;
|
|
2253
|
+
#flushIntervalId;
|
|
2254
|
+
#pendingWrites = 0;
|
|
2255
|
+
constructor(instanceId2) {
|
|
2256
|
+
this.#instanceId = instanceId2;
|
|
2257
|
+
this.#timestamp = 0;
|
|
2258
|
+
this.#flushIntervalId = setInterval(() => {
|
|
2259
|
+
this.#flush().catch((err) => {
|
|
2260
|
+
});
|
|
2261
|
+
}, _LamportClock.#FLUSH_INTERVAL);
|
|
2262
|
+
}
|
|
2263
|
+
async init(gossipTimestamp = 0) {
|
|
2264
|
+
const baseTimestamp = await this.#loadSnapshot();
|
|
2265
|
+
const journaledIncrements = await this.#replayWal();
|
|
2266
|
+
this.#timestamp = Math.max(baseTimestamp + journaledIncrements, gossipTimestamp);
|
|
2267
|
+
}
|
|
2268
|
+
tick() {
|
|
2269
|
+
this.#timestamp += 1;
|
|
2270
|
+
this.#pendingWrites += 1;
|
|
2271
|
+
this.#appendToWal().catch((_err) => {
|
|
2272
|
+
});
|
|
2273
|
+
return { timestamp: this.#timestamp, instanceId: this.#instanceId };
|
|
2274
|
+
}
|
|
2275
|
+
observe(gossipTimestamp) {
|
|
2276
|
+
this.#timestamp = Math.max(this.#timestamp, gossipTimestamp) + 1;
|
|
2277
|
+
this.#pendingWrites += 1;
|
|
2278
|
+
this.#appendToWal().catch((_err) => {
|
|
2279
|
+
});
|
|
2280
|
+
return { timestamp: this.#timestamp, instanceId: this.#instanceId };
|
|
2281
|
+
}
|
|
2282
|
+
timestamp() {
|
|
2283
|
+
return { timestamp: this.#timestamp, instanceId: this.#instanceId };
|
|
2284
|
+
}
|
|
2285
|
+
async shutdown() {
|
|
2286
|
+
clearInterval(this.#flushIntervalId);
|
|
2287
|
+
await this.#flush();
|
|
2288
|
+
}
|
|
2289
|
+
async #appendToWal() {
|
|
2290
|
+
const data = `${this.#timestamp}
|
|
2291
|
+
`;
|
|
2292
|
+
await appendFile(_LamportClock.#WAL_FILE, data, "utf-8");
|
|
2293
|
+
}
|
|
2294
|
+
async #replayWal() {
|
|
2295
|
+
try {
|
|
2296
|
+
const data = await readFile(_LamportClock.#WAL_FILE, "utf-8");
|
|
2297
|
+
return data.trim().split("\n").filter(Boolean).length;
|
|
2298
|
+
} catch {
|
|
2299
|
+
}
|
|
2300
|
+
return 0;
|
|
2301
|
+
}
|
|
2302
|
+
async #loadSnapshot() {
|
|
2303
|
+
try {
|
|
2304
|
+
const data = await readFile(_LamportClock.#CLOCK_FILE, "utf-8");
|
|
2305
|
+
return parseInt(data, 10) || 0;
|
|
2306
|
+
} catch {
|
|
2307
|
+
}
|
|
2308
|
+
return 0;
|
|
2309
|
+
}
|
|
2310
|
+
async #flush() {
|
|
2311
|
+
if (this.#pendingWrites === 0) {
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
const tmp = `${_LamportClock.#CLOCK_FILE}.tmp`;
|
|
2315
|
+
const data = `${this.#timestamp}
|
|
2316
|
+
`;
|
|
2317
|
+
await writeFile(tmp, data, { flag: "w" });
|
|
2318
|
+
await rename(tmp, _LamportClock.#CLOCK_FILE);
|
|
2319
|
+
await writeFile(_LamportClock.#WAL_FILE, "", { flag: "w" });
|
|
2320
|
+
this.#pendingWrites = 0;
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2323
|
+
|
|
2324
|
+
// ../bridge-mesh/src/mesh/connections.ts
|
|
2325
|
+
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
2326
|
+
var InMemoryNodeConnections = class {
|
|
2327
|
+
#logger;
|
|
2328
|
+
#eventEmitter = new EventEmitter2();
|
|
2329
|
+
#nodes = /* @__PURE__ */ new Map();
|
|
2330
|
+
#clusterNodes;
|
|
2331
|
+
#lamport;
|
|
2332
|
+
#timeout;
|
|
2333
|
+
constructor(logger, instanceId2, timeout = 6e4) {
|
|
2334
|
+
this.#logger = logger;
|
|
2335
|
+
this.#clusterNodes = [instanceId2];
|
|
2336
|
+
this.#lamport = new LamportClock(instanceId2);
|
|
2337
|
+
this.#timeout = timeout;
|
|
2338
|
+
}
|
|
2339
|
+
async init(gossipTimestamp = 0) {
|
|
2340
|
+
await this.#lamport.init(gossipTimestamp);
|
|
2341
|
+
}
|
|
2342
|
+
async close() {
|
|
2343
|
+
await this.#lamport.shutdown();
|
|
2344
|
+
}
|
|
2345
|
+
announce(announcements) {
|
|
2346
|
+
const now = Date.now();
|
|
2347
|
+
for (const announcement of announcements) {
|
|
2348
|
+
const { node: nodeId, users, endpoint: announcedEndpoint, metadata } = announcement;
|
|
2349
|
+
const replicas = hrwHash(nodeId, this.#clusterNodes);
|
|
2350
|
+
const owner = replicas[0];
|
|
2351
|
+
const externalEndpoint = announcedEndpoint !== void 0 && announcedEndpoint !== `/cluster?node=${nodeId}`;
|
|
2352
|
+
const endpoint = externalEndpoint ? announcedEndpoint : `/cluster?node=${nodeId}&owner=${owner}`;
|
|
2353
|
+
const lamport = this.#lamport.tick();
|
|
2354
|
+
const userSet = new Set(users ?? []);
|
|
2355
|
+
let node = this.#nodes.get(nodeId);
|
|
2356
|
+
if (!node) {
|
|
2357
|
+
node = { node: nodeId, users: userSet, firstSeen: lamport, lastSeen: now, owner, replicas, endpoint, metadata };
|
|
2358
|
+
this.#nodes.set(nodeId, node);
|
|
2359
|
+
this.#logger.info(`${nodeId} announced at endpoint ${endpoint} with meta: ${JSON.stringify(metadata)}`);
|
|
2360
|
+
}
|
|
2361
|
+
node.owner = owner;
|
|
2362
|
+
if (!externalEndpoint) {
|
|
2363
|
+
node.endpoint = endpoint;
|
|
2364
|
+
}
|
|
2365
|
+
node.replicas = replicas;
|
|
2366
|
+
node.lastSeen = now;
|
|
2367
|
+
node.users = userSet;
|
|
2368
|
+
this.#eventEmitter.emit("node-announced", nodeId, endpoint, userSet);
|
|
2369
|
+
}
|
|
2370
|
+
this.cleanupOldNodes();
|
|
2371
|
+
const sortedNodes = this.#sortedNodeValues();
|
|
2372
|
+
return announcements.map((e) => {
|
|
2373
|
+
const node = this.#nodes.get(e.node);
|
|
2374
|
+
const connect = this.#findConnections(sortedNodes, node);
|
|
2375
|
+
return { node: node.node, owner: node.owner, connect };
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
find(nodeId) {
|
|
2379
|
+
const e = this.#nodes.get(nodeId);
|
|
2380
|
+
if (e !== void 0) {
|
|
2381
|
+
const sortedNodes = this.#sortedNodeValues();
|
|
2382
|
+
return this.#findConnections(sortedNodes, e);
|
|
2383
|
+
}
|
|
2384
|
+
return void 0;
|
|
2385
|
+
}
|
|
2386
|
+
remove(nodeId) {
|
|
2387
|
+
const removed = this.#nodes.get(nodeId);
|
|
2388
|
+
if (removed !== void 0) {
|
|
2389
|
+
this.#nodes.delete(nodeId);
|
|
2390
|
+
const endpoint = removed.endpoint;
|
|
2391
|
+
this.#logger.info(`endpoint ${endpoint} removed for ${nodeId}`);
|
|
2392
|
+
this.#eventEmitter.emit("node-deleted", nodeId, endpoint, "removed");
|
|
2393
|
+
return true;
|
|
2394
|
+
}
|
|
2395
|
+
return false;
|
|
2396
|
+
}
|
|
2397
|
+
#sortedNodeValues() {
|
|
2398
|
+
return Array.from(this.#nodes.values()).sort((a, b) => {
|
|
2399
|
+
const diff = a.firstSeen.timestamp - b.firstSeen.timestamp;
|
|
2400
|
+
if (diff === 0) {
|
|
2401
|
+
return a.node.localeCompare(b.node);
|
|
2402
|
+
}
|
|
2403
|
+
return diff;
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
cleanupOldNodes() {
|
|
2407
|
+
const threshold = Date.now() - this.#timeout;
|
|
2408
|
+
for (const [nodeId, v] of this.#nodes) {
|
|
2409
|
+
if (v.lastSeen < threshold) {
|
|
2410
|
+
if (this.#logger.enabledFor("debug")) {
|
|
2411
|
+
this.#logger.debug(`${nodeId} expired - no announcement since ${new Date(v.lastSeen).toISOString()}, timeout is ${this.#timeout} ms.`);
|
|
2412
|
+
}
|
|
2413
|
+
this.#nodes.delete(nodeId);
|
|
2414
|
+
const endpoint = v.endpoint;
|
|
2415
|
+
this.#eventEmitter.emit("node-deleted", nodeId, endpoint, "expired");
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
on(event, listener) {
|
|
2420
|
+
this.#eventEmitter.on(event, listener);
|
|
2421
|
+
return this;
|
|
2422
|
+
}
|
|
2423
|
+
off(event, listener) {
|
|
2424
|
+
this.#eventEmitter.off(event, listener);
|
|
2425
|
+
return this;
|
|
2426
|
+
}
|
|
2427
|
+
#findConnections(sortedNodes, node) {
|
|
2428
|
+
const connections = sortedNodes.reduce((l, c) => {
|
|
2429
|
+
if (node !== void 0 && c.firstSeen.timestamp < node.firstSeen.timestamp) {
|
|
2430
|
+
const intersection = new Set(c.users);
|
|
2431
|
+
node.users.forEach((user) => {
|
|
2432
|
+
if (!c.users.has(user)) {
|
|
2433
|
+
intersection.delete(user);
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2436
|
+
c.users.forEach((user) => {
|
|
2437
|
+
if (!node.users.has(user)) {
|
|
2438
|
+
intersection.delete(user);
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
2441
|
+
if (intersection.size > 0) {
|
|
2442
|
+
const e = { node: c.node, endpoint: c.endpoint, owner: c.owner };
|
|
2443
|
+
return l.concat(e);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
return l;
|
|
2447
|
+
}, new Array());
|
|
2448
|
+
if (this.#logger.enabledFor("debug")) {
|
|
2449
|
+
this.#logger.debug(`found ${connections.length} connection(s) for node ${node?.node}: [${connections.map((e) => `${e.node}@${e.endpoint}`).join(", ")}]`);
|
|
2450
|
+
}
|
|
2451
|
+
return connections;
|
|
2452
|
+
}
|
|
2453
|
+
};
|
|
2454
|
+
|
|
2455
|
+
// ../bridge-gateway/src/desktop.ts
|
|
2456
|
+
function isRunningInConnectDesktop(env = process.env) {
|
|
2457
|
+
return env._GD_STARTING_CONTEXT_ !== void 0;
|
|
2458
|
+
}
|
|
2459
|
+
function parseStartingContext(env = process.env) {
|
|
2460
|
+
if (!isRunningInConnectDesktop(env)) {
|
|
2461
|
+
throw new Error("Not running in io.Connect Desktop");
|
|
2462
|
+
}
|
|
2463
|
+
return JSON.parse(env._GD_STARTING_CONTEXT_);
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// ../bridge-gateway/src/index.ts
|
|
2467
|
+
import { tmpdir } from "node:os";
|
|
2468
|
+
import { createServer as createServer2 } from "node:net";
|
|
2469
|
+
import { join } from "node:path";
|
|
2470
|
+
import "@interopio/gateway";
|
|
2471
|
+
import "@interopio/gateway-server";
|
|
2472
|
+
function onGatewayStarted(log) {
|
|
2473
|
+
return (gateway2) => {
|
|
2474
|
+
const info = JSON.stringify({ ...gateway2.info(), pid: process.pid });
|
|
2475
|
+
if (isRunningInConnectDesktop()) {
|
|
2476
|
+
const env = process.env["GLUE-ENV"] || "DEMO";
|
|
2477
|
+
const region = process.env["GLUE-REGION"] || "INTEROP.IO";
|
|
2478
|
+
const user = process.env.USERNAME ?? process.env.USER;
|
|
2479
|
+
const pipe = `glue42-${env}-${region}-${user}`;
|
|
2480
|
+
const pipeName = process.platform === "win32" ? `\\\\.\\pipe\\${pipe}` : `${join(tmpdir(), pipe + ".sock")}`;
|
|
2481
|
+
log.info(`gateway started: ${info}, opening ${pipeName}`);
|
|
2482
|
+
const server = createServer2((stream) => {
|
|
2483
|
+
log.info(`stream connected, sending info...`);
|
|
2484
|
+
stream.write(info);
|
|
2485
|
+
stream.end(() => {
|
|
2486
|
+
server.close();
|
|
2487
|
+
});
|
|
2488
|
+
});
|
|
2489
|
+
server.listen(pipeName);
|
|
2490
|
+
} else {
|
|
2491
|
+
log.info(`gateway started: ${info}`);
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
}
|
|
2495
|
+
async function serverGatewayConfig(gateway2, bridge, env = process.env) {
|
|
2496
|
+
let enabled = gateway2.enabled;
|
|
2497
|
+
if (enabled === void 0) {
|
|
2498
|
+
if (isRunningInConnectDesktop(env)) {
|
|
2499
|
+
const applicationConfig = parseStartingContext(env)?.applicationConfig;
|
|
2500
|
+
enabled = applicationConfig?.customProperties?.gatewayApp === true || applicationConfig?.name === "io-connect-gateway";
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
let contextsLifetime = gateway2.contexts.lifetime;
|
|
2504
|
+
if (contextsLifetime === void 0) {
|
|
2505
|
+
if (isRunningInConnectDesktop(env)) {
|
|
2506
|
+
contextsLifetime = "retained";
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
if (enabled) {
|
|
2510
|
+
return {
|
|
2511
|
+
// globals: { websocket },
|
|
2512
|
+
clients: {
|
|
2513
|
+
inactive_seconds: 0
|
|
2514
|
+
},
|
|
2515
|
+
ping: bridge.wsPingInterval ?? void 0,
|
|
2516
|
+
mesh: {
|
|
2517
|
+
auth: { user: null },
|
|
2518
|
+
node: "bridge-gateway",
|
|
2519
|
+
channel: bridge.meshChannel
|
|
2520
|
+
},
|
|
2521
|
+
contexts: {
|
|
2522
|
+
lifetime: contextsLifetime,
|
|
2523
|
+
visibility: [
|
|
2524
|
+
{ context: /___channel___.+/, restrictions: "cluster" },
|
|
2525
|
+
{ context: /T42\..*/, restrictions: "local" },
|
|
2526
|
+
{ context: "___platform_prefs___", restrictions: "local" },
|
|
2527
|
+
{ context: /___workspace___.+/, restrictions: "local" },
|
|
2528
|
+
{ context: /___window-hibernation___.+/, restrictions: "local" },
|
|
2529
|
+
{ context: /___instance___.+/, restrictions: "local" },
|
|
2530
|
+
{ context: /___window___.+/, restrictions: "local" },
|
|
2531
|
+
{ restrictions: "cluster" }
|
|
2532
|
+
]
|
|
2533
|
+
},
|
|
2534
|
+
methods: {
|
|
2535
|
+
visibility: [
|
|
2536
|
+
{ method: /T42\..*/, restrictions: "local" },
|
|
2537
|
+
{ restrictions: "cluster" }
|
|
2538
|
+
]
|
|
2539
|
+
},
|
|
2540
|
+
peers: {
|
|
2541
|
+
visibility: [
|
|
2542
|
+
{ domain: "context", restrictions: "cluster" },
|
|
2543
|
+
{ domain: "interop", restrictions: "cluster" },
|
|
2544
|
+
{ domain: "bus", restrictions: "local" }
|
|
2545
|
+
]
|
|
2546
|
+
}
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2039
2551
|
// src/instance/BridgeNode.ts
|
|
2040
2552
|
function logStartingInfo(logger) {
|
|
2041
2553
|
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 +2573,13 @@ var BridgeNode = class {
|
|
|
2061
2573
|
config;
|
|
2062
2574
|
uuid;
|
|
2063
2575
|
discoveryService;
|
|
2576
|
+
meshChannel;
|
|
2064
2577
|
version;
|
|
2065
2578
|
server;
|
|
2066
2579
|
licenseValidator;
|
|
2067
2580
|
constructor(config) {
|
|
2068
2581
|
this.config = config;
|
|
2069
|
-
this.licenseValidator = BridgeLicenseValidator(this.getLogger("license
|
|
2582
|
+
this.licenseValidator = BridgeLicenseValidator(this.getLogger("license"));
|
|
2070
2583
|
this.licenseValidator.validate(config.license);
|
|
2071
2584
|
this.uuid = newUUID();
|
|
2072
2585
|
this.version = parseVersion(package_default.version);
|
|
@@ -2082,7 +2595,10 @@ var BridgeNode = class {
|
|
|
2082
2595
|
const joinConfig = config.network.join;
|
|
2083
2596
|
const isAutoDetectionEnabled = joinConfig.autoDetectionEnabled;
|
|
2084
2597
|
const discoveryConfig = joinConfig.discovery;
|
|
2085
|
-
this.discoveryService = createDiscoveryService(getLogger("discovery
|
|
2598
|
+
this.discoveryService = createDiscoveryService(getLogger("discovery"), discoveryConfig, isAutoDetectionEnabled, localMemberPromise);
|
|
2599
|
+
const relays = new InternalRelays(this.getLogger("relays"));
|
|
2600
|
+
const connections = new InMemoryNodeConnections(this.getLogger("nodes"), this.uuid, this.config.mesh.timeout ?? 6e4);
|
|
2601
|
+
this.meshChannel = new BridgeMeshChannel(relays, connections, this.getLogger("channel"));
|
|
2086
2602
|
}
|
|
2087
2603
|
getLogger(name) {
|
|
2088
2604
|
return getLogger(name);
|
|
@@ -2101,6 +2617,7 @@ var BridgeNode = class {
|
|
|
2101
2617
|
return setInterval(task, licenseCheckInterval);
|
|
2102
2618
|
}
|
|
2103
2619
|
async start() {
|
|
2620
|
+
await this.meshChannel.init();
|
|
2104
2621
|
const config = {
|
|
2105
2622
|
port: this.config.server.port,
|
|
2106
2623
|
host: this.config.server.host,
|
|
@@ -2108,8 +2625,8 @@ var BridgeNode = class {
|
|
|
2108
2625
|
await mesh({
|
|
2109
2626
|
logger: this.getLogger("mesh"),
|
|
2110
2627
|
enabled: true,
|
|
2111
|
-
|
|
2112
|
-
|
|
2628
|
+
relays: this.meshChannel.relays,
|
|
2629
|
+
connections: this.meshChannel.connections,
|
|
2113
2630
|
socket: {
|
|
2114
2631
|
ping: this.config.server.wsPingInterval ?? 3e4
|
|
2115
2632
|
// 30 seconds
|
|
@@ -2119,50 +2636,15 @@ var BridgeNode = class {
|
|
|
2119
2636
|
auth: { type: "none", ...this.config.server.auth },
|
|
2120
2637
|
cors: this.config.server.cors.disabled ? false : this.config.server.cors
|
|
2121
2638
|
};
|
|
2122
|
-
|
|
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
|
-
}
|
|
2639
|
+
config.gateway = await serverGatewayConfig(this.config.gateway, { meshChannel: this.meshChannel, wsPingInterval: this.config.server.wsPingInterval });
|
|
2161
2640
|
logStartingInfo(this.getLogger("node"));
|
|
2162
2641
|
this.server = await gatewayServer(config);
|
|
2163
2642
|
if (false) {
|
|
2164
2643
|
await this.discoveryService.start();
|
|
2165
2644
|
}
|
|
2645
|
+
if (config.gateway !== void 0) {
|
|
2646
|
+
onGatewayStarted(this.getLogger("gw"))(this.server.gateway);
|
|
2647
|
+
}
|
|
2166
2648
|
await this.scheduleLicenseCheck();
|
|
2167
2649
|
}
|
|
2168
2650
|
async stop() {
|
|
@@ -2173,6 +2655,7 @@ var BridgeNode = class {
|
|
|
2173
2655
|
await this.discoveryService.close();
|
|
2174
2656
|
} catch (e) {
|
|
2175
2657
|
}
|
|
2658
|
+
await this.meshChannel.close();
|
|
2176
2659
|
}
|
|
2177
2660
|
};
|
|
2178
2661
|
|
|
@@ -2234,12 +2717,15 @@ try {
|
|
|
2234
2717
|
await import("dotenv/config");
|
|
2235
2718
|
const config = new Config();
|
|
2236
2719
|
config.license ??= loadLicense();
|
|
2237
|
-
config.server.port ??= loadConfig("server.port", NUMBER) ??
|
|
2720
|
+
config.server.port ??= loadConfig("server.port", NUMBER) ?? 8084;
|
|
2238
2721
|
config.server.host ??= loadConfig("server.host", STRING);
|
|
2239
2722
|
config.server.wsPingInterval ??= loadConfig("server.ws-ping-interval", NUMBER);
|
|
2240
2723
|
config.gateway.enabled ??= loadConfig("gateway.enabled", BOOLEAN);
|
|
2241
2724
|
config.gateway.contexts.lifetime ??= loadConfig("gateway.contexts.lifetime", STRING);
|
|
2242
2725
|
config.server.auth.type = loadConfig("server.auth.type", STRING) ?? "none";
|
|
2726
|
+
config.server.auth.oauth2.jwt.issuerUri = loadConfig("server.auth.oauth2.jwt.issuer-uri", STRING) ?? void 0;
|
|
2727
|
+
config.server.auth.oauth2.jwt.issuer = loadConfig("server.auth.oauth2.jwt.issuer", STRING) ?? void 0;
|
|
2728
|
+
config.server.auth.oauth2.jwt.audience = loadConfig("server.auth.oauth2.jwt.audience", STRING) ?? void 0;
|
|
2243
2729
|
config.server.cors.allowOrigin = loadConfig("server.cors.allow-origin", STRING) ?? "*";
|
|
2244
2730
|
config.server.cors.allowCredentials = loadConfig("server.cors.allow-credentials", BOOLEAN) ?? true;
|
|
2245
2731
|
config.server.cors.disabled = loadConfig("server.cors.disabled", BOOLEAN) ?? false ? true : void 0;
|