@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.
@@ -9,6 +9,7 @@ import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
9
9
  import { v4 } from 'uuid';
10
10
  import { ProtoCallContext, RpcCommunicator, toProtoRpcClient, protoClasses as protoClasses$1, RpcError } from '@streamr/proto-rpc';
11
11
  import ipaddr from 'ipaddr.js';
12
+ import * as Comlink from 'comlink';
12
13
  import { w3cwebsocket } from 'websocket';
13
14
  import { SERVICE_ID } from '@streamr/autocertifier-client';
14
15
  import shuffle from 'lodash/shuffle';
@@ -239,6 +240,65 @@ class SendFailed extends Err {
239
240
  constructor(message, originalError) { super(ErrorCode.SEND_FAILED, message, originalError); }
240
241
  }
241
242
 
243
+ let enabled = false;
244
+ function setGapDiagnosticsEnabled(val) {
245
+ enabled = val;
246
+ globalThis.__dhtGapDiagEnabled = val;
247
+ }
248
+ const SUMMARY_INTERVAL_MS = 2000;
249
+ const accumulators = new Map();
250
+ function logGapDiagnosticSampled(layer, opts = {}) {
251
+ if (!enabled)
252
+ return;
253
+ const now = performance.now();
254
+ const threshold = opts.outlierThresholdMs ?? 30;
255
+ let acc = accumulators.get(layer);
256
+ if (acc === undefined) {
257
+ acc = { count: 0, sumDeltaMs: 0, maxDeltaMs: 0, outlierCount: 0, lastReportMs: now, lastEventMs: now };
258
+ accumulators.set(layer, acc);
259
+ return;
260
+ }
261
+ const deltaMs = now - acc.lastEventMs;
262
+ acc.lastEventMs = now;
263
+ acc.count++;
264
+ if (deltaMs > acc.maxDeltaMs)
265
+ acc.maxDeltaMs = deltaMs;
266
+ acc.sumDeltaMs += deltaMs;
267
+ if (deltaMs > threshold)
268
+ acc.outlierCount++;
269
+ if (deltaMs > threshold) {
270
+ const payload = {
271
+ layer,
272
+ timestampMs: now,
273
+ deltaMs: +deltaMs.toFixed(2),
274
+ detail: opts.detail,
275
+ };
276
+ // eslint-disable-next-line no-console
277
+ console.log('[gap-diagnostics]', JSON.stringify(payload));
278
+ }
279
+ if (now - acc.lastReportMs >= SUMMARY_INTERVAL_MS) {
280
+ const summaryDetail = {
281
+ count: acc.count,
282
+ meanDeltaMs: acc.count > 0 ? +(acc.sumDeltaMs / acc.count).toFixed(2) : 0,
283
+ maxDeltaMs: +acc.maxDeltaMs.toFixed(2),
284
+ outlierCount: acc.outlierCount,
285
+ periodMs: +(now - acc.lastReportMs).toFixed(1),
286
+ };
287
+ const summary = {
288
+ layer: `${layer}.summary`,
289
+ timestampMs: now,
290
+ detail: summaryDetail,
291
+ };
292
+ // eslint-disable-next-line no-console
293
+ console.log('[gap-diagnostics]', JSON.stringify(summary));
294
+ acc.count = 0;
295
+ acc.sumDeltaMs = 0;
296
+ acc.maxDeltaMs = 0;
297
+ acc.outlierCount = 0;
298
+ acc.lastReportMs = now;
299
+ }
300
+ }
301
+
242
302
  // @generated message type with reflection information, may provide speed optimized methods
243
303
  class Empty$Type extends MessageType {
244
304
  constructor() {
@@ -1798,7 +1858,7 @@ const createRandomConnectionId = () => {
1798
1858
  return v4();
1799
1859
  };
1800
1860
 
1801
- const logger$C = new Logger('ManagedConnection');
1861
+ const logger$D = new Logger('ManagedConnection');
1802
1862
  // ManagedConnection is a component used as a wrapper for IConnection after they have been successfully handshaked.
1803
1863
  // Should only be used in the ConnectionManager.
1804
1864
  class ManagedConnection extends EventEmitter {
@@ -1828,7 +1888,7 @@ class ManagedConnection extends EventEmitter {
1828
1888
  this.remotePeerDescriptor = peerDescriptor;
1829
1889
  }
1830
1890
  onDisconnected(gracefulLeave) {
1831
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1891
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1832
1892
  if (!this.replacedAsDuplicate) {
1833
1893
  this.emit('disconnected', gracefulLeave);
1834
1894
  }
@@ -1837,7 +1897,7 @@ class ManagedConnection extends EventEmitter {
1837
1897
  // TODO: Can this be removed if ManagedConnections can never be duplicates?
1838
1898
  // Handle duplicates in the ConncetorFacade and no longer have PendingConnections in ConnectionManager
1839
1899
  replaceAsDuplicate() {
1840
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1900
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1841
1901
  this.replacedAsDuplicate = true;
1842
1902
  }
1843
1903
  send(data) {
@@ -1984,10 +2044,10 @@ class RpcRemote {
1984
2044
  }
1985
2045
  }
1986
2046
 
1987
- const logger$B = new Logger('ConnectionLockRpcRemote');
2047
+ const logger$C = new Logger('ConnectionLockRpcRemote');
1988
2048
  class ConnectionLockRpcRemote extends RpcRemote {
1989
2049
  async lockRequest(lockId) {
1990
- logger$B.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
2050
+ logger$C.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
1991
2051
  const request = {
1992
2052
  lockId
1993
2053
  };
@@ -1997,12 +2057,12 @@ class ConnectionLockRpcRemote extends RpcRemote {
1997
2057
  return res.accepted;
1998
2058
  }
1999
2059
  catch (err) {
2000
- logger$B.debug('Connection lock rejected', { err });
2060
+ logger$C.debug('Connection lock rejected', { err });
2001
2061
  return false;
2002
2062
  }
2003
2063
  }
2004
2064
  unlockRequest(lockId) {
2005
- logger$B.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
2065
+ logger$C.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
2006
2066
  const request = {
2007
2067
  lockId
2008
2068
  };
@@ -2010,11 +2070,11 @@ class ConnectionLockRpcRemote extends RpcRemote {
2010
2070
  notification: true
2011
2071
  });
2012
2072
  this.getClient().unlockRequest(request, options).catch((_e) => {
2013
- logger$B.trace('failed to send unlockRequest');
2073
+ logger$C.trace('failed to send unlockRequest');
2014
2074
  });
2015
2075
  }
2016
2076
  async gracefulDisconnect(disconnectMode) {
2017
- logger$B.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2077
+ logger$C.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2018
2078
  const request = {
2019
2079
  disconnectMode
2020
2080
  };
@@ -2026,7 +2086,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2026
2086
  await this.getClient().gracefulDisconnect(request, options);
2027
2087
  }
2028
2088
  async setPrivate(isPrivate) {
2029
- logger$B.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2089
+ logger$C.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2030
2090
  const request = {
2031
2091
  isPrivate
2032
2092
  };
@@ -2038,7 +2098,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2038
2098
  }
2039
2099
  }
2040
2100
 
2041
- const logger$A = new Logger('ConnectionLockRpcLocal');
2101
+ const logger$B = new Logger('ConnectionLockRpcLocal');
2042
2102
  class ConnectionLockRpcLocal {
2043
2103
  options;
2044
2104
  constructor(options) {
@@ -2067,7 +2127,7 @@ class ConnectionLockRpcLocal {
2067
2127
  }
2068
2128
  async gracefulDisconnect(disconnectNotice, context) {
2069
2129
  const senderPeerDescriptor = context.incomingSourceDescriptor;
2070
- logger$A.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2130
+ logger$B.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2071
2131
  if (disconnectNotice.disconnectMode === DisconnectMode.LEAVING) {
2072
2132
  await this.options.closeConnection(senderPeerDescriptor, true, 'graceful leave notified');
2073
2133
  }
@@ -2118,7 +2178,7 @@ var NatType;
2118
2178
  NatType["OPEN_INTERNET"] = "open_internet";
2119
2179
  NatType["UNKNOWN"] = "unknown";
2120
2180
  })(NatType || (NatType = {}));
2121
- const logger$z = new Logger('ConnectionManager');
2181
+ const logger$A = new Logger('ConnectionManager');
2122
2182
  var ConnectionManagerState;
2123
2183
  (function (ConnectionManagerState) {
2124
2184
  ConnectionManagerState["IDLE"] = "idle";
@@ -2168,7 +2228,7 @@ class ConnectionManager extends EventEmitter {
2168
2228
  getLocalPeerDescriptor: () => this.getLocalPeerDescriptor(),
2169
2229
  setPrivate: (id, isPrivate) => {
2170
2230
  if (!this.options.allowIncomingPrivateConnections) {
2171
- logger$z.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
2231
+ logger$A.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
2172
2232
  return;
2173
2233
  }
2174
2234
  if (isPrivate) {
@@ -2202,7 +2262,7 @@ class ConnectionManager extends EventEmitter {
2202
2262
  const connection = endpoint.connection;
2203
2263
  const nodeId = connection.getNodeId();
2204
2264
  if (!this.locks.isLocked(nodeId) && !this.locks.isPrivate(nodeId) && Date.now() - connection.getLastUsedTimestamp() > maxIdleTime) {
2205
- logger$z.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2265
+ logger$A.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2206
2266
  disconnectionCandidates.addContact(connection);
2207
2267
  }
2208
2268
  }
@@ -2210,7 +2270,7 @@ class ConnectionManager extends EventEmitter {
2210
2270
  const disconnectables = disconnectionCandidates.getFurthestContacts(this.endpoints.size - maxConnections);
2211
2271
  for (const disconnectable of disconnectables) {
2212
2272
  const peerDescriptor = disconnectable.getPeerDescriptor();
2213
- logger$z.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2273
+ logger$A.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2214
2274
  this.gracefullyDisconnectAsync(peerDescriptor, DisconnectMode.NORMAL).catch((_e) => { });
2215
2275
  }
2216
2276
  }
@@ -2219,11 +2279,11 @@ class ConnectionManager extends EventEmitter {
2219
2279
  throw new CouldNotStart(`Cannot start already ${this.state} module`);
2220
2280
  }
2221
2281
  this.state = ConnectionManagerState.RUNNING;
2222
- logger$z.trace(`Starting ConnectionManager...`);
2282
+ logger$A.trace(`Starting ConnectionManager...`);
2223
2283
  await this.connectorFacade.start((connection) => this.onNewConnection(connection), (nodeId) => this.hasConnection(nodeId), this);
2224
2284
  // Garbage collection of connections
2225
2285
  this.disconnectorIntervalRef = setInterval(() => {
2226
- logger$z.trace('disconnectorInterval');
2286
+ logger$A.trace('disconnectorInterval');
2227
2287
  const LAST_USED_LIMIT = 20000;
2228
2288
  this.garbageCollectConnections(this.options.maxConnections ?? 80, LAST_USED_LIMIT);
2229
2289
  }, 5000); // TODO use options option or named constant?
@@ -2233,7 +2293,7 @@ class ConnectionManager extends EventEmitter {
2233
2293
  return;
2234
2294
  }
2235
2295
  this.state = ConnectionManagerState.STOPPING;
2236
- logger$z.trace(`Stopping ConnectionManager`);
2296
+ logger$A.trace(`Stopping ConnectionManager`);
2237
2297
  if (this.disconnectorIntervalRef) {
2238
2298
  clearInterval(this.disconnectorIntervalRef);
2239
2299
  }
@@ -2243,23 +2303,23 @@ class ConnectionManager extends EventEmitter {
2243
2303
  await this.gracefullyDisconnectAsync(endpoint.connection.getPeerDescriptor(), DisconnectMode.LEAVING);
2244
2304
  }
2245
2305
  catch (e) {
2246
- logger$z.error(e);
2306
+ logger$A.error(e);
2247
2307
  }
2248
2308
  }
2249
2309
  else {
2250
2310
  const connection = endpoint.connection;
2251
- logger$z.trace('handshake of connection not completed, force-closing');
2311
+ logger$A.trace('handshake of connection not completed, force-closing');
2252
2312
  // TODO use options option or named constant?
2253
2313
  const eventReceived = waitForEvent(connection, 'disconnected', 2000);
2254
2314
  // TODO should we have some handling for this floating promise?
2255
2315
  connection.close(true);
2256
2316
  try {
2257
2317
  await eventReceived;
2258
- logger$z.trace('resolving after receiving disconnected event from non-handshaked connection');
2318
+ logger$A.trace('resolving after receiving disconnected event from non-handshaked connection');
2259
2319
  }
2260
2320
  catch (e) {
2261
2321
  endpoint.buffer.reject();
2262
- logger$z.trace('force-closing non-handshaked connection timed out ' + e);
2322
+ logger$A.trace('force-closing non-handshaked connection timed out ' + e);
2263
2323
  }
2264
2324
  }
2265
2325
  }));
@@ -2283,12 +2343,13 @@ class ConnectionManager extends EventEmitter {
2283
2343
  if ((this.state === ConnectionManagerState.STOPPED || this.state === ConnectionManagerState.STOPPING) && !opts.sendIfStopped) {
2284
2344
  return;
2285
2345
  }
2346
+ logGapDiagnosticSampled('dht.connMgr.send');
2286
2347
  const peerDescriptor = message.targetDescriptor;
2287
2348
  if (this.isConnectionToSelf(peerDescriptor)) {
2288
2349
  throw new CannotConnectToSelf('Cannot send to self');
2289
2350
  }
2290
2351
  const nodeId = toNodeId(peerDescriptor);
2291
- logger$z.trace(`Sending message to: ${nodeId}`);
2352
+ logger$A.trace(`Sending message to: ${nodeId}`);
2292
2353
  message = {
2293
2354
  ...message,
2294
2355
  sourceDescriptor: this.getLocalPeerDescriptor()
@@ -2343,13 +2404,13 @@ class ConnectionManager extends EventEmitter {
2343
2404
  }
2344
2405
  handleMessage(message) {
2345
2406
  const messageType = message.body.oneofKind;
2346
- logger$z.trace('Received message of type ' + messageType);
2407
+ logger$A.trace('Received message of type ' + messageType);
2347
2408
  if (messageType !== 'rpcMessage') {
2348
- logger$z.trace('Filtered out non-RPC message of type ' + messageType);
2409
+ logger$A.trace('Filtered out non-RPC message of type ' + messageType);
2349
2410
  return;
2350
2411
  }
2351
2412
  if (this.duplicateMessageDetector.isMostLikelyDuplicate(message.messageId)) {
2352
- logger$z.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2413
+ logger$A.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2353
2414
  + ' ' + message.serviceId + ' ' + message.messageId);
2354
2415
  return;
2355
2416
  }
@@ -2358,7 +2419,8 @@ class ConnectionManager extends EventEmitter {
2358
2419
  this.rpcCommunicator?.handleMessageFromPeer(message);
2359
2420
  }
2360
2421
  else {
2361
- logger$z.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2422
+ logGapDiagnosticSampled('dht.connMgr.emitMessage');
2423
+ logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2362
2424
  + ' ' + message.serviceId + ' ' + message.messageId);
2363
2425
  this.emit('message', message);
2364
2426
  }
@@ -2367,6 +2429,7 @@ class ConnectionManager extends EventEmitter {
2367
2429
  if (this.state === ConnectionManagerState.STOPPED) {
2368
2430
  return;
2369
2431
  }
2432
+ logGapDiagnosticSampled('dht.connMgr.onData');
2370
2433
  this.metrics.receiveBytesPerSecond.record(data.byteLength);
2371
2434
  this.metrics.receiveMessagesPerSecond.record(1);
2372
2435
  let message;
@@ -2375,7 +2438,7 @@ class ConnectionManager extends EventEmitter {
2375
2438
  }
2376
2439
  catch (e) {
2377
2440
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2378
- logger$z.debug(`Parsing incoming data into Message failed: ${e}`);
2441
+ logger$A.debug(`Parsing incoming data into Message failed: ${e}`);
2379
2442
  return;
2380
2443
  }
2381
2444
  message.sourceDescriptor = peerDescriptor;
@@ -2384,7 +2447,7 @@ class ConnectionManager extends EventEmitter {
2384
2447
  }
2385
2448
  catch (e) {
2386
2449
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2387
- logger$z.debug(`Handling incoming data failed: ${e}`);
2450
+ logger$A.debug(`Handling incoming data failed: ${e}`);
2388
2451
  }
2389
2452
  }
2390
2453
  onConnected(peerDescriptor, connection) {
@@ -2397,7 +2460,7 @@ class ConnectionManager extends EventEmitter {
2397
2460
  const pendingConnection = endpoint.connection;
2398
2461
  const buffer = outputBuffer.getBuffer();
2399
2462
  while (buffer.length > 0) {
2400
- logger$z.trace('emptying buffer');
2463
+ logger$A.trace('emptying buffer');
2401
2464
  managedConnection.send(buffer.shift());
2402
2465
  }
2403
2466
  outputBuffer.resolve();
@@ -2414,7 +2477,7 @@ class ConnectionManager extends EventEmitter {
2414
2477
  }
2415
2478
  onDisconnected(peerDescriptor, gracefulLeave) {
2416
2479
  const nodeId = toNodeId(peerDescriptor);
2417
- logger$z.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2480
+ logger$A.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2418
2481
  const endpoint = this.endpoints.get(nodeId);
2419
2482
  if (endpoint) {
2420
2483
  this.locks.clearAllLocks(nodeId);
@@ -2422,7 +2485,7 @@ class ConnectionManager extends EventEmitter {
2422
2485
  endpoint.buffer.reject();
2423
2486
  }
2424
2487
  this.endpoints.delete(nodeId);
2425
- logger$z.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2488
+ logger$A.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2426
2489
  this.emit('disconnected', peerDescriptor, gracefulLeave);
2427
2490
  this.onConnectionCountChange();
2428
2491
  }
@@ -2431,7 +2494,7 @@ class ConnectionManager extends EventEmitter {
2431
2494
  if (this.state === ConnectionManagerState.STOPPED) {
2432
2495
  return false;
2433
2496
  }
2434
- logger$z.trace('onNewConnection()');
2497
+ logger$A.trace('onNewConnection()');
2435
2498
  if (!this.acceptNewConnection(connection)) {
2436
2499
  return false;
2437
2500
  }
@@ -2441,7 +2504,7 @@ class ConnectionManager extends EventEmitter {
2441
2504
  }
2442
2505
  acceptNewConnection(newConnection) {
2443
2506
  const nodeId = toNodeId(newConnection.getPeerDescriptor());
2444
- logger$z.trace(nodeId + ' acceptNewConnection()');
2507
+ logger$A.trace(nodeId + ' acceptNewConnection()');
2445
2508
  if (this.endpoints.has(nodeId)) {
2446
2509
  if (getOfferer(toNodeId(this.getLocalPeerDescriptor()), nodeId) === 'remote') {
2447
2510
  let buffer;
@@ -2450,14 +2513,14 @@ class ConnectionManager extends EventEmitter {
2450
2513
  // Could be related to WS client connections not realizing that they have been disconnected.
2451
2514
  // Makes refactoring duplicate connection handling to the connectors very difficult.
2452
2515
  if (this.endpoints.get(nodeId).connected) {
2453
- logger$z.debug('replacing connected connection', { nodeId });
2516
+ logger$A.debug('replacing connected connection', { nodeId });
2454
2517
  buffer = new OutputBuffer();
2455
2518
  }
2456
2519
  else {
2457
2520
  buffer = endpoint.buffer;
2458
2521
  }
2459
2522
  const oldConnection = endpoint.connection;
2460
- logger$z.trace('replaced: ' + nodeId);
2523
+ logger$A.trace('replaced: ' + nodeId);
2461
2524
  oldConnection.replaceAsDuplicate();
2462
2525
  this.endpoints.set(nodeId, { connected: false, connection: newConnection, buffer: buffer });
2463
2526
  return true;
@@ -2466,7 +2529,7 @@ class ConnectionManager extends EventEmitter {
2466
2529
  return false;
2467
2530
  }
2468
2531
  }
2469
- logger$z.trace(nodeId + ' added to connections at acceptNewConnection');
2532
+ logger$A.trace(nodeId + ' added to connections at acceptNewConnection');
2470
2533
  this.endpoints.set(nodeId, {
2471
2534
  connected: false,
2472
2535
  buffer: new OutputBuffer(),
@@ -2476,14 +2539,14 @@ class ConnectionManager extends EventEmitter {
2476
2539
  }
2477
2540
  async closeConnection(peerDescriptor, gracefulLeave, reason) {
2478
2541
  const nodeId = toNodeId(peerDescriptor);
2479
- logger$z.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2542
+ logger$A.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2480
2543
  this.locks.clearAllLocks(nodeId);
2481
2544
  if (this.endpoints.has(nodeId)) {
2482
2545
  const connectionToClose = this.endpoints.get(nodeId).connection;
2483
2546
  await connectionToClose.close(gracefulLeave);
2484
2547
  }
2485
2548
  else {
2486
- logger$z.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
2549
+ logger$A.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
2487
2550
  this.emit('disconnected', peerDescriptor, false);
2488
2551
  }
2489
2552
  }
@@ -2495,8 +2558,8 @@ class ConnectionManager extends EventEmitter {
2495
2558
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2496
2559
  this.locks.addLocalLocked(nodeId, lockId);
2497
2560
  rpcRemote.lockRequest(lockId)
2498
- .then((_accepted) => logger$z.trace('LockRequest successful'))
2499
- .catch((err) => { logger$z.debug(err); });
2561
+ .then((_accepted) => logger$A.trace('LockRequest successful'))
2562
+ .catch((err) => { logger$A.debug(err); });
2500
2563
  }
2501
2564
  unlockConnection(targetDescriptor, lockId) {
2502
2565
  if (this.state === ConnectionManagerState.STOPPED || areEqualPeerDescriptors(targetDescriptor, this.getLocalPeerDescriptor())) {
@@ -2548,7 +2611,7 @@ class ConnectionManager extends EventEmitter {
2548
2611
  async gracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2549
2612
  const endpoint = this.endpoints.get(toNodeId(targetDescriptor));
2550
2613
  if (!endpoint) {
2551
- logger$z.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2614
+ logger$A.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2552
2615
  return;
2553
2616
  }
2554
2617
  if (endpoint.connected) {
@@ -2557,15 +2620,15 @@ class ConnectionManager extends EventEmitter {
2557
2620
  // TODO use options option or named constant?
2558
2621
  // eslint-disable-next-line promise/catch-or-return
2559
2622
  waitForEvent(connection, 'disconnected', 2000).then(() => {
2560
- logger$z.trace('disconnected event received in gracefullyDisconnectAsync()');
2623
+ logger$A.trace('disconnected event received in gracefullyDisconnectAsync()');
2561
2624
  })
2562
2625
  .catch((e) => {
2563
- logger$z.trace('force-closing connection after timeout ' + e);
2626
+ logger$A.trace('force-closing connection after timeout ' + e);
2564
2627
  // TODO should we have some handling for this floating promise?
2565
2628
  connection.close(true);
2566
2629
  })
2567
2630
  .finally(() => {
2568
- logger$z.trace('resolving after receiving disconnected event');
2631
+ logger$A.trace('resolving after receiving disconnected event');
2569
2632
  resolve();
2570
2633
  });
2571
2634
  });
@@ -2580,13 +2643,13 @@ class ConnectionManager extends EventEmitter {
2580
2643
  }
2581
2644
  async doGracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2582
2645
  const nodeId = toNodeId(targetDescriptor);
2583
- logger$z.trace(nodeId + ' gracefullyDisconnectAsync()');
2646
+ logger$A.trace(nodeId + ' gracefullyDisconnectAsync()');
2584
2647
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2585
2648
  try {
2586
2649
  await rpcRemote.gracefulDisconnect(disconnectMode);
2587
2650
  }
2588
2651
  catch (ex) {
2589
- logger$z.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2652
+ logger$A.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2590
2653
  }
2591
2654
  }
2592
2655
  getConnections() {
@@ -2653,7 +2716,7 @@ function protoToString(protoObj, objectType) {
2653
2716
  return ret;
2654
2717
  }
2655
2718
 
2656
- const logger$y = new Logger('SimulatorConnection');
2719
+ const logger$z = new Logger('SimulatorConnection');
2657
2720
  class SimulatorConnection extends Connection {
2658
2721
  stopped = false;
2659
2722
  localPeerDescriptor;
@@ -2675,46 +2738,46 @@ class SimulatorConnection extends Connection {
2675
2738
  this.doDisconnect = this.doDisconnect.bind(this);
2676
2739
  }
2677
2740
  send(data) {
2678
- logger$y.trace('send()');
2741
+ logger$z.trace('send()');
2679
2742
  if (!this.stopped) {
2680
2743
  this.simulator.send(this, data);
2681
2744
  }
2682
2745
  else {
2683
2746
  const localNodeId = toNodeId(this.localPeerDescriptor);
2684
2747
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2685
- logger$y.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
2748
+ logger$z.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
2686
2749
  }
2687
2750
  }
2688
2751
  async close(gracefulLeave) {
2689
2752
  const localNodeId = toNodeId(this.localPeerDescriptor);
2690
2753
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2691
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close()');
2754
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close()');
2692
2755
  if (!this.stopped) {
2693
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2756
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2694
2757
  this.stopped = true;
2695
2758
  try {
2696
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2759
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2697
2760
  this.simulator.close(this);
2698
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2761
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2699
2762
  }
2700
2763
  catch (e) {
2701
- logger$y.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2764
+ logger$z.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2702
2765
  }
2703
2766
  finally {
2704
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2767
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2705
2768
  this.doDisconnect(gracefulLeave);
2706
2769
  }
2707
2770
  }
2708
2771
  else {
2709
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
2772
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
2710
2773
  }
2711
2774
  }
2712
2775
  connect() {
2713
2776
  if (!this.stopped) {
2714
- logger$y.trace('connect() called');
2777
+ logger$z.trace('connect() called');
2715
2778
  this.simulator.connect(this, this.targetPeerDescriptor, (error) => {
2716
2779
  if (error !== undefined) {
2717
- logger$y.trace(error);
2780
+ logger$z.trace(error);
2718
2781
  this.doDisconnect(false);
2719
2782
  }
2720
2783
  else {
@@ -2723,46 +2786,46 @@ class SimulatorConnection extends Connection {
2723
2786
  });
2724
2787
  }
2725
2788
  else {
2726
- logger$y.trace('tried to connect() a stopped connection');
2789
+ logger$z.trace('tried to connect() a stopped connection');
2727
2790
  }
2728
2791
  }
2729
2792
  handleIncomingData(data) {
2730
2793
  if (!this.stopped) {
2731
- logger$y.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2794
+ logger$z.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2732
2795
  this.emit('data', data);
2733
2796
  }
2734
2797
  else {
2735
- logger$y.trace('tried to call handleIncomingData() a stopped connection');
2798
+ logger$z.trace('tried to call handleIncomingData() a stopped connection');
2736
2799
  }
2737
2800
  }
2738
2801
  handleIncomingDisconnection() {
2739
2802
  if (!this.stopped) {
2740
2803
  const localNodeId = toNodeId(this.localPeerDescriptor);
2741
- logger$y.trace(localNodeId + ' handleIncomingDisconnection()');
2804
+ logger$z.trace(localNodeId + ' handleIncomingDisconnection()');
2742
2805
  this.stopped = true;
2743
2806
  this.doDisconnect(false);
2744
2807
  }
2745
2808
  else {
2746
- logger$y.trace('tried to call handleIncomingDisconnection() a stopped connection');
2809
+ logger$z.trace('tried to call handleIncomingDisconnection() a stopped connection');
2747
2810
  }
2748
2811
  }
2749
2812
  destroy() {
2750
2813
  const localNodeId = toNodeId(this.localPeerDescriptor);
2751
2814
  if (!this.stopped) {
2752
- logger$y.trace(localNodeId + ' destroy()');
2815
+ logger$z.trace(localNodeId + ' destroy()');
2753
2816
  this.removeAllListeners();
2754
2817
  this.close(false).catch((_e) => { });
2755
2818
  }
2756
2819
  else {
2757
- logger$y.trace(localNodeId + ' tried to call destroy() a stopped connection');
2820
+ logger$z.trace(localNodeId + ' tried to call destroy() a stopped connection');
2758
2821
  }
2759
2822
  }
2760
2823
  doDisconnect(gracefulLeave) {
2761
2824
  const localNodeId = toNodeId(this.localPeerDescriptor);
2762
2825
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2763
- logger$y.trace(localNodeId + ' doDisconnect()');
2826
+ logger$z.trace(localNodeId + ' doDisconnect()');
2764
2827
  this.stopped = true;
2765
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2828
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2766
2829
  this.emit('disconnected', gracefulLeave);
2767
2830
  }
2768
2831
  }
@@ -2800,9 +2863,9 @@ const parseVersion = (version) => {
2800
2863
  }
2801
2864
  };
2802
2865
 
2803
- var version = "103.3.1";
2866
+ var version = "103.7.0-rc.2";
2804
2867
 
2805
- const logger$x = new Logger('Handshaker');
2868
+ const logger$y = new Logger('Handshaker');
2806
2869
  // Optimally the Outgoing and Incoming Handshakers could be their own separate classes
2807
2870
  // However, in cases where the PeerDescriptor of the other end of the connection can be known
2808
2871
  // only after a HandshakeRequest a base Handshaker class is needed as the IncomingHandshaker currently
@@ -2824,7 +2887,7 @@ const createOutgoingHandshaker = (localPeerDescriptor, pendingConnection, connec
2824
2887
  }
2825
2888
  };
2826
2889
  const handshakeCompletedListener = (peerDescriptor) => {
2827
- logger$x.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2890
+ logger$y.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2828
2891
  pendingConnection.onHandshakeCompleted(connection);
2829
2892
  stopHandshaker();
2830
2893
  };
@@ -2924,12 +2987,12 @@ class Handshaker extends EventEmitter {
2924
2987
  try {
2925
2988
  const message = Message.fromBinary(data);
2926
2989
  if (message.body.oneofKind === 'handshakeRequest') {
2927
- logger$x.trace('handshake request received');
2990
+ logger$y.trace('handshake request received');
2928
2991
  const handshake = message.body.handshakeRequest;
2929
2992
  this.emit('handshakeRequest', handshake.sourcePeerDescriptor, handshake.protocolVersion, handshake.targetPeerDescriptor);
2930
2993
  }
2931
2994
  if (message.body.oneofKind === 'handshakeResponse') {
2932
- logger$x.trace('handshake response received');
2995
+ logger$y.trace('handshake response received');
2933
2996
  const handshake = message.body.handshakeResponse;
2934
2997
  const error = !isMaybeSupportedProtocolVersion(handshake.protocolVersion)
2935
2998
  ? HandshakeError.UNSUPPORTED_PROTOCOL_VERSION : handshake.error;
@@ -2942,18 +3005,18 @@ class Handshaker extends EventEmitter {
2942
3005
  }
2943
3006
  }
2944
3007
  catch (err) {
2945
- logger$x.debug('error while parsing handshake message', err);
3008
+ logger$y.debug('error while parsing handshake message', err);
2946
3009
  }
2947
3010
  }
2948
3011
  sendHandshakeRequest(remotePeerDescriptor) {
2949
3012
  const msg = createHandshakeRequest(this.localPeerDescriptor, remotePeerDescriptor);
2950
3013
  this.connection.send(Message.toBinary(msg));
2951
- logger$x.trace('handshake request sent');
3014
+ logger$y.trace('handshake request sent');
2952
3015
  }
2953
3016
  sendHandshakeResponse(error) {
2954
3017
  const msg = createHandshakeResponse(this.localPeerDescriptor, error);
2955
3018
  this.connection.send(Message.toBinary(msg));
2956
- logger$x.trace('handshake response sent');
3019
+ logger$y.trace('handshake response sent');
2957
3020
  }
2958
3021
  stop() {
2959
3022
  this.connection.off('data', this.onDataListener);
@@ -2961,7 +3024,7 @@ class Handshaker extends EventEmitter {
2961
3024
  }
2962
3025
  }
2963
3026
 
2964
- const logger$w = new Logger('PendingConnection');
3027
+ const logger$x = new Logger('PendingConnection');
2965
3028
  // PendingConnection is used as a reference to a connection that should be opened and handshaked to a given PeerDescriptor
2966
3029
  // It does not hold a connection internally. The public method onHandshakedCompleted should be called once a connection for the
2967
3030
  // remotePeerDescriptor is opened and handshaked successfully.
@@ -2979,7 +3042,7 @@ class PendingConnection extends EventEmitter {
2979
3042
  }, timeout, this.connectingAbortController.signal);
2980
3043
  }
2981
3044
  replaceAsDuplicate() {
2982
- logger$w.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
3045
+ logger$x.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
2983
3046
  this.replacedAsDuplicate = true;
2984
3047
  }
2985
3048
  onHandshakeCompleted(connection) {
@@ -3010,7 +3073,7 @@ class PendingConnection extends EventEmitter {
3010
3073
  }
3011
3074
  }
3012
3075
 
3013
- const logger$v = new Logger('SimulatorConnector');
3076
+ const logger$w = new Logger('SimulatorConnector');
3014
3077
  class SimulatorConnector {
3015
3078
  connectingConnections = new Map();
3016
3079
  stopped = false;
@@ -3023,7 +3086,7 @@ class SimulatorConnector {
3023
3086
  this.onNewConnection = onNewConnection;
3024
3087
  }
3025
3088
  connect(targetPeerDescriptor) {
3026
- logger$v.trace('connect() ' + toNodeId(targetPeerDescriptor));
3089
+ logger$w.trace('connect() ' + toNodeId(targetPeerDescriptor));
3027
3090
  const nodeId = toNodeId(targetPeerDescriptor);
3028
3091
  const existingConnection = this.connectingConnections.get(nodeId);
3029
3092
  if (existingConnection) {
@@ -3052,18 +3115,18 @@ class SimulatorConnector {
3052
3115
  // connection is incoming, so remotePeerDescriptor is localPeerDescriptor
3053
3116
  const remotePeerDescriptor = sourceConnection.localPeerDescriptor;
3054
3117
  const remoteNodeId = toNodeId(sourceConnection.localPeerDescriptor);
3055
- logger$v.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3118
+ logger$w.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3056
3119
  if (this.stopped) {
3057
3120
  return;
3058
3121
  }
3059
3122
  const connection = new SimulatorConnection(this.localPeerDescriptor, remotePeerDescriptor, ConnectionType.SIMULATOR_SERVER, this.simulator);
3060
3123
  const pendingConnection = new PendingConnection(remotePeerDescriptor);
3061
3124
  const handshaker = createIncomingHandshaker(this.localPeerDescriptor, pendingConnection, connection);
3062
- logger$v.trace('connected');
3125
+ logger$w.trace('connected');
3063
3126
  handshaker.once('handshakeRequest', () => {
3064
- logger$v.trace(remoteNodeId + ' incoming handshake request');
3127
+ logger$w.trace(remoteNodeId + ' incoming handshake request');
3065
3128
  if (this.onNewConnection(pendingConnection)) {
3066
- logger$v.trace(remoteNodeId + ' calling acceptHandshake');
3129
+ logger$w.trace(remoteNodeId + ' calling acceptHandshake');
3067
3130
  acceptHandshake(handshaker, pendingConnection, connection);
3068
3131
  }
3069
3132
  else {
@@ -3103,6 +3166,8 @@ class ListeningRpcCommunicator extends RoutingRpcCommunicator {
3103
3166
  }
3104
3167
  }
3105
3168
 
3169
+ const isWorkerEnvironment = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
3170
+
3106
3171
  var RtcDescription;
3107
3172
  (function (RtcDescription) {
3108
3173
  RtcDescription["OFFER"] = "offer";
@@ -3117,8 +3182,8 @@ var DisconnectedRtcPeerConnectionStateEnum;
3117
3182
  DisconnectedRtcPeerConnectionStateEnum["FAILED"] = "failed";
3118
3183
  DisconnectedRtcPeerConnectionStateEnum["CLOSED"] = "closed";
3119
3184
  })(DisconnectedRtcPeerConnectionStateEnum || (DisconnectedRtcPeerConnectionStateEnum = {}));
3120
- const logger$u = new Logger('WebrtcConnection (browser)');
3121
- class WebrtcConnection extends EventEmitter {
3185
+ const logger$v = new Logger('DirectWebrtcConnection (browser)');
3186
+ class DirectWebrtcConnection extends EventEmitter {
3122
3187
  connectionId;
3123
3188
  connectionType = ConnectionType.WEBRTC;
3124
3189
  // We need to keep track of connection state ourselves because
@@ -3156,7 +3221,7 @@ class WebrtcConnection extends EventEmitter {
3156
3221
  }
3157
3222
  };
3158
3223
  this.peerConnection.onicegatheringstatechange = () => {
3159
- logger$u.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3224
+ logger$v.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3160
3225
  };
3161
3226
  this.peerConnection.onconnectionstatechange = () => this.onStateChange();
3162
3227
  if (isOffering) {
@@ -3167,7 +3232,7 @@ class WebrtcConnection extends EventEmitter {
3167
3232
  await this.peerConnection.setLocalDescription();
3168
3233
  }
3169
3234
  catch (err) {
3170
- logger$u.warn('Failed to set local description', { err });
3235
+ logger$v.warn('Failed to set local description', { err });
3171
3236
  }
3172
3237
  if (this.peerConnection.localDescription !== null) {
3173
3238
  this.emit('localDescription', this.peerConnection.localDescription?.sdp, this.peerConnection.localDescription?.type);
@@ -3195,14 +3260,14 @@ class WebrtcConnection extends EventEmitter {
3195
3260
  clearTimeout(this.earlyTimeout);
3196
3261
  }
3197
3262
  catch (err) {
3198
- logger$u.warn('Failed to set remote description', { err });
3263
+ logger$v.warn('Failed to set remote description', { err });
3199
3264
  }
3200
3265
  if ((type.toLowerCase() === RtcDescription.OFFER) && (this.peerConnection !== undefined)) {
3201
3266
  try {
3202
3267
  await this.peerConnection.setLocalDescription();
3203
3268
  }
3204
3269
  catch (err) {
3205
- logger$u.warn('Failed to set local description', { err });
3270
+ logger$v.warn('Failed to set local description', { err });
3206
3271
  }
3207
3272
  if (this.peerConnection.localDescription !== null) {
3208
3273
  this.emit('localDescription', this.peerConnection.localDescription.sdp, this.peerConnection.localDescription.type);
@@ -3212,7 +3277,7 @@ class WebrtcConnection extends EventEmitter {
3212
3277
  addRemoteCandidate(candidate, mid) {
3213
3278
  this.peerConnection?.addIceCandidate({ candidate: candidate, sdpMid: mid })
3214
3279
  .catch((err) => {
3215
- logger$u.warn('Failed to add ICE candidate', { err });
3280
+ logger$v.warn('Failed to add ICE candidate', { err });
3216
3281
  });
3217
3282
  }
3218
3283
  isOpen() {
@@ -3235,7 +3300,7 @@ class WebrtcConnection extends EventEmitter {
3235
3300
  this.dataChannel.close();
3236
3301
  }
3237
3302
  catch (err) {
3238
- logger$u.warn('Failed to close data channel', { err });
3303
+ logger$v.warn('Failed to close data channel', { err });
3239
3304
  }
3240
3305
  }
3241
3306
  this.dataChannel = undefined;
@@ -3244,7 +3309,7 @@ class WebrtcConnection extends EventEmitter {
3244
3309
  this.peerConnection.close();
3245
3310
  }
3246
3311
  catch (err) {
3247
- logger$u.warn('Failed to close connection', { err });
3312
+ logger$v.warn('Failed to close connection', { err });
3248
3313
  }
3249
3314
  }
3250
3315
  this.peerConnection = undefined;
@@ -3256,6 +3321,9 @@ class WebrtcConnection extends EventEmitter {
3256
3321
  }
3257
3322
  send(data) {
3258
3323
  if (this.lastState === 'connected') {
3324
+ logGapDiagnosticSampled('dht.dc.send', {
3325
+ detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
3326
+ });
3259
3327
  if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
3260
3328
  this.messageQueue.push(data);
3261
3329
  }
@@ -3264,7 +3332,7 @@ class WebrtcConnection extends EventEmitter {
3264
3332
  }
3265
3333
  }
3266
3334
  else {
3267
- logger$u.warn('Tried to send on a connection with last state ' + this.lastState);
3335
+ logger$v.warn('Tried to send on a connection with last state ' + this.lastState);
3268
3336
  }
3269
3337
  }
3270
3338
  setupDataChannel(dataChannel) {
@@ -3272,22 +3340,23 @@ class WebrtcConnection extends EventEmitter {
3272
3340
  this.dataChannel.binaryType = 'arraybuffer';
3273
3341
  this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3274
3342
  dataChannel.onopen = () => {
3275
- logger$u.trace('dc.onOpen');
3343
+ logger$v.trace('dc.onOpen');
3276
3344
  this.onDataChannelOpen();
3277
3345
  };
3278
3346
  dataChannel.onclose = () => {
3279
- logger$u.trace('dc.onClosed');
3347
+ logger$v.trace('dc.onClosed');
3280
3348
  this.doClose(false);
3281
3349
  };
3282
3350
  dataChannel.onerror = (err) => {
3283
- logger$u.warn('Data channel error', { err });
3351
+ logger$v.warn('Data channel error', { err });
3284
3352
  };
3285
3353
  dataChannel.onmessage = (msg) => {
3286
- logger$u.trace('dc.onmessage');
3354
+ logger$v.trace('dc.onmessage');
3355
+ logGapDiagnosticSampled('dht.dc.onmessage');
3287
3356
  this.emit('data', new Uint8Array(msg.data));
3288
3357
  };
3289
3358
  dataChannel.onbufferedamountlow = () => {
3290
- logger$u.trace('dc.onBufferedAmountLow');
3359
+ logger$v.trace('dc.onBufferedAmountLow');
3291
3360
  while (this.messageQueue.length > 0 && this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3292
3361
  const data = this.messageQueue.shift();
3293
3362
  this.dataChannel.send(data);
@@ -3326,6 +3395,435 @@ class WebrtcConnection extends EventEmitter {
3326
3395
  }
3327
3396
  }
3328
3397
 
3398
+ /**
3399
+ * WebrtcBridge — runs on the MAIN THREAD.
3400
+ *
3401
+ * Manages RTCPeerConnection instances on behalf of a worker that cannot
3402
+ * access them directly. Each logical connection is identified by a
3403
+ * `connectionId` string supplied by the worker.
3404
+ *
3405
+ * Signaling (ICE candidates, SDP offers/answers, connection-state changes)
3406
+ * is relayed to the worker through Comlink proxy callbacks.
3407
+ *
3408
+ * Once a DataChannel is created (offerer) or received (answerer) it is
3409
+ * **transferred** to the worker so that all data-path events fire inside
3410
+ * the worker's event loop — the main thread never touches data traffic.
3411
+ */
3412
+ // ── Bridge implementation ───────────────────────────────────────────
3413
+ class WebrtcBridge {
3414
+ connections = new Map();
3415
+ async start(connectionId, iceServers, isOffering, callbacks) {
3416
+ const pc = new RTCPeerConnection({ iceServers });
3417
+ const conn = {
3418
+ pc,
3419
+ callbacks,
3420
+ isOffering,
3421
+ makingOffer: false,
3422
+ };
3423
+ this.connections.set(connectionId, conn);
3424
+ // ── ICE candidates ──────────────────────────────────────
3425
+ pc.onicecandidate = (event) => {
3426
+ if (event.candidate !== null && event.candidate.sdpMid !== null) {
3427
+ callbacks.onLocalCandidate(event.candidate.candidate, event.candidate.sdpMid);
3428
+ }
3429
+ };
3430
+ // ── Connection state → forwarded to worker ──────────────
3431
+ pc.onconnectionstatechange = () => {
3432
+ callbacks.onConnectionStateChange(pc.connectionState);
3433
+ };
3434
+ // ── Offerer path ────────────────────────────────────────
3435
+ if (isOffering) {
3436
+ pc.onnegotiationneeded = async () => {
3437
+ conn.makingOffer = true;
3438
+ try {
3439
+ await pc.setLocalDescription();
3440
+ }
3441
+ catch (_err) {
3442
+ // intentionally swallowed – mirrors DirectWebrtcConnection
3443
+ }
3444
+ if (pc.localDescription !== null) {
3445
+ callbacks.onLocalDescription(pc.localDescription.sdp, pc.localDescription.type);
3446
+ }
3447
+ conn.makingOffer = false;
3448
+ };
3449
+ const dc = pc.createDataChannel('streamrDataChannel');
3450
+ // Transfer DataChannel ownership to the worker immediately.
3451
+ // The worker will attach onopen/onclose/onmessage handlers.
3452
+ callbacks.onDataChannel(Comlink.transfer(dc, [dc]));
3453
+ }
3454
+ else {
3455
+ // ── Answerer path ───────────────────────────────────
3456
+ pc.ondatachannel = (event) => {
3457
+ callbacks.onDataChannel(Comlink.transfer(event.channel, [event.channel]));
3458
+ };
3459
+ }
3460
+ }
3461
+ async setRemoteDescription(connectionId, description, type) {
3462
+ const conn = this.connections.get(connectionId);
3463
+ if (!conn) {
3464
+ return false;
3465
+ }
3466
+ const lowerType = type.toLowerCase();
3467
+ // Perfect-negotiation collision detection
3468
+ const offerCollision = lowerType === 'offer' &&
3469
+ (conn.makingOffer || conn.pc.signalingState !== 'stable');
3470
+ if (conn.isOffering && offerCollision) {
3471
+ return false;
3472
+ }
3473
+ try {
3474
+ await conn.pc.setRemoteDescription({ sdp: description, type: lowerType });
3475
+ }
3476
+ catch (_err) {
3477
+ return false;
3478
+ }
3479
+ // If we received an offer, create an answer
3480
+ if (lowerType === 'offer') {
3481
+ try {
3482
+ await conn.pc.setLocalDescription();
3483
+ }
3484
+ catch (_err) {
3485
+ // intentionally swallowed
3486
+ }
3487
+ if (conn.pc.localDescription !== null) {
3488
+ conn.callbacks.onLocalDescription(conn.pc.localDescription.sdp, conn.pc.localDescription.type);
3489
+ }
3490
+ }
3491
+ return true;
3492
+ }
3493
+ async addRemoteCandidate(connectionId, candidate, mid) {
3494
+ const conn = this.connections.get(connectionId);
3495
+ if (!conn) {
3496
+ return;
3497
+ }
3498
+ try {
3499
+ await conn.pc.addIceCandidate({ candidate, sdpMid: mid });
3500
+ }
3501
+ catch (_err) {
3502
+ // intentionally swallowed
3503
+ }
3504
+ }
3505
+ async renameConnection(oldId, newId) {
3506
+ const conn = this.connections.get(oldId);
3507
+ if (conn) {
3508
+ this.connections.delete(oldId);
3509
+ this.connections.set(newId, conn);
3510
+ }
3511
+ }
3512
+ async close(connectionId) {
3513
+ const conn = this.connections.get(connectionId);
3514
+ if (!conn) {
3515
+ return;
3516
+ }
3517
+ this.connections.delete(connectionId);
3518
+ // Tear down event handlers
3519
+ conn.pc.onicecandidate = null;
3520
+ conn.pc.onconnectionstatechange = null;
3521
+ conn.pc.onnegotiationneeded = null;
3522
+ conn.pc.ondatachannel = null;
3523
+ try {
3524
+ conn.pc.close();
3525
+ }
3526
+ catch (_err) {
3527
+ // intentionally swallowed
3528
+ }
3529
+ }
3530
+ }
3531
+
3532
+ /**
3533
+ * Call this function on the **main thread** before the worker starts using
3534
+ * WebRTC connections. It creates a dedicated `MessageChannel`, exposes a
3535
+ * {@link WebrtcBridge} instance on one port, and sends the other port to
3536
+ * the worker so that `WorkerWebrtcConnection` can reach the bridge.
3537
+ *
3538
+ * @example
3539
+ * ```ts
3540
+ * import { installWebrtcBridge } from '@streamr/dht'
3541
+ *
3542
+ * const worker = new Worker('./my-worker.ts', { type: 'module' })
3543
+ * installWebrtcBridge(worker)
3544
+ * ```
3545
+ */
3546
+ const WEBRTC_BRIDGE_PORT_MESSAGE_TYPE = 'streamr-webrtc-bridge-port';
3547
+ function installWebrtcBridge(worker) {
3548
+ const bridge = new WebrtcBridge();
3549
+ const channel = new MessageChannel();
3550
+ // Expose the bridge API on port1 — the worker will Comlink.wrap(port2).
3551
+ Comlink.expose(bridge, channel.port1);
3552
+ // Send port2 to the worker. It is transferred (not cloned).
3553
+ worker.postMessage({ type: WEBRTC_BRIDGE_PORT_MESSAGE_TYPE, port: channel.port2 }, [channel.port2]);
3554
+ }
3555
+
3556
+ /**
3557
+ * WorkerWebrtcConnection — runs inside a **Web Worker**.
3558
+ *
3559
+ * Implements the same IWebrtcConnection + IConnection interfaces as
3560
+ * DirectWebrtcConnection, but delegates RTCPeerConnection management to
3561
+ * the main-thread {@link WebrtcBridge} via Comlink.
3562
+ *
3563
+ * The RTCDataChannel is **transferred** from the main thread and lives
3564
+ * entirely in the worker — all data events (onmessage, onopen, onclose,
3565
+ * onbufferedamountlow) fire in the worker's event loop. The main thread
3566
+ * is never involved in the data path.
3567
+ */
3568
+ // ── Module-level bridge client (initialized once per worker) ────────
3569
+ let resolveBridgeProxy;
3570
+ const bridgeProxyPromise = new Promise((resolve) => {
3571
+ resolveBridgeProxy = resolve;
3572
+ });
3573
+ // Listen for the bridge port message from the main thread.
3574
+ // This is guarded so it only runs inside a worker context.
3575
+ if (isWorkerEnvironment) {
3576
+ const handler = (e) => {
3577
+ if (e.data?.type === WEBRTC_BRIDGE_PORT_MESSAGE_TYPE && e.data.port) {
3578
+ const proxy = Comlink.wrap(e.data.port);
3579
+ resolveBridgeProxy(proxy);
3580
+ self.removeEventListener('message', handler);
3581
+ }
3582
+ };
3583
+ self.addEventListener('message', handler);
3584
+ }
3585
+ function getBridgeProxy() {
3586
+ return bridgeProxyPromise;
3587
+ }
3588
+ // ── Disconnection states ────────────────────────────────────────────
3589
+ var DisconnectedState;
3590
+ (function (DisconnectedState) {
3591
+ DisconnectedState["DISCONNECTED"] = "disconnected";
3592
+ DisconnectedState["FAILED"] = "failed";
3593
+ DisconnectedState["CLOSED"] = "closed";
3594
+ })(DisconnectedState || (DisconnectedState = {}));
3595
+ const logger$u = new Logger('WorkerWebrtcConnection');
3596
+ // ── WorkerWebrtcConnection ──────────────────────────────────────────
3597
+ class WorkerWebrtcConnection extends EventEmitter {
3598
+ connectionId;
3599
+ connectionType = ConnectionType.WEBRTC;
3600
+ iceServers;
3601
+ bufferThresholdHigh;
3602
+ bufferThresholdLow;
3603
+ dataChannel;
3604
+ bridge;
3605
+ closed = false;
3606
+ connected = false;
3607
+ earlyTimeout;
3608
+ messageQueue = [];
3609
+ startPromise;
3610
+ constructor(params) {
3611
+ super();
3612
+ this.connectionId = createRandomConnectionId();
3613
+ this.iceServers = params.iceServers ?? [];
3614
+ this.bufferThresholdHigh = params.bufferThresholdHigh ?? 2 ** 17;
3615
+ this.bufferThresholdLow = params.bufferThresholdLow ?? 2 ** 15;
3616
+ this.earlyTimeout = setTimeout(() => {
3617
+ this.doClose(false, 'timed out due to remote descriptor not being set');
3618
+ }, EARLY_TIMEOUT);
3619
+ }
3620
+ // ── IWebrtcConnection ───────────────────────────────────────
3621
+ start(isOffering) {
3622
+ this.startPromise = this.doStart(isOffering);
3623
+ this.startPromise.catch((err) => {
3624
+ logger$u.warn('Failed to start worker WebRTC connection', { err });
3625
+ this.doClose(false, 'Failed to start');
3626
+ });
3627
+ }
3628
+ async doStart(isOffering) {
3629
+ this.bridge = await getBridgeProxy();
3630
+ const iceServers = this.iceServers.map(({ url, port, username, password }) => ({
3631
+ urls: `${url}:${port}`,
3632
+ username,
3633
+ credential: password,
3634
+ }));
3635
+ await this.bridge.start(this.connectionId, iceServers, isOffering, Comlink.proxy({
3636
+ onLocalCandidate: (candidate, mid) => {
3637
+ if (!this.closed) {
3638
+ this.emit('localCandidate', candidate, mid);
3639
+ }
3640
+ },
3641
+ onLocalDescription: (description, type) => {
3642
+ if (!this.closed) {
3643
+ this.emit('localDescription', description, type);
3644
+ }
3645
+ },
3646
+ onConnectionStateChange: (state) => {
3647
+ if (state === DisconnectedState.CLOSED ||
3648
+ state === DisconnectedState.DISCONNECTED ||
3649
+ state === DisconnectedState.FAILED) {
3650
+ this.doClose(false, `pcState=${state}`);
3651
+ }
3652
+ },
3653
+ onDataChannel: (channel) => {
3654
+ if (!this.closed) {
3655
+ this.setupDataChannel(channel);
3656
+ // If the channel was already open at transfer time
3657
+ if (channel.readyState === 'open') {
3658
+ this.onDataChannelOpen();
3659
+ }
3660
+ }
3661
+ },
3662
+ }));
3663
+ }
3664
+ async setRemoteDescription(description, type) {
3665
+ if (this.startPromise) {
3666
+ await this.startPromise;
3667
+ }
3668
+ if (!this.bridge || this.closed) {
3669
+ return;
3670
+ }
3671
+ const wasSet = await this.bridge.setRemoteDescription(this.connectionId, description, type);
3672
+ if (wasSet) {
3673
+ clearTimeout(this.earlyTimeout);
3674
+ }
3675
+ }
3676
+ addRemoteCandidate(candidate, mid) {
3677
+ this.doAddRemoteCandidate(candidate, mid).catch((err) => {
3678
+ logger$u.warn('Failed to add remote candidate via bridge', { err });
3679
+ });
3680
+ }
3681
+ async doAddRemoteCandidate(candidate, mid) {
3682
+ if (this.startPromise) {
3683
+ await this.startPromise;
3684
+ }
3685
+ if (!this.bridge || this.closed) {
3686
+ return;
3687
+ }
3688
+ await this.bridge.addRemoteCandidate(this.connectionId, candidate, mid);
3689
+ }
3690
+ isOpen() {
3691
+ return this.connected;
3692
+ }
3693
+ // ── IConnection ─────────────────────────────────────────────
3694
+ async close(gracefulLeave, reason) {
3695
+ this.doClose(gracefulLeave, reason);
3696
+ }
3697
+ destroy() {
3698
+ this.removeAllListeners();
3699
+ this.doClose(false);
3700
+ }
3701
+ send(data) {
3702
+ if (this.connected && this.dataChannel) {
3703
+ logGapDiagnosticSampled('dht.dc.send', {
3704
+ detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
3705
+ });
3706
+ if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
3707
+ this.messageQueue.push(data);
3708
+ }
3709
+ else {
3710
+ this.dataChannel.send(data);
3711
+ }
3712
+ }
3713
+ else if (!this.closed) {
3714
+ this.messageQueue.push(data);
3715
+ }
3716
+ }
3717
+ setConnectionId(connectionId) {
3718
+ const oldId = this.connectionId;
3719
+ this.connectionId = connectionId;
3720
+ if (this.bridge && oldId !== connectionId) {
3721
+ this.bridge.renameConnection(oldId, connectionId).catch(() => { });
3722
+ }
3723
+ }
3724
+ // ── DataChannel handling (runs entirely in the worker) ──────
3725
+ setupDataChannel(dataChannel) {
3726
+ this.dataChannel = dataChannel;
3727
+ this.dataChannel.binaryType = 'arraybuffer';
3728
+ this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3729
+ dataChannel.onopen = () => {
3730
+ logger$u.trace('dc.onOpen (worker)');
3731
+ this.onDataChannelOpen();
3732
+ };
3733
+ dataChannel.onclose = () => {
3734
+ logger$u.trace('dc.onClosed (worker)');
3735
+ this.doClose(false, 'dataChannel.onclose');
3736
+ };
3737
+ dataChannel.onerror = (err) => {
3738
+ logger$u.warn('Data channel error (worker)', { err });
3739
+ };
3740
+ dataChannel.onmessage = (msg) => {
3741
+ logger$u.trace('dc.onmessage (worker)');
3742
+ logGapDiagnosticSampled('dht.dc.onmessage');
3743
+ this.emit('data', new Uint8Array(msg.data));
3744
+ };
3745
+ dataChannel.onbufferedamountlow = () => {
3746
+ logger$u.trace('dc.onBufferedAmountLow (worker)');
3747
+ while (this.messageQueue.length > 0 &&
3748
+ this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3749
+ const data = this.messageQueue.shift();
3750
+ this.dataChannel.send(data);
3751
+ }
3752
+ };
3753
+ }
3754
+ onDataChannelOpen() {
3755
+ this.connected = true;
3756
+ this.flushMessageQueue();
3757
+ this.emit('connected');
3758
+ }
3759
+ flushMessageQueue() {
3760
+ while (this.messageQueue.length > 0 &&
3761
+ this.dataChannel &&
3762
+ this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3763
+ const data = this.messageQueue.shift();
3764
+ this.dataChannel.send(data);
3765
+ }
3766
+ }
3767
+ // ── Teardown ────────────────────────────────────────────────
3768
+ doClose(gracefulLeave, reason) {
3769
+ if (!this.closed) {
3770
+ this.closed = true;
3771
+ this.connected = false;
3772
+ this.messageQueue.length = 0;
3773
+ clearTimeout(this.earlyTimeout);
3774
+ this.stopListening();
3775
+ this.emit('disconnected', gracefulLeave, undefined, reason);
3776
+ this.removeAllListeners();
3777
+ if (this.dataChannel !== undefined) {
3778
+ try {
3779
+ this.dataChannel.close();
3780
+ }
3781
+ catch (err) {
3782
+ logger$u.warn('Failed to close data channel (worker)', { err });
3783
+ }
3784
+ }
3785
+ this.dataChannel = undefined;
3786
+ // Tell the main-thread bridge to tear down the RTCPeerConnection.
3787
+ // Fire-and-forget — we don't block on this.
3788
+ this.bridge
3789
+ ?.close(this.connectionId)
3790
+ .catch(() => {
3791
+ // intentionally swallowed
3792
+ });
3793
+ }
3794
+ }
3795
+ stopListening() {
3796
+ if (this.dataChannel !== undefined) {
3797
+ this.dataChannel.onopen = null;
3798
+ this.dataChannel.onclose = null;
3799
+ this.dataChannel.onerror = null;
3800
+ this.dataChannel.onbufferedamountlow = null;
3801
+ this.dataChannel.onmessage = null;
3802
+ }
3803
+ }
3804
+ }
3805
+
3806
+ /**
3807
+ * Conditional re-export of the browser WebrtcConnection.
3808
+ *
3809
+ * At module-load time we detect whether we are running inside a Web Worker.
3810
+ * - **Main thread** → use {@link DirectWebrtcConnection} which owns the
3811
+ * `RTCPeerConnection` and `RTCDataChannel` directly.
3812
+ * - **Worker thread** → use {@link WorkerWebrtcConnection} which delegates
3813
+ * `RTCPeerConnection` signaling to the main thread via a Comlink bridge
3814
+ * and receives a transferred `RTCDataChannel` that lives entirely in the
3815
+ * worker.
3816
+ *
3817
+ * Both classes implement `IWebrtcConnection & IConnection` and expose the
3818
+ * same public API, so upstream code (WebrtcConnector, etc.) is unaffected.
3819
+ */
3820
+ // The constructor — points to the right class based on the runtime
3821
+ // environment. The type assertion is safe because both implementations
3822
+ // share the same public interface surface.
3823
+ const WebrtcConnection = (isWorkerEnvironment
3824
+ ? WorkerWebrtcConnection
3825
+ : DirectWebrtcConnection);
3826
+
3329
3827
  const logger$t = new Logger('WebrtcConnectorRpcRemote');
3330
3828
  class WebrtcConnectorRpcRemote extends RpcRemote {
3331
3829
  requestConnection() {
@@ -5511,7 +6009,7 @@ class PeerDiscovery {
5511
6009
  logger$d.debug(`Ring join on ${this.options.serviceId} timed out`);
5512
6010
  }
5513
6011
  finally {
5514
- sessions.forEach((session) => this.ongoingDiscoverySessions.delete(session.id));
6012
+ sessions.forEach((session) => this.ongoingRingDiscoverySessions.delete(session.id));
5515
6013
  }
5516
6014
  }
5517
6015
  async rejoinDht(entryPoint, contactedPeers = new Set(), distantJoinContactPeers = new Set()) {
@@ -7608,5 +8106,5 @@ class SimulatorTransport extends ConnectionManager {
7608
8106
  }
7609
8107
  }
7610
8108
 
7611
- export { ConnectionManager, ConnectionType, DataEntry, DefaultConnectorFacade, DhtCallContext, DhtNode, EXISTING_CONNECTION_TIMEOUT, LatencyType, ListeningRpcCommunicator, ManagedConnection, Message, NodeType, PeerDescriptor, PendingConnection, RoutingRpcCommunicator, RpcRemote, Simulator, SimulatorTransport, WebsocketClientConnection, areEqualPeerDescriptors, createOutgoingHandshaker, getRandomRegion, getRegionDelayMatrix, randomDhtAddress, toDhtAddress, toDhtAddressRaw, toNodeId };
8109
+ export { ConnectionManager, ConnectionType, DataEntry, DefaultConnectorFacade, DhtCallContext, DhtNode, EXISTING_CONNECTION_TIMEOUT, LatencyType, ListeningRpcCommunicator, ManagedConnection, Message, NodeType, PeerDescriptor, PendingConnection, RoutingRpcCommunicator, RpcRemote, Simulator, SimulatorTransport, WebsocketClientConnection, areEqualPeerDescriptors, createOutgoingHandshaker, getRandomRegion, getRegionDelayMatrix, installWebrtcBridge, logGapDiagnosticSampled, randomDhtAddress, setGapDiagnosticsEnabled, toDhtAddress, toDhtAddressRaw, toNodeId };
7612
8110
  //# sourceMappingURL=exports-browser.js.map