@interopio/bridge 0.1.0-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 +11 -0
- package/dist/main.js +224 -119
- package/dist/main.js.map +4 -4
- package/package.json +5 -5
package/changelog.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
# Change Log
|
|
4
4
|
|
|
5
|
+
## 1.0.0 (2025-11-17)
|
|
6
|
+
### Fixed
|
|
7
|
+
- relay disconnect handling
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- bump @interopio/gateway to 0.20.0
|
|
11
|
+
- bump @interopio/gateway-server to 0.17.0
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- lamport clock for cluster state
|
|
15
|
+
|
|
5
16
|
## 0.1.0-beta.0 (2025-10-28)
|
|
6
17
|
|
|
7
18
|
### Added
|
package/dist/main.js
CHANGED
|
@@ -35,8 +35,8 @@ var InternalRelays = class {
|
|
|
35
35
|
for (const [node, k] of this.#links) {
|
|
36
36
|
if (k.key === key && (from === void 0 || k.node === from)) {
|
|
37
37
|
const links = k.linksByNode;
|
|
38
|
-
if (this.#logger.enabledFor("
|
|
39
|
-
this.#logger.
|
|
38
|
+
if (this.#logger.enabledFor("info")) {
|
|
39
|
+
this.#logger.info(`${key} unregisters node ${node}, linked to ${Array.from(links.keys()).join(", ")}`);
|
|
40
40
|
}
|
|
41
41
|
this.#links.delete(node);
|
|
42
42
|
this.#eventEmitter.emit("disconnect", key, node, links);
|
|
@@ -44,8 +44,8 @@ var InternalRelays = class {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
#connect(key, node) {
|
|
47
|
-
if (this.#logger.enabledFor("
|
|
48
|
-
this.#logger.
|
|
47
|
+
if (this.#logger.enabledFor("info")) {
|
|
48
|
+
this.#logger.info(`${key} registers node ${node}`);
|
|
49
49
|
}
|
|
50
50
|
this.#links.set(node, { key, node, linksByNode: /* @__PURE__ */ new Map() });
|
|
51
51
|
this.#eventEmitter.emit("connect", key, node);
|
|
@@ -72,6 +72,25 @@ var InternalRelays = class {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
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
|
+
}
|
|
75
94
|
}
|
|
76
95
|
const encoded = isEncoded(msg) ? msg : codec.encode(msg);
|
|
77
96
|
return { node: from, decoded, encoded };
|
|
@@ -86,23 +105,28 @@ var InternalRelays = class {
|
|
|
86
105
|
return this;
|
|
87
106
|
}
|
|
88
107
|
send(key, node, msg, cb) {
|
|
89
|
-
if (this.#logger.enabledFor("
|
|
108
|
+
if (this.#logger.enabledFor("trace")) {
|
|
90
109
|
this.#logger.debug(`${key} sending msg to ${node}`);
|
|
91
110
|
}
|
|
92
111
|
const encoded = isEncoded(msg) ? msg : codec.encode(msg);
|
|
93
112
|
const decoded = isEncoded(msg) ? codec.decode(msg) : msg;
|
|
94
113
|
{
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
node ??= decoded.to;
|
|
115
|
+
const link = decoded.from;
|
|
116
|
+
const type = decoded.type;
|
|
117
|
+
const senderLinkData = this.#links.get(link);
|
|
118
|
+
switch (type) {
|
|
97
119
|
case "hello":
|
|
98
|
-
if (
|
|
99
|
-
|
|
120
|
+
if (this.#logger.enabledFor("debug")) {
|
|
121
|
+
this.#logger.debug(`${key} connecting ${link} to ${node}`);
|
|
100
122
|
}
|
|
123
|
+
senderLinkData?.linksByNode.set(node, key);
|
|
101
124
|
break;
|
|
102
125
|
case "bye":
|
|
103
|
-
if (
|
|
104
|
-
|
|
126
|
+
if (this.#logger.enabledFor("debug")) {
|
|
127
|
+
this.#logger.debug(`${key} disconnecting ${link} from ${node}`);
|
|
105
128
|
}
|
|
129
|
+
senderLinkData?.linksByNode.delete(node);
|
|
106
130
|
break;
|
|
107
131
|
}
|
|
108
132
|
}
|
|
@@ -116,6 +140,9 @@ var InternalRelays = class {
|
|
|
116
140
|
}
|
|
117
141
|
}
|
|
118
142
|
}
|
|
143
|
+
if (decoded.type === "bye") {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
119
146
|
throw new Error(`${key} no active link for ${node}`);
|
|
120
147
|
}
|
|
121
148
|
};
|
|
@@ -1334,7 +1361,7 @@ function parseVersion(version) {
|
|
|
1334
1361
|
// package.json
|
|
1335
1362
|
var package_default = {
|
|
1336
1363
|
name: "@interopio/bridge",
|
|
1337
|
-
version: "
|
|
1364
|
+
version: "1.0.0",
|
|
1338
1365
|
license: "see license in license.md",
|
|
1339
1366
|
author: "interop.io",
|
|
1340
1367
|
homepage: "https://docs.interop.io/bridge",
|
|
@@ -1347,7 +1374,7 @@ var package_default = {
|
|
|
1347
1374
|
],
|
|
1348
1375
|
repository: {
|
|
1349
1376
|
type: "git",
|
|
1350
|
-
url: "https://github.com/InteropIO/bridge.git",
|
|
1377
|
+
url: "git+https://github.com/InteropIO/bridge.git",
|
|
1351
1378
|
directory: "packages/bridge"
|
|
1352
1379
|
},
|
|
1353
1380
|
type: "module",
|
|
@@ -1364,7 +1391,7 @@ var package_default = {
|
|
|
1364
1391
|
}
|
|
1365
1392
|
},
|
|
1366
1393
|
bin: {
|
|
1367
|
-
bridge: "
|
|
1394
|
+
bridge: "bin/bridge.js"
|
|
1368
1395
|
},
|
|
1369
1396
|
scripts: {
|
|
1370
1397
|
test: "mocha test --recursive",
|
|
@@ -1374,13 +1401,13 @@ var package_default = {
|
|
|
1374
1401
|
build: "npm run build:main && npm run build:index"
|
|
1375
1402
|
},
|
|
1376
1403
|
dependencies: {
|
|
1377
|
-
"@interopio/gateway-server": "^0.
|
|
1404
|
+
"@interopio/gateway-server": "^0.17.0",
|
|
1378
1405
|
dotenv: "^17.2.3",
|
|
1379
1406
|
jsrsasign: "^11.1.0",
|
|
1380
1407
|
nanoid: "^5.1.6"
|
|
1381
1408
|
},
|
|
1382
1409
|
devDependencies: {
|
|
1383
|
-
"@interopio/gateway": "^0.
|
|
1410
|
+
"@interopio/gateway": "^0.20.0",
|
|
1384
1411
|
"@types/jsrsasign": "^10.5.15",
|
|
1385
1412
|
"@types/ws": "^8.18.1",
|
|
1386
1413
|
"rand-seed": "^3.0.0"
|
|
@@ -1759,9 +1786,13 @@ function routes(config, { handle }) {
|
|
|
1759
1786
|
);
|
|
1760
1787
|
await response.end();
|
|
1761
1788
|
} else {
|
|
1762
|
-
const nodes = json.map((node) =>
|
|
1789
|
+
const nodes = json.map((node) => {
|
|
1790
|
+
return fromRequestNode(node, request);
|
|
1791
|
+
});
|
|
1763
1792
|
const result = connections.announce(nodes).map((connection) => {
|
|
1764
|
-
const connect = connection.connect?.map((node) =>
|
|
1793
|
+
const connect = connection.connect?.map((node) => {
|
|
1794
|
+
return toRequestNode(node, request);
|
|
1795
|
+
});
|
|
1765
1796
|
return { ...connection, connect };
|
|
1766
1797
|
});
|
|
1767
1798
|
const buffer = Buffer.from(JSON.stringify(result), "utf8");
|
|
@@ -1827,9 +1858,10 @@ async function create(log, internal, env) {
|
|
|
1827
1858
|
log.info(`relays-${id} server is listening on ${env.endpoint}`);
|
|
1828
1859
|
let keyId = 0;
|
|
1829
1860
|
return async ({ socket, handshake }) => {
|
|
1830
|
-
const logPrefix = handshake.logPrefix;
|
|
1861
|
+
const logPrefix = handshake.logPrefix ?? "";
|
|
1862
|
+
const query = handshake.url.searchParams;
|
|
1831
1863
|
const key = `r.${id}.${++keyId}`;
|
|
1832
|
-
log.info(`${logPrefix}connected on /relays with assigned key ${key}`);
|
|
1864
|
+
log.info(`${logPrefix}connected on /relays with ${query} and assigned key ${key}`);
|
|
1833
1865
|
internal.add(key, (msg, c, cb) => {
|
|
1834
1866
|
socket.send(msg, { binary: false }, (err) => {
|
|
1835
1867
|
cb(key, err);
|
|
@@ -1869,7 +1901,7 @@ function onMessage(relays, log, key, node, socketsByNodeId, msg) {
|
|
|
1869
1901
|
log.warn(`${k} error writing msg ${msg}: ${err}`);
|
|
1870
1902
|
return;
|
|
1871
1903
|
}
|
|
1872
|
-
if (log.enabledFor("
|
|
1904
|
+
if (log.enabledFor("trace")) {
|
|
1873
1905
|
log.debug(`${k} sent msg ${msg}`);
|
|
1874
1906
|
}
|
|
1875
1907
|
});
|
|
@@ -1897,7 +1929,7 @@ async function create2(log, relays, env) {
|
|
|
1897
1929
|
log.warn(`${key} error writing from ${k} msg ${msg}: ${err}`);
|
|
1898
1930
|
return;
|
|
1899
1931
|
}
|
|
1900
|
-
if (log.enabledFor("
|
|
1932
|
+
if (log.enabledFor("trace")) {
|
|
1901
1933
|
log.debug(`${key} sent from ${k} msg ${msg}`);
|
|
1902
1934
|
}
|
|
1903
1935
|
});
|
|
@@ -1924,7 +1956,7 @@ async function create2(log, relays, env) {
|
|
|
1924
1956
|
const logPrefix = handshake.logPrefix ?? "";
|
|
1925
1957
|
const query = handshake.url.searchParams;
|
|
1926
1958
|
const key = `c.${id}.${++keyId}`;
|
|
1927
|
-
log.info(`${logPrefix}connected on /cluster with ${query}
|
|
1959
|
+
log.info(`${logPrefix}connected on /cluster with ${query} and assigned key ${key}`);
|
|
1928
1960
|
const node = query.get("node");
|
|
1929
1961
|
if (node) {
|
|
1930
1962
|
let sockets = socketsByNodeId.get(node);
|
|
@@ -1969,6 +2001,7 @@ import "@interopio/gateway-server";
|
|
|
1969
2001
|
|
|
1970
2002
|
// ../bridge-mesh/src/mesh/gateway/mesh.ts
|
|
1971
2003
|
import { nanoid as nanoid2 } from "nanoid";
|
|
2004
|
+
import gateway from "@interopio/gateway/package.json" with { type: "json" };
|
|
1972
2005
|
var instanceId = 0;
|
|
1973
2006
|
var BridgeMeshChannel = class {
|
|
1974
2007
|
#logger;
|
|
@@ -2136,23 +2169,31 @@ var BridgeMeshChannel = class {
|
|
|
2136
2169
|
this.#state.delete(node);
|
|
2137
2170
|
this.#connections.remove(node);
|
|
2138
2171
|
}
|
|
2139
|
-
|
|
2172
|
+
async init(gossipTimestamp = 0) {
|
|
2173
|
+
await this.#connections.init(gossipTimestamp);
|
|
2174
|
+
}
|
|
2175
|
+
async close() {
|
|
2140
2176
|
for (const node of this.#state.keys()) {
|
|
2141
2177
|
this.#delete(node);
|
|
2142
2178
|
this.#relays.remove(`${this.#keyPrefix}-${node}`);
|
|
2143
2179
|
}
|
|
2144
2180
|
clearInterval(this.#intervalId);
|
|
2181
|
+
await this.#connections.close();
|
|
2145
2182
|
}
|
|
2146
2183
|
};
|
|
2147
2184
|
var entryToNode = ([node, state]) => {
|
|
2148
2185
|
const users = Array.from(state?.users);
|
|
2149
|
-
|
|
2150
|
-
return { node, endpoint, users };
|
|
2186
|
+
return { node, users, metadata: { version: gateway.version, type: "bridge" } };
|
|
2151
2187
|
};
|
|
2152
2188
|
|
|
2153
2189
|
// ../bridge-mesh/src/index.ts
|
|
2154
2190
|
function connectNodeRelays(logger, relays, connections) {
|
|
2155
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
|
+
}
|
|
2156
2197
|
for (const [linkNode, linkKey] of links) {
|
|
2157
2198
|
try {
|
|
2158
2199
|
relays.send(linkKey, linkNode, { type: "bye", from: node, to: linkNode }, (k, err) => {
|
|
@@ -2161,11 +2202,11 @@ function connectNodeRelays(logger, relays, connections) {
|
|
|
2161
2202
|
return;
|
|
2162
2203
|
}
|
|
2163
2204
|
if (logger.enabledFor("debug")) {
|
|
2164
|
-
logger.debug(`${k} sent 'bye' msg to ${linkNode}`);
|
|
2205
|
+
logger.debug(`${k} sent 'bye' msg to ${linkNode} from ${node}`);
|
|
2165
2206
|
}
|
|
2166
2207
|
});
|
|
2167
2208
|
} catch (err) {
|
|
2168
|
-
logger.warn(`${linkKey} exception writing 'bye' msg to ${linkNode}: ${err}`);
|
|
2209
|
+
logger.warn(`${linkKey} exception writing 'bye' msg to ${linkNode} from ${node}: ${err}`);
|
|
2169
2210
|
}
|
|
2170
2211
|
}
|
|
2171
2212
|
});
|
|
@@ -2198,93 +2239,147 @@ var mesh = async (options, configurer, config) => {
|
|
|
2198
2239
|
};
|
|
2199
2240
|
|
|
2200
2241
|
// ../bridge-mesh/src/mesh/connections.ts
|
|
2201
|
-
import "
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
} else {
|
|
2222
|
-
this.#elements.splice(~idx, 0, [key, value]);
|
|
2223
|
-
}
|
|
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);
|
|
2224
2262
|
}
|
|
2225
|
-
|
|
2226
|
-
const
|
|
2227
|
-
|
|
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);
|
|
2228
2267
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2268
|
+
tick() {
|
|
2269
|
+
this.#timestamp += 1;
|
|
2270
|
+
this.#pendingWrites += 1;
|
|
2271
|
+
this.#appendToWal().catch((_err) => {
|
|
2272
|
+
});
|
|
2273
|
+
return { timestamp: this.#timestamp, instanceId: this.#instanceId };
|
|
2231
2274
|
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
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 {
|
|
2237
2299
|
}
|
|
2238
|
-
return
|
|
2300
|
+
return 0;
|
|
2239
2301
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
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;
|
|
2242
2309
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
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;
|
|
2245
2321
|
}
|
|
2246
2322
|
};
|
|
2323
|
+
|
|
2324
|
+
// ../bridge-mesh/src/mesh/connections.ts
|
|
2325
|
+
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
2247
2326
|
var InMemoryNodeConnections = class {
|
|
2248
2327
|
#logger;
|
|
2249
2328
|
#eventEmitter = new EventEmitter2();
|
|
2250
|
-
#nodes = new
|
|
2251
|
-
#
|
|
2329
|
+
#nodes = /* @__PURE__ */ new Map();
|
|
2330
|
+
#clusterNodes;
|
|
2331
|
+
#lamport;
|
|
2252
2332
|
#timeout;
|
|
2253
|
-
constructor(logger, timeout = 6e4) {
|
|
2333
|
+
constructor(logger, instanceId2, timeout = 6e4) {
|
|
2254
2334
|
this.#logger = logger;
|
|
2335
|
+
this.#clusterNodes = [instanceId2];
|
|
2336
|
+
this.#lamport = new LamportClock(instanceId2);
|
|
2255
2337
|
this.#timeout = timeout;
|
|
2256
2338
|
}
|
|
2257
|
-
|
|
2339
|
+
async init(gossipTimestamp = 0) {
|
|
2340
|
+
await this.#lamport.init(gossipTimestamp);
|
|
2341
|
+
}
|
|
2342
|
+
async close() {
|
|
2343
|
+
await this.#lamport.shutdown();
|
|
2344
|
+
}
|
|
2345
|
+
announce(announcements) {
|
|
2258
2346
|
const now = Date.now();
|
|
2259
|
-
for (const
|
|
2260
|
-
const { node: nodeId, users, endpoint } =
|
|
2261
|
-
const
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
this.#
|
|
2271
|
-
this.#
|
|
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;
|
|
2272
2364
|
}
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2365
|
+
node.replicas = replicas;
|
|
2366
|
+
node.lastSeen = now;
|
|
2367
|
+
node.users = userSet;
|
|
2368
|
+
this.#eventEmitter.emit("node-announced", nodeId, endpoint, userSet);
|
|
2276
2369
|
}
|
|
2277
2370
|
this.cleanupOldNodes();
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
const
|
|
2281
|
-
|
|
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 };
|
|
2282
2376
|
});
|
|
2283
2377
|
}
|
|
2284
2378
|
find(nodeId) {
|
|
2285
2379
|
const e = this.#nodes.get(nodeId);
|
|
2286
2380
|
if (e !== void 0) {
|
|
2287
|
-
|
|
2381
|
+
const sortedNodes = this.#sortedNodeValues();
|
|
2382
|
+
return this.#findConnections(sortedNodes, e);
|
|
2288
2383
|
}
|
|
2289
2384
|
return void 0;
|
|
2290
2385
|
}
|
|
@@ -2293,28 +2388,31 @@ var InMemoryNodeConnections = class {
|
|
|
2293
2388
|
if (removed !== void 0) {
|
|
2294
2389
|
this.#nodes.delete(nodeId);
|
|
2295
2390
|
const endpoint = removed.endpoint;
|
|
2296
|
-
this.#nodesByEndpoint.delete(endpoint);
|
|
2297
2391
|
this.#logger.info(`endpoint ${endpoint} removed for ${nodeId}`);
|
|
2298
|
-
this.#eventEmitter.emit("
|
|
2392
|
+
this.#eventEmitter.emit("node-deleted", nodeId, endpoint, "removed");
|
|
2299
2393
|
return true;
|
|
2300
2394
|
}
|
|
2301
2395
|
return false;
|
|
2302
2396
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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
|
+
});
|
|
2306
2405
|
}
|
|
2307
2406
|
cleanupOldNodes() {
|
|
2308
2407
|
const threshold = Date.now() - this.#timeout;
|
|
2309
|
-
for (const [nodeId, v] of this.#nodes
|
|
2310
|
-
if (v.
|
|
2408
|
+
for (const [nodeId, v] of this.#nodes) {
|
|
2409
|
+
if (v.lastSeen < threshold) {
|
|
2311
2410
|
if (this.#logger.enabledFor("debug")) {
|
|
2312
|
-
this.#logger.debug(`${nodeId} expired - no announcement since ${new Date(v.
|
|
2411
|
+
this.#logger.debug(`${nodeId} expired - no announcement since ${new Date(v.lastSeen).toISOString()}, timeout is ${this.#timeout} ms.`);
|
|
2313
2412
|
}
|
|
2314
2413
|
this.#nodes.delete(nodeId);
|
|
2315
2414
|
const endpoint = v.endpoint;
|
|
2316
|
-
this.#
|
|
2317
|
-
this.#eventEmitter.emit("endpoint-deleted", nodeId, endpoint, "expired");
|
|
2415
|
+
this.#eventEmitter.emit("node-deleted", nodeId, endpoint, "expired");
|
|
2318
2416
|
}
|
|
2319
2417
|
}
|
|
2320
2418
|
}
|
|
@@ -2326,26 +2424,29 @@ var InMemoryNodeConnections = class {
|
|
|
2326
2424
|
this.#eventEmitter.off(event, listener);
|
|
2327
2425
|
return this;
|
|
2328
2426
|
}
|
|
2329
|
-
findConnections(node) {
|
|
2330
|
-
const connections =
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
const intersection = new Set(value.users);
|
|
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);
|
|
2334
2431
|
node.users.forEach((user) => {
|
|
2335
|
-
if (!
|
|
2432
|
+
if (!c.users.has(user)) {
|
|
2336
2433
|
intersection.delete(user);
|
|
2337
2434
|
}
|
|
2338
2435
|
});
|
|
2339
|
-
|
|
2436
|
+
c.users.forEach((user) => {
|
|
2340
2437
|
if (!node.users.has(user)) {
|
|
2341
2438
|
intersection.delete(user);
|
|
2342
2439
|
}
|
|
2343
2440
|
});
|
|
2344
2441
|
if (intersection.size > 0) {
|
|
2345
|
-
const e = { node:
|
|
2346
|
-
|
|
2442
|
+
const e = { node: c.node, endpoint: c.endpoint, owner: c.owner };
|
|
2443
|
+
return l.concat(e);
|
|
2347
2444
|
}
|
|
2348
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(", ")}]`);
|
|
2349
2450
|
}
|
|
2350
2451
|
return connections;
|
|
2351
2452
|
}
|
|
@@ -2359,7 +2460,7 @@ function parseStartingContext(env = process.env) {
|
|
|
2359
2460
|
if (!isRunningInConnectDesktop(env)) {
|
|
2360
2461
|
throw new Error("Not running in io.Connect Desktop");
|
|
2361
2462
|
}
|
|
2362
|
-
return JSON.parse(env._GD_STARTING_CONTEXT_)
|
|
2463
|
+
return JSON.parse(env._GD_STARTING_CONTEXT_);
|
|
2363
2464
|
}
|
|
2364
2465
|
|
|
2365
2466
|
// ../bridge-gateway/src/index.ts
|
|
@@ -2369,8 +2470,8 @@ import { join } from "node:path";
|
|
|
2369
2470
|
import "@interopio/gateway";
|
|
2370
2471
|
import "@interopio/gateway-server";
|
|
2371
2472
|
function onGatewayStarted(log) {
|
|
2372
|
-
return (
|
|
2373
|
-
const info = JSON.stringify(
|
|
2473
|
+
return (gateway2) => {
|
|
2474
|
+
const info = JSON.stringify({ ...gateway2.info(), pid: process.pid });
|
|
2374
2475
|
if (isRunningInConnectDesktop()) {
|
|
2375
2476
|
const env = process.env["GLUE-ENV"] || "DEMO";
|
|
2376
2477
|
const region = process.env["GLUE-REGION"] || "INTEROP.IO";
|
|
@@ -2391,14 +2492,15 @@ function onGatewayStarted(log) {
|
|
|
2391
2492
|
}
|
|
2392
2493
|
};
|
|
2393
2494
|
}
|
|
2394
|
-
async function serverGatewayConfig(
|
|
2395
|
-
let enabled =
|
|
2495
|
+
async function serverGatewayConfig(gateway2, bridge, env = process.env) {
|
|
2496
|
+
let enabled = gateway2.enabled;
|
|
2396
2497
|
if (enabled === void 0) {
|
|
2397
2498
|
if (isRunningInConnectDesktop(env)) {
|
|
2398
|
-
|
|
2499
|
+
const applicationConfig = parseStartingContext(env)?.applicationConfig;
|
|
2500
|
+
enabled = applicationConfig?.customProperties?.gatewayApp === true || applicationConfig?.name === "io-connect-gateway";
|
|
2399
2501
|
}
|
|
2400
2502
|
}
|
|
2401
|
-
let contextsLifetime =
|
|
2503
|
+
let contextsLifetime = gateway2.contexts.lifetime;
|
|
2402
2504
|
if (contextsLifetime === void 0) {
|
|
2403
2505
|
if (isRunningInConnectDesktop(env)) {
|
|
2404
2506
|
contextsLifetime = "retained";
|
|
@@ -2412,6 +2514,8 @@ async function serverGatewayConfig(gateway, bridge, env = process.env) {
|
|
|
2412
2514
|
},
|
|
2413
2515
|
ping: bridge.wsPingInterval ?? void 0,
|
|
2414
2516
|
mesh: {
|
|
2517
|
+
auth: { user: null },
|
|
2518
|
+
node: "bridge-gateway",
|
|
2415
2519
|
channel: bridge.meshChannel
|
|
2416
2520
|
},
|
|
2417
2521
|
contexts: {
|
|
@@ -2493,7 +2597,7 @@ var BridgeNode = class {
|
|
|
2493
2597
|
const discoveryConfig = joinConfig.discovery;
|
|
2494
2598
|
this.discoveryService = createDiscoveryService(getLogger("discovery"), discoveryConfig, isAutoDetectionEnabled, localMemberPromise);
|
|
2495
2599
|
const relays = new InternalRelays(this.getLogger("relays"));
|
|
2496
|
-
const connections = new InMemoryNodeConnections(this.getLogger("nodes"), this.config.mesh.timeout ?? 6e4);
|
|
2600
|
+
const connections = new InMemoryNodeConnections(this.getLogger("nodes"), this.uuid, this.config.mesh.timeout ?? 6e4);
|
|
2497
2601
|
this.meshChannel = new BridgeMeshChannel(relays, connections, this.getLogger("channel"));
|
|
2498
2602
|
}
|
|
2499
2603
|
getLogger(name) {
|
|
@@ -2513,6 +2617,7 @@ var BridgeNode = class {
|
|
|
2513
2617
|
return setInterval(task, licenseCheckInterval);
|
|
2514
2618
|
}
|
|
2515
2619
|
async start() {
|
|
2620
|
+
await this.meshChannel.init();
|
|
2516
2621
|
const config = {
|
|
2517
2622
|
port: this.config.server.port,
|
|
2518
2623
|
host: this.config.server.host,
|
|
@@ -2550,7 +2655,7 @@ var BridgeNode = class {
|
|
|
2550
2655
|
await this.discoveryService.close();
|
|
2551
2656
|
} catch (e) {
|
|
2552
2657
|
}
|
|
2553
|
-
this.meshChannel.close();
|
|
2658
|
+
await this.meshChannel.close();
|
|
2554
2659
|
}
|
|
2555
2660
|
};
|
|
2556
2661
|
|