@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.
@@ -11,6 +11,7 @@ var runtimeRpc = require('@protobuf-ts/runtime-rpc');
11
11
  var uuid = require('uuid');
12
12
  var protoRpc = require('@streamr/proto-rpc');
13
13
  var ipaddr = require('ipaddr.js');
14
+ var Comlink = require('comlink');
14
15
  var websocket = require('websocket');
15
16
  var autocertifierClient = require('@streamr/autocertifier-client');
16
17
  var shuffle = require('lodash/shuffle');
@@ -21,6 +22,25 @@ var lruCache = require('lru-cache');
21
22
  var cdnLocation = require('@streamr/cdn-location');
22
23
  var Heap = require('heap');
23
24
 
25
+ function _interopNamespaceDefault(e) {
26
+ var n = Object.create(null);
27
+ if (e) {
28
+ Object.keys(e).forEach(function (k) {
29
+ if (k !== 'default') {
30
+ var d = Object.getOwnPropertyDescriptor(e, k);
31
+ Object.defineProperty(n, k, d.get ? d : {
32
+ enumerable: true,
33
+ get: function () { return e[k]; }
34
+ });
35
+ }
36
+ });
37
+ }
38
+ n.default = e;
39
+ return Object.freeze(n);
40
+ }
41
+
42
+ var Comlink__namespace = /*#__PURE__*/_interopNamespaceDefault(Comlink);
43
+
24
44
  const getPeerDistance = (nodeIdOrDataKeyRaw1, nodeIdOrDataKeyRaw2) => {
25
45
  return KBucket.distance(nodeIdOrDataKeyRaw1, nodeIdOrDataKeyRaw2);
26
46
  };
@@ -1800,7 +1820,7 @@ const createRandomConnectionId = () => {
1800
1820
  return uuid.v4();
1801
1821
  };
1802
1822
 
1803
- const logger$C = new utils.Logger('ManagedConnection');
1823
+ const logger$D = new utils.Logger('ManagedConnection');
1804
1824
  // ManagedConnection is a component used as a wrapper for IConnection after they have been successfully handshaked.
1805
1825
  // Should only be used in the ConnectionManager.
1806
1826
  class ManagedConnection extends eventemitter3.EventEmitter {
@@ -1830,7 +1850,7 @@ class ManagedConnection extends eventemitter3.EventEmitter {
1830
1850
  this.remotePeerDescriptor = peerDescriptor;
1831
1851
  }
1832
1852
  onDisconnected(gracefulLeave) {
1833
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1853
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1834
1854
  if (!this.replacedAsDuplicate) {
1835
1855
  this.emit('disconnected', gracefulLeave);
1836
1856
  }
@@ -1839,7 +1859,7 @@ class ManagedConnection extends eventemitter3.EventEmitter {
1839
1859
  // TODO: Can this be removed if ManagedConnections can never be duplicates?
1840
1860
  // Handle duplicates in the ConncetorFacade and no longer have PendingConnections in ConnectionManager
1841
1861
  replaceAsDuplicate() {
1842
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1862
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1843
1863
  this.replacedAsDuplicate = true;
1844
1864
  }
1845
1865
  send(data) {
@@ -1986,10 +2006,10 @@ class RpcRemote {
1986
2006
  }
1987
2007
  }
1988
2008
 
1989
- const logger$B = new utils.Logger('ConnectionLockRpcRemote');
2009
+ const logger$C = new utils.Logger('ConnectionLockRpcRemote');
1990
2010
  class ConnectionLockRpcRemote extends RpcRemote {
1991
2011
  async lockRequest(lockId) {
1992
- logger$B.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
2012
+ logger$C.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
1993
2013
  const request = {
1994
2014
  lockId
1995
2015
  };
@@ -1999,12 +2019,12 @@ class ConnectionLockRpcRemote extends RpcRemote {
1999
2019
  return res.accepted;
2000
2020
  }
2001
2021
  catch (err) {
2002
- logger$B.debug('Connection lock rejected', { err });
2022
+ logger$C.debug('Connection lock rejected', { err });
2003
2023
  return false;
2004
2024
  }
2005
2025
  }
2006
2026
  unlockRequest(lockId) {
2007
- logger$B.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
2027
+ logger$C.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
2008
2028
  const request = {
2009
2029
  lockId
2010
2030
  };
@@ -2012,11 +2032,11 @@ class ConnectionLockRpcRemote extends RpcRemote {
2012
2032
  notification: true
2013
2033
  });
2014
2034
  this.getClient().unlockRequest(request, options).catch((_e) => {
2015
- logger$B.trace('failed to send unlockRequest');
2035
+ logger$C.trace('failed to send unlockRequest');
2016
2036
  });
2017
2037
  }
2018
2038
  async gracefulDisconnect(disconnectMode) {
2019
- logger$B.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2039
+ logger$C.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2020
2040
  const request = {
2021
2041
  disconnectMode
2022
2042
  };
@@ -2028,7 +2048,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2028
2048
  await this.getClient().gracefulDisconnect(request, options);
2029
2049
  }
2030
2050
  async setPrivate(isPrivate) {
2031
- logger$B.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2051
+ logger$C.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2032
2052
  const request = {
2033
2053
  isPrivate
2034
2054
  };
@@ -2040,7 +2060,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2040
2060
  }
2041
2061
  }
2042
2062
 
2043
- const logger$A = new utils.Logger('ConnectionLockRpcLocal');
2063
+ const logger$B = new utils.Logger('ConnectionLockRpcLocal');
2044
2064
  class ConnectionLockRpcLocal {
2045
2065
  options;
2046
2066
  constructor(options) {
@@ -2069,7 +2089,7 @@ class ConnectionLockRpcLocal {
2069
2089
  }
2070
2090
  async gracefulDisconnect(disconnectNotice, context) {
2071
2091
  const senderPeerDescriptor = context.incomingSourceDescriptor;
2072
- logger$A.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2092
+ logger$B.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2073
2093
  if (disconnectNotice.disconnectMode === DisconnectMode.LEAVING) {
2074
2094
  await this.options.closeConnection(senderPeerDescriptor, true, 'graceful leave notified');
2075
2095
  }
@@ -2120,7 +2140,7 @@ var NatType;
2120
2140
  NatType["OPEN_INTERNET"] = "open_internet";
2121
2141
  NatType["UNKNOWN"] = "unknown";
2122
2142
  })(NatType || (NatType = {}));
2123
- const logger$z = new utils.Logger('ConnectionManager');
2143
+ const logger$A = new utils.Logger('ConnectionManager');
2124
2144
  var ConnectionManagerState;
2125
2145
  (function (ConnectionManagerState) {
2126
2146
  ConnectionManagerState["IDLE"] = "idle";
@@ -2170,7 +2190,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2170
2190
  getLocalPeerDescriptor: () => this.getLocalPeerDescriptor(),
2171
2191
  setPrivate: (id, isPrivate) => {
2172
2192
  if (!this.options.allowIncomingPrivateConnections) {
2173
- logger$z.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
2193
+ logger$A.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
2174
2194
  return;
2175
2195
  }
2176
2196
  if (isPrivate) {
@@ -2204,7 +2224,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2204
2224
  const connection = endpoint.connection;
2205
2225
  const nodeId = connection.getNodeId();
2206
2226
  if (!this.locks.isLocked(nodeId) && !this.locks.isPrivate(nodeId) && Date.now() - connection.getLastUsedTimestamp() > maxIdleTime) {
2207
- logger$z.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2227
+ logger$A.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2208
2228
  disconnectionCandidates.addContact(connection);
2209
2229
  }
2210
2230
  }
@@ -2212,7 +2232,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2212
2232
  const disconnectables = disconnectionCandidates.getFurthestContacts(this.endpoints.size - maxConnections);
2213
2233
  for (const disconnectable of disconnectables) {
2214
2234
  const peerDescriptor = disconnectable.getPeerDescriptor();
2215
- logger$z.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2235
+ logger$A.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2216
2236
  this.gracefullyDisconnectAsync(peerDescriptor, DisconnectMode.NORMAL).catch((_e) => { });
2217
2237
  }
2218
2238
  }
@@ -2221,11 +2241,11 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2221
2241
  throw new CouldNotStart(`Cannot start already ${this.state} module`);
2222
2242
  }
2223
2243
  this.state = ConnectionManagerState.RUNNING;
2224
- logger$z.trace(`Starting ConnectionManager...`);
2244
+ logger$A.trace(`Starting ConnectionManager...`);
2225
2245
  await this.connectorFacade.start((connection) => this.onNewConnection(connection), (nodeId) => this.hasConnection(nodeId), this);
2226
2246
  // Garbage collection of connections
2227
2247
  this.disconnectorIntervalRef = setInterval(() => {
2228
- logger$z.trace('disconnectorInterval');
2248
+ logger$A.trace('disconnectorInterval');
2229
2249
  const LAST_USED_LIMIT = 20000;
2230
2250
  this.garbageCollectConnections(this.options.maxConnections ?? 80, LAST_USED_LIMIT);
2231
2251
  }, 5000); // TODO use options option or named constant?
@@ -2235,7 +2255,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2235
2255
  return;
2236
2256
  }
2237
2257
  this.state = ConnectionManagerState.STOPPING;
2238
- logger$z.trace(`Stopping ConnectionManager`);
2258
+ logger$A.trace(`Stopping ConnectionManager`);
2239
2259
  if (this.disconnectorIntervalRef) {
2240
2260
  clearInterval(this.disconnectorIntervalRef);
2241
2261
  }
@@ -2245,23 +2265,23 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2245
2265
  await this.gracefullyDisconnectAsync(endpoint.connection.getPeerDescriptor(), DisconnectMode.LEAVING);
2246
2266
  }
2247
2267
  catch (e) {
2248
- logger$z.error(e);
2268
+ logger$A.error(e);
2249
2269
  }
2250
2270
  }
2251
2271
  else {
2252
2272
  const connection = endpoint.connection;
2253
- logger$z.trace('handshake of connection not completed, force-closing');
2273
+ logger$A.trace('handshake of connection not completed, force-closing');
2254
2274
  // TODO use options option or named constant?
2255
2275
  const eventReceived = utils.waitForEvent(connection, 'disconnected', 2000);
2256
2276
  // TODO should we have some handling for this floating promise?
2257
2277
  connection.close(true);
2258
2278
  try {
2259
2279
  await eventReceived;
2260
- logger$z.trace('resolving after receiving disconnected event from non-handshaked connection');
2280
+ logger$A.trace('resolving after receiving disconnected event from non-handshaked connection');
2261
2281
  }
2262
2282
  catch (e) {
2263
2283
  endpoint.buffer.reject();
2264
- logger$z.trace('force-closing non-handshaked connection timed out ' + e);
2284
+ logger$A.trace('force-closing non-handshaked connection timed out ' + e);
2265
2285
  }
2266
2286
  }
2267
2287
  }));
@@ -2290,7 +2310,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2290
2310
  throw new CannotConnectToSelf('Cannot send to self');
2291
2311
  }
2292
2312
  const nodeId = toNodeId(peerDescriptor);
2293
- logger$z.trace(`Sending message to: ${nodeId}`);
2313
+ logger$A.trace(`Sending message to: ${nodeId}`);
2294
2314
  message = {
2295
2315
  ...message,
2296
2316
  sourceDescriptor: this.getLocalPeerDescriptor()
@@ -2345,13 +2365,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2345
2365
  }
2346
2366
  handleMessage(message) {
2347
2367
  const messageType = message.body.oneofKind;
2348
- logger$z.trace('Received message of type ' + messageType);
2368
+ logger$A.trace('Received message of type ' + messageType);
2349
2369
  if (messageType !== 'rpcMessage') {
2350
- logger$z.trace('Filtered out non-RPC message of type ' + messageType);
2370
+ logger$A.trace('Filtered out non-RPC message of type ' + messageType);
2351
2371
  return;
2352
2372
  }
2353
2373
  if (this.duplicateMessageDetector.isMostLikelyDuplicate(message.messageId)) {
2354
- logger$z.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2374
+ logger$A.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2355
2375
  + ' ' + message.serviceId + ' ' + message.messageId);
2356
2376
  return;
2357
2377
  }
@@ -2360,7 +2380,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2360
2380
  this.rpcCommunicator?.handleMessageFromPeer(message);
2361
2381
  }
2362
2382
  else {
2363
- logger$z.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2383
+ logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2364
2384
  + ' ' + message.serviceId + ' ' + message.messageId);
2365
2385
  this.emit('message', message);
2366
2386
  }
@@ -2377,7 +2397,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2377
2397
  }
2378
2398
  catch (e) {
2379
2399
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2380
- logger$z.debug(`Parsing incoming data into Message failed: ${e}`);
2400
+ logger$A.debug(`Parsing incoming data into Message failed: ${e}`);
2381
2401
  return;
2382
2402
  }
2383
2403
  message.sourceDescriptor = peerDescriptor;
@@ -2386,7 +2406,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2386
2406
  }
2387
2407
  catch (e) {
2388
2408
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2389
- logger$z.debug(`Handling incoming data failed: ${e}`);
2409
+ logger$A.debug(`Handling incoming data failed: ${e}`);
2390
2410
  }
2391
2411
  }
2392
2412
  onConnected(peerDescriptor, connection) {
@@ -2399,7 +2419,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2399
2419
  const pendingConnection = endpoint.connection;
2400
2420
  const buffer = outputBuffer.getBuffer();
2401
2421
  while (buffer.length > 0) {
2402
- logger$z.trace('emptying buffer');
2422
+ logger$A.trace('emptying buffer');
2403
2423
  managedConnection.send(buffer.shift());
2404
2424
  }
2405
2425
  outputBuffer.resolve();
@@ -2416,7 +2436,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2416
2436
  }
2417
2437
  onDisconnected(peerDescriptor, gracefulLeave) {
2418
2438
  const nodeId = toNodeId(peerDescriptor);
2419
- logger$z.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2439
+ logger$A.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2420
2440
  const endpoint = this.endpoints.get(nodeId);
2421
2441
  if (endpoint) {
2422
2442
  this.locks.clearAllLocks(nodeId);
@@ -2424,7 +2444,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2424
2444
  endpoint.buffer.reject();
2425
2445
  }
2426
2446
  this.endpoints.delete(nodeId);
2427
- logger$z.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2447
+ logger$A.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2428
2448
  this.emit('disconnected', peerDescriptor, gracefulLeave);
2429
2449
  this.onConnectionCountChange();
2430
2450
  }
@@ -2433,7 +2453,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2433
2453
  if (this.state === ConnectionManagerState.STOPPED) {
2434
2454
  return false;
2435
2455
  }
2436
- logger$z.trace('onNewConnection()');
2456
+ logger$A.trace('onNewConnection()');
2437
2457
  if (!this.acceptNewConnection(connection)) {
2438
2458
  return false;
2439
2459
  }
@@ -2443,7 +2463,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2443
2463
  }
2444
2464
  acceptNewConnection(newConnection) {
2445
2465
  const nodeId = toNodeId(newConnection.getPeerDescriptor());
2446
- logger$z.trace(nodeId + ' acceptNewConnection()');
2466
+ logger$A.trace(nodeId + ' acceptNewConnection()');
2447
2467
  if (this.endpoints.has(nodeId)) {
2448
2468
  if (getOfferer(toNodeId(this.getLocalPeerDescriptor()), nodeId) === 'remote') {
2449
2469
  let buffer;
@@ -2452,14 +2472,14 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2452
2472
  // Could be related to WS client connections not realizing that they have been disconnected.
2453
2473
  // Makes refactoring duplicate connection handling to the connectors very difficult.
2454
2474
  if (this.endpoints.get(nodeId).connected) {
2455
- logger$z.debug('replacing connected connection', { nodeId });
2475
+ logger$A.debug('replacing connected connection', { nodeId });
2456
2476
  buffer = new OutputBuffer();
2457
2477
  }
2458
2478
  else {
2459
2479
  buffer = endpoint.buffer;
2460
2480
  }
2461
2481
  const oldConnection = endpoint.connection;
2462
- logger$z.trace('replaced: ' + nodeId);
2482
+ logger$A.trace('replaced: ' + nodeId);
2463
2483
  oldConnection.replaceAsDuplicate();
2464
2484
  this.endpoints.set(nodeId, { connected: false, connection: newConnection, buffer: buffer });
2465
2485
  return true;
@@ -2468,7 +2488,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2468
2488
  return false;
2469
2489
  }
2470
2490
  }
2471
- logger$z.trace(nodeId + ' added to connections at acceptNewConnection');
2491
+ logger$A.trace(nodeId + ' added to connections at acceptNewConnection');
2472
2492
  this.endpoints.set(nodeId, {
2473
2493
  connected: false,
2474
2494
  buffer: new OutputBuffer(),
@@ -2478,14 +2498,14 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2478
2498
  }
2479
2499
  async closeConnection(peerDescriptor, gracefulLeave, reason) {
2480
2500
  const nodeId = toNodeId(peerDescriptor);
2481
- logger$z.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2501
+ logger$A.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2482
2502
  this.locks.clearAllLocks(nodeId);
2483
2503
  if (this.endpoints.has(nodeId)) {
2484
2504
  const connectionToClose = this.endpoints.get(nodeId).connection;
2485
2505
  await connectionToClose.close(gracefulLeave);
2486
2506
  }
2487
2507
  else {
2488
- logger$z.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
2508
+ logger$A.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
2489
2509
  this.emit('disconnected', peerDescriptor, false);
2490
2510
  }
2491
2511
  }
@@ -2497,8 +2517,8 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2497
2517
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2498
2518
  this.locks.addLocalLocked(nodeId, lockId);
2499
2519
  rpcRemote.lockRequest(lockId)
2500
- .then((_accepted) => logger$z.trace('LockRequest successful'))
2501
- .catch((err) => { logger$z.debug(err); });
2520
+ .then((_accepted) => logger$A.trace('LockRequest successful'))
2521
+ .catch((err) => { logger$A.debug(err); });
2502
2522
  }
2503
2523
  unlockConnection(targetDescriptor, lockId) {
2504
2524
  if (this.state === ConnectionManagerState.STOPPED || areEqualPeerDescriptors(targetDescriptor, this.getLocalPeerDescriptor())) {
@@ -2550,7 +2570,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2550
2570
  async gracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2551
2571
  const endpoint = this.endpoints.get(toNodeId(targetDescriptor));
2552
2572
  if (!endpoint) {
2553
- logger$z.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2573
+ logger$A.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2554
2574
  return;
2555
2575
  }
2556
2576
  if (endpoint.connected) {
@@ -2559,15 +2579,15 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2559
2579
  // TODO use options option or named constant?
2560
2580
  // eslint-disable-next-line promise/catch-or-return
2561
2581
  utils.waitForEvent(connection, 'disconnected', 2000).then(() => {
2562
- logger$z.trace('disconnected event received in gracefullyDisconnectAsync()');
2582
+ logger$A.trace('disconnected event received in gracefullyDisconnectAsync()');
2563
2583
  })
2564
2584
  .catch((e) => {
2565
- logger$z.trace('force-closing connection after timeout ' + e);
2585
+ logger$A.trace('force-closing connection after timeout ' + e);
2566
2586
  // TODO should we have some handling for this floating promise?
2567
2587
  connection.close(true);
2568
2588
  })
2569
2589
  .finally(() => {
2570
- logger$z.trace('resolving after receiving disconnected event');
2590
+ logger$A.trace('resolving after receiving disconnected event');
2571
2591
  resolve();
2572
2592
  });
2573
2593
  });
@@ -2582,13 +2602,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2582
2602
  }
2583
2603
  async doGracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2584
2604
  const nodeId = toNodeId(targetDescriptor);
2585
- logger$z.trace(nodeId + ' gracefullyDisconnectAsync()');
2605
+ logger$A.trace(nodeId + ' gracefullyDisconnectAsync()');
2586
2606
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2587
2607
  try {
2588
2608
  await rpcRemote.gracefulDisconnect(disconnectMode);
2589
2609
  }
2590
2610
  catch (ex) {
2591
- logger$z.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2611
+ logger$A.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2592
2612
  }
2593
2613
  }
2594
2614
  getConnections() {
@@ -2655,7 +2675,7 @@ function protoToString(protoObj, objectType) {
2655
2675
  return ret;
2656
2676
  }
2657
2677
 
2658
- const logger$y = new utils.Logger('SimulatorConnection');
2678
+ const logger$z = new utils.Logger('SimulatorConnection');
2659
2679
  class SimulatorConnection extends Connection {
2660
2680
  stopped = false;
2661
2681
  localPeerDescriptor;
@@ -2677,46 +2697,46 @@ class SimulatorConnection extends Connection {
2677
2697
  this.doDisconnect = this.doDisconnect.bind(this);
2678
2698
  }
2679
2699
  send(data) {
2680
- logger$y.trace('send()');
2700
+ logger$z.trace('send()');
2681
2701
  if (!this.stopped) {
2682
2702
  this.simulator.send(this, data);
2683
2703
  }
2684
2704
  else {
2685
2705
  const localNodeId = toNodeId(this.localPeerDescriptor);
2686
2706
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2687
- logger$y.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
2707
+ logger$z.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
2688
2708
  }
2689
2709
  }
2690
2710
  async close(gracefulLeave) {
2691
2711
  const localNodeId = toNodeId(this.localPeerDescriptor);
2692
2712
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2693
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close()');
2713
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close()');
2694
2714
  if (!this.stopped) {
2695
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2715
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2696
2716
  this.stopped = true;
2697
2717
  try {
2698
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2718
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2699
2719
  this.simulator.close(this);
2700
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2720
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2701
2721
  }
2702
2722
  catch (e) {
2703
- logger$y.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2723
+ logger$z.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2704
2724
  }
2705
2725
  finally {
2706
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2726
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2707
2727
  this.doDisconnect(gracefulLeave);
2708
2728
  }
2709
2729
  }
2710
2730
  else {
2711
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
2731
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
2712
2732
  }
2713
2733
  }
2714
2734
  connect() {
2715
2735
  if (!this.stopped) {
2716
- logger$y.trace('connect() called');
2736
+ logger$z.trace('connect() called');
2717
2737
  this.simulator.connect(this, this.targetPeerDescriptor, (error) => {
2718
2738
  if (error !== undefined) {
2719
- logger$y.trace(error);
2739
+ logger$z.trace(error);
2720
2740
  this.doDisconnect(false);
2721
2741
  }
2722
2742
  else {
@@ -2725,46 +2745,46 @@ class SimulatorConnection extends Connection {
2725
2745
  });
2726
2746
  }
2727
2747
  else {
2728
- logger$y.trace('tried to connect() a stopped connection');
2748
+ logger$z.trace('tried to connect() a stopped connection');
2729
2749
  }
2730
2750
  }
2731
2751
  handleIncomingData(data) {
2732
2752
  if (!this.stopped) {
2733
- logger$y.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2753
+ logger$z.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2734
2754
  this.emit('data', data);
2735
2755
  }
2736
2756
  else {
2737
- logger$y.trace('tried to call handleIncomingData() a stopped connection');
2757
+ logger$z.trace('tried to call handleIncomingData() a stopped connection');
2738
2758
  }
2739
2759
  }
2740
2760
  handleIncomingDisconnection() {
2741
2761
  if (!this.stopped) {
2742
2762
  const localNodeId = toNodeId(this.localPeerDescriptor);
2743
- logger$y.trace(localNodeId + ' handleIncomingDisconnection()');
2763
+ logger$z.trace(localNodeId + ' handleIncomingDisconnection()');
2744
2764
  this.stopped = true;
2745
2765
  this.doDisconnect(false);
2746
2766
  }
2747
2767
  else {
2748
- logger$y.trace('tried to call handleIncomingDisconnection() a stopped connection');
2768
+ logger$z.trace('tried to call handleIncomingDisconnection() a stopped connection');
2749
2769
  }
2750
2770
  }
2751
2771
  destroy() {
2752
2772
  const localNodeId = toNodeId(this.localPeerDescriptor);
2753
2773
  if (!this.stopped) {
2754
- logger$y.trace(localNodeId + ' destroy()');
2774
+ logger$z.trace(localNodeId + ' destroy()');
2755
2775
  this.removeAllListeners();
2756
2776
  this.close(false).catch((_e) => { });
2757
2777
  }
2758
2778
  else {
2759
- logger$y.trace(localNodeId + ' tried to call destroy() a stopped connection');
2779
+ logger$z.trace(localNodeId + ' tried to call destroy() a stopped connection');
2760
2780
  }
2761
2781
  }
2762
2782
  doDisconnect(gracefulLeave) {
2763
2783
  const localNodeId = toNodeId(this.localPeerDescriptor);
2764
2784
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2765
- logger$y.trace(localNodeId + ' doDisconnect()');
2785
+ logger$z.trace(localNodeId + ' doDisconnect()');
2766
2786
  this.stopped = true;
2767
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2787
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2768
2788
  this.emit('disconnected', gracefulLeave);
2769
2789
  }
2770
2790
  }
@@ -2802,9 +2822,9 @@ const parseVersion = (version) => {
2802
2822
  }
2803
2823
  };
2804
2824
 
2805
- var version = "103.3.1";
2825
+ var version = "103.6.0-rc.0";
2806
2826
 
2807
- const logger$x = new utils.Logger('Handshaker');
2827
+ const logger$y = new utils.Logger('Handshaker');
2808
2828
  // Optimally the Outgoing and Incoming Handshakers could be their own separate classes
2809
2829
  // However, in cases where the PeerDescriptor of the other end of the connection can be known
2810
2830
  // only after a HandshakeRequest a base Handshaker class is needed as the IncomingHandshaker currently
@@ -2826,7 +2846,7 @@ const createOutgoingHandshaker = (localPeerDescriptor, pendingConnection, connec
2826
2846
  }
2827
2847
  };
2828
2848
  const handshakeCompletedListener = (peerDescriptor) => {
2829
- logger$x.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2849
+ logger$y.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2830
2850
  pendingConnection.onHandshakeCompleted(connection);
2831
2851
  stopHandshaker();
2832
2852
  };
@@ -2926,12 +2946,12 @@ class Handshaker extends eventemitter3.EventEmitter {
2926
2946
  try {
2927
2947
  const message = Message.fromBinary(data);
2928
2948
  if (message.body.oneofKind === 'handshakeRequest') {
2929
- logger$x.trace('handshake request received');
2949
+ logger$y.trace('handshake request received');
2930
2950
  const handshake = message.body.handshakeRequest;
2931
2951
  this.emit('handshakeRequest', handshake.sourcePeerDescriptor, handshake.protocolVersion, handshake.targetPeerDescriptor);
2932
2952
  }
2933
2953
  if (message.body.oneofKind === 'handshakeResponse') {
2934
- logger$x.trace('handshake response received');
2954
+ logger$y.trace('handshake response received');
2935
2955
  const handshake = message.body.handshakeResponse;
2936
2956
  const error = !isMaybeSupportedProtocolVersion(handshake.protocolVersion)
2937
2957
  ? HandshakeError.UNSUPPORTED_PROTOCOL_VERSION : handshake.error;
@@ -2944,18 +2964,18 @@ class Handshaker extends eventemitter3.EventEmitter {
2944
2964
  }
2945
2965
  }
2946
2966
  catch (err) {
2947
- logger$x.debug('error while parsing handshake message', err);
2967
+ logger$y.debug('error while parsing handshake message', err);
2948
2968
  }
2949
2969
  }
2950
2970
  sendHandshakeRequest(remotePeerDescriptor) {
2951
2971
  const msg = createHandshakeRequest(this.localPeerDescriptor, remotePeerDescriptor);
2952
2972
  this.connection.send(Message.toBinary(msg));
2953
- logger$x.trace('handshake request sent');
2973
+ logger$y.trace('handshake request sent');
2954
2974
  }
2955
2975
  sendHandshakeResponse(error) {
2956
2976
  const msg = createHandshakeResponse(this.localPeerDescriptor, error);
2957
2977
  this.connection.send(Message.toBinary(msg));
2958
- logger$x.trace('handshake response sent');
2978
+ logger$y.trace('handshake response sent');
2959
2979
  }
2960
2980
  stop() {
2961
2981
  this.connection.off('data', this.onDataListener);
@@ -2963,7 +2983,7 @@ class Handshaker extends eventemitter3.EventEmitter {
2963
2983
  }
2964
2984
  }
2965
2985
 
2966
- const logger$w = new utils.Logger('PendingConnection');
2986
+ const logger$x = new utils.Logger('PendingConnection');
2967
2987
  // PendingConnection is used as a reference to a connection that should be opened and handshaked to a given PeerDescriptor
2968
2988
  // It does not hold a connection internally. The public method onHandshakedCompleted should be called once a connection for the
2969
2989
  // remotePeerDescriptor is opened and handshaked successfully.
@@ -2981,7 +3001,7 @@ class PendingConnection extends eventemitter3.EventEmitter {
2981
3001
  }, timeout, this.connectingAbortController.signal);
2982
3002
  }
2983
3003
  replaceAsDuplicate() {
2984
- logger$w.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
3004
+ logger$x.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
2985
3005
  this.replacedAsDuplicate = true;
2986
3006
  }
2987
3007
  onHandshakeCompleted(connection) {
@@ -3012,7 +3032,7 @@ class PendingConnection extends eventemitter3.EventEmitter {
3012
3032
  }
3013
3033
  }
3014
3034
 
3015
- const logger$v = new utils.Logger('SimulatorConnector');
3035
+ const logger$w = new utils.Logger('SimulatorConnector');
3016
3036
  class SimulatorConnector {
3017
3037
  connectingConnections = new Map();
3018
3038
  stopped = false;
@@ -3025,7 +3045,7 @@ class SimulatorConnector {
3025
3045
  this.onNewConnection = onNewConnection;
3026
3046
  }
3027
3047
  connect(targetPeerDescriptor) {
3028
- logger$v.trace('connect() ' + toNodeId(targetPeerDescriptor));
3048
+ logger$w.trace('connect() ' + toNodeId(targetPeerDescriptor));
3029
3049
  const nodeId = toNodeId(targetPeerDescriptor);
3030
3050
  const existingConnection = this.connectingConnections.get(nodeId);
3031
3051
  if (existingConnection) {
@@ -3054,18 +3074,18 @@ class SimulatorConnector {
3054
3074
  // connection is incoming, so remotePeerDescriptor is localPeerDescriptor
3055
3075
  const remotePeerDescriptor = sourceConnection.localPeerDescriptor;
3056
3076
  const remoteNodeId = toNodeId(sourceConnection.localPeerDescriptor);
3057
- logger$v.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3077
+ logger$w.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3058
3078
  if (this.stopped) {
3059
3079
  return;
3060
3080
  }
3061
3081
  const connection = new SimulatorConnection(this.localPeerDescriptor, remotePeerDescriptor, exports.ConnectionType.SIMULATOR_SERVER, this.simulator);
3062
3082
  const pendingConnection = new PendingConnection(remotePeerDescriptor);
3063
3083
  const handshaker = createIncomingHandshaker(this.localPeerDescriptor, pendingConnection, connection);
3064
- logger$v.trace('connected');
3084
+ logger$w.trace('connected');
3065
3085
  handshaker.once('handshakeRequest', () => {
3066
- logger$v.trace(remoteNodeId + ' incoming handshake request');
3086
+ logger$w.trace(remoteNodeId + ' incoming handshake request');
3067
3087
  if (this.onNewConnection(pendingConnection)) {
3068
- logger$v.trace(remoteNodeId + ' calling acceptHandshake');
3088
+ logger$w.trace(remoteNodeId + ' calling acceptHandshake');
3069
3089
  acceptHandshake(handshaker, pendingConnection, connection);
3070
3090
  }
3071
3091
  else {
@@ -3105,6 +3125,8 @@ class ListeningRpcCommunicator extends RoutingRpcCommunicator {
3105
3125
  }
3106
3126
  }
3107
3127
 
3128
+ const isWorkerEnvironment = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
3129
+
3108
3130
  var RtcDescription;
3109
3131
  (function (RtcDescription) {
3110
3132
  RtcDescription["OFFER"] = "offer";
@@ -3119,8 +3141,8 @@ var DisconnectedRtcPeerConnectionStateEnum;
3119
3141
  DisconnectedRtcPeerConnectionStateEnum["FAILED"] = "failed";
3120
3142
  DisconnectedRtcPeerConnectionStateEnum["CLOSED"] = "closed";
3121
3143
  })(DisconnectedRtcPeerConnectionStateEnum || (DisconnectedRtcPeerConnectionStateEnum = {}));
3122
- const logger$u = new utils.Logger('WebrtcConnection (browser)');
3123
- class WebrtcConnection extends eventemitter3.EventEmitter {
3144
+ const logger$v = new utils.Logger('DirectWebrtcConnection (browser)');
3145
+ class DirectWebrtcConnection extends eventemitter3.EventEmitter {
3124
3146
  connectionId;
3125
3147
  connectionType = exports.ConnectionType.WEBRTC;
3126
3148
  // We need to keep track of connection state ourselves because
@@ -3158,7 +3180,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3158
3180
  }
3159
3181
  };
3160
3182
  this.peerConnection.onicegatheringstatechange = () => {
3161
- logger$u.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3183
+ logger$v.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3162
3184
  };
3163
3185
  this.peerConnection.onconnectionstatechange = () => this.onStateChange();
3164
3186
  if (isOffering) {
@@ -3169,7 +3191,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3169
3191
  await this.peerConnection.setLocalDescription();
3170
3192
  }
3171
3193
  catch (err) {
3172
- logger$u.warn('Failed to set local description', { err });
3194
+ logger$v.warn('Failed to set local description', { err });
3173
3195
  }
3174
3196
  if (this.peerConnection.localDescription !== null) {
3175
3197
  this.emit('localDescription', this.peerConnection.localDescription?.sdp, this.peerConnection.localDescription?.type);
@@ -3197,14 +3219,14 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3197
3219
  clearTimeout(this.earlyTimeout);
3198
3220
  }
3199
3221
  catch (err) {
3200
- logger$u.warn('Failed to set remote description', { err });
3222
+ logger$v.warn('Failed to set remote description', { err });
3201
3223
  }
3202
3224
  if ((type.toLowerCase() === RtcDescription.OFFER) && (this.peerConnection !== undefined)) {
3203
3225
  try {
3204
3226
  await this.peerConnection.setLocalDescription();
3205
3227
  }
3206
3228
  catch (err) {
3207
- logger$u.warn('Failed to set local description', { err });
3229
+ logger$v.warn('Failed to set local description', { err });
3208
3230
  }
3209
3231
  if (this.peerConnection.localDescription !== null) {
3210
3232
  this.emit('localDescription', this.peerConnection.localDescription.sdp, this.peerConnection.localDescription.type);
@@ -3214,7 +3236,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3214
3236
  addRemoteCandidate(candidate, mid) {
3215
3237
  this.peerConnection?.addIceCandidate({ candidate: candidate, sdpMid: mid })
3216
3238
  .catch((err) => {
3217
- logger$u.warn('Failed to add ICE candidate', { err });
3239
+ logger$v.warn('Failed to add ICE candidate', { err });
3218
3240
  });
3219
3241
  }
3220
3242
  isOpen() {
@@ -3237,7 +3259,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3237
3259
  this.dataChannel.close();
3238
3260
  }
3239
3261
  catch (err) {
3240
- logger$u.warn('Failed to close data channel', { err });
3262
+ logger$v.warn('Failed to close data channel', { err });
3241
3263
  }
3242
3264
  }
3243
3265
  this.dataChannel = undefined;
@@ -3246,7 +3268,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3246
3268
  this.peerConnection.close();
3247
3269
  }
3248
3270
  catch (err) {
3249
- logger$u.warn('Failed to close connection', { err });
3271
+ logger$v.warn('Failed to close connection', { err });
3250
3272
  }
3251
3273
  }
3252
3274
  this.peerConnection = undefined;
@@ -3266,7 +3288,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3266
3288
  }
3267
3289
  }
3268
3290
  else {
3269
- logger$u.warn('Tried to send on a connection with last state ' + this.lastState);
3291
+ logger$v.warn('Tried to send on a connection with last state ' + this.lastState);
3270
3292
  }
3271
3293
  }
3272
3294
  setupDataChannel(dataChannel) {
@@ -3274,22 +3296,22 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3274
3296
  this.dataChannel.binaryType = 'arraybuffer';
3275
3297
  this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3276
3298
  dataChannel.onopen = () => {
3277
- logger$u.trace('dc.onOpen');
3299
+ logger$v.trace('dc.onOpen');
3278
3300
  this.onDataChannelOpen();
3279
3301
  };
3280
3302
  dataChannel.onclose = () => {
3281
- logger$u.trace('dc.onClosed');
3303
+ logger$v.trace('dc.onClosed');
3282
3304
  this.doClose(false);
3283
3305
  };
3284
3306
  dataChannel.onerror = (err) => {
3285
- logger$u.warn('Data channel error', { err });
3307
+ logger$v.warn('Data channel error', { err });
3286
3308
  };
3287
3309
  dataChannel.onmessage = (msg) => {
3288
- logger$u.trace('dc.onmessage');
3310
+ logger$v.trace('dc.onmessage');
3289
3311
  this.emit('data', new Uint8Array(msg.data));
3290
3312
  };
3291
3313
  dataChannel.onbufferedamountlow = () => {
3292
- logger$u.trace('dc.onBufferedAmountLow');
3314
+ logger$v.trace('dc.onBufferedAmountLow');
3293
3315
  while (this.messageQueue.length > 0 && this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3294
3316
  const data = this.messageQueue.shift();
3295
3317
  this.dataChannel.send(data);
@@ -3328,6 +3350,431 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3328
3350
  }
3329
3351
  }
3330
3352
 
3353
+ /**
3354
+ * WebrtcBridge — runs on the MAIN THREAD.
3355
+ *
3356
+ * Manages RTCPeerConnection instances on behalf of a worker that cannot
3357
+ * access them directly. Each logical connection is identified by a
3358
+ * `connectionId` string supplied by the worker.
3359
+ *
3360
+ * Signaling (ICE candidates, SDP offers/answers, connection-state changes)
3361
+ * is relayed to the worker through Comlink proxy callbacks.
3362
+ *
3363
+ * Once a DataChannel is created (offerer) or received (answerer) it is
3364
+ * **transferred** to the worker so that all data-path events fire inside
3365
+ * the worker's event loop — the main thread never touches data traffic.
3366
+ */
3367
+ // ── Bridge implementation ───────────────────────────────────────────
3368
+ class WebrtcBridge {
3369
+ connections = new Map();
3370
+ async start(connectionId, iceServers, isOffering, callbacks) {
3371
+ const pc = new RTCPeerConnection({ iceServers });
3372
+ const conn = {
3373
+ pc,
3374
+ callbacks,
3375
+ isOffering,
3376
+ makingOffer: false,
3377
+ };
3378
+ this.connections.set(connectionId, conn);
3379
+ // ── ICE candidates ──────────────────────────────────────
3380
+ pc.onicecandidate = (event) => {
3381
+ if (event.candidate !== null && event.candidate.sdpMid !== null) {
3382
+ callbacks.onLocalCandidate(event.candidate.candidate, event.candidate.sdpMid);
3383
+ }
3384
+ };
3385
+ // ── Connection state → forwarded to worker ──────────────
3386
+ pc.onconnectionstatechange = () => {
3387
+ callbacks.onConnectionStateChange(pc.connectionState);
3388
+ };
3389
+ // ── Offerer path ────────────────────────────────────────
3390
+ if (isOffering) {
3391
+ pc.onnegotiationneeded = async () => {
3392
+ conn.makingOffer = true;
3393
+ try {
3394
+ await pc.setLocalDescription();
3395
+ }
3396
+ catch (_err) {
3397
+ // intentionally swallowed – mirrors DirectWebrtcConnection
3398
+ }
3399
+ if (pc.localDescription !== null) {
3400
+ callbacks.onLocalDescription(pc.localDescription.sdp, pc.localDescription.type);
3401
+ }
3402
+ conn.makingOffer = false;
3403
+ };
3404
+ const dc = pc.createDataChannel('streamrDataChannel');
3405
+ // Transfer DataChannel ownership to the worker immediately.
3406
+ // The worker will attach onopen/onclose/onmessage handlers.
3407
+ callbacks.onDataChannel(Comlink__namespace.transfer(dc, [dc]));
3408
+ }
3409
+ else {
3410
+ // ── Answerer path ───────────────────────────────────
3411
+ pc.ondatachannel = (event) => {
3412
+ callbacks.onDataChannel(Comlink__namespace.transfer(event.channel, [event.channel]));
3413
+ };
3414
+ }
3415
+ }
3416
+ async setRemoteDescription(connectionId, description, type) {
3417
+ const conn = this.connections.get(connectionId);
3418
+ if (!conn) {
3419
+ return false;
3420
+ }
3421
+ const lowerType = type.toLowerCase();
3422
+ // Perfect-negotiation collision detection
3423
+ const offerCollision = lowerType === 'offer' &&
3424
+ (conn.makingOffer || conn.pc.signalingState !== 'stable');
3425
+ if (conn.isOffering && offerCollision) {
3426
+ return false;
3427
+ }
3428
+ try {
3429
+ await conn.pc.setRemoteDescription({ sdp: description, type: lowerType });
3430
+ }
3431
+ catch (_err) {
3432
+ return false;
3433
+ }
3434
+ // If we received an offer, create an answer
3435
+ if (lowerType === 'offer') {
3436
+ try {
3437
+ await conn.pc.setLocalDescription();
3438
+ }
3439
+ catch (_err) {
3440
+ // intentionally swallowed
3441
+ }
3442
+ if (conn.pc.localDescription !== null) {
3443
+ conn.callbacks.onLocalDescription(conn.pc.localDescription.sdp, conn.pc.localDescription.type);
3444
+ }
3445
+ }
3446
+ return true;
3447
+ }
3448
+ async addRemoteCandidate(connectionId, candidate, mid) {
3449
+ const conn = this.connections.get(connectionId);
3450
+ if (!conn) {
3451
+ return;
3452
+ }
3453
+ try {
3454
+ await conn.pc.addIceCandidate({ candidate, sdpMid: mid });
3455
+ }
3456
+ catch (_err) {
3457
+ // intentionally swallowed
3458
+ }
3459
+ }
3460
+ async renameConnection(oldId, newId) {
3461
+ const conn = this.connections.get(oldId);
3462
+ if (conn) {
3463
+ this.connections.delete(oldId);
3464
+ this.connections.set(newId, conn);
3465
+ }
3466
+ }
3467
+ async close(connectionId) {
3468
+ const conn = this.connections.get(connectionId);
3469
+ if (!conn) {
3470
+ return;
3471
+ }
3472
+ this.connections.delete(connectionId);
3473
+ // Tear down event handlers
3474
+ conn.pc.onicecandidate = null;
3475
+ conn.pc.onconnectionstatechange = null;
3476
+ conn.pc.onnegotiationneeded = null;
3477
+ conn.pc.ondatachannel = null;
3478
+ try {
3479
+ conn.pc.close();
3480
+ }
3481
+ catch (_err) {
3482
+ // intentionally swallowed
3483
+ }
3484
+ }
3485
+ }
3486
+
3487
+ /**
3488
+ * Call this function on the **main thread** before the worker starts using
3489
+ * WebRTC connections. It creates a dedicated `MessageChannel`, exposes a
3490
+ * {@link WebrtcBridge} instance on one port, and sends the other port to
3491
+ * the worker so that `WorkerWebrtcConnection` can reach the bridge.
3492
+ *
3493
+ * @example
3494
+ * ```ts
3495
+ * import { installWebrtcBridge } from '@streamr/dht'
3496
+ *
3497
+ * const worker = new Worker('./my-worker.ts', { type: 'module' })
3498
+ * installWebrtcBridge(worker)
3499
+ * ```
3500
+ */
3501
+ const WEBRTC_BRIDGE_PORT_MESSAGE_TYPE = 'streamr-webrtc-bridge-port';
3502
+ function installWebrtcBridge(worker) {
3503
+ const bridge = new WebrtcBridge();
3504
+ const channel = new MessageChannel();
3505
+ // Expose the bridge API on port1 — the worker will Comlink.wrap(port2).
3506
+ Comlink__namespace.expose(bridge, channel.port1);
3507
+ // Send port2 to the worker. It is transferred (not cloned).
3508
+ worker.postMessage({ type: WEBRTC_BRIDGE_PORT_MESSAGE_TYPE, port: channel.port2 }, [channel.port2]);
3509
+ }
3510
+
3511
+ /**
3512
+ * WorkerWebrtcConnection — runs inside a **Web Worker**.
3513
+ *
3514
+ * Implements the same IWebrtcConnection + IConnection interfaces as
3515
+ * DirectWebrtcConnection, but delegates RTCPeerConnection management to
3516
+ * the main-thread {@link WebrtcBridge} via Comlink.
3517
+ *
3518
+ * The RTCDataChannel is **transferred** from the main thread and lives
3519
+ * entirely in the worker — all data events (onmessage, onopen, onclose,
3520
+ * onbufferedamountlow) fire in the worker's event loop. The main thread
3521
+ * is never involved in the data path.
3522
+ */
3523
+ // ── Module-level bridge client (initialized once per worker) ────────
3524
+ let resolveBridgeProxy;
3525
+ const bridgeProxyPromise = new Promise((resolve) => {
3526
+ resolveBridgeProxy = resolve;
3527
+ });
3528
+ // Listen for the bridge port message from the main thread.
3529
+ // This is guarded so it only runs inside a worker context.
3530
+ if (isWorkerEnvironment) {
3531
+ const handler = (e) => {
3532
+ if (e.data?.type === WEBRTC_BRIDGE_PORT_MESSAGE_TYPE && e.data.port) {
3533
+ const proxy = Comlink__namespace.wrap(e.data.port);
3534
+ resolveBridgeProxy(proxy);
3535
+ self.removeEventListener('message', handler);
3536
+ }
3537
+ };
3538
+ self.addEventListener('message', handler);
3539
+ }
3540
+ function getBridgeProxy() {
3541
+ return bridgeProxyPromise;
3542
+ }
3543
+ // ── Disconnection states ────────────────────────────────────────────
3544
+ var DisconnectedState;
3545
+ (function (DisconnectedState) {
3546
+ DisconnectedState["DISCONNECTED"] = "disconnected";
3547
+ DisconnectedState["FAILED"] = "failed";
3548
+ DisconnectedState["CLOSED"] = "closed";
3549
+ })(DisconnectedState || (DisconnectedState = {}));
3550
+ const logger$u = new utils.Logger('WorkerWebrtcConnection');
3551
+ // ── WorkerWebrtcConnection ──────────────────────────────────────────
3552
+ class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
3553
+ connectionId;
3554
+ connectionType = exports.ConnectionType.WEBRTC;
3555
+ iceServers;
3556
+ bufferThresholdHigh;
3557
+ bufferThresholdLow;
3558
+ dataChannel;
3559
+ bridge;
3560
+ closed = false;
3561
+ connected = false;
3562
+ earlyTimeout;
3563
+ messageQueue = [];
3564
+ startPromise;
3565
+ constructor(params) {
3566
+ super();
3567
+ this.connectionId = createRandomConnectionId();
3568
+ this.iceServers = params.iceServers ?? [];
3569
+ this.bufferThresholdHigh = params.bufferThresholdHigh ?? 2 ** 17;
3570
+ this.bufferThresholdLow = params.bufferThresholdLow ?? 2 ** 15;
3571
+ this.earlyTimeout = setTimeout(() => {
3572
+ this.doClose(false, 'timed out due to remote descriptor not being set');
3573
+ }, EARLY_TIMEOUT);
3574
+ }
3575
+ // ── IWebrtcConnection ───────────────────────────────────────
3576
+ start(isOffering) {
3577
+ this.startPromise = this.doStart(isOffering);
3578
+ this.startPromise.catch((err) => {
3579
+ logger$u.warn('Failed to start worker WebRTC connection', { err });
3580
+ this.doClose(false, 'Failed to start');
3581
+ });
3582
+ }
3583
+ async doStart(isOffering) {
3584
+ this.bridge = await getBridgeProxy();
3585
+ const iceServers = this.iceServers.map(({ url, port, username, password }) => ({
3586
+ urls: `${url}:${port}`,
3587
+ username,
3588
+ credential: password,
3589
+ }));
3590
+ await this.bridge.start(this.connectionId, iceServers, isOffering, Comlink__namespace.proxy({
3591
+ onLocalCandidate: (candidate, mid) => {
3592
+ if (!this.closed) {
3593
+ this.emit('localCandidate', candidate, mid);
3594
+ }
3595
+ },
3596
+ onLocalDescription: (description, type) => {
3597
+ if (!this.closed) {
3598
+ this.emit('localDescription', description, type);
3599
+ }
3600
+ },
3601
+ onConnectionStateChange: (state) => {
3602
+ if (state === DisconnectedState.CLOSED ||
3603
+ state === DisconnectedState.DISCONNECTED ||
3604
+ state === DisconnectedState.FAILED) {
3605
+ this.doClose(false);
3606
+ }
3607
+ },
3608
+ onDataChannel: (channel) => {
3609
+ if (!this.closed) {
3610
+ this.setupDataChannel(channel);
3611
+ // If the channel was already open at transfer time
3612
+ if (channel.readyState === 'open') {
3613
+ this.onDataChannelOpen();
3614
+ }
3615
+ }
3616
+ },
3617
+ }));
3618
+ }
3619
+ async setRemoteDescription(description, type) {
3620
+ if (this.startPromise) {
3621
+ await this.startPromise;
3622
+ }
3623
+ if (!this.bridge || this.closed) {
3624
+ return;
3625
+ }
3626
+ const wasSet = await this.bridge.setRemoteDescription(this.connectionId, description, type);
3627
+ if (wasSet) {
3628
+ clearTimeout(this.earlyTimeout);
3629
+ }
3630
+ }
3631
+ addRemoteCandidate(candidate, mid) {
3632
+ this.doAddRemoteCandidate(candidate, mid).catch((err) => {
3633
+ logger$u.warn('Failed to add remote candidate via bridge', { err });
3634
+ });
3635
+ }
3636
+ async doAddRemoteCandidate(candidate, mid) {
3637
+ if (this.startPromise) {
3638
+ await this.startPromise;
3639
+ }
3640
+ if (!this.bridge || this.closed) {
3641
+ return;
3642
+ }
3643
+ await this.bridge.addRemoteCandidate(this.connectionId, candidate, mid);
3644
+ }
3645
+ isOpen() {
3646
+ return this.connected;
3647
+ }
3648
+ // ── IConnection ─────────────────────────────────────────────
3649
+ async close(gracefulLeave, reason) {
3650
+ this.doClose(gracefulLeave, reason);
3651
+ }
3652
+ destroy() {
3653
+ this.removeAllListeners();
3654
+ this.doClose(false);
3655
+ }
3656
+ send(data) {
3657
+ if (this.connected && this.dataChannel) {
3658
+ if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
3659
+ this.messageQueue.push(data);
3660
+ }
3661
+ else {
3662
+ this.dataChannel.send(data);
3663
+ }
3664
+ }
3665
+ else if (!this.closed) {
3666
+ this.messageQueue.push(data);
3667
+ }
3668
+ }
3669
+ setConnectionId(connectionId) {
3670
+ const oldId = this.connectionId;
3671
+ this.connectionId = connectionId;
3672
+ if (this.bridge && oldId !== connectionId) {
3673
+ this.bridge.renameConnection(oldId, connectionId).catch(() => { });
3674
+ }
3675
+ }
3676
+ // ── DataChannel handling (runs entirely in the worker) ──────
3677
+ setupDataChannel(dataChannel) {
3678
+ this.dataChannel = dataChannel;
3679
+ this.dataChannel.binaryType = 'arraybuffer';
3680
+ this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3681
+ dataChannel.onopen = () => {
3682
+ logger$u.trace('dc.onOpen (worker)');
3683
+ this.onDataChannelOpen();
3684
+ };
3685
+ dataChannel.onclose = () => {
3686
+ logger$u.trace('dc.onClosed (worker)');
3687
+ this.doClose(false);
3688
+ };
3689
+ dataChannel.onerror = (err) => {
3690
+ logger$u.warn('Data channel error (worker)', { err });
3691
+ };
3692
+ dataChannel.onmessage = (msg) => {
3693
+ logger$u.trace('dc.onmessage (worker)');
3694
+ this.emit('data', new Uint8Array(msg.data));
3695
+ };
3696
+ dataChannel.onbufferedamountlow = () => {
3697
+ logger$u.trace('dc.onBufferedAmountLow (worker)');
3698
+ while (this.messageQueue.length > 0 &&
3699
+ this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3700
+ const data = this.messageQueue.shift();
3701
+ this.dataChannel.send(data);
3702
+ }
3703
+ };
3704
+ }
3705
+ onDataChannelOpen() {
3706
+ this.connected = true;
3707
+ this.flushMessageQueue();
3708
+ this.emit('connected');
3709
+ }
3710
+ flushMessageQueue() {
3711
+ while (this.messageQueue.length > 0 &&
3712
+ this.dataChannel &&
3713
+ this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3714
+ const data = this.messageQueue.shift();
3715
+ this.dataChannel.send(data);
3716
+ }
3717
+ }
3718
+ // ── Teardown ────────────────────────────────────────────────
3719
+ doClose(gracefulLeave, reason) {
3720
+ if (!this.closed) {
3721
+ this.closed = true;
3722
+ this.connected = false;
3723
+ this.messageQueue.length = 0;
3724
+ clearTimeout(this.earlyTimeout);
3725
+ this.stopListening();
3726
+ this.emit('disconnected', gracefulLeave, undefined, reason);
3727
+ this.removeAllListeners();
3728
+ if (this.dataChannel !== undefined) {
3729
+ try {
3730
+ this.dataChannel.close();
3731
+ }
3732
+ catch (err) {
3733
+ logger$u.warn('Failed to close data channel (worker)', { err });
3734
+ }
3735
+ }
3736
+ this.dataChannel = undefined;
3737
+ // Tell the main-thread bridge to tear down the RTCPeerConnection.
3738
+ // Fire-and-forget — we don't block on this.
3739
+ this.bridge
3740
+ ?.close(this.connectionId)
3741
+ .catch(() => {
3742
+ // intentionally swallowed
3743
+ });
3744
+ }
3745
+ }
3746
+ stopListening() {
3747
+ if (this.dataChannel !== undefined) {
3748
+ this.dataChannel.onopen = null;
3749
+ this.dataChannel.onclose = null;
3750
+ this.dataChannel.onerror = null;
3751
+ this.dataChannel.onbufferedamountlow = null;
3752
+ this.dataChannel.onmessage = null;
3753
+ }
3754
+ }
3755
+ }
3756
+
3757
+ /**
3758
+ * Conditional re-export of the browser WebrtcConnection.
3759
+ *
3760
+ * At module-load time we detect whether we are running inside a Web Worker.
3761
+ * - **Main thread** → use {@link DirectWebrtcConnection} which owns the
3762
+ * `RTCPeerConnection` and `RTCDataChannel` directly.
3763
+ * - **Worker thread** → use {@link WorkerWebrtcConnection} which delegates
3764
+ * `RTCPeerConnection` signaling to the main thread via a Comlink bridge
3765
+ * and receives a transferred `RTCDataChannel` that lives entirely in the
3766
+ * worker.
3767
+ *
3768
+ * Both classes implement `IWebrtcConnection & IConnection` and expose the
3769
+ * same public API, so upstream code (WebrtcConnector, etc.) is unaffected.
3770
+ */
3771
+ // The constructor — points to the right class based on the runtime
3772
+ // environment. The type assertion is safe because both implementations
3773
+ // share the same public interface surface.
3774
+ const WebrtcConnection = (isWorkerEnvironment
3775
+ ? WorkerWebrtcConnection
3776
+ : DirectWebrtcConnection);
3777
+
3331
3778
  const logger$t = new utils.Logger('WebrtcConnectorRpcRemote');
3332
3779
  class WebrtcConnectorRpcRemote extends RpcRemote {
3333
3780
  requestConnection() {
@@ -7630,6 +8077,7 @@ exports.areEqualPeerDescriptors = areEqualPeerDescriptors;
7630
8077
  exports.createOutgoingHandshaker = createOutgoingHandshaker;
7631
8078
  exports.getRandomRegion = getRandomRegion;
7632
8079
  exports.getRegionDelayMatrix = getRegionDelayMatrix;
8080
+ exports.installWebrtcBridge = installWebrtcBridge;
7633
8081
  exports.randomDhtAddress = randomDhtAddress;
7634
8082
  exports.toDhtAddress = toDhtAddress;
7635
8083
  exports.toDhtAddressRaw = toDhtAddressRaw;