@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 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("debug")) {
39
- this.#logger.debug(`${key} unregisters node ${node}, linked to ${Array.from(links.keys()).join(", ")}`);
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("debug")) {
48
- this.#logger.debug(`${key} registers node ${node}`);
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("debug")) {
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
- const senderLinkData = this.#links.get(decoded.from);
96
- switch (decoded.type) {
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 (node === decoded.to) {
99
- senderLinkData?.linksByNode.set(decoded.to, key);
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 (node === decoded.to) {
104
- senderLinkData?.linksByNode.delete(decoded.to);
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: "0.1.0-beta.0",
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: "./bin/bridge.js"
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.13.0-beta.1",
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.16.1-beta.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) => fromRequestNode(node, request));
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) => toRequestNode(node, request));
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("debug")) {
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("debug")) {
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} with assigned key ${key}`);
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
- close() {
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
- const endpoint = `/cluster?node=${node}`;
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 "@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
- }
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
- get(key) {
2226
- const idx = this.#binarySearch(key);
2227
- return idx >= 0 ? this.#elements[idx][1] : void 0;
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
- has(key) {
2230
- return this.#binarySearch(key) >= 0;
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
- delete(key) {
2233
- const idx = this.#binarySearch(key);
2234
- if (idx >= 0) {
2235
- this.#elements.splice(idx, 1);
2236
- return true;
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 false;
2300
+ return 0;
2239
2301
  }
2240
- entries() {
2241
- return this.#elements[Symbol.iterator]();
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
- values() {
2244
- return this.#elements.map((e) => e[1]);
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 OrderedMap();
2251
- #nodesByEndpoint = /* @__PURE__ */ new Map();
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
- announce(nodes) {
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 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);
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
- 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);
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
- return nodes.map((e) => {
2279
- const { node } = e;
2280
- const connect = this.findConnections(this.#nodes.get(node));
2281
- return { node, connect };
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
- return this.findConnections(e);
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("endpoint-deleted", nodeId, endpoint, "removed");
2392
+ this.#eventEmitter.emit("node-deleted", nodeId, endpoint, "removed");
2299
2393
  return true;
2300
2394
  }
2301
2395
  return false;
2302
2396
  }
2303
- updateNode(newNode, users, _key, lastAccess, oldNode) {
2304
- const node = oldNode ?? { ...newNode };
2305
- return { ...node, users, lastAccess };
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.entries()) {
2310
- if (v.lastAccess < threshold) {
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.lastAccess).toISOString()}, timeout is ${this.#timeout} ms.`);
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.#nodesByEndpoint.delete(endpoint);
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 = 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);
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 (!value.users.has(user)) {
2432
+ if (!c.users.has(user)) {
2336
2433
  intersection.delete(user);
2337
2434
  }
2338
2435
  });
2339
- value.users.forEach((user) => {
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: value.node, endpoint: value.endpoint };
2346
- connections.push(e);
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_).applicationConfig?.customProperties;
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 (gateway) => {
2373
- const info = JSON.stringify(gateway.info());
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(gateway, bridge, env = process.env) {
2395
- let enabled = gateway.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
- enabled = parseStartingContext(env)?.gatewayApp === true;
2499
+ const applicationConfig = parseStartingContext(env)?.applicationConfig;
2500
+ enabled = applicationConfig?.customProperties?.gatewayApp === true || applicationConfig?.name === "io-connect-gateway";
2399
2501
  }
2400
2502
  }
2401
- let contextsLifetime = gateway.contexts.lifetime;
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