@streamr/dht 103.3.1 → 103.6.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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';
@@ -1798,7 +1799,7 @@ const createRandomConnectionId = () => {
1798
1799
  return v4();
1799
1800
  };
1800
1801
 
1801
- const logger$C = new Logger('ManagedConnection');
1802
+ const logger$D = new Logger('ManagedConnection');
1802
1803
  // ManagedConnection is a component used as a wrapper for IConnection after they have been successfully handshaked.
1803
1804
  // Should only be used in the ConnectionManager.
1804
1805
  class ManagedConnection extends EventEmitter {
@@ -1828,7 +1829,7 @@ class ManagedConnection extends EventEmitter {
1828
1829
  this.remotePeerDescriptor = peerDescriptor;
1829
1830
  }
1830
1831
  onDisconnected(gracefulLeave) {
1831
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1832
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1832
1833
  if (!this.replacedAsDuplicate) {
1833
1834
  this.emit('disconnected', gracefulLeave);
1834
1835
  }
@@ -1837,7 +1838,7 @@ class ManagedConnection extends EventEmitter {
1837
1838
  // TODO: Can this be removed if ManagedConnections can never be duplicates?
1838
1839
  // Handle duplicates in the ConncetorFacade and no longer have PendingConnections in ConnectionManager
1839
1840
  replaceAsDuplicate() {
1840
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1841
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1841
1842
  this.replacedAsDuplicate = true;
1842
1843
  }
1843
1844
  send(data) {
@@ -1984,10 +1985,10 @@ class RpcRemote {
1984
1985
  }
1985
1986
  }
1986
1987
 
1987
- const logger$B = new Logger('ConnectionLockRpcRemote');
1988
+ const logger$C = new Logger('ConnectionLockRpcRemote');
1988
1989
  class ConnectionLockRpcRemote extends RpcRemote {
1989
1990
  async lockRequest(lockId) {
1990
- logger$B.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
1991
+ logger$C.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
1991
1992
  const request = {
1992
1993
  lockId
1993
1994
  };
@@ -1997,12 +1998,12 @@ class ConnectionLockRpcRemote extends RpcRemote {
1997
1998
  return res.accepted;
1998
1999
  }
1999
2000
  catch (err) {
2000
- logger$B.debug('Connection lock rejected', { err });
2001
+ logger$C.debug('Connection lock rejected', { err });
2001
2002
  return false;
2002
2003
  }
2003
2004
  }
2004
2005
  unlockRequest(lockId) {
2005
- logger$B.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
2006
+ logger$C.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
2006
2007
  const request = {
2007
2008
  lockId
2008
2009
  };
@@ -2010,11 +2011,11 @@ class ConnectionLockRpcRemote extends RpcRemote {
2010
2011
  notification: true
2011
2012
  });
2012
2013
  this.getClient().unlockRequest(request, options).catch((_e) => {
2013
- logger$B.trace('failed to send unlockRequest');
2014
+ logger$C.trace('failed to send unlockRequest');
2014
2015
  });
2015
2016
  }
2016
2017
  async gracefulDisconnect(disconnectMode) {
2017
- logger$B.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2018
+ logger$C.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2018
2019
  const request = {
2019
2020
  disconnectMode
2020
2021
  };
@@ -2026,7 +2027,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2026
2027
  await this.getClient().gracefulDisconnect(request, options);
2027
2028
  }
2028
2029
  async setPrivate(isPrivate) {
2029
- logger$B.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2030
+ logger$C.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2030
2031
  const request = {
2031
2032
  isPrivate
2032
2033
  };
@@ -2038,7 +2039,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2038
2039
  }
2039
2040
  }
2040
2041
 
2041
- const logger$A = new Logger('ConnectionLockRpcLocal');
2042
+ const logger$B = new Logger('ConnectionLockRpcLocal');
2042
2043
  class ConnectionLockRpcLocal {
2043
2044
  options;
2044
2045
  constructor(options) {
@@ -2067,7 +2068,7 @@ class ConnectionLockRpcLocal {
2067
2068
  }
2068
2069
  async gracefulDisconnect(disconnectNotice, context) {
2069
2070
  const senderPeerDescriptor = context.incomingSourceDescriptor;
2070
- logger$A.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2071
+ logger$B.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2071
2072
  if (disconnectNotice.disconnectMode === DisconnectMode.LEAVING) {
2072
2073
  await this.options.closeConnection(senderPeerDescriptor, true, 'graceful leave notified');
2073
2074
  }
@@ -2118,7 +2119,7 @@ var NatType;
2118
2119
  NatType["OPEN_INTERNET"] = "open_internet";
2119
2120
  NatType["UNKNOWN"] = "unknown";
2120
2121
  })(NatType || (NatType = {}));
2121
- const logger$z = new Logger('ConnectionManager');
2122
+ const logger$A = new Logger('ConnectionManager');
2122
2123
  var ConnectionManagerState;
2123
2124
  (function (ConnectionManagerState) {
2124
2125
  ConnectionManagerState["IDLE"] = "idle";
@@ -2168,7 +2169,7 @@ class ConnectionManager extends EventEmitter {
2168
2169
  getLocalPeerDescriptor: () => this.getLocalPeerDescriptor(),
2169
2170
  setPrivate: (id, isPrivate) => {
2170
2171
  if (!this.options.allowIncomingPrivateConnections) {
2171
- logger$z.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
2172
+ logger$A.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
2172
2173
  return;
2173
2174
  }
2174
2175
  if (isPrivate) {
@@ -2202,7 +2203,7 @@ class ConnectionManager extends EventEmitter {
2202
2203
  const connection = endpoint.connection;
2203
2204
  const nodeId = connection.getNodeId();
2204
2205
  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()));
2206
+ logger$A.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2206
2207
  disconnectionCandidates.addContact(connection);
2207
2208
  }
2208
2209
  }
@@ -2210,7 +2211,7 @@ class ConnectionManager extends EventEmitter {
2210
2211
  const disconnectables = disconnectionCandidates.getFurthestContacts(this.endpoints.size - maxConnections);
2211
2212
  for (const disconnectable of disconnectables) {
2212
2213
  const peerDescriptor = disconnectable.getPeerDescriptor();
2213
- logger$z.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2214
+ logger$A.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2214
2215
  this.gracefullyDisconnectAsync(peerDescriptor, DisconnectMode.NORMAL).catch((_e) => { });
2215
2216
  }
2216
2217
  }
@@ -2219,11 +2220,11 @@ class ConnectionManager extends EventEmitter {
2219
2220
  throw new CouldNotStart(`Cannot start already ${this.state} module`);
2220
2221
  }
2221
2222
  this.state = ConnectionManagerState.RUNNING;
2222
- logger$z.trace(`Starting ConnectionManager...`);
2223
+ logger$A.trace(`Starting ConnectionManager...`);
2223
2224
  await this.connectorFacade.start((connection) => this.onNewConnection(connection), (nodeId) => this.hasConnection(nodeId), this);
2224
2225
  // Garbage collection of connections
2225
2226
  this.disconnectorIntervalRef = setInterval(() => {
2226
- logger$z.trace('disconnectorInterval');
2227
+ logger$A.trace('disconnectorInterval');
2227
2228
  const LAST_USED_LIMIT = 20000;
2228
2229
  this.garbageCollectConnections(this.options.maxConnections ?? 80, LAST_USED_LIMIT);
2229
2230
  }, 5000); // TODO use options option or named constant?
@@ -2233,7 +2234,7 @@ class ConnectionManager extends EventEmitter {
2233
2234
  return;
2234
2235
  }
2235
2236
  this.state = ConnectionManagerState.STOPPING;
2236
- logger$z.trace(`Stopping ConnectionManager`);
2237
+ logger$A.trace(`Stopping ConnectionManager`);
2237
2238
  if (this.disconnectorIntervalRef) {
2238
2239
  clearInterval(this.disconnectorIntervalRef);
2239
2240
  }
@@ -2243,23 +2244,23 @@ class ConnectionManager extends EventEmitter {
2243
2244
  await this.gracefullyDisconnectAsync(endpoint.connection.getPeerDescriptor(), DisconnectMode.LEAVING);
2244
2245
  }
2245
2246
  catch (e) {
2246
- logger$z.error(e);
2247
+ logger$A.error(e);
2247
2248
  }
2248
2249
  }
2249
2250
  else {
2250
2251
  const connection = endpoint.connection;
2251
- logger$z.trace('handshake of connection not completed, force-closing');
2252
+ logger$A.trace('handshake of connection not completed, force-closing');
2252
2253
  // TODO use options option or named constant?
2253
2254
  const eventReceived = waitForEvent(connection, 'disconnected', 2000);
2254
2255
  // TODO should we have some handling for this floating promise?
2255
2256
  connection.close(true);
2256
2257
  try {
2257
2258
  await eventReceived;
2258
- logger$z.trace('resolving after receiving disconnected event from non-handshaked connection');
2259
+ logger$A.trace('resolving after receiving disconnected event from non-handshaked connection');
2259
2260
  }
2260
2261
  catch (e) {
2261
2262
  endpoint.buffer.reject();
2262
- logger$z.trace('force-closing non-handshaked connection timed out ' + e);
2263
+ logger$A.trace('force-closing non-handshaked connection timed out ' + e);
2263
2264
  }
2264
2265
  }
2265
2266
  }));
@@ -2288,7 +2289,7 @@ class ConnectionManager extends EventEmitter {
2288
2289
  throw new CannotConnectToSelf('Cannot send to self');
2289
2290
  }
2290
2291
  const nodeId = toNodeId(peerDescriptor);
2291
- logger$z.trace(`Sending message to: ${nodeId}`);
2292
+ logger$A.trace(`Sending message to: ${nodeId}`);
2292
2293
  message = {
2293
2294
  ...message,
2294
2295
  sourceDescriptor: this.getLocalPeerDescriptor()
@@ -2343,13 +2344,13 @@ class ConnectionManager extends EventEmitter {
2343
2344
  }
2344
2345
  handleMessage(message) {
2345
2346
  const messageType = message.body.oneofKind;
2346
- logger$z.trace('Received message of type ' + messageType);
2347
+ logger$A.trace('Received message of type ' + messageType);
2347
2348
  if (messageType !== 'rpcMessage') {
2348
- logger$z.trace('Filtered out non-RPC message of type ' + messageType);
2349
+ logger$A.trace('Filtered out non-RPC message of type ' + messageType);
2349
2350
  return;
2350
2351
  }
2351
2352
  if (this.duplicateMessageDetector.isMostLikelyDuplicate(message.messageId)) {
2352
- logger$z.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2353
+ logger$A.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2353
2354
  + ' ' + message.serviceId + ' ' + message.messageId);
2354
2355
  return;
2355
2356
  }
@@ -2358,7 +2359,7 @@ class ConnectionManager extends EventEmitter {
2358
2359
  this.rpcCommunicator?.handleMessageFromPeer(message);
2359
2360
  }
2360
2361
  else {
2361
- logger$z.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2362
+ logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2362
2363
  + ' ' + message.serviceId + ' ' + message.messageId);
2363
2364
  this.emit('message', message);
2364
2365
  }
@@ -2375,7 +2376,7 @@ class ConnectionManager extends EventEmitter {
2375
2376
  }
2376
2377
  catch (e) {
2377
2378
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2378
- logger$z.debug(`Parsing incoming data into Message failed: ${e}`);
2379
+ logger$A.debug(`Parsing incoming data into Message failed: ${e}`);
2379
2380
  return;
2380
2381
  }
2381
2382
  message.sourceDescriptor = peerDescriptor;
@@ -2384,7 +2385,7 @@ class ConnectionManager extends EventEmitter {
2384
2385
  }
2385
2386
  catch (e) {
2386
2387
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2387
- logger$z.debug(`Handling incoming data failed: ${e}`);
2388
+ logger$A.debug(`Handling incoming data failed: ${e}`);
2388
2389
  }
2389
2390
  }
2390
2391
  onConnected(peerDescriptor, connection) {
@@ -2397,7 +2398,7 @@ class ConnectionManager extends EventEmitter {
2397
2398
  const pendingConnection = endpoint.connection;
2398
2399
  const buffer = outputBuffer.getBuffer();
2399
2400
  while (buffer.length > 0) {
2400
- logger$z.trace('emptying buffer');
2401
+ logger$A.trace('emptying buffer');
2401
2402
  managedConnection.send(buffer.shift());
2402
2403
  }
2403
2404
  outputBuffer.resolve();
@@ -2414,7 +2415,7 @@ class ConnectionManager extends EventEmitter {
2414
2415
  }
2415
2416
  onDisconnected(peerDescriptor, gracefulLeave) {
2416
2417
  const nodeId = toNodeId(peerDescriptor);
2417
- logger$z.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2418
+ logger$A.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2418
2419
  const endpoint = this.endpoints.get(nodeId);
2419
2420
  if (endpoint) {
2420
2421
  this.locks.clearAllLocks(nodeId);
@@ -2422,7 +2423,7 @@ class ConnectionManager extends EventEmitter {
2422
2423
  endpoint.buffer.reject();
2423
2424
  }
2424
2425
  this.endpoints.delete(nodeId);
2425
- logger$z.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2426
+ logger$A.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2426
2427
  this.emit('disconnected', peerDescriptor, gracefulLeave);
2427
2428
  this.onConnectionCountChange();
2428
2429
  }
@@ -2431,7 +2432,7 @@ class ConnectionManager extends EventEmitter {
2431
2432
  if (this.state === ConnectionManagerState.STOPPED) {
2432
2433
  return false;
2433
2434
  }
2434
- logger$z.trace('onNewConnection()');
2435
+ logger$A.trace('onNewConnection()');
2435
2436
  if (!this.acceptNewConnection(connection)) {
2436
2437
  return false;
2437
2438
  }
@@ -2441,7 +2442,7 @@ class ConnectionManager extends EventEmitter {
2441
2442
  }
2442
2443
  acceptNewConnection(newConnection) {
2443
2444
  const nodeId = toNodeId(newConnection.getPeerDescriptor());
2444
- logger$z.trace(nodeId + ' acceptNewConnection()');
2445
+ logger$A.trace(nodeId + ' acceptNewConnection()');
2445
2446
  if (this.endpoints.has(nodeId)) {
2446
2447
  if (getOfferer(toNodeId(this.getLocalPeerDescriptor()), nodeId) === 'remote') {
2447
2448
  let buffer;
@@ -2450,14 +2451,14 @@ class ConnectionManager extends EventEmitter {
2450
2451
  // Could be related to WS client connections not realizing that they have been disconnected.
2451
2452
  // Makes refactoring duplicate connection handling to the connectors very difficult.
2452
2453
  if (this.endpoints.get(nodeId).connected) {
2453
- logger$z.debug('replacing connected connection', { nodeId });
2454
+ logger$A.debug('replacing connected connection', { nodeId });
2454
2455
  buffer = new OutputBuffer();
2455
2456
  }
2456
2457
  else {
2457
2458
  buffer = endpoint.buffer;
2458
2459
  }
2459
2460
  const oldConnection = endpoint.connection;
2460
- logger$z.trace('replaced: ' + nodeId);
2461
+ logger$A.trace('replaced: ' + nodeId);
2461
2462
  oldConnection.replaceAsDuplicate();
2462
2463
  this.endpoints.set(nodeId, { connected: false, connection: newConnection, buffer: buffer });
2463
2464
  return true;
@@ -2466,7 +2467,7 @@ class ConnectionManager extends EventEmitter {
2466
2467
  return false;
2467
2468
  }
2468
2469
  }
2469
- logger$z.trace(nodeId + ' added to connections at acceptNewConnection');
2470
+ logger$A.trace(nodeId + ' added to connections at acceptNewConnection');
2470
2471
  this.endpoints.set(nodeId, {
2471
2472
  connected: false,
2472
2473
  buffer: new OutputBuffer(),
@@ -2476,14 +2477,14 @@ class ConnectionManager extends EventEmitter {
2476
2477
  }
2477
2478
  async closeConnection(peerDescriptor, gracefulLeave, reason) {
2478
2479
  const nodeId = toNodeId(peerDescriptor);
2479
- logger$z.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2480
+ logger$A.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2480
2481
  this.locks.clearAllLocks(nodeId);
2481
2482
  if (this.endpoints.has(nodeId)) {
2482
2483
  const connectionToClose = this.endpoints.get(nodeId).connection;
2483
2484
  await connectionToClose.close(gracefulLeave);
2484
2485
  }
2485
2486
  else {
2486
- logger$z.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
2487
+ logger$A.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
2487
2488
  this.emit('disconnected', peerDescriptor, false);
2488
2489
  }
2489
2490
  }
@@ -2495,8 +2496,8 @@ class ConnectionManager extends EventEmitter {
2495
2496
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2496
2497
  this.locks.addLocalLocked(nodeId, lockId);
2497
2498
  rpcRemote.lockRequest(lockId)
2498
- .then((_accepted) => logger$z.trace('LockRequest successful'))
2499
- .catch((err) => { logger$z.debug(err); });
2499
+ .then((_accepted) => logger$A.trace('LockRequest successful'))
2500
+ .catch((err) => { logger$A.debug(err); });
2500
2501
  }
2501
2502
  unlockConnection(targetDescriptor, lockId) {
2502
2503
  if (this.state === ConnectionManagerState.STOPPED || areEqualPeerDescriptors(targetDescriptor, this.getLocalPeerDescriptor())) {
@@ -2548,7 +2549,7 @@ class ConnectionManager extends EventEmitter {
2548
2549
  async gracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2549
2550
  const endpoint = this.endpoints.get(toNodeId(targetDescriptor));
2550
2551
  if (!endpoint) {
2551
- logger$z.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2552
+ logger$A.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2552
2553
  return;
2553
2554
  }
2554
2555
  if (endpoint.connected) {
@@ -2557,15 +2558,15 @@ class ConnectionManager extends EventEmitter {
2557
2558
  // TODO use options option or named constant?
2558
2559
  // eslint-disable-next-line promise/catch-or-return
2559
2560
  waitForEvent(connection, 'disconnected', 2000).then(() => {
2560
- logger$z.trace('disconnected event received in gracefullyDisconnectAsync()');
2561
+ logger$A.trace('disconnected event received in gracefullyDisconnectAsync()');
2561
2562
  })
2562
2563
  .catch((e) => {
2563
- logger$z.trace('force-closing connection after timeout ' + e);
2564
+ logger$A.trace('force-closing connection after timeout ' + e);
2564
2565
  // TODO should we have some handling for this floating promise?
2565
2566
  connection.close(true);
2566
2567
  })
2567
2568
  .finally(() => {
2568
- logger$z.trace('resolving after receiving disconnected event');
2569
+ logger$A.trace('resolving after receiving disconnected event');
2569
2570
  resolve();
2570
2571
  });
2571
2572
  });
@@ -2580,13 +2581,13 @@ class ConnectionManager extends EventEmitter {
2580
2581
  }
2581
2582
  async doGracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2582
2583
  const nodeId = toNodeId(targetDescriptor);
2583
- logger$z.trace(nodeId + ' gracefullyDisconnectAsync()');
2584
+ logger$A.trace(nodeId + ' gracefullyDisconnectAsync()');
2584
2585
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2585
2586
  try {
2586
2587
  await rpcRemote.gracefulDisconnect(disconnectMode);
2587
2588
  }
2588
2589
  catch (ex) {
2589
- logger$z.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2590
+ logger$A.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2590
2591
  }
2591
2592
  }
2592
2593
  getConnections() {
@@ -2653,7 +2654,7 @@ function protoToString(protoObj, objectType) {
2653
2654
  return ret;
2654
2655
  }
2655
2656
 
2656
- const logger$y = new Logger('SimulatorConnection');
2657
+ const logger$z = new Logger('SimulatorConnection');
2657
2658
  class SimulatorConnection extends Connection {
2658
2659
  stopped = false;
2659
2660
  localPeerDescriptor;
@@ -2675,46 +2676,46 @@ class SimulatorConnection extends Connection {
2675
2676
  this.doDisconnect = this.doDisconnect.bind(this);
2676
2677
  }
2677
2678
  send(data) {
2678
- logger$y.trace('send()');
2679
+ logger$z.trace('send()');
2679
2680
  if (!this.stopped) {
2680
2681
  this.simulator.send(this, data);
2681
2682
  }
2682
2683
  else {
2683
2684
  const localNodeId = toNodeId(this.localPeerDescriptor);
2684
2685
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2685
- logger$y.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
2686
+ logger$z.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
2686
2687
  }
2687
2688
  }
2688
2689
  async close(gracefulLeave) {
2689
2690
  const localNodeId = toNodeId(this.localPeerDescriptor);
2690
2691
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2691
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close()');
2692
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close()');
2692
2693
  if (!this.stopped) {
2693
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2694
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2694
2695
  this.stopped = true;
2695
2696
  try {
2696
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2697
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2697
2698
  this.simulator.close(this);
2698
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2699
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2699
2700
  }
2700
2701
  catch (e) {
2701
- logger$y.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2702
+ logger$z.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2702
2703
  }
2703
2704
  finally {
2704
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2705
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2705
2706
  this.doDisconnect(gracefulLeave);
2706
2707
  }
2707
2708
  }
2708
2709
  else {
2709
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
2710
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
2710
2711
  }
2711
2712
  }
2712
2713
  connect() {
2713
2714
  if (!this.stopped) {
2714
- logger$y.trace('connect() called');
2715
+ logger$z.trace('connect() called');
2715
2716
  this.simulator.connect(this, this.targetPeerDescriptor, (error) => {
2716
2717
  if (error !== undefined) {
2717
- logger$y.trace(error);
2718
+ logger$z.trace(error);
2718
2719
  this.doDisconnect(false);
2719
2720
  }
2720
2721
  else {
@@ -2723,46 +2724,46 @@ class SimulatorConnection extends Connection {
2723
2724
  });
2724
2725
  }
2725
2726
  else {
2726
- logger$y.trace('tried to connect() a stopped connection');
2727
+ logger$z.trace('tried to connect() a stopped connection');
2727
2728
  }
2728
2729
  }
2729
2730
  handleIncomingData(data) {
2730
2731
  if (!this.stopped) {
2731
- logger$y.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2732
+ logger$z.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2732
2733
  this.emit('data', data);
2733
2734
  }
2734
2735
  else {
2735
- logger$y.trace('tried to call handleIncomingData() a stopped connection');
2736
+ logger$z.trace('tried to call handleIncomingData() a stopped connection');
2736
2737
  }
2737
2738
  }
2738
2739
  handleIncomingDisconnection() {
2739
2740
  if (!this.stopped) {
2740
2741
  const localNodeId = toNodeId(this.localPeerDescriptor);
2741
- logger$y.trace(localNodeId + ' handleIncomingDisconnection()');
2742
+ logger$z.trace(localNodeId + ' handleIncomingDisconnection()');
2742
2743
  this.stopped = true;
2743
2744
  this.doDisconnect(false);
2744
2745
  }
2745
2746
  else {
2746
- logger$y.trace('tried to call handleIncomingDisconnection() a stopped connection');
2747
+ logger$z.trace('tried to call handleIncomingDisconnection() a stopped connection');
2747
2748
  }
2748
2749
  }
2749
2750
  destroy() {
2750
2751
  const localNodeId = toNodeId(this.localPeerDescriptor);
2751
2752
  if (!this.stopped) {
2752
- logger$y.trace(localNodeId + ' destroy()');
2753
+ logger$z.trace(localNodeId + ' destroy()');
2753
2754
  this.removeAllListeners();
2754
2755
  this.close(false).catch((_e) => { });
2755
2756
  }
2756
2757
  else {
2757
- logger$y.trace(localNodeId + ' tried to call destroy() a stopped connection');
2758
+ logger$z.trace(localNodeId + ' tried to call destroy() a stopped connection');
2758
2759
  }
2759
2760
  }
2760
2761
  doDisconnect(gracefulLeave) {
2761
2762
  const localNodeId = toNodeId(this.localPeerDescriptor);
2762
2763
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2763
- logger$y.trace(localNodeId + ' doDisconnect()');
2764
+ logger$z.trace(localNodeId + ' doDisconnect()');
2764
2765
  this.stopped = true;
2765
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2766
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2766
2767
  this.emit('disconnected', gracefulLeave);
2767
2768
  }
2768
2769
  }
@@ -2800,9 +2801,9 @@ const parseVersion = (version) => {
2800
2801
  }
2801
2802
  };
2802
2803
 
2803
- var version = "103.3.1";
2804
+ var version = "103.6.0-rc.0";
2804
2805
 
2805
- const logger$x = new Logger('Handshaker');
2806
+ const logger$y = new Logger('Handshaker');
2806
2807
  // Optimally the Outgoing and Incoming Handshakers could be their own separate classes
2807
2808
  // However, in cases where the PeerDescriptor of the other end of the connection can be known
2808
2809
  // only after a HandshakeRequest a base Handshaker class is needed as the IncomingHandshaker currently
@@ -2824,7 +2825,7 @@ const createOutgoingHandshaker = (localPeerDescriptor, pendingConnection, connec
2824
2825
  }
2825
2826
  };
2826
2827
  const handshakeCompletedListener = (peerDescriptor) => {
2827
- logger$x.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2828
+ logger$y.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2828
2829
  pendingConnection.onHandshakeCompleted(connection);
2829
2830
  stopHandshaker();
2830
2831
  };
@@ -2924,12 +2925,12 @@ class Handshaker extends EventEmitter {
2924
2925
  try {
2925
2926
  const message = Message.fromBinary(data);
2926
2927
  if (message.body.oneofKind === 'handshakeRequest') {
2927
- logger$x.trace('handshake request received');
2928
+ logger$y.trace('handshake request received');
2928
2929
  const handshake = message.body.handshakeRequest;
2929
2930
  this.emit('handshakeRequest', handshake.sourcePeerDescriptor, handshake.protocolVersion, handshake.targetPeerDescriptor);
2930
2931
  }
2931
2932
  if (message.body.oneofKind === 'handshakeResponse') {
2932
- logger$x.trace('handshake response received');
2933
+ logger$y.trace('handshake response received');
2933
2934
  const handshake = message.body.handshakeResponse;
2934
2935
  const error = !isMaybeSupportedProtocolVersion(handshake.protocolVersion)
2935
2936
  ? HandshakeError.UNSUPPORTED_PROTOCOL_VERSION : handshake.error;
@@ -2942,18 +2943,18 @@ class Handshaker extends EventEmitter {
2942
2943
  }
2943
2944
  }
2944
2945
  catch (err) {
2945
- logger$x.debug('error while parsing handshake message', err);
2946
+ logger$y.debug('error while parsing handshake message', err);
2946
2947
  }
2947
2948
  }
2948
2949
  sendHandshakeRequest(remotePeerDescriptor) {
2949
2950
  const msg = createHandshakeRequest(this.localPeerDescriptor, remotePeerDescriptor);
2950
2951
  this.connection.send(Message.toBinary(msg));
2951
- logger$x.trace('handshake request sent');
2952
+ logger$y.trace('handshake request sent');
2952
2953
  }
2953
2954
  sendHandshakeResponse(error) {
2954
2955
  const msg = createHandshakeResponse(this.localPeerDescriptor, error);
2955
2956
  this.connection.send(Message.toBinary(msg));
2956
- logger$x.trace('handshake response sent');
2957
+ logger$y.trace('handshake response sent');
2957
2958
  }
2958
2959
  stop() {
2959
2960
  this.connection.off('data', this.onDataListener);
@@ -2961,7 +2962,7 @@ class Handshaker extends EventEmitter {
2961
2962
  }
2962
2963
  }
2963
2964
 
2964
- const logger$w = new Logger('PendingConnection');
2965
+ const logger$x = new Logger('PendingConnection');
2965
2966
  // PendingConnection is used as a reference to a connection that should be opened and handshaked to a given PeerDescriptor
2966
2967
  // It does not hold a connection internally. The public method onHandshakedCompleted should be called once a connection for the
2967
2968
  // remotePeerDescriptor is opened and handshaked successfully.
@@ -2979,7 +2980,7 @@ class PendingConnection extends EventEmitter {
2979
2980
  }, timeout, this.connectingAbortController.signal);
2980
2981
  }
2981
2982
  replaceAsDuplicate() {
2982
- logger$w.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
2983
+ logger$x.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
2983
2984
  this.replacedAsDuplicate = true;
2984
2985
  }
2985
2986
  onHandshakeCompleted(connection) {
@@ -3010,7 +3011,7 @@ class PendingConnection extends EventEmitter {
3010
3011
  }
3011
3012
  }
3012
3013
 
3013
- const logger$v = new Logger('SimulatorConnector');
3014
+ const logger$w = new Logger('SimulatorConnector');
3014
3015
  class SimulatorConnector {
3015
3016
  connectingConnections = new Map();
3016
3017
  stopped = false;
@@ -3023,7 +3024,7 @@ class SimulatorConnector {
3023
3024
  this.onNewConnection = onNewConnection;
3024
3025
  }
3025
3026
  connect(targetPeerDescriptor) {
3026
- logger$v.trace('connect() ' + toNodeId(targetPeerDescriptor));
3027
+ logger$w.trace('connect() ' + toNodeId(targetPeerDescriptor));
3027
3028
  const nodeId = toNodeId(targetPeerDescriptor);
3028
3029
  const existingConnection = this.connectingConnections.get(nodeId);
3029
3030
  if (existingConnection) {
@@ -3052,18 +3053,18 @@ class SimulatorConnector {
3052
3053
  // connection is incoming, so remotePeerDescriptor is localPeerDescriptor
3053
3054
  const remotePeerDescriptor = sourceConnection.localPeerDescriptor;
3054
3055
  const remoteNodeId = toNodeId(sourceConnection.localPeerDescriptor);
3055
- logger$v.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3056
+ logger$w.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3056
3057
  if (this.stopped) {
3057
3058
  return;
3058
3059
  }
3059
3060
  const connection = new SimulatorConnection(this.localPeerDescriptor, remotePeerDescriptor, ConnectionType.SIMULATOR_SERVER, this.simulator);
3060
3061
  const pendingConnection = new PendingConnection(remotePeerDescriptor);
3061
3062
  const handshaker = createIncomingHandshaker(this.localPeerDescriptor, pendingConnection, connection);
3062
- logger$v.trace('connected');
3063
+ logger$w.trace('connected');
3063
3064
  handshaker.once('handshakeRequest', () => {
3064
- logger$v.trace(remoteNodeId + ' incoming handshake request');
3065
+ logger$w.trace(remoteNodeId + ' incoming handshake request');
3065
3066
  if (this.onNewConnection(pendingConnection)) {
3066
- logger$v.trace(remoteNodeId + ' calling acceptHandshake');
3067
+ logger$w.trace(remoteNodeId + ' calling acceptHandshake');
3067
3068
  acceptHandshake(handshaker, pendingConnection, connection);
3068
3069
  }
3069
3070
  else {
@@ -3103,6 +3104,8 @@ class ListeningRpcCommunicator extends RoutingRpcCommunicator {
3103
3104
  }
3104
3105
  }
3105
3106
 
3107
+ const isWorkerEnvironment = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
3108
+
3106
3109
  var RtcDescription;
3107
3110
  (function (RtcDescription) {
3108
3111
  RtcDescription["OFFER"] = "offer";
@@ -3117,8 +3120,8 @@ var DisconnectedRtcPeerConnectionStateEnum;
3117
3120
  DisconnectedRtcPeerConnectionStateEnum["FAILED"] = "failed";
3118
3121
  DisconnectedRtcPeerConnectionStateEnum["CLOSED"] = "closed";
3119
3122
  })(DisconnectedRtcPeerConnectionStateEnum || (DisconnectedRtcPeerConnectionStateEnum = {}));
3120
- const logger$u = new Logger('WebrtcConnection (browser)');
3121
- class WebrtcConnection extends EventEmitter {
3123
+ const logger$v = new Logger('DirectWebrtcConnection (browser)');
3124
+ class DirectWebrtcConnection extends EventEmitter {
3122
3125
  connectionId;
3123
3126
  connectionType = ConnectionType.WEBRTC;
3124
3127
  // We need to keep track of connection state ourselves because
@@ -3156,7 +3159,7 @@ class WebrtcConnection extends EventEmitter {
3156
3159
  }
3157
3160
  };
3158
3161
  this.peerConnection.onicegatheringstatechange = () => {
3159
- logger$u.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3162
+ logger$v.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3160
3163
  };
3161
3164
  this.peerConnection.onconnectionstatechange = () => this.onStateChange();
3162
3165
  if (isOffering) {
@@ -3167,7 +3170,7 @@ class WebrtcConnection extends EventEmitter {
3167
3170
  await this.peerConnection.setLocalDescription();
3168
3171
  }
3169
3172
  catch (err) {
3170
- logger$u.warn('Failed to set local description', { err });
3173
+ logger$v.warn('Failed to set local description', { err });
3171
3174
  }
3172
3175
  if (this.peerConnection.localDescription !== null) {
3173
3176
  this.emit('localDescription', this.peerConnection.localDescription?.sdp, this.peerConnection.localDescription?.type);
@@ -3195,14 +3198,14 @@ class WebrtcConnection extends EventEmitter {
3195
3198
  clearTimeout(this.earlyTimeout);
3196
3199
  }
3197
3200
  catch (err) {
3198
- logger$u.warn('Failed to set remote description', { err });
3201
+ logger$v.warn('Failed to set remote description', { err });
3199
3202
  }
3200
3203
  if ((type.toLowerCase() === RtcDescription.OFFER) && (this.peerConnection !== undefined)) {
3201
3204
  try {
3202
3205
  await this.peerConnection.setLocalDescription();
3203
3206
  }
3204
3207
  catch (err) {
3205
- logger$u.warn('Failed to set local description', { err });
3208
+ logger$v.warn('Failed to set local description', { err });
3206
3209
  }
3207
3210
  if (this.peerConnection.localDescription !== null) {
3208
3211
  this.emit('localDescription', this.peerConnection.localDescription.sdp, this.peerConnection.localDescription.type);
@@ -3212,7 +3215,7 @@ class WebrtcConnection extends EventEmitter {
3212
3215
  addRemoteCandidate(candidate, mid) {
3213
3216
  this.peerConnection?.addIceCandidate({ candidate: candidate, sdpMid: mid })
3214
3217
  .catch((err) => {
3215
- logger$u.warn('Failed to add ICE candidate', { err });
3218
+ logger$v.warn('Failed to add ICE candidate', { err });
3216
3219
  });
3217
3220
  }
3218
3221
  isOpen() {
@@ -3235,7 +3238,7 @@ class WebrtcConnection extends EventEmitter {
3235
3238
  this.dataChannel.close();
3236
3239
  }
3237
3240
  catch (err) {
3238
- logger$u.warn('Failed to close data channel', { err });
3241
+ logger$v.warn('Failed to close data channel', { err });
3239
3242
  }
3240
3243
  }
3241
3244
  this.dataChannel = undefined;
@@ -3244,7 +3247,7 @@ class WebrtcConnection extends EventEmitter {
3244
3247
  this.peerConnection.close();
3245
3248
  }
3246
3249
  catch (err) {
3247
- logger$u.warn('Failed to close connection', { err });
3250
+ logger$v.warn('Failed to close connection', { err });
3248
3251
  }
3249
3252
  }
3250
3253
  this.peerConnection = undefined;
@@ -3264,7 +3267,7 @@ class WebrtcConnection extends EventEmitter {
3264
3267
  }
3265
3268
  }
3266
3269
  else {
3267
- logger$u.warn('Tried to send on a connection with last state ' + this.lastState);
3270
+ logger$v.warn('Tried to send on a connection with last state ' + this.lastState);
3268
3271
  }
3269
3272
  }
3270
3273
  setupDataChannel(dataChannel) {
@@ -3272,22 +3275,22 @@ class WebrtcConnection extends EventEmitter {
3272
3275
  this.dataChannel.binaryType = 'arraybuffer';
3273
3276
  this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3274
3277
  dataChannel.onopen = () => {
3275
- logger$u.trace('dc.onOpen');
3278
+ logger$v.trace('dc.onOpen');
3276
3279
  this.onDataChannelOpen();
3277
3280
  };
3278
3281
  dataChannel.onclose = () => {
3279
- logger$u.trace('dc.onClosed');
3282
+ logger$v.trace('dc.onClosed');
3280
3283
  this.doClose(false);
3281
3284
  };
3282
3285
  dataChannel.onerror = (err) => {
3283
- logger$u.warn('Data channel error', { err });
3286
+ logger$v.warn('Data channel error', { err });
3284
3287
  };
3285
3288
  dataChannel.onmessage = (msg) => {
3286
- logger$u.trace('dc.onmessage');
3289
+ logger$v.trace('dc.onmessage');
3287
3290
  this.emit('data', new Uint8Array(msg.data));
3288
3291
  };
3289
3292
  dataChannel.onbufferedamountlow = () => {
3290
- logger$u.trace('dc.onBufferedAmountLow');
3293
+ logger$v.trace('dc.onBufferedAmountLow');
3291
3294
  while (this.messageQueue.length > 0 && this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3292
3295
  const data = this.messageQueue.shift();
3293
3296
  this.dataChannel.send(data);
@@ -3326,6 +3329,431 @@ class WebrtcConnection extends EventEmitter {
3326
3329
  }
3327
3330
  }
3328
3331
 
3332
+ /**
3333
+ * WebrtcBridge — runs on the MAIN THREAD.
3334
+ *
3335
+ * Manages RTCPeerConnection instances on behalf of a worker that cannot
3336
+ * access them directly. Each logical connection is identified by a
3337
+ * `connectionId` string supplied by the worker.
3338
+ *
3339
+ * Signaling (ICE candidates, SDP offers/answers, connection-state changes)
3340
+ * is relayed to the worker through Comlink proxy callbacks.
3341
+ *
3342
+ * Once a DataChannel is created (offerer) or received (answerer) it is
3343
+ * **transferred** to the worker so that all data-path events fire inside
3344
+ * the worker's event loop — the main thread never touches data traffic.
3345
+ */
3346
+ // ── Bridge implementation ───────────────────────────────────────────
3347
+ class WebrtcBridge {
3348
+ connections = new Map();
3349
+ async start(connectionId, iceServers, isOffering, callbacks) {
3350
+ const pc = new RTCPeerConnection({ iceServers });
3351
+ const conn = {
3352
+ pc,
3353
+ callbacks,
3354
+ isOffering,
3355
+ makingOffer: false,
3356
+ };
3357
+ this.connections.set(connectionId, conn);
3358
+ // ── ICE candidates ──────────────────────────────────────
3359
+ pc.onicecandidate = (event) => {
3360
+ if (event.candidate !== null && event.candidate.sdpMid !== null) {
3361
+ callbacks.onLocalCandidate(event.candidate.candidate, event.candidate.sdpMid);
3362
+ }
3363
+ };
3364
+ // ── Connection state → forwarded to worker ──────────────
3365
+ pc.onconnectionstatechange = () => {
3366
+ callbacks.onConnectionStateChange(pc.connectionState);
3367
+ };
3368
+ // ── Offerer path ────────────────────────────────────────
3369
+ if (isOffering) {
3370
+ pc.onnegotiationneeded = async () => {
3371
+ conn.makingOffer = true;
3372
+ try {
3373
+ await pc.setLocalDescription();
3374
+ }
3375
+ catch (_err) {
3376
+ // intentionally swallowed – mirrors DirectWebrtcConnection
3377
+ }
3378
+ if (pc.localDescription !== null) {
3379
+ callbacks.onLocalDescription(pc.localDescription.sdp, pc.localDescription.type);
3380
+ }
3381
+ conn.makingOffer = false;
3382
+ };
3383
+ const dc = pc.createDataChannel('streamrDataChannel');
3384
+ // Transfer DataChannel ownership to the worker immediately.
3385
+ // The worker will attach onopen/onclose/onmessage handlers.
3386
+ callbacks.onDataChannel(Comlink.transfer(dc, [dc]));
3387
+ }
3388
+ else {
3389
+ // ── Answerer path ───────────────────────────────────
3390
+ pc.ondatachannel = (event) => {
3391
+ callbacks.onDataChannel(Comlink.transfer(event.channel, [event.channel]));
3392
+ };
3393
+ }
3394
+ }
3395
+ async setRemoteDescription(connectionId, description, type) {
3396
+ const conn = this.connections.get(connectionId);
3397
+ if (!conn) {
3398
+ return false;
3399
+ }
3400
+ const lowerType = type.toLowerCase();
3401
+ // Perfect-negotiation collision detection
3402
+ const offerCollision = lowerType === 'offer' &&
3403
+ (conn.makingOffer || conn.pc.signalingState !== 'stable');
3404
+ if (conn.isOffering && offerCollision) {
3405
+ return false;
3406
+ }
3407
+ try {
3408
+ await conn.pc.setRemoteDescription({ sdp: description, type: lowerType });
3409
+ }
3410
+ catch (_err) {
3411
+ return false;
3412
+ }
3413
+ // If we received an offer, create an answer
3414
+ if (lowerType === 'offer') {
3415
+ try {
3416
+ await conn.pc.setLocalDescription();
3417
+ }
3418
+ catch (_err) {
3419
+ // intentionally swallowed
3420
+ }
3421
+ if (conn.pc.localDescription !== null) {
3422
+ conn.callbacks.onLocalDescription(conn.pc.localDescription.sdp, conn.pc.localDescription.type);
3423
+ }
3424
+ }
3425
+ return true;
3426
+ }
3427
+ async addRemoteCandidate(connectionId, candidate, mid) {
3428
+ const conn = this.connections.get(connectionId);
3429
+ if (!conn) {
3430
+ return;
3431
+ }
3432
+ try {
3433
+ await conn.pc.addIceCandidate({ candidate, sdpMid: mid });
3434
+ }
3435
+ catch (_err) {
3436
+ // intentionally swallowed
3437
+ }
3438
+ }
3439
+ async renameConnection(oldId, newId) {
3440
+ const conn = this.connections.get(oldId);
3441
+ if (conn) {
3442
+ this.connections.delete(oldId);
3443
+ this.connections.set(newId, conn);
3444
+ }
3445
+ }
3446
+ async close(connectionId) {
3447
+ const conn = this.connections.get(connectionId);
3448
+ if (!conn) {
3449
+ return;
3450
+ }
3451
+ this.connections.delete(connectionId);
3452
+ // Tear down event handlers
3453
+ conn.pc.onicecandidate = null;
3454
+ conn.pc.onconnectionstatechange = null;
3455
+ conn.pc.onnegotiationneeded = null;
3456
+ conn.pc.ondatachannel = null;
3457
+ try {
3458
+ conn.pc.close();
3459
+ }
3460
+ catch (_err) {
3461
+ // intentionally swallowed
3462
+ }
3463
+ }
3464
+ }
3465
+
3466
+ /**
3467
+ * Call this function on the **main thread** before the worker starts using
3468
+ * WebRTC connections. It creates a dedicated `MessageChannel`, exposes a
3469
+ * {@link WebrtcBridge} instance on one port, and sends the other port to
3470
+ * the worker so that `WorkerWebrtcConnection` can reach the bridge.
3471
+ *
3472
+ * @example
3473
+ * ```ts
3474
+ * import { installWebrtcBridge } from '@streamr/dht'
3475
+ *
3476
+ * const worker = new Worker('./my-worker.ts', { type: 'module' })
3477
+ * installWebrtcBridge(worker)
3478
+ * ```
3479
+ */
3480
+ const WEBRTC_BRIDGE_PORT_MESSAGE_TYPE = 'streamr-webrtc-bridge-port';
3481
+ function installWebrtcBridge(worker) {
3482
+ const bridge = new WebrtcBridge();
3483
+ const channel = new MessageChannel();
3484
+ // Expose the bridge API on port1 — the worker will Comlink.wrap(port2).
3485
+ Comlink.expose(bridge, channel.port1);
3486
+ // Send port2 to the worker. It is transferred (not cloned).
3487
+ worker.postMessage({ type: WEBRTC_BRIDGE_PORT_MESSAGE_TYPE, port: channel.port2 }, [channel.port2]);
3488
+ }
3489
+
3490
+ /**
3491
+ * WorkerWebrtcConnection — runs inside a **Web Worker**.
3492
+ *
3493
+ * Implements the same IWebrtcConnection + IConnection interfaces as
3494
+ * DirectWebrtcConnection, but delegates RTCPeerConnection management to
3495
+ * the main-thread {@link WebrtcBridge} via Comlink.
3496
+ *
3497
+ * The RTCDataChannel is **transferred** from the main thread and lives
3498
+ * entirely in the worker — all data events (onmessage, onopen, onclose,
3499
+ * onbufferedamountlow) fire in the worker's event loop. The main thread
3500
+ * is never involved in the data path.
3501
+ */
3502
+ // ── Module-level bridge client (initialized once per worker) ────────
3503
+ let resolveBridgeProxy;
3504
+ const bridgeProxyPromise = new Promise((resolve) => {
3505
+ resolveBridgeProxy = resolve;
3506
+ });
3507
+ // Listen for the bridge port message from the main thread.
3508
+ // This is guarded so it only runs inside a worker context.
3509
+ if (isWorkerEnvironment) {
3510
+ const handler = (e) => {
3511
+ if (e.data?.type === WEBRTC_BRIDGE_PORT_MESSAGE_TYPE && e.data.port) {
3512
+ const proxy = Comlink.wrap(e.data.port);
3513
+ resolveBridgeProxy(proxy);
3514
+ self.removeEventListener('message', handler);
3515
+ }
3516
+ };
3517
+ self.addEventListener('message', handler);
3518
+ }
3519
+ function getBridgeProxy() {
3520
+ return bridgeProxyPromise;
3521
+ }
3522
+ // ── Disconnection states ────────────────────────────────────────────
3523
+ var DisconnectedState;
3524
+ (function (DisconnectedState) {
3525
+ DisconnectedState["DISCONNECTED"] = "disconnected";
3526
+ DisconnectedState["FAILED"] = "failed";
3527
+ DisconnectedState["CLOSED"] = "closed";
3528
+ })(DisconnectedState || (DisconnectedState = {}));
3529
+ const logger$u = new Logger('WorkerWebrtcConnection');
3530
+ // ── WorkerWebrtcConnection ──────────────────────────────────────────
3531
+ class WorkerWebrtcConnection extends EventEmitter {
3532
+ connectionId;
3533
+ connectionType = ConnectionType.WEBRTC;
3534
+ iceServers;
3535
+ bufferThresholdHigh;
3536
+ bufferThresholdLow;
3537
+ dataChannel;
3538
+ bridge;
3539
+ closed = false;
3540
+ connected = false;
3541
+ earlyTimeout;
3542
+ messageQueue = [];
3543
+ startPromise;
3544
+ constructor(params) {
3545
+ super();
3546
+ this.connectionId = createRandomConnectionId();
3547
+ this.iceServers = params.iceServers ?? [];
3548
+ this.bufferThresholdHigh = params.bufferThresholdHigh ?? 2 ** 17;
3549
+ this.bufferThresholdLow = params.bufferThresholdLow ?? 2 ** 15;
3550
+ this.earlyTimeout = setTimeout(() => {
3551
+ this.doClose(false, 'timed out due to remote descriptor not being set');
3552
+ }, EARLY_TIMEOUT);
3553
+ }
3554
+ // ── IWebrtcConnection ───────────────────────────────────────
3555
+ start(isOffering) {
3556
+ this.startPromise = this.doStart(isOffering);
3557
+ this.startPromise.catch((err) => {
3558
+ logger$u.warn('Failed to start worker WebRTC connection', { err });
3559
+ this.doClose(false, 'Failed to start');
3560
+ });
3561
+ }
3562
+ async doStart(isOffering) {
3563
+ this.bridge = await getBridgeProxy();
3564
+ const iceServers = this.iceServers.map(({ url, port, username, password }) => ({
3565
+ urls: `${url}:${port}`,
3566
+ username,
3567
+ credential: password,
3568
+ }));
3569
+ await this.bridge.start(this.connectionId, iceServers, isOffering, Comlink.proxy({
3570
+ onLocalCandidate: (candidate, mid) => {
3571
+ if (!this.closed) {
3572
+ this.emit('localCandidate', candidate, mid);
3573
+ }
3574
+ },
3575
+ onLocalDescription: (description, type) => {
3576
+ if (!this.closed) {
3577
+ this.emit('localDescription', description, type);
3578
+ }
3579
+ },
3580
+ onConnectionStateChange: (state) => {
3581
+ if (state === DisconnectedState.CLOSED ||
3582
+ state === DisconnectedState.DISCONNECTED ||
3583
+ state === DisconnectedState.FAILED) {
3584
+ this.doClose(false);
3585
+ }
3586
+ },
3587
+ onDataChannel: (channel) => {
3588
+ if (!this.closed) {
3589
+ this.setupDataChannel(channel);
3590
+ // If the channel was already open at transfer time
3591
+ if (channel.readyState === 'open') {
3592
+ this.onDataChannelOpen();
3593
+ }
3594
+ }
3595
+ },
3596
+ }));
3597
+ }
3598
+ async setRemoteDescription(description, type) {
3599
+ if (this.startPromise) {
3600
+ await this.startPromise;
3601
+ }
3602
+ if (!this.bridge || this.closed) {
3603
+ return;
3604
+ }
3605
+ const wasSet = await this.bridge.setRemoteDescription(this.connectionId, description, type);
3606
+ if (wasSet) {
3607
+ clearTimeout(this.earlyTimeout);
3608
+ }
3609
+ }
3610
+ addRemoteCandidate(candidate, mid) {
3611
+ this.doAddRemoteCandidate(candidate, mid).catch((err) => {
3612
+ logger$u.warn('Failed to add remote candidate via bridge', { err });
3613
+ });
3614
+ }
3615
+ async doAddRemoteCandidate(candidate, mid) {
3616
+ if (this.startPromise) {
3617
+ await this.startPromise;
3618
+ }
3619
+ if (!this.bridge || this.closed) {
3620
+ return;
3621
+ }
3622
+ await this.bridge.addRemoteCandidate(this.connectionId, candidate, mid);
3623
+ }
3624
+ isOpen() {
3625
+ return this.connected;
3626
+ }
3627
+ // ── IConnection ─────────────────────────────────────────────
3628
+ async close(gracefulLeave, reason) {
3629
+ this.doClose(gracefulLeave, reason);
3630
+ }
3631
+ destroy() {
3632
+ this.removeAllListeners();
3633
+ this.doClose(false);
3634
+ }
3635
+ send(data) {
3636
+ if (this.connected && this.dataChannel) {
3637
+ if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
3638
+ this.messageQueue.push(data);
3639
+ }
3640
+ else {
3641
+ this.dataChannel.send(data);
3642
+ }
3643
+ }
3644
+ else if (!this.closed) {
3645
+ this.messageQueue.push(data);
3646
+ }
3647
+ }
3648
+ setConnectionId(connectionId) {
3649
+ const oldId = this.connectionId;
3650
+ this.connectionId = connectionId;
3651
+ if (this.bridge && oldId !== connectionId) {
3652
+ this.bridge.renameConnection(oldId, connectionId).catch(() => { });
3653
+ }
3654
+ }
3655
+ // ── DataChannel handling (runs entirely in the worker) ──────
3656
+ setupDataChannel(dataChannel) {
3657
+ this.dataChannel = dataChannel;
3658
+ this.dataChannel.binaryType = 'arraybuffer';
3659
+ this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3660
+ dataChannel.onopen = () => {
3661
+ logger$u.trace('dc.onOpen (worker)');
3662
+ this.onDataChannelOpen();
3663
+ };
3664
+ dataChannel.onclose = () => {
3665
+ logger$u.trace('dc.onClosed (worker)');
3666
+ this.doClose(false);
3667
+ };
3668
+ dataChannel.onerror = (err) => {
3669
+ logger$u.warn('Data channel error (worker)', { err });
3670
+ };
3671
+ dataChannel.onmessage = (msg) => {
3672
+ logger$u.trace('dc.onmessage (worker)');
3673
+ this.emit('data', new Uint8Array(msg.data));
3674
+ };
3675
+ dataChannel.onbufferedamountlow = () => {
3676
+ logger$u.trace('dc.onBufferedAmountLow (worker)');
3677
+ while (this.messageQueue.length > 0 &&
3678
+ this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3679
+ const data = this.messageQueue.shift();
3680
+ this.dataChannel.send(data);
3681
+ }
3682
+ };
3683
+ }
3684
+ onDataChannelOpen() {
3685
+ this.connected = true;
3686
+ this.flushMessageQueue();
3687
+ this.emit('connected');
3688
+ }
3689
+ flushMessageQueue() {
3690
+ while (this.messageQueue.length > 0 &&
3691
+ this.dataChannel &&
3692
+ this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3693
+ const data = this.messageQueue.shift();
3694
+ this.dataChannel.send(data);
3695
+ }
3696
+ }
3697
+ // ── Teardown ────────────────────────────────────────────────
3698
+ doClose(gracefulLeave, reason) {
3699
+ if (!this.closed) {
3700
+ this.closed = true;
3701
+ this.connected = false;
3702
+ this.messageQueue.length = 0;
3703
+ clearTimeout(this.earlyTimeout);
3704
+ this.stopListening();
3705
+ this.emit('disconnected', gracefulLeave, undefined, reason);
3706
+ this.removeAllListeners();
3707
+ if (this.dataChannel !== undefined) {
3708
+ try {
3709
+ this.dataChannel.close();
3710
+ }
3711
+ catch (err) {
3712
+ logger$u.warn('Failed to close data channel (worker)', { err });
3713
+ }
3714
+ }
3715
+ this.dataChannel = undefined;
3716
+ // Tell the main-thread bridge to tear down the RTCPeerConnection.
3717
+ // Fire-and-forget — we don't block on this.
3718
+ this.bridge
3719
+ ?.close(this.connectionId)
3720
+ .catch(() => {
3721
+ // intentionally swallowed
3722
+ });
3723
+ }
3724
+ }
3725
+ stopListening() {
3726
+ if (this.dataChannel !== undefined) {
3727
+ this.dataChannel.onopen = null;
3728
+ this.dataChannel.onclose = null;
3729
+ this.dataChannel.onerror = null;
3730
+ this.dataChannel.onbufferedamountlow = null;
3731
+ this.dataChannel.onmessage = null;
3732
+ }
3733
+ }
3734
+ }
3735
+
3736
+ /**
3737
+ * Conditional re-export of the browser WebrtcConnection.
3738
+ *
3739
+ * At module-load time we detect whether we are running inside a Web Worker.
3740
+ * - **Main thread** → use {@link DirectWebrtcConnection} which owns the
3741
+ * `RTCPeerConnection` and `RTCDataChannel` directly.
3742
+ * - **Worker thread** → use {@link WorkerWebrtcConnection} which delegates
3743
+ * `RTCPeerConnection` signaling to the main thread via a Comlink bridge
3744
+ * and receives a transferred `RTCDataChannel` that lives entirely in the
3745
+ * worker.
3746
+ *
3747
+ * Both classes implement `IWebrtcConnection & IConnection` and expose the
3748
+ * same public API, so upstream code (WebrtcConnector, etc.) is unaffected.
3749
+ */
3750
+ // The constructor — points to the right class based on the runtime
3751
+ // environment. The type assertion is safe because both implementations
3752
+ // share the same public interface surface.
3753
+ const WebrtcConnection = (isWorkerEnvironment
3754
+ ? WorkerWebrtcConnection
3755
+ : DirectWebrtcConnection);
3756
+
3329
3757
  const logger$t = new Logger('WebrtcConnectorRpcRemote');
3330
3758
  class WebrtcConnectorRpcRemote extends RpcRemote {
3331
3759
  requestConnection() {
@@ -7608,5 +8036,5 @@ class SimulatorTransport extends ConnectionManager {
7608
8036
  }
7609
8037
  }
7610
8038
 
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 };
8039
+ 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, randomDhtAddress, toDhtAddress, toDhtAddressRaw, toNodeId };
7612
8040
  //# sourceMappingURL=exports-browser.js.map