@streamr/dht 103.3.1 → 103.7.0-rc.2

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.
@@ -11,6 +11,7 @@ var runtimeRpc = require('@protobuf-ts/runtime-rpc');
11
11
  var uuid = require('uuid');
12
12
  var protoRpc = require('@streamr/proto-rpc');
13
13
  var ipaddr = require('ipaddr.js');
14
+ var Comlink = require('comlink');
14
15
  var websocket = require('websocket');
15
16
  var autocertifierClient = require('@streamr/autocertifier-client');
16
17
  var shuffle = require('lodash/shuffle');
@@ -21,6 +22,25 @@ var lruCache = require('lru-cache');
21
22
  var cdnLocation = require('@streamr/cdn-location');
22
23
  var Heap = require('heap');
23
24
 
25
+ function _interopNamespaceDefault(e) {
26
+ var n = Object.create(null);
27
+ if (e) {
28
+ Object.keys(e).forEach(function (k) {
29
+ if (k !== 'default') {
30
+ var d = Object.getOwnPropertyDescriptor(e, k);
31
+ Object.defineProperty(n, k, d.get ? d : {
32
+ enumerable: true,
33
+ get: function () { return e[k]; }
34
+ });
35
+ }
36
+ });
37
+ }
38
+ n.default = e;
39
+ return Object.freeze(n);
40
+ }
41
+
42
+ var Comlink__namespace = /*#__PURE__*/_interopNamespaceDefault(Comlink);
43
+
24
44
  const getPeerDistance = (nodeIdOrDataKeyRaw1, nodeIdOrDataKeyRaw2) => {
25
45
  return KBucket.distance(nodeIdOrDataKeyRaw1, nodeIdOrDataKeyRaw2);
26
46
  };
@@ -241,6 +261,65 @@ class SendFailed extends Err {
241
261
  constructor(message, originalError) { super(ErrorCode.SEND_FAILED, message, originalError); }
242
262
  }
243
263
 
264
+ let enabled = false;
265
+ function setGapDiagnosticsEnabled(val) {
266
+ enabled = val;
267
+ globalThis.__dhtGapDiagEnabled = val;
268
+ }
269
+ const SUMMARY_INTERVAL_MS = 2000;
270
+ const accumulators = new Map();
271
+ function logGapDiagnosticSampled(layer, opts = {}) {
272
+ if (!enabled)
273
+ return;
274
+ const now = performance.now();
275
+ const threshold = opts.outlierThresholdMs ?? 30;
276
+ let acc = accumulators.get(layer);
277
+ if (acc === undefined) {
278
+ acc = { count: 0, sumDeltaMs: 0, maxDeltaMs: 0, outlierCount: 0, lastReportMs: now, lastEventMs: now };
279
+ accumulators.set(layer, acc);
280
+ return;
281
+ }
282
+ const deltaMs = now - acc.lastEventMs;
283
+ acc.lastEventMs = now;
284
+ acc.count++;
285
+ if (deltaMs > acc.maxDeltaMs)
286
+ acc.maxDeltaMs = deltaMs;
287
+ acc.sumDeltaMs += deltaMs;
288
+ if (deltaMs > threshold)
289
+ acc.outlierCount++;
290
+ if (deltaMs > threshold) {
291
+ const payload = {
292
+ layer,
293
+ timestampMs: now,
294
+ deltaMs: +deltaMs.toFixed(2),
295
+ detail: opts.detail,
296
+ };
297
+ // eslint-disable-next-line no-console
298
+ console.log('[gap-diagnostics]', JSON.stringify(payload));
299
+ }
300
+ if (now - acc.lastReportMs >= SUMMARY_INTERVAL_MS) {
301
+ const summaryDetail = {
302
+ count: acc.count,
303
+ meanDeltaMs: acc.count > 0 ? +(acc.sumDeltaMs / acc.count).toFixed(2) : 0,
304
+ maxDeltaMs: +acc.maxDeltaMs.toFixed(2),
305
+ outlierCount: acc.outlierCount,
306
+ periodMs: +(now - acc.lastReportMs).toFixed(1),
307
+ };
308
+ const summary = {
309
+ layer: `${layer}.summary`,
310
+ timestampMs: now,
311
+ detail: summaryDetail,
312
+ };
313
+ // eslint-disable-next-line no-console
314
+ console.log('[gap-diagnostics]', JSON.stringify(summary));
315
+ acc.count = 0;
316
+ acc.sumDeltaMs = 0;
317
+ acc.maxDeltaMs = 0;
318
+ acc.outlierCount = 0;
319
+ acc.lastReportMs = now;
320
+ }
321
+ }
322
+
244
323
  // @generated message type with reflection information, may provide speed optimized methods
245
324
  class Empty$Type extends runtime.MessageType {
246
325
  constructor() {
@@ -1800,7 +1879,7 @@ const createRandomConnectionId = () => {
1800
1879
  return uuid.v4();
1801
1880
  };
1802
1881
 
1803
- const logger$C = new utils.Logger('ManagedConnection');
1882
+ const logger$D = new utils.Logger('ManagedConnection');
1804
1883
  // ManagedConnection is a component used as a wrapper for IConnection after they have been successfully handshaked.
1805
1884
  // Should only be used in the ConnectionManager.
1806
1885
  class ManagedConnection extends eventemitter3.EventEmitter {
@@ -1830,7 +1909,7 @@ class ManagedConnection extends eventemitter3.EventEmitter {
1830
1909
  this.remotePeerDescriptor = peerDescriptor;
1831
1910
  }
1832
1911
  onDisconnected(gracefulLeave) {
1833
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1912
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1834
1913
  if (!this.replacedAsDuplicate) {
1835
1914
  this.emit('disconnected', gracefulLeave);
1836
1915
  }
@@ -1839,7 +1918,7 @@ class ManagedConnection extends eventemitter3.EventEmitter {
1839
1918
  // TODO: Can this be removed if ManagedConnections can never be duplicates?
1840
1919
  // Handle duplicates in the ConncetorFacade and no longer have PendingConnections in ConnectionManager
1841
1920
  replaceAsDuplicate() {
1842
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1921
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1843
1922
  this.replacedAsDuplicate = true;
1844
1923
  }
1845
1924
  send(data) {
@@ -1986,10 +2065,10 @@ class RpcRemote {
1986
2065
  }
1987
2066
  }
1988
2067
 
1989
- const logger$B = new utils.Logger('ConnectionLockRpcRemote');
2068
+ const logger$C = new utils.Logger('ConnectionLockRpcRemote');
1990
2069
  class ConnectionLockRpcRemote extends RpcRemote {
1991
2070
  async lockRequest(lockId) {
1992
- logger$B.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
2071
+ logger$C.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
1993
2072
  const request = {
1994
2073
  lockId
1995
2074
  };
@@ -1999,12 +2078,12 @@ class ConnectionLockRpcRemote extends RpcRemote {
1999
2078
  return res.accepted;
2000
2079
  }
2001
2080
  catch (err) {
2002
- logger$B.debug('Connection lock rejected', { err });
2081
+ logger$C.debug('Connection lock rejected', { err });
2003
2082
  return false;
2004
2083
  }
2005
2084
  }
2006
2085
  unlockRequest(lockId) {
2007
- logger$B.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
2086
+ logger$C.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
2008
2087
  const request = {
2009
2088
  lockId
2010
2089
  };
@@ -2012,11 +2091,11 @@ class ConnectionLockRpcRemote extends RpcRemote {
2012
2091
  notification: true
2013
2092
  });
2014
2093
  this.getClient().unlockRequest(request, options).catch((_e) => {
2015
- logger$B.trace('failed to send unlockRequest');
2094
+ logger$C.trace('failed to send unlockRequest');
2016
2095
  });
2017
2096
  }
2018
2097
  async gracefulDisconnect(disconnectMode) {
2019
- logger$B.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2098
+ logger$C.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2020
2099
  const request = {
2021
2100
  disconnectMode
2022
2101
  };
@@ -2028,7 +2107,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2028
2107
  await this.getClient().gracefulDisconnect(request, options);
2029
2108
  }
2030
2109
  async setPrivate(isPrivate) {
2031
- logger$B.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2110
+ logger$C.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2032
2111
  const request = {
2033
2112
  isPrivate
2034
2113
  };
@@ -2040,7 +2119,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2040
2119
  }
2041
2120
  }
2042
2121
 
2043
- const logger$A = new utils.Logger('ConnectionLockRpcLocal');
2122
+ const logger$B = new utils.Logger('ConnectionLockRpcLocal');
2044
2123
  class ConnectionLockRpcLocal {
2045
2124
  options;
2046
2125
  constructor(options) {
@@ -2069,7 +2148,7 @@ class ConnectionLockRpcLocal {
2069
2148
  }
2070
2149
  async gracefulDisconnect(disconnectNotice, context) {
2071
2150
  const senderPeerDescriptor = context.incomingSourceDescriptor;
2072
- logger$A.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2151
+ logger$B.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2073
2152
  if (disconnectNotice.disconnectMode === DisconnectMode.LEAVING) {
2074
2153
  await this.options.closeConnection(senderPeerDescriptor, true, 'graceful leave notified');
2075
2154
  }
@@ -2120,7 +2199,7 @@ var NatType;
2120
2199
  NatType["OPEN_INTERNET"] = "open_internet";
2121
2200
  NatType["UNKNOWN"] = "unknown";
2122
2201
  })(NatType || (NatType = {}));
2123
- const logger$z = new utils.Logger('ConnectionManager');
2202
+ const logger$A = new utils.Logger('ConnectionManager');
2124
2203
  var ConnectionManagerState;
2125
2204
  (function (ConnectionManagerState) {
2126
2205
  ConnectionManagerState["IDLE"] = "idle";
@@ -2170,7 +2249,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2170
2249
  getLocalPeerDescriptor: () => this.getLocalPeerDescriptor(),
2171
2250
  setPrivate: (id, isPrivate) => {
2172
2251
  if (!this.options.allowIncomingPrivateConnections) {
2173
- logger$z.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
2252
+ logger$A.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
2174
2253
  return;
2175
2254
  }
2176
2255
  if (isPrivate) {
@@ -2204,7 +2283,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2204
2283
  const connection = endpoint.connection;
2205
2284
  const nodeId = connection.getNodeId();
2206
2285
  if (!this.locks.isLocked(nodeId) && !this.locks.isPrivate(nodeId) && Date.now() - connection.getLastUsedTimestamp() > maxIdleTime) {
2207
- logger$z.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2286
+ logger$A.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2208
2287
  disconnectionCandidates.addContact(connection);
2209
2288
  }
2210
2289
  }
@@ -2212,7 +2291,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2212
2291
  const disconnectables = disconnectionCandidates.getFurthestContacts(this.endpoints.size - maxConnections);
2213
2292
  for (const disconnectable of disconnectables) {
2214
2293
  const peerDescriptor = disconnectable.getPeerDescriptor();
2215
- logger$z.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2294
+ logger$A.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2216
2295
  this.gracefullyDisconnectAsync(peerDescriptor, DisconnectMode.NORMAL).catch((_e) => { });
2217
2296
  }
2218
2297
  }
@@ -2221,11 +2300,11 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2221
2300
  throw new CouldNotStart(`Cannot start already ${this.state} module`);
2222
2301
  }
2223
2302
  this.state = ConnectionManagerState.RUNNING;
2224
- logger$z.trace(`Starting ConnectionManager...`);
2303
+ logger$A.trace(`Starting ConnectionManager...`);
2225
2304
  await this.connectorFacade.start((connection) => this.onNewConnection(connection), (nodeId) => this.hasConnection(nodeId), this);
2226
2305
  // Garbage collection of connections
2227
2306
  this.disconnectorIntervalRef = setInterval(() => {
2228
- logger$z.trace('disconnectorInterval');
2307
+ logger$A.trace('disconnectorInterval');
2229
2308
  const LAST_USED_LIMIT = 20000;
2230
2309
  this.garbageCollectConnections(this.options.maxConnections ?? 80, LAST_USED_LIMIT);
2231
2310
  }, 5000); // TODO use options option or named constant?
@@ -2235,7 +2314,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2235
2314
  return;
2236
2315
  }
2237
2316
  this.state = ConnectionManagerState.STOPPING;
2238
- logger$z.trace(`Stopping ConnectionManager`);
2317
+ logger$A.trace(`Stopping ConnectionManager`);
2239
2318
  if (this.disconnectorIntervalRef) {
2240
2319
  clearInterval(this.disconnectorIntervalRef);
2241
2320
  }
@@ -2245,23 +2324,23 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2245
2324
  await this.gracefullyDisconnectAsync(endpoint.connection.getPeerDescriptor(), DisconnectMode.LEAVING);
2246
2325
  }
2247
2326
  catch (e) {
2248
- logger$z.error(e);
2327
+ logger$A.error(e);
2249
2328
  }
2250
2329
  }
2251
2330
  else {
2252
2331
  const connection = endpoint.connection;
2253
- logger$z.trace('handshake of connection not completed, force-closing');
2332
+ logger$A.trace('handshake of connection not completed, force-closing');
2254
2333
  // TODO use options option or named constant?
2255
2334
  const eventReceived = utils.waitForEvent(connection, 'disconnected', 2000);
2256
2335
  // TODO should we have some handling for this floating promise?
2257
2336
  connection.close(true);
2258
2337
  try {
2259
2338
  await eventReceived;
2260
- logger$z.trace('resolving after receiving disconnected event from non-handshaked connection');
2339
+ logger$A.trace('resolving after receiving disconnected event from non-handshaked connection');
2261
2340
  }
2262
2341
  catch (e) {
2263
2342
  endpoint.buffer.reject();
2264
- logger$z.trace('force-closing non-handshaked connection timed out ' + e);
2343
+ logger$A.trace('force-closing non-handshaked connection timed out ' + e);
2265
2344
  }
2266
2345
  }
2267
2346
  }));
@@ -2285,12 +2364,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2285
2364
  if ((this.state === ConnectionManagerState.STOPPED || this.state === ConnectionManagerState.STOPPING) && !opts.sendIfStopped) {
2286
2365
  return;
2287
2366
  }
2367
+ logGapDiagnosticSampled('dht.connMgr.send');
2288
2368
  const peerDescriptor = message.targetDescriptor;
2289
2369
  if (this.isConnectionToSelf(peerDescriptor)) {
2290
2370
  throw new CannotConnectToSelf('Cannot send to self');
2291
2371
  }
2292
2372
  const nodeId = toNodeId(peerDescriptor);
2293
- logger$z.trace(`Sending message to: ${nodeId}`);
2373
+ logger$A.trace(`Sending message to: ${nodeId}`);
2294
2374
  message = {
2295
2375
  ...message,
2296
2376
  sourceDescriptor: this.getLocalPeerDescriptor()
@@ -2345,13 +2425,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2345
2425
  }
2346
2426
  handleMessage(message) {
2347
2427
  const messageType = message.body.oneofKind;
2348
- logger$z.trace('Received message of type ' + messageType);
2428
+ logger$A.trace('Received message of type ' + messageType);
2349
2429
  if (messageType !== 'rpcMessage') {
2350
- logger$z.trace('Filtered out non-RPC message of type ' + messageType);
2430
+ logger$A.trace('Filtered out non-RPC message of type ' + messageType);
2351
2431
  return;
2352
2432
  }
2353
2433
  if (this.duplicateMessageDetector.isMostLikelyDuplicate(message.messageId)) {
2354
- logger$z.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2434
+ logger$A.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2355
2435
  + ' ' + message.serviceId + ' ' + message.messageId);
2356
2436
  return;
2357
2437
  }
@@ -2360,7 +2440,8 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2360
2440
  this.rpcCommunicator?.handleMessageFromPeer(message);
2361
2441
  }
2362
2442
  else {
2363
- logger$z.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2443
+ logGapDiagnosticSampled('dht.connMgr.emitMessage');
2444
+ logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2364
2445
  + ' ' + message.serviceId + ' ' + message.messageId);
2365
2446
  this.emit('message', message);
2366
2447
  }
@@ -2369,6 +2450,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2369
2450
  if (this.state === ConnectionManagerState.STOPPED) {
2370
2451
  return;
2371
2452
  }
2453
+ logGapDiagnosticSampled('dht.connMgr.onData');
2372
2454
  this.metrics.receiveBytesPerSecond.record(data.byteLength);
2373
2455
  this.metrics.receiveMessagesPerSecond.record(1);
2374
2456
  let message;
@@ -2377,7 +2459,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2377
2459
  }
2378
2460
  catch (e) {
2379
2461
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2380
- logger$z.debug(`Parsing incoming data into Message failed: ${e}`);
2462
+ logger$A.debug(`Parsing incoming data into Message failed: ${e}`);
2381
2463
  return;
2382
2464
  }
2383
2465
  message.sourceDescriptor = peerDescriptor;
@@ -2386,7 +2468,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2386
2468
  }
2387
2469
  catch (e) {
2388
2470
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2389
- logger$z.debug(`Handling incoming data failed: ${e}`);
2471
+ logger$A.debug(`Handling incoming data failed: ${e}`);
2390
2472
  }
2391
2473
  }
2392
2474
  onConnected(peerDescriptor, connection) {
@@ -2399,7 +2481,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2399
2481
  const pendingConnection = endpoint.connection;
2400
2482
  const buffer = outputBuffer.getBuffer();
2401
2483
  while (buffer.length > 0) {
2402
- logger$z.trace('emptying buffer');
2484
+ logger$A.trace('emptying buffer');
2403
2485
  managedConnection.send(buffer.shift());
2404
2486
  }
2405
2487
  outputBuffer.resolve();
@@ -2416,7 +2498,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2416
2498
  }
2417
2499
  onDisconnected(peerDescriptor, gracefulLeave) {
2418
2500
  const nodeId = toNodeId(peerDescriptor);
2419
- logger$z.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2501
+ logger$A.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2420
2502
  const endpoint = this.endpoints.get(nodeId);
2421
2503
  if (endpoint) {
2422
2504
  this.locks.clearAllLocks(nodeId);
@@ -2424,7 +2506,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2424
2506
  endpoint.buffer.reject();
2425
2507
  }
2426
2508
  this.endpoints.delete(nodeId);
2427
- logger$z.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2509
+ logger$A.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2428
2510
  this.emit('disconnected', peerDescriptor, gracefulLeave);
2429
2511
  this.onConnectionCountChange();
2430
2512
  }
@@ -2433,7 +2515,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2433
2515
  if (this.state === ConnectionManagerState.STOPPED) {
2434
2516
  return false;
2435
2517
  }
2436
- logger$z.trace('onNewConnection()');
2518
+ logger$A.trace('onNewConnection()');
2437
2519
  if (!this.acceptNewConnection(connection)) {
2438
2520
  return false;
2439
2521
  }
@@ -2443,7 +2525,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2443
2525
  }
2444
2526
  acceptNewConnection(newConnection) {
2445
2527
  const nodeId = toNodeId(newConnection.getPeerDescriptor());
2446
- logger$z.trace(nodeId + ' acceptNewConnection()');
2528
+ logger$A.trace(nodeId + ' acceptNewConnection()');
2447
2529
  if (this.endpoints.has(nodeId)) {
2448
2530
  if (getOfferer(toNodeId(this.getLocalPeerDescriptor()), nodeId) === 'remote') {
2449
2531
  let buffer;
@@ -2452,14 +2534,14 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2452
2534
  // Could be related to WS client connections not realizing that they have been disconnected.
2453
2535
  // Makes refactoring duplicate connection handling to the connectors very difficult.
2454
2536
  if (this.endpoints.get(nodeId).connected) {
2455
- logger$z.debug('replacing connected connection', { nodeId });
2537
+ logger$A.debug('replacing connected connection', { nodeId });
2456
2538
  buffer = new OutputBuffer();
2457
2539
  }
2458
2540
  else {
2459
2541
  buffer = endpoint.buffer;
2460
2542
  }
2461
2543
  const oldConnection = endpoint.connection;
2462
- logger$z.trace('replaced: ' + nodeId);
2544
+ logger$A.trace('replaced: ' + nodeId);
2463
2545
  oldConnection.replaceAsDuplicate();
2464
2546
  this.endpoints.set(nodeId, { connected: false, connection: newConnection, buffer: buffer });
2465
2547
  return true;
@@ -2468,7 +2550,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2468
2550
  return false;
2469
2551
  }
2470
2552
  }
2471
- logger$z.trace(nodeId + ' added to connections at acceptNewConnection');
2553
+ logger$A.trace(nodeId + ' added to connections at acceptNewConnection');
2472
2554
  this.endpoints.set(nodeId, {
2473
2555
  connected: false,
2474
2556
  buffer: new OutputBuffer(),
@@ -2478,14 +2560,14 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2478
2560
  }
2479
2561
  async closeConnection(peerDescriptor, gracefulLeave, reason) {
2480
2562
  const nodeId = toNodeId(peerDescriptor);
2481
- logger$z.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2563
+ logger$A.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2482
2564
  this.locks.clearAllLocks(nodeId);
2483
2565
  if (this.endpoints.has(nodeId)) {
2484
2566
  const connectionToClose = this.endpoints.get(nodeId).connection;
2485
2567
  await connectionToClose.close(gracefulLeave);
2486
2568
  }
2487
2569
  else {
2488
- logger$z.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
2570
+ logger$A.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
2489
2571
  this.emit('disconnected', peerDescriptor, false);
2490
2572
  }
2491
2573
  }
@@ -2497,8 +2579,8 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2497
2579
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2498
2580
  this.locks.addLocalLocked(nodeId, lockId);
2499
2581
  rpcRemote.lockRequest(lockId)
2500
- .then((_accepted) => logger$z.trace('LockRequest successful'))
2501
- .catch((err) => { logger$z.debug(err); });
2582
+ .then((_accepted) => logger$A.trace('LockRequest successful'))
2583
+ .catch((err) => { logger$A.debug(err); });
2502
2584
  }
2503
2585
  unlockConnection(targetDescriptor, lockId) {
2504
2586
  if (this.state === ConnectionManagerState.STOPPED || areEqualPeerDescriptors(targetDescriptor, this.getLocalPeerDescriptor())) {
@@ -2550,7 +2632,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2550
2632
  async gracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2551
2633
  const endpoint = this.endpoints.get(toNodeId(targetDescriptor));
2552
2634
  if (!endpoint) {
2553
- logger$z.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2635
+ logger$A.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2554
2636
  return;
2555
2637
  }
2556
2638
  if (endpoint.connected) {
@@ -2559,15 +2641,15 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2559
2641
  // TODO use options option or named constant?
2560
2642
  // eslint-disable-next-line promise/catch-or-return
2561
2643
  utils.waitForEvent(connection, 'disconnected', 2000).then(() => {
2562
- logger$z.trace('disconnected event received in gracefullyDisconnectAsync()');
2644
+ logger$A.trace('disconnected event received in gracefullyDisconnectAsync()');
2563
2645
  })
2564
2646
  .catch((e) => {
2565
- logger$z.trace('force-closing connection after timeout ' + e);
2647
+ logger$A.trace('force-closing connection after timeout ' + e);
2566
2648
  // TODO should we have some handling for this floating promise?
2567
2649
  connection.close(true);
2568
2650
  })
2569
2651
  .finally(() => {
2570
- logger$z.trace('resolving after receiving disconnected event');
2652
+ logger$A.trace('resolving after receiving disconnected event');
2571
2653
  resolve();
2572
2654
  });
2573
2655
  });
@@ -2582,13 +2664,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2582
2664
  }
2583
2665
  async doGracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2584
2666
  const nodeId = toNodeId(targetDescriptor);
2585
- logger$z.trace(nodeId + ' gracefullyDisconnectAsync()');
2667
+ logger$A.trace(nodeId + ' gracefullyDisconnectAsync()');
2586
2668
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2587
2669
  try {
2588
2670
  await rpcRemote.gracefulDisconnect(disconnectMode);
2589
2671
  }
2590
2672
  catch (ex) {
2591
- logger$z.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2673
+ logger$A.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2592
2674
  }
2593
2675
  }
2594
2676
  getConnections() {
@@ -2655,7 +2737,7 @@ function protoToString(protoObj, objectType) {
2655
2737
  return ret;
2656
2738
  }
2657
2739
 
2658
- const logger$y = new utils.Logger('SimulatorConnection');
2740
+ const logger$z = new utils.Logger('SimulatorConnection');
2659
2741
  class SimulatorConnection extends Connection {
2660
2742
  stopped = false;
2661
2743
  localPeerDescriptor;
@@ -2677,46 +2759,46 @@ class SimulatorConnection extends Connection {
2677
2759
  this.doDisconnect = this.doDisconnect.bind(this);
2678
2760
  }
2679
2761
  send(data) {
2680
- logger$y.trace('send()');
2762
+ logger$z.trace('send()');
2681
2763
  if (!this.stopped) {
2682
2764
  this.simulator.send(this, data);
2683
2765
  }
2684
2766
  else {
2685
2767
  const localNodeId = toNodeId(this.localPeerDescriptor);
2686
2768
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2687
- logger$y.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
2769
+ logger$z.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
2688
2770
  }
2689
2771
  }
2690
2772
  async close(gracefulLeave) {
2691
2773
  const localNodeId = toNodeId(this.localPeerDescriptor);
2692
2774
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2693
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close()');
2775
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close()');
2694
2776
  if (!this.stopped) {
2695
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2777
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2696
2778
  this.stopped = true;
2697
2779
  try {
2698
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2780
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2699
2781
  this.simulator.close(this);
2700
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2782
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2701
2783
  }
2702
2784
  catch (e) {
2703
- logger$y.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2785
+ logger$z.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2704
2786
  }
2705
2787
  finally {
2706
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2788
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2707
2789
  this.doDisconnect(gracefulLeave);
2708
2790
  }
2709
2791
  }
2710
2792
  else {
2711
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
2793
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
2712
2794
  }
2713
2795
  }
2714
2796
  connect() {
2715
2797
  if (!this.stopped) {
2716
- logger$y.trace('connect() called');
2798
+ logger$z.trace('connect() called');
2717
2799
  this.simulator.connect(this, this.targetPeerDescriptor, (error) => {
2718
2800
  if (error !== undefined) {
2719
- logger$y.trace(error);
2801
+ logger$z.trace(error);
2720
2802
  this.doDisconnect(false);
2721
2803
  }
2722
2804
  else {
@@ -2725,46 +2807,46 @@ class SimulatorConnection extends Connection {
2725
2807
  });
2726
2808
  }
2727
2809
  else {
2728
- logger$y.trace('tried to connect() a stopped connection');
2810
+ logger$z.trace('tried to connect() a stopped connection');
2729
2811
  }
2730
2812
  }
2731
2813
  handleIncomingData(data) {
2732
2814
  if (!this.stopped) {
2733
- logger$y.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2815
+ logger$z.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2734
2816
  this.emit('data', data);
2735
2817
  }
2736
2818
  else {
2737
- logger$y.trace('tried to call handleIncomingData() a stopped connection');
2819
+ logger$z.trace('tried to call handleIncomingData() a stopped connection');
2738
2820
  }
2739
2821
  }
2740
2822
  handleIncomingDisconnection() {
2741
2823
  if (!this.stopped) {
2742
2824
  const localNodeId = toNodeId(this.localPeerDescriptor);
2743
- logger$y.trace(localNodeId + ' handleIncomingDisconnection()');
2825
+ logger$z.trace(localNodeId + ' handleIncomingDisconnection()');
2744
2826
  this.stopped = true;
2745
2827
  this.doDisconnect(false);
2746
2828
  }
2747
2829
  else {
2748
- logger$y.trace('tried to call handleIncomingDisconnection() a stopped connection');
2830
+ logger$z.trace('tried to call handleIncomingDisconnection() a stopped connection');
2749
2831
  }
2750
2832
  }
2751
2833
  destroy() {
2752
2834
  const localNodeId = toNodeId(this.localPeerDescriptor);
2753
2835
  if (!this.stopped) {
2754
- logger$y.trace(localNodeId + ' destroy()');
2836
+ logger$z.trace(localNodeId + ' destroy()');
2755
2837
  this.removeAllListeners();
2756
2838
  this.close(false).catch((_e) => { });
2757
2839
  }
2758
2840
  else {
2759
- logger$y.trace(localNodeId + ' tried to call destroy() a stopped connection');
2841
+ logger$z.trace(localNodeId + ' tried to call destroy() a stopped connection');
2760
2842
  }
2761
2843
  }
2762
2844
  doDisconnect(gracefulLeave) {
2763
2845
  const localNodeId = toNodeId(this.localPeerDescriptor);
2764
2846
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2765
- logger$y.trace(localNodeId + ' doDisconnect()');
2847
+ logger$z.trace(localNodeId + ' doDisconnect()');
2766
2848
  this.stopped = true;
2767
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2849
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2768
2850
  this.emit('disconnected', gracefulLeave);
2769
2851
  }
2770
2852
  }
@@ -2802,9 +2884,9 @@ const parseVersion = (version) => {
2802
2884
  }
2803
2885
  };
2804
2886
 
2805
- var version = "103.3.1";
2887
+ var version = "103.7.0-rc.2";
2806
2888
 
2807
- const logger$x = new utils.Logger('Handshaker');
2889
+ const logger$y = new utils.Logger('Handshaker');
2808
2890
  // Optimally the Outgoing and Incoming Handshakers could be their own separate classes
2809
2891
  // However, in cases where the PeerDescriptor of the other end of the connection can be known
2810
2892
  // only after a HandshakeRequest a base Handshaker class is needed as the IncomingHandshaker currently
@@ -2826,7 +2908,7 @@ const createOutgoingHandshaker = (localPeerDescriptor, pendingConnection, connec
2826
2908
  }
2827
2909
  };
2828
2910
  const handshakeCompletedListener = (peerDescriptor) => {
2829
- logger$x.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2911
+ logger$y.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2830
2912
  pendingConnection.onHandshakeCompleted(connection);
2831
2913
  stopHandshaker();
2832
2914
  };
@@ -2926,12 +3008,12 @@ class Handshaker extends eventemitter3.EventEmitter {
2926
3008
  try {
2927
3009
  const message = Message.fromBinary(data);
2928
3010
  if (message.body.oneofKind === 'handshakeRequest') {
2929
- logger$x.trace('handshake request received');
3011
+ logger$y.trace('handshake request received');
2930
3012
  const handshake = message.body.handshakeRequest;
2931
3013
  this.emit('handshakeRequest', handshake.sourcePeerDescriptor, handshake.protocolVersion, handshake.targetPeerDescriptor);
2932
3014
  }
2933
3015
  if (message.body.oneofKind === 'handshakeResponse') {
2934
- logger$x.trace('handshake response received');
3016
+ logger$y.trace('handshake response received');
2935
3017
  const handshake = message.body.handshakeResponse;
2936
3018
  const error = !isMaybeSupportedProtocolVersion(handshake.protocolVersion)
2937
3019
  ? HandshakeError.UNSUPPORTED_PROTOCOL_VERSION : handshake.error;
@@ -2944,18 +3026,18 @@ class Handshaker extends eventemitter3.EventEmitter {
2944
3026
  }
2945
3027
  }
2946
3028
  catch (err) {
2947
- logger$x.debug('error while parsing handshake message', err);
3029
+ logger$y.debug('error while parsing handshake message', err);
2948
3030
  }
2949
3031
  }
2950
3032
  sendHandshakeRequest(remotePeerDescriptor) {
2951
3033
  const msg = createHandshakeRequest(this.localPeerDescriptor, remotePeerDescriptor);
2952
3034
  this.connection.send(Message.toBinary(msg));
2953
- logger$x.trace('handshake request sent');
3035
+ logger$y.trace('handshake request sent');
2954
3036
  }
2955
3037
  sendHandshakeResponse(error) {
2956
3038
  const msg = createHandshakeResponse(this.localPeerDescriptor, error);
2957
3039
  this.connection.send(Message.toBinary(msg));
2958
- logger$x.trace('handshake response sent');
3040
+ logger$y.trace('handshake response sent');
2959
3041
  }
2960
3042
  stop() {
2961
3043
  this.connection.off('data', this.onDataListener);
@@ -2963,7 +3045,7 @@ class Handshaker extends eventemitter3.EventEmitter {
2963
3045
  }
2964
3046
  }
2965
3047
 
2966
- const logger$w = new utils.Logger('PendingConnection');
3048
+ const logger$x = new utils.Logger('PendingConnection');
2967
3049
  // PendingConnection is used as a reference to a connection that should be opened and handshaked to a given PeerDescriptor
2968
3050
  // It does not hold a connection internally. The public method onHandshakedCompleted should be called once a connection for the
2969
3051
  // remotePeerDescriptor is opened and handshaked successfully.
@@ -2981,7 +3063,7 @@ class PendingConnection extends eventemitter3.EventEmitter {
2981
3063
  }, timeout, this.connectingAbortController.signal);
2982
3064
  }
2983
3065
  replaceAsDuplicate() {
2984
- logger$w.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
3066
+ logger$x.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
2985
3067
  this.replacedAsDuplicate = true;
2986
3068
  }
2987
3069
  onHandshakeCompleted(connection) {
@@ -3012,7 +3094,7 @@ class PendingConnection extends eventemitter3.EventEmitter {
3012
3094
  }
3013
3095
  }
3014
3096
 
3015
- const logger$v = new utils.Logger('SimulatorConnector');
3097
+ const logger$w = new utils.Logger('SimulatorConnector');
3016
3098
  class SimulatorConnector {
3017
3099
  connectingConnections = new Map();
3018
3100
  stopped = false;
@@ -3025,7 +3107,7 @@ class SimulatorConnector {
3025
3107
  this.onNewConnection = onNewConnection;
3026
3108
  }
3027
3109
  connect(targetPeerDescriptor) {
3028
- logger$v.trace('connect() ' + toNodeId(targetPeerDescriptor));
3110
+ logger$w.trace('connect() ' + toNodeId(targetPeerDescriptor));
3029
3111
  const nodeId = toNodeId(targetPeerDescriptor);
3030
3112
  const existingConnection = this.connectingConnections.get(nodeId);
3031
3113
  if (existingConnection) {
@@ -3054,18 +3136,18 @@ class SimulatorConnector {
3054
3136
  // connection is incoming, so remotePeerDescriptor is localPeerDescriptor
3055
3137
  const remotePeerDescriptor = sourceConnection.localPeerDescriptor;
3056
3138
  const remoteNodeId = toNodeId(sourceConnection.localPeerDescriptor);
3057
- logger$v.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3139
+ logger$w.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3058
3140
  if (this.stopped) {
3059
3141
  return;
3060
3142
  }
3061
3143
  const connection = new SimulatorConnection(this.localPeerDescriptor, remotePeerDescriptor, exports.ConnectionType.SIMULATOR_SERVER, this.simulator);
3062
3144
  const pendingConnection = new PendingConnection(remotePeerDescriptor);
3063
3145
  const handshaker = createIncomingHandshaker(this.localPeerDescriptor, pendingConnection, connection);
3064
- logger$v.trace('connected');
3146
+ logger$w.trace('connected');
3065
3147
  handshaker.once('handshakeRequest', () => {
3066
- logger$v.trace(remoteNodeId + ' incoming handshake request');
3148
+ logger$w.trace(remoteNodeId + ' incoming handshake request');
3067
3149
  if (this.onNewConnection(pendingConnection)) {
3068
- logger$v.trace(remoteNodeId + ' calling acceptHandshake');
3150
+ logger$w.trace(remoteNodeId + ' calling acceptHandshake');
3069
3151
  acceptHandshake(handshaker, pendingConnection, connection);
3070
3152
  }
3071
3153
  else {
@@ -3105,6 +3187,8 @@ class ListeningRpcCommunicator extends RoutingRpcCommunicator {
3105
3187
  }
3106
3188
  }
3107
3189
 
3190
+ const isWorkerEnvironment = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
3191
+
3108
3192
  var RtcDescription;
3109
3193
  (function (RtcDescription) {
3110
3194
  RtcDescription["OFFER"] = "offer";
@@ -3119,8 +3203,8 @@ var DisconnectedRtcPeerConnectionStateEnum;
3119
3203
  DisconnectedRtcPeerConnectionStateEnum["FAILED"] = "failed";
3120
3204
  DisconnectedRtcPeerConnectionStateEnum["CLOSED"] = "closed";
3121
3205
  })(DisconnectedRtcPeerConnectionStateEnum || (DisconnectedRtcPeerConnectionStateEnum = {}));
3122
- const logger$u = new utils.Logger('WebrtcConnection (browser)');
3123
- class WebrtcConnection extends eventemitter3.EventEmitter {
3206
+ const logger$v = new utils.Logger('DirectWebrtcConnection (browser)');
3207
+ class DirectWebrtcConnection extends eventemitter3.EventEmitter {
3124
3208
  connectionId;
3125
3209
  connectionType = exports.ConnectionType.WEBRTC;
3126
3210
  // We need to keep track of connection state ourselves because
@@ -3158,7 +3242,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3158
3242
  }
3159
3243
  };
3160
3244
  this.peerConnection.onicegatheringstatechange = () => {
3161
- logger$u.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3245
+ logger$v.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3162
3246
  };
3163
3247
  this.peerConnection.onconnectionstatechange = () => this.onStateChange();
3164
3248
  if (isOffering) {
@@ -3169,7 +3253,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3169
3253
  await this.peerConnection.setLocalDescription();
3170
3254
  }
3171
3255
  catch (err) {
3172
- logger$u.warn('Failed to set local description', { err });
3256
+ logger$v.warn('Failed to set local description', { err });
3173
3257
  }
3174
3258
  if (this.peerConnection.localDescription !== null) {
3175
3259
  this.emit('localDescription', this.peerConnection.localDescription?.sdp, this.peerConnection.localDescription?.type);
@@ -3197,14 +3281,14 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3197
3281
  clearTimeout(this.earlyTimeout);
3198
3282
  }
3199
3283
  catch (err) {
3200
- logger$u.warn('Failed to set remote description', { err });
3284
+ logger$v.warn('Failed to set remote description', { err });
3201
3285
  }
3202
3286
  if ((type.toLowerCase() === RtcDescription.OFFER) && (this.peerConnection !== undefined)) {
3203
3287
  try {
3204
3288
  await this.peerConnection.setLocalDescription();
3205
3289
  }
3206
3290
  catch (err) {
3207
- logger$u.warn('Failed to set local description', { err });
3291
+ logger$v.warn('Failed to set local description', { err });
3208
3292
  }
3209
3293
  if (this.peerConnection.localDescription !== null) {
3210
3294
  this.emit('localDescription', this.peerConnection.localDescription.sdp, this.peerConnection.localDescription.type);
@@ -3214,7 +3298,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3214
3298
  addRemoteCandidate(candidate, mid) {
3215
3299
  this.peerConnection?.addIceCandidate({ candidate: candidate, sdpMid: mid })
3216
3300
  .catch((err) => {
3217
- logger$u.warn('Failed to add ICE candidate', { err });
3301
+ logger$v.warn('Failed to add ICE candidate', { err });
3218
3302
  });
3219
3303
  }
3220
3304
  isOpen() {
@@ -3237,7 +3321,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3237
3321
  this.dataChannel.close();
3238
3322
  }
3239
3323
  catch (err) {
3240
- logger$u.warn('Failed to close data channel', { err });
3324
+ logger$v.warn('Failed to close data channel', { err });
3241
3325
  }
3242
3326
  }
3243
3327
  this.dataChannel = undefined;
@@ -3246,7 +3330,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3246
3330
  this.peerConnection.close();
3247
3331
  }
3248
3332
  catch (err) {
3249
- logger$u.warn('Failed to close connection', { err });
3333
+ logger$v.warn('Failed to close connection', { err });
3250
3334
  }
3251
3335
  }
3252
3336
  this.peerConnection = undefined;
@@ -3258,6 +3342,9 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3258
3342
  }
3259
3343
  send(data) {
3260
3344
  if (this.lastState === 'connected') {
3345
+ logGapDiagnosticSampled('dht.dc.send', {
3346
+ detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
3347
+ });
3261
3348
  if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
3262
3349
  this.messageQueue.push(data);
3263
3350
  }
@@ -3266,7 +3353,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3266
3353
  }
3267
3354
  }
3268
3355
  else {
3269
- logger$u.warn('Tried to send on a connection with last state ' + this.lastState);
3356
+ logger$v.warn('Tried to send on a connection with last state ' + this.lastState);
3270
3357
  }
3271
3358
  }
3272
3359
  setupDataChannel(dataChannel) {
@@ -3274,22 +3361,23 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3274
3361
  this.dataChannel.binaryType = 'arraybuffer';
3275
3362
  this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3276
3363
  dataChannel.onopen = () => {
3277
- logger$u.trace('dc.onOpen');
3364
+ logger$v.trace('dc.onOpen');
3278
3365
  this.onDataChannelOpen();
3279
3366
  };
3280
3367
  dataChannel.onclose = () => {
3281
- logger$u.trace('dc.onClosed');
3368
+ logger$v.trace('dc.onClosed');
3282
3369
  this.doClose(false);
3283
3370
  };
3284
3371
  dataChannel.onerror = (err) => {
3285
- logger$u.warn('Data channel error', { err });
3372
+ logger$v.warn('Data channel error', { err });
3286
3373
  };
3287
3374
  dataChannel.onmessage = (msg) => {
3288
- logger$u.trace('dc.onmessage');
3375
+ logger$v.trace('dc.onmessage');
3376
+ logGapDiagnosticSampled('dht.dc.onmessage');
3289
3377
  this.emit('data', new Uint8Array(msg.data));
3290
3378
  };
3291
3379
  dataChannel.onbufferedamountlow = () => {
3292
- logger$u.trace('dc.onBufferedAmountLow');
3380
+ logger$v.trace('dc.onBufferedAmountLow');
3293
3381
  while (this.messageQueue.length > 0 && this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3294
3382
  const data = this.messageQueue.shift();
3295
3383
  this.dataChannel.send(data);
@@ -3328,6 +3416,435 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3328
3416
  }
3329
3417
  }
3330
3418
 
3419
+ /**
3420
+ * WebrtcBridge — runs on the MAIN THREAD.
3421
+ *
3422
+ * Manages RTCPeerConnection instances on behalf of a worker that cannot
3423
+ * access them directly. Each logical connection is identified by a
3424
+ * `connectionId` string supplied by the worker.
3425
+ *
3426
+ * Signaling (ICE candidates, SDP offers/answers, connection-state changes)
3427
+ * is relayed to the worker through Comlink proxy callbacks.
3428
+ *
3429
+ * Once a DataChannel is created (offerer) or received (answerer) it is
3430
+ * **transferred** to the worker so that all data-path events fire inside
3431
+ * the worker's event loop — the main thread never touches data traffic.
3432
+ */
3433
+ // ── Bridge implementation ───────────────────────────────────────────
3434
+ class WebrtcBridge {
3435
+ connections = new Map();
3436
+ async start(connectionId, iceServers, isOffering, callbacks) {
3437
+ const pc = new RTCPeerConnection({ iceServers });
3438
+ const conn = {
3439
+ pc,
3440
+ callbacks,
3441
+ isOffering,
3442
+ makingOffer: false,
3443
+ };
3444
+ this.connections.set(connectionId, conn);
3445
+ // ── ICE candidates ──────────────────────────────────────
3446
+ pc.onicecandidate = (event) => {
3447
+ if (event.candidate !== null && event.candidate.sdpMid !== null) {
3448
+ callbacks.onLocalCandidate(event.candidate.candidate, event.candidate.sdpMid);
3449
+ }
3450
+ };
3451
+ // ── Connection state → forwarded to worker ──────────────
3452
+ pc.onconnectionstatechange = () => {
3453
+ callbacks.onConnectionStateChange(pc.connectionState);
3454
+ };
3455
+ // ── Offerer path ────────────────────────────────────────
3456
+ if (isOffering) {
3457
+ pc.onnegotiationneeded = async () => {
3458
+ conn.makingOffer = true;
3459
+ try {
3460
+ await pc.setLocalDescription();
3461
+ }
3462
+ catch (_err) {
3463
+ // intentionally swallowed – mirrors DirectWebrtcConnection
3464
+ }
3465
+ if (pc.localDescription !== null) {
3466
+ callbacks.onLocalDescription(pc.localDescription.sdp, pc.localDescription.type);
3467
+ }
3468
+ conn.makingOffer = false;
3469
+ };
3470
+ const dc = pc.createDataChannel('streamrDataChannel');
3471
+ // Transfer DataChannel ownership to the worker immediately.
3472
+ // The worker will attach onopen/onclose/onmessage handlers.
3473
+ callbacks.onDataChannel(Comlink__namespace.transfer(dc, [dc]));
3474
+ }
3475
+ else {
3476
+ // ── Answerer path ───────────────────────────────────
3477
+ pc.ondatachannel = (event) => {
3478
+ callbacks.onDataChannel(Comlink__namespace.transfer(event.channel, [event.channel]));
3479
+ };
3480
+ }
3481
+ }
3482
+ async setRemoteDescription(connectionId, description, type) {
3483
+ const conn = this.connections.get(connectionId);
3484
+ if (!conn) {
3485
+ return false;
3486
+ }
3487
+ const lowerType = type.toLowerCase();
3488
+ // Perfect-negotiation collision detection
3489
+ const offerCollision = lowerType === 'offer' &&
3490
+ (conn.makingOffer || conn.pc.signalingState !== 'stable');
3491
+ if (conn.isOffering && offerCollision) {
3492
+ return false;
3493
+ }
3494
+ try {
3495
+ await conn.pc.setRemoteDescription({ sdp: description, type: lowerType });
3496
+ }
3497
+ catch (_err) {
3498
+ return false;
3499
+ }
3500
+ // If we received an offer, create an answer
3501
+ if (lowerType === 'offer') {
3502
+ try {
3503
+ await conn.pc.setLocalDescription();
3504
+ }
3505
+ catch (_err) {
3506
+ // intentionally swallowed
3507
+ }
3508
+ if (conn.pc.localDescription !== null) {
3509
+ conn.callbacks.onLocalDescription(conn.pc.localDescription.sdp, conn.pc.localDescription.type);
3510
+ }
3511
+ }
3512
+ return true;
3513
+ }
3514
+ async addRemoteCandidate(connectionId, candidate, mid) {
3515
+ const conn = this.connections.get(connectionId);
3516
+ if (!conn) {
3517
+ return;
3518
+ }
3519
+ try {
3520
+ await conn.pc.addIceCandidate({ candidate, sdpMid: mid });
3521
+ }
3522
+ catch (_err) {
3523
+ // intentionally swallowed
3524
+ }
3525
+ }
3526
+ async renameConnection(oldId, newId) {
3527
+ const conn = this.connections.get(oldId);
3528
+ if (conn) {
3529
+ this.connections.delete(oldId);
3530
+ this.connections.set(newId, conn);
3531
+ }
3532
+ }
3533
+ async close(connectionId) {
3534
+ const conn = this.connections.get(connectionId);
3535
+ if (!conn) {
3536
+ return;
3537
+ }
3538
+ this.connections.delete(connectionId);
3539
+ // Tear down event handlers
3540
+ conn.pc.onicecandidate = null;
3541
+ conn.pc.onconnectionstatechange = null;
3542
+ conn.pc.onnegotiationneeded = null;
3543
+ conn.pc.ondatachannel = null;
3544
+ try {
3545
+ conn.pc.close();
3546
+ }
3547
+ catch (_err) {
3548
+ // intentionally swallowed
3549
+ }
3550
+ }
3551
+ }
3552
+
3553
+ /**
3554
+ * Call this function on the **main thread** before the worker starts using
3555
+ * WebRTC connections. It creates a dedicated `MessageChannel`, exposes a
3556
+ * {@link WebrtcBridge} instance on one port, and sends the other port to
3557
+ * the worker so that `WorkerWebrtcConnection` can reach the bridge.
3558
+ *
3559
+ * @example
3560
+ * ```ts
3561
+ * import { installWebrtcBridge } from '@streamr/dht'
3562
+ *
3563
+ * const worker = new Worker('./my-worker.ts', { type: 'module' })
3564
+ * installWebrtcBridge(worker)
3565
+ * ```
3566
+ */
3567
+ const WEBRTC_BRIDGE_PORT_MESSAGE_TYPE = 'streamr-webrtc-bridge-port';
3568
+ function installWebrtcBridge(worker) {
3569
+ const bridge = new WebrtcBridge();
3570
+ const channel = new MessageChannel();
3571
+ // Expose the bridge API on port1 — the worker will Comlink.wrap(port2).
3572
+ Comlink__namespace.expose(bridge, channel.port1);
3573
+ // Send port2 to the worker. It is transferred (not cloned).
3574
+ worker.postMessage({ type: WEBRTC_BRIDGE_PORT_MESSAGE_TYPE, port: channel.port2 }, [channel.port2]);
3575
+ }
3576
+
3577
+ /**
3578
+ * WorkerWebrtcConnection — runs inside a **Web Worker**.
3579
+ *
3580
+ * Implements the same IWebrtcConnection + IConnection interfaces as
3581
+ * DirectWebrtcConnection, but delegates RTCPeerConnection management to
3582
+ * the main-thread {@link WebrtcBridge} via Comlink.
3583
+ *
3584
+ * The RTCDataChannel is **transferred** from the main thread and lives
3585
+ * entirely in the worker — all data events (onmessage, onopen, onclose,
3586
+ * onbufferedamountlow) fire in the worker's event loop. The main thread
3587
+ * is never involved in the data path.
3588
+ */
3589
+ // ── Module-level bridge client (initialized once per worker) ────────
3590
+ let resolveBridgeProxy;
3591
+ const bridgeProxyPromise = new Promise((resolve) => {
3592
+ resolveBridgeProxy = resolve;
3593
+ });
3594
+ // Listen for the bridge port message from the main thread.
3595
+ // This is guarded so it only runs inside a worker context.
3596
+ if (isWorkerEnvironment) {
3597
+ const handler = (e) => {
3598
+ if (e.data?.type === WEBRTC_BRIDGE_PORT_MESSAGE_TYPE && e.data.port) {
3599
+ const proxy = Comlink__namespace.wrap(e.data.port);
3600
+ resolveBridgeProxy(proxy);
3601
+ self.removeEventListener('message', handler);
3602
+ }
3603
+ };
3604
+ self.addEventListener('message', handler);
3605
+ }
3606
+ function getBridgeProxy() {
3607
+ return bridgeProxyPromise;
3608
+ }
3609
+ // ── Disconnection states ────────────────────────────────────────────
3610
+ var DisconnectedState;
3611
+ (function (DisconnectedState) {
3612
+ DisconnectedState["DISCONNECTED"] = "disconnected";
3613
+ DisconnectedState["FAILED"] = "failed";
3614
+ DisconnectedState["CLOSED"] = "closed";
3615
+ })(DisconnectedState || (DisconnectedState = {}));
3616
+ const logger$u = new utils.Logger('WorkerWebrtcConnection');
3617
+ // ── WorkerWebrtcConnection ──────────────────────────────────────────
3618
+ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
3619
+ connectionId;
3620
+ connectionType = exports.ConnectionType.WEBRTC;
3621
+ iceServers;
3622
+ bufferThresholdHigh;
3623
+ bufferThresholdLow;
3624
+ dataChannel;
3625
+ bridge;
3626
+ closed = false;
3627
+ connected = false;
3628
+ earlyTimeout;
3629
+ messageQueue = [];
3630
+ startPromise;
3631
+ constructor(params) {
3632
+ super();
3633
+ this.connectionId = createRandomConnectionId();
3634
+ this.iceServers = params.iceServers ?? [];
3635
+ this.bufferThresholdHigh = params.bufferThresholdHigh ?? 2 ** 17;
3636
+ this.bufferThresholdLow = params.bufferThresholdLow ?? 2 ** 15;
3637
+ this.earlyTimeout = setTimeout(() => {
3638
+ this.doClose(false, 'timed out due to remote descriptor not being set');
3639
+ }, EARLY_TIMEOUT);
3640
+ }
3641
+ // ── IWebrtcConnection ───────────────────────────────────────
3642
+ start(isOffering) {
3643
+ this.startPromise = this.doStart(isOffering);
3644
+ this.startPromise.catch((err) => {
3645
+ logger$u.warn('Failed to start worker WebRTC connection', { err });
3646
+ this.doClose(false, 'Failed to start');
3647
+ });
3648
+ }
3649
+ async doStart(isOffering) {
3650
+ this.bridge = await getBridgeProxy();
3651
+ const iceServers = this.iceServers.map(({ url, port, username, password }) => ({
3652
+ urls: `${url}:${port}`,
3653
+ username,
3654
+ credential: password,
3655
+ }));
3656
+ await this.bridge.start(this.connectionId, iceServers, isOffering, Comlink__namespace.proxy({
3657
+ onLocalCandidate: (candidate, mid) => {
3658
+ if (!this.closed) {
3659
+ this.emit('localCandidate', candidate, mid);
3660
+ }
3661
+ },
3662
+ onLocalDescription: (description, type) => {
3663
+ if (!this.closed) {
3664
+ this.emit('localDescription', description, type);
3665
+ }
3666
+ },
3667
+ onConnectionStateChange: (state) => {
3668
+ if (state === DisconnectedState.CLOSED ||
3669
+ state === DisconnectedState.DISCONNECTED ||
3670
+ state === DisconnectedState.FAILED) {
3671
+ this.doClose(false, `pcState=${state}`);
3672
+ }
3673
+ },
3674
+ onDataChannel: (channel) => {
3675
+ if (!this.closed) {
3676
+ this.setupDataChannel(channel);
3677
+ // If the channel was already open at transfer time
3678
+ if (channel.readyState === 'open') {
3679
+ this.onDataChannelOpen();
3680
+ }
3681
+ }
3682
+ },
3683
+ }));
3684
+ }
3685
+ async setRemoteDescription(description, type) {
3686
+ if (this.startPromise) {
3687
+ await this.startPromise;
3688
+ }
3689
+ if (!this.bridge || this.closed) {
3690
+ return;
3691
+ }
3692
+ const wasSet = await this.bridge.setRemoteDescription(this.connectionId, description, type);
3693
+ if (wasSet) {
3694
+ clearTimeout(this.earlyTimeout);
3695
+ }
3696
+ }
3697
+ addRemoteCandidate(candidate, mid) {
3698
+ this.doAddRemoteCandidate(candidate, mid).catch((err) => {
3699
+ logger$u.warn('Failed to add remote candidate via bridge', { err });
3700
+ });
3701
+ }
3702
+ async doAddRemoteCandidate(candidate, mid) {
3703
+ if (this.startPromise) {
3704
+ await this.startPromise;
3705
+ }
3706
+ if (!this.bridge || this.closed) {
3707
+ return;
3708
+ }
3709
+ await this.bridge.addRemoteCandidate(this.connectionId, candidate, mid);
3710
+ }
3711
+ isOpen() {
3712
+ return this.connected;
3713
+ }
3714
+ // ── IConnection ─────────────────────────────────────────────
3715
+ async close(gracefulLeave, reason) {
3716
+ this.doClose(gracefulLeave, reason);
3717
+ }
3718
+ destroy() {
3719
+ this.removeAllListeners();
3720
+ this.doClose(false);
3721
+ }
3722
+ send(data) {
3723
+ if (this.connected && this.dataChannel) {
3724
+ logGapDiagnosticSampled('dht.dc.send', {
3725
+ detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
3726
+ });
3727
+ if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
3728
+ this.messageQueue.push(data);
3729
+ }
3730
+ else {
3731
+ this.dataChannel.send(data);
3732
+ }
3733
+ }
3734
+ else if (!this.closed) {
3735
+ this.messageQueue.push(data);
3736
+ }
3737
+ }
3738
+ setConnectionId(connectionId) {
3739
+ const oldId = this.connectionId;
3740
+ this.connectionId = connectionId;
3741
+ if (this.bridge && oldId !== connectionId) {
3742
+ this.bridge.renameConnection(oldId, connectionId).catch(() => { });
3743
+ }
3744
+ }
3745
+ // ── DataChannel handling (runs entirely in the worker) ──────
3746
+ setupDataChannel(dataChannel) {
3747
+ this.dataChannel = dataChannel;
3748
+ this.dataChannel.binaryType = 'arraybuffer';
3749
+ this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3750
+ dataChannel.onopen = () => {
3751
+ logger$u.trace('dc.onOpen (worker)');
3752
+ this.onDataChannelOpen();
3753
+ };
3754
+ dataChannel.onclose = () => {
3755
+ logger$u.trace('dc.onClosed (worker)');
3756
+ this.doClose(false, 'dataChannel.onclose');
3757
+ };
3758
+ dataChannel.onerror = (err) => {
3759
+ logger$u.warn('Data channel error (worker)', { err });
3760
+ };
3761
+ dataChannel.onmessage = (msg) => {
3762
+ logger$u.trace('dc.onmessage (worker)');
3763
+ logGapDiagnosticSampled('dht.dc.onmessage');
3764
+ this.emit('data', new Uint8Array(msg.data));
3765
+ };
3766
+ dataChannel.onbufferedamountlow = () => {
3767
+ logger$u.trace('dc.onBufferedAmountLow (worker)');
3768
+ while (this.messageQueue.length > 0 &&
3769
+ this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3770
+ const data = this.messageQueue.shift();
3771
+ this.dataChannel.send(data);
3772
+ }
3773
+ };
3774
+ }
3775
+ onDataChannelOpen() {
3776
+ this.connected = true;
3777
+ this.flushMessageQueue();
3778
+ this.emit('connected');
3779
+ }
3780
+ flushMessageQueue() {
3781
+ while (this.messageQueue.length > 0 &&
3782
+ this.dataChannel &&
3783
+ this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3784
+ const data = this.messageQueue.shift();
3785
+ this.dataChannel.send(data);
3786
+ }
3787
+ }
3788
+ // ── Teardown ────────────────────────────────────────────────
3789
+ doClose(gracefulLeave, reason) {
3790
+ if (!this.closed) {
3791
+ this.closed = true;
3792
+ this.connected = false;
3793
+ this.messageQueue.length = 0;
3794
+ clearTimeout(this.earlyTimeout);
3795
+ this.stopListening();
3796
+ this.emit('disconnected', gracefulLeave, undefined, reason);
3797
+ this.removeAllListeners();
3798
+ if (this.dataChannel !== undefined) {
3799
+ try {
3800
+ this.dataChannel.close();
3801
+ }
3802
+ catch (err) {
3803
+ logger$u.warn('Failed to close data channel (worker)', { err });
3804
+ }
3805
+ }
3806
+ this.dataChannel = undefined;
3807
+ // Tell the main-thread bridge to tear down the RTCPeerConnection.
3808
+ // Fire-and-forget — we don't block on this.
3809
+ this.bridge
3810
+ ?.close(this.connectionId)
3811
+ .catch(() => {
3812
+ // intentionally swallowed
3813
+ });
3814
+ }
3815
+ }
3816
+ stopListening() {
3817
+ if (this.dataChannel !== undefined) {
3818
+ this.dataChannel.onopen = null;
3819
+ this.dataChannel.onclose = null;
3820
+ this.dataChannel.onerror = null;
3821
+ this.dataChannel.onbufferedamountlow = null;
3822
+ this.dataChannel.onmessage = null;
3823
+ }
3824
+ }
3825
+ }
3826
+
3827
+ /**
3828
+ * Conditional re-export of the browser WebrtcConnection.
3829
+ *
3830
+ * At module-load time we detect whether we are running inside a Web Worker.
3831
+ * - **Main thread** → use {@link DirectWebrtcConnection} which owns the
3832
+ * `RTCPeerConnection` and `RTCDataChannel` directly.
3833
+ * - **Worker thread** → use {@link WorkerWebrtcConnection} which delegates
3834
+ * `RTCPeerConnection` signaling to the main thread via a Comlink bridge
3835
+ * and receives a transferred `RTCDataChannel` that lives entirely in the
3836
+ * worker.
3837
+ *
3838
+ * Both classes implement `IWebrtcConnection & IConnection` and expose the
3839
+ * same public API, so upstream code (WebrtcConnector, etc.) is unaffected.
3840
+ */
3841
+ // The constructor — points to the right class based on the runtime
3842
+ // environment. The type assertion is safe because both implementations
3843
+ // share the same public interface surface.
3844
+ const WebrtcConnection = (isWorkerEnvironment
3845
+ ? WorkerWebrtcConnection
3846
+ : DirectWebrtcConnection);
3847
+
3331
3848
  const logger$t = new utils.Logger('WebrtcConnectorRpcRemote');
3332
3849
  class WebrtcConnectorRpcRemote extends RpcRemote {
3333
3850
  requestConnection() {
@@ -5513,7 +6030,7 @@ class PeerDiscovery {
5513
6030
  logger$d.debug(`Ring join on ${this.options.serviceId} timed out`);
5514
6031
  }
5515
6032
  finally {
5516
- sessions.forEach((session) => this.ongoingDiscoverySessions.delete(session.id));
6033
+ sessions.forEach((session) => this.ongoingRingDiscoverySessions.delete(session.id));
5517
6034
  }
5518
6035
  }
5519
6036
  async rejoinDht(entryPoint, contactedPeers = new Set(), distantJoinContactPeers = new Set()) {
@@ -7630,7 +8147,10 @@ exports.areEqualPeerDescriptors = areEqualPeerDescriptors;
7630
8147
  exports.createOutgoingHandshaker = createOutgoingHandshaker;
7631
8148
  exports.getRandomRegion = getRandomRegion;
7632
8149
  exports.getRegionDelayMatrix = getRegionDelayMatrix;
8150
+ exports.installWebrtcBridge = installWebrtcBridge;
8151
+ exports.logGapDiagnosticSampled = logGapDiagnosticSampled;
7633
8152
  exports.randomDhtAddress = randomDhtAddress;
8153
+ exports.setGapDiagnosticsEnabled = setGapDiagnosticsEnabled;
7634
8154
  exports.toDhtAddress = toDhtAddress;
7635
8155
  exports.toDhtAddressRaw = toDhtAddressRaw;
7636
8156
  exports.toNodeId = toNodeId;