@streamr/dht 103.3.0 → 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.
@@ -1,3 +1,4 @@
1
+ import 'timers-browserify';
1
2
  import { binaryToHex, hexToBinary, randomBytes, areEqualBinaries, Logger, computeMd5, Defer, MetricsContext, CountMetric, LevelMetric, RateMetric, waitForEvent, setAbortableTimeout, raceForEvent, ipv4ToNumber, wait, EcdsaSecp256k1Evm, Gate, withTimeout, scheduleAtInterval, MapWithTtl, executeSafePromise, merge, addManagedEventListener, until } from '@streamr/utils';
2
3
  import { EventEmitter } from 'eventemitter3';
3
4
  import sample from 'lodash/sample';
@@ -8,6 +9,7 @@ import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
8
9
  import { v4 } from 'uuid';
9
10
  import { ProtoCallContext, RpcCommunicator, toProtoRpcClient, protoClasses as protoClasses$1, RpcError } from '@streamr/proto-rpc';
10
11
  import ipaddr from 'ipaddr.js';
12
+ import * as Comlink from 'comlink';
11
13
  import { w3cwebsocket } from 'websocket';
12
14
  import { SERVICE_ID } from '@streamr/autocertifier-client';
13
15
  import shuffle from 'lodash/shuffle';
@@ -1797,7 +1799,7 @@ const createRandomConnectionId = () => {
1797
1799
  return v4();
1798
1800
  };
1799
1801
 
1800
- const logger$C = new Logger('ManagedConnection');
1802
+ const logger$D = new Logger('ManagedConnection');
1801
1803
  // ManagedConnection is a component used as a wrapper for IConnection after they have been successfully handshaked.
1802
1804
  // Should only be used in the ConnectionManager.
1803
1805
  class ManagedConnection extends EventEmitter {
@@ -1827,7 +1829,7 @@ class ManagedConnection extends EventEmitter {
1827
1829
  this.remotePeerDescriptor = peerDescriptor;
1828
1830
  }
1829
1831
  onDisconnected(gracefulLeave) {
1830
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1832
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1831
1833
  if (!this.replacedAsDuplicate) {
1832
1834
  this.emit('disconnected', gracefulLeave);
1833
1835
  }
@@ -1836,7 +1838,7 @@ class ManagedConnection extends EventEmitter {
1836
1838
  // TODO: Can this be removed if ManagedConnections can never be duplicates?
1837
1839
  // Handle duplicates in the ConncetorFacade and no longer have PendingConnections in ConnectionManager
1838
1840
  replaceAsDuplicate() {
1839
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1841
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1840
1842
  this.replacedAsDuplicate = true;
1841
1843
  }
1842
1844
  send(data) {
@@ -1983,10 +1985,10 @@ class RpcRemote {
1983
1985
  }
1984
1986
  }
1985
1987
 
1986
- const logger$B = new Logger('ConnectionLockRpcRemote');
1988
+ const logger$C = new Logger('ConnectionLockRpcRemote');
1987
1989
  class ConnectionLockRpcRemote extends RpcRemote {
1988
1990
  async lockRequest(lockId) {
1989
- logger$B.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
1991
+ logger$C.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
1990
1992
  const request = {
1991
1993
  lockId
1992
1994
  };
@@ -1996,12 +1998,12 @@ class ConnectionLockRpcRemote extends RpcRemote {
1996
1998
  return res.accepted;
1997
1999
  }
1998
2000
  catch (err) {
1999
- logger$B.debug('Connection lock rejected', { err });
2001
+ logger$C.debug('Connection lock rejected', { err });
2000
2002
  return false;
2001
2003
  }
2002
2004
  }
2003
2005
  unlockRequest(lockId) {
2004
- 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())}`);
2005
2007
  const request = {
2006
2008
  lockId
2007
2009
  };
@@ -2009,11 +2011,11 @@ class ConnectionLockRpcRemote extends RpcRemote {
2009
2011
  notification: true
2010
2012
  });
2011
2013
  this.getClient().unlockRequest(request, options).catch((_e) => {
2012
- logger$B.trace('failed to send unlockRequest');
2014
+ logger$C.trace('failed to send unlockRequest');
2013
2015
  });
2014
2016
  }
2015
2017
  async gracefulDisconnect(disconnectMode) {
2016
- logger$B.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2018
+ logger$C.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2017
2019
  const request = {
2018
2020
  disconnectMode
2019
2021
  };
@@ -2025,7 +2027,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2025
2027
  await this.getClient().gracefulDisconnect(request, options);
2026
2028
  }
2027
2029
  async setPrivate(isPrivate) {
2028
- logger$B.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2030
+ logger$C.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2029
2031
  const request = {
2030
2032
  isPrivate
2031
2033
  };
@@ -2037,7 +2039,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2037
2039
  }
2038
2040
  }
2039
2041
 
2040
- const logger$A = new Logger('ConnectionLockRpcLocal');
2042
+ const logger$B = new Logger('ConnectionLockRpcLocal');
2041
2043
  class ConnectionLockRpcLocal {
2042
2044
  options;
2043
2045
  constructor(options) {
@@ -2066,7 +2068,7 @@ class ConnectionLockRpcLocal {
2066
2068
  }
2067
2069
  async gracefulDisconnect(disconnectNotice, context) {
2068
2070
  const senderPeerDescriptor = context.incomingSourceDescriptor;
2069
- logger$A.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2071
+ logger$B.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2070
2072
  if (disconnectNotice.disconnectMode === DisconnectMode.LEAVING) {
2071
2073
  await this.options.closeConnection(senderPeerDescriptor, true, 'graceful leave notified');
2072
2074
  }
@@ -2117,7 +2119,7 @@ var NatType;
2117
2119
  NatType["OPEN_INTERNET"] = "open_internet";
2118
2120
  NatType["UNKNOWN"] = "unknown";
2119
2121
  })(NatType || (NatType = {}));
2120
- const logger$z = new Logger('ConnectionManager');
2122
+ const logger$A = new Logger('ConnectionManager');
2121
2123
  var ConnectionManagerState;
2122
2124
  (function (ConnectionManagerState) {
2123
2125
  ConnectionManagerState["IDLE"] = "idle";
@@ -2167,7 +2169,7 @@ class ConnectionManager extends EventEmitter {
2167
2169
  getLocalPeerDescriptor: () => this.getLocalPeerDescriptor(),
2168
2170
  setPrivate: (id, isPrivate) => {
2169
2171
  if (!this.options.allowIncomingPrivateConnections) {
2170
- 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`);
2171
2173
  return;
2172
2174
  }
2173
2175
  if (isPrivate) {
@@ -2201,7 +2203,7 @@ class ConnectionManager extends EventEmitter {
2201
2203
  const connection = endpoint.connection;
2202
2204
  const nodeId = connection.getNodeId();
2203
2205
  if (!this.locks.isLocked(nodeId) && !this.locks.isPrivate(nodeId) && Date.now() - connection.getLastUsedTimestamp() > maxIdleTime) {
2204
- logger$z.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2206
+ logger$A.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2205
2207
  disconnectionCandidates.addContact(connection);
2206
2208
  }
2207
2209
  }
@@ -2209,7 +2211,7 @@ class ConnectionManager extends EventEmitter {
2209
2211
  const disconnectables = disconnectionCandidates.getFurthestContacts(this.endpoints.size - maxConnections);
2210
2212
  for (const disconnectable of disconnectables) {
2211
2213
  const peerDescriptor = disconnectable.getPeerDescriptor();
2212
- logger$z.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2214
+ logger$A.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2213
2215
  this.gracefullyDisconnectAsync(peerDescriptor, DisconnectMode.NORMAL).catch((_e) => { });
2214
2216
  }
2215
2217
  }
@@ -2218,11 +2220,11 @@ class ConnectionManager extends EventEmitter {
2218
2220
  throw new CouldNotStart(`Cannot start already ${this.state} module`);
2219
2221
  }
2220
2222
  this.state = ConnectionManagerState.RUNNING;
2221
- logger$z.trace(`Starting ConnectionManager...`);
2223
+ logger$A.trace(`Starting ConnectionManager...`);
2222
2224
  await this.connectorFacade.start((connection) => this.onNewConnection(connection), (nodeId) => this.hasConnection(nodeId), this);
2223
2225
  // Garbage collection of connections
2224
2226
  this.disconnectorIntervalRef = setInterval(() => {
2225
- logger$z.trace('disconnectorInterval');
2227
+ logger$A.trace('disconnectorInterval');
2226
2228
  const LAST_USED_LIMIT = 20000;
2227
2229
  this.garbageCollectConnections(this.options.maxConnections ?? 80, LAST_USED_LIMIT);
2228
2230
  }, 5000); // TODO use options option or named constant?
@@ -2232,7 +2234,7 @@ class ConnectionManager extends EventEmitter {
2232
2234
  return;
2233
2235
  }
2234
2236
  this.state = ConnectionManagerState.STOPPING;
2235
- logger$z.trace(`Stopping ConnectionManager`);
2237
+ logger$A.trace(`Stopping ConnectionManager`);
2236
2238
  if (this.disconnectorIntervalRef) {
2237
2239
  clearInterval(this.disconnectorIntervalRef);
2238
2240
  }
@@ -2242,23 +2244,23 @@ class ConnectionManager extends EventEmitter {
2242
2244
  await this.gracefullyDisconnectAsync(endpoint.connection.getPeerDescriptor(), DisconnectMode.LEAVING);
2243
2245
  }
2244
2246
  catch (e) {
2245
- logger$z.error(e);
2247
+ logger$A.error(e);
2246
2248
  }
2247
2249
  }
2248
2250
  else {
2249
2251
  const connection = endpoint.connection;
2250
- logger$z.trace('handshake of connection not completed, force-closing');
2252
+ logger$A.trace('handshake of connection not completed, force-closing');
2251
2253
  // TODO use options option or named constant?
2252
2254
  const eventReceived = waitForEvent(connection, 'disconnected', 2000);
2253
2255
  // TODO should we have some handling for this floating promise?
2254
2256
  connection.close(true);
2255
2257
  try {
2256
2258
  await eventReceived;
2257
- 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');
2258
2260
  }
2259
2261
  catch (e) {
2260
2262
  endpoint.buffer.reject();
2261
- logger$z.trace('force-closing non-handshaked connection timed out ' + e);
2263
+ logger$A.trace('force-closing non-handshaked connection timed out ' + e);
2262
2264
  }
2263
2265
  }
2264
2266
  }));
@@ -2287,7 +2289,7 @@ class ConnectionManager extends EventEmitter {
2287
2289
  throw new CannotConnectToSelf('Cannot send to self');
2288
2290
  }
2289
2291
  const nodeId = toNodeId(peerDescriptor);
2290
- logger$z.trace(`Sending message to: ${nodeId}`);
2292
+ logger$A.trace(`Sending message to: ${nodeId}`);
2291
2293
  message = {
2292
2294
  ...message,
2293
2295
  sourceDescriptor: this.getLocalPeerDescriptor()
@@ -2342,13 +2344,13 @@ class ConnectionManager extends EventEmitter {
2342
2344
  }
2343
2345
  handleMessage(message) {
2344
2346
  const messageType = message.body.oneofKind;
2345
- logger$z.trace('Received message of type ' + messageType);
2347
+ logger$A.trace('Received message of type ' + messageType);
2346
2348
  if (messageType !== 'rpcMessage') {
2347
- logger$z.trace('Filtered out non-RPC message of type ' + messageType);
2349
+ logger$A.trace('Filtered out non-RPC message of type ' + messageType);
2348
2350
  return;
2349
2351
  }
2350
2352
  if (this.duplicateMessageDetector.isMostLikelyDuplicate(message.messageId)) {
2351
- logger$z.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2353
+ logger$A.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2352
2354
  + ' ' + message.serviceId + ' ' + message.messageId);
2353
2355
  return;
2354
2356
  }
@@ -2357,7 +2359,7 @@ class ConnectionManager extends EventEmitter {
2357
2359
  this.rpcCommunicator?.handleMessageFromPeer(message);
2358
2360
  }
2359
2361
  else {
2360
- logger$z.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2362
+ logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2361
2363
  + ' ' + message.serviceId + ' ' + message.messageId);
2362
2364
  this.emit('message', message);
2363
2365
  }
@@ -2374,7 +2376,7 @@ class ConnectionManager extends EventEmitter {
2374
2376
  }
2375
2377
  catch (e) {
2376
2378
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2377
- logger$z.debug(`Parsing incoming data into Message failed: ${e}`);
2379
+ logger$A.debug(`Parsing incoming data into Message failed: ${e}`);
2378
2380
  return;
2379
2381
  }
2380
2382
  message.sourceDescriptor = peerDescriptor;
@@ -2383,7 +2385,7 @@ class ConnectionManager extends EventEmitter {
2383
2385
  }
2384
2386
  catch (e) {
2385
2387
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2386
- logger$z.debug(`Handling incoming data failed: ${e}`);
2388
+ logger$A.debug(`Handling incoming data failed: ${e}`);
2387
2389
  }
2388
2390
  }
2389
2391
  onConnected(peerDescriptor, connection) {
@@ -2396,7 +2398,7 @@ class ConnectionManager extends EventEmitter {
2396
2398
  const pendingConnection = endpoint.connection;
2397
2399
  const buffer = outputBuffer.getBuffer();
2398
2400
  while (buffer.length > 0) {
2399
- logger$z.trace('emptying buffer');
2401
+ logger$A.trace('emptying buffer');
2400
2402
  managedConnection.send(buffer.shift());
2401
2403
  }
2402
2404
  outputBuffer.resolve();
@@ -2413,7 +2415,7 @@ class ConnectionManager extends EventEmitter {
2413
2415
  }
2414
2416
  onDisconnected(peerDescriptor, gracefulLeave) {
2415
2417
  const nodeId = toNodeId(peerDescriptor);
2416
- logger$z.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2418
+ logger$A.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2417
2419
  const endpoint = this.endpoints.get(nodeId);
2418
2420
  if (endpoint) {
2419
2421
  this.locks.clearAllLocks(nodeId);
@@ -2421,7 +2423,7 @@ class ConnectionManager extends EventEmitter {
2421
2423
  endpoint.buffer.reject();
2422
2424
  }
2423
2425
  this.endpoints.delete(nodeId);
2424
- logger$z.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2426
+ logger$A.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2425
2427
  this.emit('disconnected', peerDescriptor, gracefulLeave);
2426
2428
  this.onConnectionCountChange();
2427
2429
  }
@@ -2430,7 +2432,7 @@ class ConnectionManager extends EventEmitter {
2430
2432
  if (this.state === ConnectionManagerState.STOPPED) {
2431
2433
  return false;
2432
2434
  }
2433
- logger$z.trace('onNewConnection()');
2435
+ logger$A.trace('onNewConnection()');
2434
2436
  if (!this.acceptNewConnection(connection)) {
2435
2437
  return false;
2436
2438
  }
@@ -2440,7 +2442,7 @@ class ConnectionManager extends EventEmitter {
2440
2442
  }
2441
2443
  acceptNewConnection(newConnection) {
2442
2444
  const nodeId = toNodeId(newConnection.getPeerDescriptor());
2443
- logger$z.trace(nodeId + ' acceptNewConnection()');
2445
+ logger$A.trace(nodeId + ' acceptNewConnection()');
2444
2446
  if (this.endpoints.has(nodeId)) {
2445
2447
  if (getOfferer(toNodeId(this.getLocalPeerDescriptor()), nodeId) === 'remote') {
2446
2448
  let buffer;
@@ -2449,14 +2451,14 @@ class ConnectionManager extends EventEmitter {
2449
2451
  // Could be related to WS client connections not realizing that they have been disconnected.
2450
2452
  // Makes refactoring duplicate connection handling to the connectors very difficult.
2451
2453
  if (this.endpoints.get(nodeId).connected) {
2452
- logger$z.debug('replacing connected connection', { nodeId });
2454
+ logger$A.debug('replacing connected connection', { nodeId });
2453
2455
  buffer = new OutputBuffer();
2454
2456
  }
2455
2457
  else {
2456
2458
  buffer = endpoint.buffer;
2457
2459
  }
2458
2460
  const oldConnection = endpoint.connection;
2459
- logger$z.trace('replaced: ' + nodeId);
2461
+ logger$A.trace('replaced: ' + nodeId);
2460
2462
  oldConnection.replaceAsDuplicate();
2461
2463
  this.endpoints.set(nodeId, { connected: false, connection: newConnection, buffer: buffer });
2462
2464
  return true;
@@ -2465,7 +2467,7 @@ class ConnectionManager extends EventEmitter {
2465
2467
  return false;
2466
2468
  }
2467
2469
  }
2468
- logger$z.trace(nodeId + ' added to connections at acceptNewConnection');
2470
+ logger$A.trace(nodeId + ' added to connections at acceptNewConnection');
2469
2471
  this.endpoints.set(nodeId, {
2470
2472
  connected: false,
2471
2473
  buffer: new OutputBuffer(),
@@ -2475,14 +2477,14 @@ class ConnectionManager extends EventEmitter {
2475
2477
  }
2476
2478
  async closeConnection(peerDescriptor, gracefulLeave, reason) {
2477
2479
  const nodeId = toNodeId(peerDescriptor);
2478
- logger$z.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2480
+ logger$A.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2479
2481
  this.locks.clearAllLocks(nodeId);
2480
2482
  if (this.endpoints.has(nodeId)) {
2481
2483
  const connectionToClose = this.endpoints.get(nodeId).connection;
2482
2484
  await connectionToClose.close(gracefulLeave);
2483
2485
  }
2484
2486
  else {
2485
- 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');
2486
2488
  this.emit('disconnected', peerDescriptor, false);
2487
2489
  }
2488
2490
  }
@@ -2494,8 +2496,8 @@ class ConnectionManager extends EventEmitter {
2494
2496
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2495
2497
  this.locks.addLocalLocked(nodeId, lockId);
2496
2498
  rpcRemote.lockRequest(lockId)
2497
- .then((_accepted) => logger$z.trace('LockRequest successful'))
2498
- .catch((err) => { logger$z.debug(err); });
2499
+ .then((_accepted) => logger$A.trace('LockRequest successful'))
2500
+ .catch((err) => { logger$A.debug(err); });
2499
2501
  }
2500
2502
  unlockConnection(targetDescriptor, lockId) {
2501
2503
  if (this.state === ConnectionManagerState.STOPPED || areEqualPeerDescriptors(targetDescriptor, this.getLocalPeerDescriptor())) {
@@ -2547,7 +2549,7 @@ class ConnectionManager extends EventEmitter {
2547
2549
  async gracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2548
2550
  const endpoint = this.endpoints.get(toNodeId(targetDescriptor));
2549
2551
  if (!endpoint) {
2550
- logger$z.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2552
+ logger$A.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2551
2553
  return;
2552
2554
  }
2553
2555
  if (endpoint.connected) {
@@ -2556,15 +2558,15 @@ class ConnectionManager extends EventEmitter {
2556
2558
  // TODO use options option or named constant?
2557
2559
  // eslint-disable-next-line promise/catch-or-return
2558
2560
  waitForEvent(connection, 'disconnected', 2000).then(() => {
2559
- logger$z.trace('disconnected event received in gracefullyDisconnectAsync()');
2561
+ logger$A.trace('disconnected event received in gracefullyDisconnectAsync()');
2560
2562
  })
2561
2563
  .catch((e) => {
2562
- logger$z.trace('force-closing connection after timeout ' + e);
2564
+ logger$A.trace('force-closing connection after timeout ' + e);
2563
2565
  // TODO should we have some handling for this floating promise?
2564
2566
  connection.close(true);
2565
2567
  })
2566
2568
  .finally(() => {
2567
- logger$z.trace('resolving after receiving disconnected event');
2569
+ logger$A.trace('resolving after receiving disconnected event');
2568
2570
  resolve();
2569
2571
  });
2570
2572
  });
@@ -2579,13 +2581,13 @@ class ConnectionManager extends EventEmitter {
2579
2581
  }
2580
2582
  async doGracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2581
2583
  const nodeId = toNodeId(targetDescriptor);
2582
- logger$z.trace(nodeId + ' gracefullyDisconnectAsync()');
2584
+ logger$A.trace(nodeId + ' gracefullyDisconnectAsync()');
2583
2585
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2584
2586
  try {
2585
2587
  await rpcRemote.gracefulDisconnect(disconnectMode);
2586
2588
  }
2587
2589
  catch (ex) {
2588
- logger$z.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2590
+ logger$A.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2589
2591
  }
2590
2592
  }
2591
2593
  getConnections() {
@@ -2652,7 +2654,7 @@ function protoToString(protoObj, objectType) {
2652
2654
  return ret;
2653
2655
  }
2654
2656
 
2655
- const logger$y = new Logger('SimulatorConnection');
2657
+ const logger$z = new Logger('SimulatorConnection');
2656
2658
  class SimulatorConnection extends Connection {
2657
2659
  stopped = false;
2658
2660
  localPeerDescriptor;
@@ -2674,46 +2676,46 @@ class SimulatorConnection extends Connection {
2674
2676
  this.doDisconnect = this.doDisconnect.bind(this);
2675
2677
  }
2676
2678
  send(data) {
2677
- logger$y.trace('send()');
2679
+ logger$z.trace('send()');
2678
2680
  if (!this.stopped) {
2679
2681
  this.simulator.send(this, data);
2680
2682
  }
2681
2683
  else {
2682
2684
  const localNodeId = toNodeId(this.localPeerDescriptor);
2683
2685
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2684
- 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');
2685
2687
  }
2686
2688
  }
2687
2689
  async close(gracefulLeave) {
2688
2690
  const localNodeId = toNodeId(this.localPeerDescriptor);
2689
2691
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2690
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close()');
2692
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close()');
2691
2693
  if (!this.stopped) {
2692
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2694
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2693
2695
  this.stopped = true;
2694
2696
  try {
2695
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2697
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2696
2698
  this.simulator.close(this);
2697
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2699
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2698
2700
  }
2699
2701
  catch (e) {
2700
- logger$y.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2702
+ logger$z.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2701
2703
  }
2702
2704
  finally {
2703
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2705
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2704
2706
  this.doDisconnect(gracefulLeave);
2705
2707
  }
2706
2708
  }
2707
2709
  else {
2708
- 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');
2709
2711
  }
2710
2712
  }
2711
2713
  connect() {
2712
2714
  if (!this.stopped) {
2713
- logger$y.trace('connect() called');
2715
+ logger$z.trace('connect() called');
2714
2716
  this.simulator.connect(this, this.targetPeerDescriptor, (error) => {
2715
2717
  if (error !== undefined) {
2716
- logger$y.trace(error);
2718
+ logger$z.trace(error);
2717
2719
  this.doDisconnect(false);
2718
2720
  }
2719
2721
  else {
@@ -2722,46 +2724,46 @@ class SimulatorConnection extends Connection {
2722
2724
  });
2723
2725
  }
2724
2726
  else {
2725
- logger$y.trace('tried to connect() a stopped connection');
2727
+ logger$z.trace('tried to connect() a stopped connection');
2726
2728
  }
2727
2729
  }
2728
2730
  handleIncomingData(data) {
2729
2731
  if (!this.stopped) {
2730
- logger$y.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2732
+ logger$z.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2731
2733
  this.emit('data', data);
2732
2734
  }
2733
2735
  else {
2734
- logger$y.trace('tried to call handleIncomingData() a stopped connection');
2736
+ logger$z.trace('tried to call handleIncomingData() a stopped connection');
2735
2737
  }
2736
2738
  }
2737
2739
  handleIncomingDisconnection() {
2738
2740
  if (!this.stopped) {
2739
2741
  const localNodeId = toNodeId(this.localPeerDescriptor);
2740
- logger$y.trace(localNodeId + ' handleIncomingDisconnection()');
2742
+ logger$z.trace(localNodeId + ' handleIncomingDisconnection()');
2741
2743
  this.stopped = true;
2742
2744
  this.doDisconnect(false);
2743
2745
  }
2744
2746
  else {
2745
- logger$y.trace('tried to call handleIncomingDisconnection() a stopped connection');
2747
+ logger$z.trace('tried to call handleIncomingDisconnection() a stopped connection');
2746
2748
  }
2747
2749
  }
2748
2750
  destroy() {
2749
2751
  const localNodeId = toNodeId(this.localPeerDescriptor);
2750
2752
  if (!this.stopped) {
2751
- logger$y.trace(localNodeId + ' destroy()');
2753
+ logger$z.trace(localNodeId + ' destroy()');
2752
2754
  this.removeAllListeners();
2753
2755
  this.close(false).catch((_e) => { });
2754
2756
  }
2755
2757
  else {
2756
- logger$y.trace(localNodeId + ' tried to call destroy() a stopped connection');
2758
+ logger$z.trace(localNodeId + ' tried to call destroy() a stopped connection');
2757
2759
  }
2758
2760
  }
2759
2761
  doDisconnect(gracefulLeave) {
2760
2762
  const localNodeId = toNodeId(this.localPeerDescriptor);
2761
2763
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2762
- logger$y.trace(localNodeId + ' doDisconnect()');
2764
+ logger$z.trace(localNodeId + ' doDisconnect()');
2763
2765
  this.stopped = true;
2764
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2766
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2765
2767
  this.emit('disconnected', gracefulLeave);
2766
2768
  }
2767
2769
  }
@@ -2799,9 +2801,9 @@ const parseVersion = (version) => {
2799
2801
  }
2800
2802
  };
2801
2803
 
2802
- var version = "103.3.0";
2804
+ var version = "103.6.0-rc.0";
2803
2805
 
2804
- const logger$x = new Logger('Handshaker');
2806
+ const logger$y = new Logger('Handshaker');
2805
2807
  // Optimally the Outgoing and Incoming Handshakers could be their own separate classes
2806
2808
  // However, in cases where the PeerDescriptor of the other end of the connection can be known
2807
2809
  // only after a HandshakeRequest a base Handshaker class is needed as the IncomingHandshaker currently
@@ -2823,7 +2825,7 @@ const createOutgoingHandshaker = (localPeerDescriptor, pendingConnection, connec
2823
2825
  }
2824
2826
  };
2825
2827
  const handshakeCompletedListener = (peerDescriptor) => {
2826
- logger$x.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2828
+ logger$y.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2827
2829
  pendingConnection.onHandshakeCompleted(connection);
2828
2830
  stopHandshaker();
2829
2831
  };
@@ -2923,12 +2925,12 @@ class Handshaker extends EventEmitter {
2923
2925
  try {
2924
2926
  const message = Message.fromBinary(data);
2925
2927
  if (message.body.oneofKind === 'handshakeRequest') {
2926
- logger$x.trace('handshake request received');
2928
+ logger$y.trace('handshake request received');
2927
2929
  const handshake = message.body.handshakeRequest;
2928
2930
  this.emit('handshakeRequest', handshake.sourcePeerDescriptor, handshake.protocolVersion, handshake.targetPeerDescriptor);
2929
2931
  }
2930
2932
  if (message.body.oneofKind === 'handshakeResponse') {
2931
- logger$x.trace('handshake response received');
2933
+ logger$y.trace('handshake response received');
2932
2934
  const handshake = message.body.handshakeResponse;
2933
2935
  const error = !isMaybeSupportedProtocolVersion(handshake.protocolVersion)
2934
2936
  ? HandshakeError.UNSUPPORTED_PROTOCOL_VERSION : handshake.error;
@@ -2941,18 +2943,18 @@ class Handshaker extends EventEmitter {
2941
2943
  }
2942
2944
  }
2943
2945
  catch (err) {
2944
- logger$x.debug('error while parsing handshake message', err);
2946
+ logger$y.debug('error while parsing handshake message', err);
2945
2947
  }
2946
2948
  }
2947
2949
  sendHandshakeRequest(remotePeerDescriptor) {
2948
2950
  const msg = createHandshakeRequest(this.localPeerDescriptor, remotePeerDescriptor);
2949
2951
  this.connection.send(Message.toBinary(msg));
2950
- logger$x.trace('handshake request sent');
2952
+ logger$y.trace('handshake request sent');
2951
2953
  }
2952
2954
  sendHandshakeResponse(error) {
2953
2955
  const msg = createHandshakeResponse(this.localPeerDescriptor, error);
2954
2956
  this.connection.send(Message.toBinary(msg));
2955
- logger$x.trace('handshake response sent');
2957
+ logger$y.trace('handshake response sent');
2956
2958
  }
2957
2959
  stop() {
2958
2960
  this.connection.off('data', this.onDataListener);
@@ -2960,7 +2962,7 @@ class Handshaker extends EventEmitter {
2960
2962
  }
2961
2963
  }
2962
2964
 
2963
- const logger$w = new Logger('PendingConnection');
2965
+ const logger$x = new Logger('PendingConnection');
2964
2966
  // PendingConnection is used as a reference to a connection that should be opened and handshaked to a given PeerDescriptor
2965
2967
  // It does not hold a connection internally. The public method onHandshakedCompleted should be called once a connection for the
2966
2968
  // remotePeerDescriptor is opened and handshaked successfully.
@@ -2978,7 +2980,7 @@ class PendingConnection extends EventEmitter {
2978
2980
  }, timeout, this.connectingAbortController.signal);
2979
2981
  }
2980
2982
  replaceAsDuplicate() {
2981
- logger$w.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
2983
+ logger$x.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
2982
2984
  this.replacedAsDuplicate = true;
2983
2985
  }
2984
2986
  onHandshakeCompleted(connection) {
@@ -3009,7 +3011,7 @@ class PendingConnection extends EventEmitter {
3009
3011
  }
3010
3012
  }
3011
3013
 
3012
- const logger$v = new Logger('SimulatorConnector');
3014
+ const logger$w = new Logger('SimulatorConnector');
3013
3015
  class SimulatorConnector {
3014
3016
  connectingConnections = new Map();
3015
3017
  stopped = false;
@@ -3022,7 +3024,7 @@ class SimulatorConnector {
3022
3024
  this.onNewConnection = onNewConnection;
3023
3025
  }
3024
3026
  connect(targetPeerDescriptor) {
3025
- logger$v.trace('connect() ' + toNodeId(targetPeerDescriptor));
3027
+ logger$w.trace('connect() ' + toNodeId(targetPeerDescriptor));
3026
3028
  const nodeId = toNodeId(targetPeerDescriptor);
3027
3029
  const existingConnection = this.connectingConnections.get(nodeId);
3028
3030
  if (existingConnection) {
@@ -3051,18 +3053,18 @@ class SimulatorConnector {
3051
3053
  // connection is incoming, so remotePeerDescriptor is localPeerDescriptor
3052
3054
  const remotePeerDescriptor = sourceConnection.localPeerDescriptor;
3053
3055
  const remoteNodeId = toNodeId(sourceConnection.localPeerDescriptor);
3054
- logger$v.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3056
+ logger$w.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3055
3057
  if (this.stopped) {
3056
3058
  return;
3057
3059
  }
3058
3060
  const connection = new SimulatorConnection(this.localPeerDescriptor, remotePeerDescriptor, ConnectionType.SIMULATOR_SERVER, this.simulator);
3059
3061
  const pendingConnection = new PendingConnection(remotePeerDescriptor);
3060
3062
  const handshaker = createIncomingHandshaker(this.localPeerDescriptor, pendingConnection, connection);
3061
- logger$v.trace('connected');
3063
+ logger$w.trace('connected');
3062
3064
  handshaker.once('handshakeRequest', () => {
3063
- logger$v.trace(remoteNodeId + ' incoming handshake request');
3065
+ logger$w.trace(remoteNodeId + ' incoming handshake request');
3064
3066
  if (this.onNewConnection(pendingConnection)) {
3065
- logger$v.trace(remoteNodeId + ' calling acceptHandshake');
3067
+ logger$w.trace(remoteNodeId + ' calling acceptHandshake');
3066
3068
  acceptHandshake(handshaker, pendingConnection, connection);
3067
3069
  }
3068
3070
  else {
@@ -3102,6 +3104,8 @@ class ListeningRpcCommunicator extends RoutingRpcCommunicator {
3102
3104
  }
3103
3105
  }
3104
3106
 
3107
+ const isWorkerEnvironment = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
3108
+
3105
3109
  var RtcDescription;
3106
3110
  (function (RtcDescription) {
3107
3111
  RtcDescription["OFFER"] = "offer";
@@ -3116,8 +3120,8 @@ var DisconnectedRtcPeerConnectionStateEnum;
3116
3120
  DisconnectedRtcPeerConnectionStateEnum["FAILED"] = "failed";
3117
3121
  DisconnectedRtcPeerConnectionStateEnum["CLOSED"] = "closed";
3118
3122
  })(DisconnectedRtcPeerConnectionStateEnum || (DisconnectedRtcPeerConnectionStateEnum = {}));
3119
- const logger$u = new Logger('WebrtcConnection (browser)');
3120
- class WebrtcConnection extends EventEmitter {
3123
+ const logger$v = new Logger('DirectWebrtcConnection (browser)');
3124
+ class DirectWebrtcConnection extends EventEmitter {
3121
3125
  connectionId;
3122
3126
  connectionType = ConnectionType.WEBRTC;
3123
3127
  // We need to keep track of connection state ourselves because
@@ -3155,7 +3159,7 @@ class WebrtcConnection extends EventEmitter {
3155
3159
  }
3156
3160
  };
3157
3161
  this.peerConnection.onicegatheringstatechange = () => {
3158
- logger$u.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3162
+ logger$v.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3159
3163
  };
3160
3164
  this.peerConnection.onconnectionstatechange = () => this.onStateChange();
3161
3165
  if (isOffering) {
@@ -3166,7 +3170,7 @@ class WebrtcConnection extends EventEmitter {
3166
3170
  await this.peerConnection.setLocalDescription();
3167
3171
  }
3168
3172
  catch (err) {
3169
- logger$u.warn('Failed to set local description', { err });
3173
+ logger$v.warn('Failed to set local description', { err });
3170
3174
  }
3171
3175
  if (this.peerConnection.localDescription !== null) {
3172
3176
  this.emit('localDescription', this.peerConnection.localDescription?.sdp, this.peerConnection.localDescription?.type);
@@ -3194,14 +3198,14 @@ class WebrtcConnection extends EventEmitter {
3194
3198
  clearTimeout(this.earlyTimeout);
3195
3199
  }
3196
3200
  catch (err) {
3197
- logger$u.warn('Failed to set remote description', { err });
3201
+ logger$v.warn('Failed to set remote description', { err });
3198
3202
  }
3199
3203
  if ((type.toLowerCase() === RtcDescription.OFFER) && (this.peerConnection !== undefined)) {
3200
3204
  try {
3201
3205
  await this.peerConnection.setLocalDescription();
3202
3206
  }
3203
3207
  catch (err) {
3204
- logger$u.warn('Failed to set local description', { err });
3208
+ logger$v.warn('Failed to set local description', { err });
3205
3209
  }
3206
3210
  if (this.peerConnection.localDescription !== null) {
3207
3211
  this.emit('localDescription', this.peerConnection.localDescription.sdp, this.peerConnection.localDescription.type);
@@ -3211,7 +3215,7 @@ class WebrtcConnection extends EventEmitter {
3211
3215
  addRemoteCandidate(candidate, mid) {
3212
3216
  this.peerConnection?.addIceCandidate({ candidate: candidate, sdpMid: mid })
3213
3217
  .catch((err) => {
3214
- logger$u.warn('Failed to add ICE candidate', { err });
3218
+ logger$v.warn('Failed to add ICE candidate', { err });
3215
3219
  });
3216
3220
  }
3217
3221
  isOpen() {
@@ -3234,7 +3238,7 @@ class WebrtcConnection extends EventEmitter {
3234
3238
  this.dataChannel.close();
3235
3239
  }
3236
3240
  catch (err) {
3237
- logger$u.warn('Failed to close data channel', { err });
3241
+ logger$v.warn('Failed to close data channel', { err });
3238
3242
  }
3239
3243
  }
3240
3244
  this.dataChannel = undefined;
@@ -3243,7 +3247,7 @@ class WebrtcConnection extends EventEmitter {
3243
3247
  this.peerConnection.close();
3244
3248
  }
3245
3249
  catch (err) {
3246
- logger$u.warn('Failed to close connection', { err });
3250
+ logger$v.warn('Failed to close connection', { err });
3247
3251
  }
3248
3252
  }
3249
3253
  this.peerConnection = undefined;
@@ -3263,7 +3267,7 @@ class WebrtcConnection extends EventEmitter {
3263
3267
  }
3264
3268
  }
3265
3269
  else {
3266
- 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);
3267
3271
  }
3268
3272
  }
3269
3273
  setupDataChannel(dataChannel) {
@@ -3271,22 +3275,22 @@ class WebrtcConnection extends EventEmitter {
3271
3275
  this.dataChannel.binaryType = 'arraybuffer';
3272
3276
  this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3273
3277
  dataChannel.onopen = () => {
3274
- logger$u.trace('dc.onOpen');
3278
+ logger$v.trace('dc.onOpen');
3275
3279
  this.onDataChannelOpen();
3276
3280
  };
3277
3281
  dataChannel.onclose = () => {
3278
- logger$u.trace('dc.onClosed');
3282
+ logger$v.trace('dc.onClosed');
3279
3283
  this.doClose(false);
3280
3284
  };
3281
3285
  dataChannel.onerror = (err) => {
3282
- logger$u.warn('Data channel error', { err });
3286
+ logger$v.warn('Data channel error', { err });
3283
3287
  };
3284
3288
  dataChannel.onmessage = (msg) => {
3285
- logger$u.trace('dc.onmessage');
3289
+ logger$v.trace('dc.onmessage');
3286
3290
  this.emit('data', new Uint8Array(msg.data));
3287
3291
  };
3288
3292
  dataChannel.onbufferedamountlow = () => {
3289
- logger$u.trace('dc.onBufferedAmountLow');
3293
+ logger$v.trace('dc.onBufferedAmountLow');
3290
3294
  while (this.messageQueue.length > 0 && this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3291
3295
  const data = this.messageQueue.shift();
3292
3296
  this.dataChannel.send(data);
@@ -3325,6 +3329,431 @@ class WebrtcConnection extends EventEmitter {
3325
3329
  }
3326
3330
  }
3327
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
+
3328
3757
  const logger$t = new Logger('WebrtcConnectorRpcRemote');
3329
3758
  class WebrtcConnectorRpcRemote extends RpcRemote {
3330
3759
  requestConnection() {
@@ -7607,5 +8036,5 @@ class SimulatorTransport extends ConnectionManager {
7607
8036
  }
7608
8037
  }
7609
8038
 
7610
- 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 };
7611
8040
  //# sourceMappingURL=exports-browser.js.map