@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,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ require('timers-browserify');
3
4
  var utils = require('@streamr/utils');
4
5
  var eventemitter3 = require('eventemitter3');
5
6
  var sample = require('lodash/sample');
@@ -10,6 +11,7 @@ var runtimeRpc = require('@protobuf-ts/runtime-rpc');
10
11
  var uuid = require('uuid');
11
12
  var protoRpc = require('@streamr/proto-rpc');
12
13
  var ipaddr = require('ipaddr.js');
14
+ var Comlink = require('comlink');
13
15
  var websocket = require('websocket');
14
16
  var autocertifierClient = require('@streamr/autocertifier-client');
15
17
  var shuffle = require('lodash/shuffle');
@@ -20,6 +22,25 @@ var lruCache = require('lru-cache');
20
22
  var cdnLocation = require('@streamr/cdn-location');
21
23
  var Heap = require('heap');
22
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
+
23
44
  const getPeerDistance = (nodeIdOrDataKeyRaw1, nodeIdOrDataKeyRaw2) => {
24
45
  return KBucket.distance(nodeIdOrDataKeyRaw1, nodeIdOrDataKeyRaw2);
25
46
  };
@@ -1799,7 +1820,7 @@ const createRandomConnectionId = () => {
1799
1820
  return uuid.v4();
1800
1821
  };
1801
1822
 
1802
- const logger$C = new utils.Logger('ManagedConnection');
1823
+ const logger$D = new utils.Logger('ManagedConnection');
1803
1824
  // ManagedConnection is a component used as a wrapper for IConnection after they have been successfully handshaked.
1804
1825
  // Should only be used in the ConnectionManager.
1805
1826
  class ManagedConnection extends eventemitter3.EventEmitter {
@@ -1829,7 +1850,7 @@ class ManagedConnection extends eventemitter3.EventEmitter {
1829
1850
  this.remotePeerDescriptor = peerDescriptor;
1830
1851
  }
1831
1852
  onDisconnected(gracefulLeave) {
1832
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1853
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
1833
1854
  if (!this.replacedAsDuplicate) {
1834
1855
  this.emit('disconnected', gracefulLeave);
1835
1856
  }
@@ -1838,7 +1859,7 @@ class ManagedConnection extends eventemitter3.EventEmitter {
1838
1859
  // TODO: Can this be removed if ManagedConnections can never be duplicates?
1839
1860
  // Handle duplicates in the ConncetorFacade and no longer have PendingConnections in ConnectionManager
1840
1861
  replaceAsDuplicate() {
1841
- logger$C.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1862
+ logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
1842
1863
  this.replacedAsDuplicate = true;
1843
1864
  }
1844
1865
  send(data) {
@@ -1985,10 +2006,10 @@ class RpcRemote {
1985
2006
  }
1986
2007
  }
1987
2008
 
1988
- const logger$B = new utils.Logger('ConnectionLockRpcRemote');
2009
+ const logger$C = new utils.Logger('ConnectionLockRpcRemote');
1989
2010
  class ConnectionLockRpcRemote extends RpcRemote {
1990
2011
  async lockRequest(lockId) {
1991
- logger$B.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
2012
+ logger$C.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
1992
2013
  const request = {
1993
2014
  lockId
1994
2015
  };
@@ -1998,12 +2019,12 @@ class ConnectionLockRpcRemote extends RpcRemote {
1998
2019
  return res.accepted;
1999
2020
  }
2000
2021
  catch (err) {
2001
- logger$B.debug('Connection lock rejected', { err });
2022
+ logger$C.debug('Connection lock rejected', { err });
2002
2023
  return false;
2003
2024
  }
2004
2025
  }
2005
2026
  unlockRequest(lockId) {
2006
- 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())}`);
2007
2028
  const request = {
2008
2029
  lockId
2009
2030
  };
@@ -2011,11 +2032,11 @@ class ConnectionLockRpcRemote extends RpcRemote {
2011
2032
  notification: true
2012
2033
  });
2013
2034
  this.getClient().unlockRequest(request, options).catch((_e) => {
2014
- logger$B.trace('failed to send unlockRequest');
2035
+ logger$C.trace('failed to send unlockRequest');
2015
2036
  });
2016
2037
  }
2017
2038
  async gracefulDisconnect(disconnectMode) {
2018
- logger$B.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2039
+ logger$C.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
2019
2040
  const request = {
2020
2041
  disconnectMode
2021
2042
  };
@@ -2027,7 +2048,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2027
2048
  await this.getClient().gracefulDisconnect(request, options);
2028
2049
  }
2029
2050
  async setPrivate(isPrivate) {
2030
- logger$B.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2051
+ logger$C.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
2031
2052
  const request = {
2032
2053
  isPrivate
2033
2054
  };
@@ -2039,7 +2060,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
2039
2060
  }
2040
2061
  }
2041
2062
 
2042
- const logger$A = new utils.Logger('ConnectionLockRpcLocal');
2063
+ const logger$B = new utils.Logger('ConnectionLockRpcLocal');
2043
2064
  class ConnectionLockRpcLocal {
2044
2065
  options;
2045
2066
  constructor(options) {
@@ -2068,7 +2089,7 @@ class ConnectionLockRpcLocal {
2068
2089
  }
2069
2090
  async gracefulDisconnect(disconnectNotice, context) {
2070
2091
  const senderPeerDescriptor = context.incomingSourceDescriptor;
2071
- logger$A.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2092
+ logger$B.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
2072
2093
  if (disconnectNotice.disconnectMode === DisconnectMode.LEAVING) {
2073
2094
  await this.options.closeConnection(senderPeerDescriptor, true, 'graceful leave notified');
2074
2095
  }
@@ -2119,7 +2140,7 @@ var NatType;
2119
2140
  NatType["OPEN_INTERNET"] = "open_internet";
2120
2141
  NatType["UNKNOWN"] = "unknown";
2121
2142
  })(NatType || (NatType = {}));
2122
- const logger$z = new utils.Logger('ConnectionManager');
2143
+ const logger$A = new utils.Logger('ConnectionManager');
2123
2144
  var ConnectionManagerState;
2124
2145
  (function (ConnectionManagerState) {
2125
2146
  ConnectionManagerState["IDLE"] = "idle";
@@ -2169,7 +2190,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2169
2190
  getLocalPeerDescriptor: () => this.getLocalPeerDescriptor(),
2170
2191
  setPrivate: (id, isPrivate) => {
2171
2192
  if (!this.options.allowIncomingPrivateConnections) {
2172
- 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`);
2173
2194
  return;
2174
2195
  }
2175
2196
  if (isPrivate) {
@@ -2203,7 +2224,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2203
2224
  const connection = endpoint.connection;
2204
2225
  const nodeId = connection.getNodeId();
2205
2226
  if (!this.locks.isLocked(nodeId) && !this.locks.isPrivate(nodeId) && Date.now() - connection.getLastUsedTimestamp() > maxIdleTime) {
2206
- logger$z.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2227
+ logger$A.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
2207
2228
  disconnectionCandidates.addContact(connection);
2208
2229
  }
2209
2230
  }
@@ -2211,7 +2232,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2211
2232
  const disconnectables = disconnectionCandidates.getFurthestContacts(this.endpoints.size - maxConnections);
2212
2233
  for (const disconnectable of disconnectables) {
2213
2234
  const peerDescriptor = disconnectable.getPeerDescriptor();
2214
- logger$z.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2235
+ logger$A.trace('garbageCollecting ' + toNodeId(peerDescriptor));
2215
2236
  this.gracefullyDisconnectAsync(peerDescriptor, DisconnectMode.NORMAL).catch((_e) => { });
2216
2237
  }
2217
2238
  }
@@ -2220,11 +2241,11 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2220
2241
  throw new CouldNotStart(`Cannot start already ${this.state} module`);
2221
2242
  }
2222
2243
  this.state = ConnectionManagerState.RUNNING;
2223
- logger$z.trace(`Starting ConnectionManager...`);
2244
+ logger$A.trace(`Starting ConnectionManager...`);
2224
2245
  await this.connectorFacade.start((connection) => this.onNewConnection(connection), (nodeId) => this.hasConnection(nodeId), this);
2225
2246
  // Garbage collection of connections
2226
2247
  this.disconnectorIntervalRef = setInterval(() => {
2227
- logger$z.trace('disconnectorInterval');
2248
+ logger$A.trace('disconnectorInterval');
2228
2249
  const LAST_USED_LIMIT = 20000;
2229
2250
  this.garbageCollectConnections(this.options.maxConnections ?? 80, LAST_USED_LIMIT);
2230
2251
  }, 5000); // TODO use options option or named constant?
@@ -2234,7 +2255,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2234
2255
  return;
2235
2256
  }
2236
2257
  this.state = ConnectionManagerState.STOPPING;
2237
- logger$z.trace(`Stopping ConnectionManager`);
2258
+ logger$A.trace(`Stopping ConnectionManager`);
2238
2259
  if (this.disconnectorIntervalRef) {
2239
2260
  clearInterval(this.disconnectorIntervalRef);
2240
2261
  }
@@ -2244,23 +2265,23 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2244
2265
  await this.gracefullyDisconnectAsync(endpoint.connection.getPeerDescriptor(), DisconnectMode.LEAVING);
2245
2266
  }
2246
2267
  catch (e) {
2247
- logger$z.error(e);
2268
+ logger$A.error(e);
2248
2269
  }
2249
2270
  }
2250
2271
  else {
2251
2272
  const connection = endpoint.connection;
2252
- logger$z.trace('handshake of connection not completed, force-closing');
2273
+ logger$A.trace('handshake of connection not completed, force-closing');
2253
2274
  // TODO use options option or named constant?
2254
2275
  const eventReceived = utils.waitForEvent(connection, 'disconnected', 2000);
2255
2276
  // TODO should we have some handling for this floating promise?
2256
2277
  connection.close(true);
2257
2278
  try {
2258
2279
  await eventReceived;
2259
- 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');
2260
2281
  }
2261
2282
  catch (e) {
2262
2283
  endpoint.buffer.reject();
2263
- logger$z.trace('force-closing non-handshaked connection timed out ' + e);
2284
+ logger$A.trace('force-closing non-handshaked connection timed out ' + e);
2264
2285
  }
2265
2286
  }
2266
2287
  }));
@@ -2289,7 +2310,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2289
2310
  throw new CannotConnectToSelf('Cannot send to self');
2290
2311
  }
2291
2312
  const nodeId = toNodeId(peerDescriptor);
2292
- logger$z.trace(`Sending message to: ${nodeId}`);
2313
+ logger$A.trace(`Sending message to: ${nodeId}`);
2293
2314
  message = {
2294
2315
  ...message,
2295
2316
  sourceDescriptor: this.getLocalPeerDescriptor()
@@ -2344,13 +2365,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2344
2365
  }
2345
2366
  handleMessage(message) {
2346
2367
  const messageType = message.body.oneofKind;
2347
- logger$z.trace('Received message of type ' + messageType);
2368
+ logger$A.trace('Received message of type ' + messageType);
2348
2369
  if (messageType !== 'rpcMessage') {
2349
- logger$z.trace('Filtered out non-RPC message of type ' + messageType);
2370
+ logger$A.trace('Filtered out non-RPC message of type ' + messageType);
2350
2371
  return;
2351
2372
  }
2352
2373
  if (this.duplicateMessageDetector.isMostLikelyDuplicate(message.messageId)) {
2353
- logger$z.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2374
+ logger$A.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
2354
2375
  + ' ' + message.serviceId + ' ' + message.messageId);
2355
2376
  return;
2356
2377
  }
@@ -2359,7 +2380,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2359
2380
  this.rpcCommunicator?.handleMessageFromPeer(message);
2360
2381
  }
2361
2382
  else {
2362
- logger$z.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2383
+ logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
2363
2384
  + ' ' + message.serviceId + ' ' + message.messageId);
2364
2385
  this.emit('message', message);
2365
2386
  }
@@ -2376,7 +2397,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2376
2397
  }
2377
2398
  catch (e) {
2378
2399
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2379
- logger$z.debug(`Parsing incoming data into Message failed: ${e}`);
2400
+ logger$A.debug(`Parsing incoming data into Message failed: ${e}`);
2380
2401
  return;
2381
2402
  }
2382
2403
  message.sourceDescriptor = peerDescriptor;
@@ -2385,7 +2406,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2385
2406
  }
2386
2407
  catch (e) {
2387
2408
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2388
- logger$z.debug(`Handling incoming data failed: ${e}`);
2409
+ logger$A.debug(`Handling incoming data failed: ${e}`);
2389
2410
  }
2390
2411
  }
2391
2412
  onConnected(peerDescriptor, connection) {
@@ -2398,7 +2419,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2398
2419
  const pendingConnection = endpoint.connection;
2399
2420
  const buffer = outputBuffer.getBuffer();
2400
2421
  while (buffer.length > 0) {
2401
- logger$z.trace('emptying buffer');
2422
+ logger$A.trace('emptying buffer');
2402
2423
  managedConnection.send(buffer.shift());
2403
2424
  }
2404
2425
  outputBuffer.resolve();
@@ -2415,7 +2436,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2415
2436
  }
2416
2437
  onDisconnected(peerDescriptor, gracefulLeave) {
2417
2438
  const nodeId = toNodeId(peerDescriptor);
2418
- logger$z.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2439
+ logger$A.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
2419
2440
  const endpoint = this.endpoints.get(nodeId);
2420
2441
  if (endpoint) {
2421
2442
  this.locks.clearAllLocks(nodeId);
@@ -2423,7 +2444,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2423
2444
  endpoint.buffer.reject();
2424
2445
  }
2425
2446
  this.endpoints.delete(nodeId);
2426
- logger$z.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2447
+ logger$A.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
2427
2448
  this.emit('disconnected', peerDescriptor, gracefulLeave);
2428
2449
  this.onConnectionCountChange();
2429
2450
  }
@@ -2432,7 +2453,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2432
2453
  if (this.state === ConnectionManagerState.STOPPED) {
2433
2454
  return false;
2434
2455
  }
2435
- logger$z.trace('onNewConnection()');
2456
+ logger$A.trace('onNewConnection()');
2436
2457
  if (!this.acceptNewConnection(connection)) {
2437
2458
  return false;
2438
2459
  }
@@ -2442,7 +2463,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2442
2463
  }
2443
2464
  acceptNewConnection(newConnection) {
2444
2465
  const nodeId = toNodeId(newConnection.getPeerDescriptor());
2445
- logger$z.trace(nodeId + ' acceptNewConnection()');
2466
+ logger$A.trace(nodeId + ' acceptNewConnection()');
2446
2467
  if (this.endpoints.has(nodeId)) {
2447
2468
  if (getOfferer(toNodeId(this.getLocalPeerDescriptor()), nodeId) === 'remote') {
2448
2469
  let buffer;
@@ -2451,14 +2472,14 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2451
2472
  // Could be related to WS client connections not realizing that they have been disconnected.
2452
2473
  // Makes refactoring duplicate connection handling to the connectors very difficult.
2453
2474
  if (this.endpoints.get(nodeId).connected) {
2454
- logger$z.debug('replacing connected connection', { nodeId });
2475
+ logger$A.debug('replacing connected connection', { nodeId });
2455
2476
  buffer = new OutputBuffer();
2456
2477
  }
2457
2478
  else {
2458
2479
  buffer = endpoint.buffer;
2459
2480
  }
2460
2481
  const oldConnection = endpoint.connection;
2461
- logger$z.trace('replaced: ' + nodeId);
2482
+ logger$A.trace('replaced: ' + nodeId);
2462
2483
  oldConnection.replaceAsDuplicate();
2463
2484
  this.endpoints.set(nodeId, { connected: false, connection: newConnection, buffer: buffer });
2464
2485
  return true;
@@ -2467,7 +2488,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2467
2488
  return false;
2468
2489
  }
2469
2490
  }
2470
- logger$z.trace(nodeId + ' added to connections at acceptNewConnection');
2491
+ logger$A.trace(nodeId + ' added to connections at acceptNewConnection');
2471
2492
  this.endpoints.set(nodeId, {
2472
2493
  connected: false,
2473
2494
  buffer: new OutputBuffer(),
@@ -2477,14 +2498,14 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2477
2498
  }
2478
2499
  async closeConnection(peerDescriptor, gracefulLeave, reason) {
2479
2500
  const nodeId = toNodeId(peerDescriptor);
2480
- logger$z.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2501
+ logger$A.trace(nodeId + ' ' + 'closeConnection() ' + reason);
2481
2502
  this.locks.clearAllLocks(nodeId);
2482
2503
  if (this.endpoints.has(nodeId)) {
2483
2504
  const connectionToClose = this.endpoints.get(nodeId).connection;
2484
2505
  await connectionToClose.close(gracefulLeave);
2485
2506
  }
2486
2507
  else {
2487
- 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');
2488
2509
  this.emit('disconnected', peerDescriptor, false);
2489
2510
  }
2490
2511
  }
@@ -2496,8 +2517,8 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2496
2517
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2497
2518
  this.locks.addLocalLocked(nodeId, lockId);
2498
2519
  rpcRemote.lockRequest(lockId)
2499
- .then((_accepted) => logger$z.trace('LockRequest successful'))
2500
- .catch((err) => { logger$z.debug(err); });
2520
+ .then((_accepted) => logger$A.trace('LockRequest successful'))
2521
+ .catch((err) => { logger$A.debug(err); });
2501
2522
  }
2502
2523
  unlockConnection(targetDescriptor, lockId) {
2503
2524
  if (this.state === ConnectionManagerState.STOPPED || areEqualPeerDescriptors(targetDescriptor, this.getLocalPeerDescriptor())) {
@@ -2549,7 +2570,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2549
2570
  async gracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2550
2571
  const endpoint = this.endpoints.get(toNodeId(targetDescriptor));
2551
2572
  if (!endpoint) {
2552
- logger$z.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2573
+ logger$A.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
2553
2574
  return;
2554
2575
  }
2555
2576
  if (endpoint.connected) {
@@ -2558,15 +2579,15 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2558
2579
  // TODO use options option or named constant?
2559
2580
  // eslint-disable-next-line promise/catch-or-return
2560
2581
  utils.waitForEvent(connection, 'disconnected', 2000).then(() => {
2561
- logger$z.trace('disconnected event received in gracefullyDisconnectAsync()');
2582
+ logger$A.trace('disconnected event received in gracefullyDisconnectAsync()');
2562
2583
  })
2563
2584
  .catch((e) => {
2564
- logger$z.trace('force-closing connection after timeout ' + e);
2585
+ logger$A.trace('force-closing connection after timeout ' + e);
2565
2586
  // TODO should we have some handling for this floating promise?
2566
2587
  connection.close(true);
2567
2588
  })
2568
2589
  .finally(() => {
2569
- logger$z.trace('resolving after receiving disconnected event');
2590
+ logger$A.trace('resolving after receiving disconnected event');
2570
2591
  resolve();
2571
2592
  });
2572
2593
  });
@@ -2581,13 +2602,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
2581
2602
  }
2582
2603
  async doGracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
2583
2604
  const nodeId = toNodeId(targetDescriptor);
2584
- logger$z.trace(nodeId + ' gracefullyDisconnectAsync()');
2605
+ logger$A.trace(nodeId + ' gracefullyDisconnectAsync()');
2585
2606
  const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
2586
2607
  try {
2587
2608
  await rpcRemote.gracefulDisconnect(disconnectMode);
2588
2609
  }
2589
2610
  catch (ex) {
2590
- logger$z.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2611
+ logger$A.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
2591
2612
  }
2592
2613
  }
2593
2614
  getConnections() {
@@ -2654,7 +2675,7 @@ function protoToString(protoObj, objectType) {
2654
2675
  return ret;
2655
2676
  }
2656
2677
 
2657
- const logger$y = new utils.Logger('SimulatorConnection');
2678
+ const logger$z = new utils.Logger('SimulatorConnection');
2658
2679
  class SimulatorConnection extends Connection {
2659
2680
  stopped = false;
2660
2681
  localPeerDescriptor;
@@ -2676,46 +2697,46 @@ class SimulatorConnection extends Connection {
2676
2697
  this.doDisconnect = this.doDisconnect.bind(this);
2677
2698
  }
2678
2699
  send(data) {
2679
- logger$y.trace('send()');
2700
+ logger$z.trace('send()');
2680
2701
  if (!this.stopped) {
2681
2702
  this.simulator.send(this, data);
2682
2703
  }
2683
2704
  else {
2684
2705
  const localNodeId = toNodeId(this.localPeerDescriptor);
2685
2706
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2686
- 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');
2687
2708
  }
2688
2709
  }
2689
2710
  async close(gracefulLeave) {
2690
2711
  const localNodeId = toNodeId(this.localPeerDescriptor);
2691
2712
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2692
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close()');
2713
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close()');
2693
2714
  if (!this.stopped) {
2694
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2715
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
2695
2716
  this.stopped = true;
2696
2717
  try {
2697
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2718
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
2698
2719
  this.simulator.close(this);
2699
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2720
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
2700
2721
  }
2701
2722
  catch (e) {
2702
- logger$y.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2723
+ logger$z.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
2703
2724
  }
2704
2725
  finally {
2705
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2726
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
2706
2727
  this.doDisconnect(gracefulLeave);
2707
2728
  }
2708
2729
  }
2709
2730
  else {
2710
- 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');
2711
2732
  }
2712
2733
  }
2713
2734
  connect() {
2714
2735
  if (!this.stopped) {
2715
- logger$y.trace('connect() called');
2736
+ logger$z.trace('connect() called');
2716
2737
  this.simulator.connect(this, this.targetPeerDescriptor, (error) => {
2717
2738
  if (error !== undefined) {
2718
- logger$y.trace(error);
2739
+ logger$z.trace(error);
2719
2740
  this.doDisconnect(false);
2720
2741
  }
2721
2742
  else {
@@ -2724,46 +2745,46 @@ class SimulatorConnection extends Connection {
2724
2745
  });
2725
2746
  }
2726
2747
  else {
2727
- logger$y.trace('tried to connect() a stopped connection');
2748
+ logger$z.trace('tried to connect() a stopped connection');
2728
2749
  }
2729
2750
  }
2730
2751
  handleIncomingData(data) {
2731
2752
  if (!this.stopped) {
2732
- logger$y.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2753
+ logger$z.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
2733
2754
  this.emit('data', data);
2734
2755
  }
2735
2756
  else {
2736
- logger$y.trace('tried to call handleIncomingData() a stopped connection');
2757
+ logger$z.trace('tried to call handleIncomingData() a stopped connection');
2737
2758
  }
2738
2759
  }
2739
2760
  handleIncomingDisconnection() {
2740
2761
  if (!this.stopped) {
2741
2762
  const localNodeId = toNodeId(this.localPeerDescriptor);
2742
- logger$y.trace(localNodeId + ' handleIncomingDisconnection()');
2763
+ logger$z.trace(localNodeId + ' handleIncomingDisconnection()');
2743
2764
  this.stopped = true;
2744
2765
  this.doDisconnect(false);
2745
2766
  }
2746
2767
  else {
2747
- logger$y.trace('tried to call handleIncomingDisconnection() a stopped connection');
2768
+ logger$z.trace('tried to call handleIncomingDisconnection() a stopped connection');
2748
2769
  }
2749
2770
  }
2750
2771
  destroy() {
2751
2772
  const localNodeId = toNodeId(this.localPeerDescriptor);
2752
2773
  if (!this.stopped) {
2753
- logger$y.trace(localNodeId + ' destroy()');
2774
+ logger$z.trace(localNodeId + ' destroy()');
2754
2775
  this.removeAllListeners();
2755
2776
  this.close(false).catch((_e) => { });
2756
2777
  }
2757
2778
  else {
2758
- logger$y.trace(localNodeId + ' tried to call destroy() a stopped connection');
2779
+ logger$z.trace(localNodeId + ' tried to call destroy() a stopped connection');
2759
2780
  }
2760
2781
  }
2761
2782
  doDisconnect(gracefulLeave) {
2762
2783
  const localNodeId = toNodeId(this.localPeerDescriptor);
2763
2784
  const targetNodeId = toNodeId(this.targetPeerDescriptor);
2764
- logger$y.trace(localNodeId + ' doDisconnect()');
2785
+ logger$z.trace(localNodeId + ' doDisconnect()');
2765
2786
  this.stopped = true;
2766
- logger$y.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2787
+ logger$z.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
2767
2788
  this.emit('disconnected', gracefulLeave);
2768
2789
  }
2769
2790
  }
@@ -2801,9 +2822,9 @@ const parseVersion = (version) => {
2801
2822
  }
2802
2823
  };
2803
2824
 
2804
- var version = "103.3.0";
2825
+ var version = "103.6.0-rc.0";
2805
2826
 
2806
- const logger$x = new utils.Logger('Handshaker');
2827
+ const logger$y = new utils.Logger('Handshaker');
2807
2828
  // Optimally the Outgoing and Incoming Handshakers could be their own separate classes
2808
2829
  // However, in cases where the PeerDescriptor of the other end of the connection can be known
2809
2830
  // only after a HandshakeRequest a base Handshaker class is needed as the IncomingHandshaker currently
@@ -2825,7 +2846,7 @@ const createOutgoingHandshaker = (localPeerDescriptor, pendingConnection, connec
2825
2846
  }
2826
2847
  };
2827
2848
  const handshakeCompletedListener = (peerDescriptor) => {
2828
- logger$x.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2849
+ logger$y.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
2829
2850
  pendingConnection.onHandshakeCompleted(connection);
2830
2851
  stopHandshaker();
2831
2852
  };
@@ -2925,12 +2946,12 @@ class Handshaker extends eventemitter3.EventEmitter {
2925
2946
  try {
2926
2947
  const message = Message.fromBinary(data);
2927
2948
  if (message.body.oneofKind === 'handshakeRequest') {
2928
- logger$x.trace('handshake request received');
2949
+ logger$y.trace('handshake request received');
2929
2950
  const handshake = message.body.handshakeRequest;
2930
2951
  this.emit('handshakeRequest', handshake.sourcePeerDescriptor, handshake.protocolVersion, handshake.targetPeerDescriptor);
2931
2952
  }
2932
2953
  if (message.body.oneofKind === 'handshakeResponse') {
2933
- logger$x.trace('handshake response received');
2954
+ logger$y.trace('handshake response received');
2934
2955
  const handshake = message.body.handshakeResponse;
2935
2956
  const error = !isMaybeSupportedProtocolVersion(handshake.protocolVersion)
2936
2957
  ? HandshakeError.UNSUPPORTED_PROTOCOL_VERSION : handshake.error;
@@ -2943,18 +2964,18 @@ class Handshaker extends eventemitter3.EventEmitter {
2943
2964
  }
2944
2965
  }
2945
2966
  catch (err) {
2946
- logger$x.debug('error while parsing handshake message', err);
2967
+ logger$y.debug('error while parsing handshake message', err);
2947
2968
  }
2948
2969
  }
2949
2970
  sendHandshakeRequest(remotePeerDescriptor) {
2950
2971
  const msg = createHandshakeRequest(this.localPeerDescriptor, remotePeerDescriptor);
2951
2972
  this.connection.send(Message.toBinary(msg));
2952
- logger$x.trace('handshake request sent');
2973
+ logger$y.trace('handshake request sent');
2953
2974
  }
2954
2975
  sendHandshakeResponse(error) {
2955
2976
  const msg = createHandshakeResponse(this.localPeerDescriptor, error);
2956
2977
  this.connection.send(Message.toBinary(msg));
2957
- logger$x.trace('handshake response sent');
2978
+ logger$y.trace('handshake response sent');
2958
2979
  }
2959
2980
  stop() {
2960
2981
  this.connection.off('data', this.onDataListener);
@@ -2962,7 +2983,7 @@ class Handshaker extends eventemitter3.EventEmitter {
2962
2983
  }
2963
2984
  }
2964
2985
 
2965
- const logger$w = new utils.Logger('PendingConnection');
2986
+ const logger$x = new utils.Logger('PendingConnection');
2966
2987
  // PendingConnection is used as a reference to a connection that should be opened and handshaked to a given PeerDescriptor
2967
2988
  // It does not hold a connection internally. The public method onHandshakedCompleted should be called once a connection for the
2968
2989
  // remotePeerDescriptor is opened and handshaked successfully.
@@ -2980,7 +3001,7 @@ class PendingConnection extends eventemitter3.EventEmitter {
2980
3001
  }, timeout, this.connectingAbortController.signal);
2981
3002
  }
2982
3003
  replaceAsDuplicate() {
2983
- logger$w.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
3004
+ logger$x.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
2984
3005
  this.replacedAsDuplicate = true;
2985
3006
  }
2986
3007
  onHandshakeCompleted(connection) {
@@ -3011,7 +3032,7 @@ class PendingConnection extends eventemitter3.EventEmitter {
3011
3032
  }
3012
3033
  }
3013
3034
 
3014
- const logger$v = new utils.Logger('SimulatorConnector');
3035
+ const logger$w = new utils.Logger('SimulatorConnector');
3015
3036
  class SimulatorConnector {
3016
3037
  connectingConnections = new Map();
3017
3038
  stopped = false;
@@ -3024,7 +3045,7 @@ class SimulatorConnector {
3024
3045
  this.onNewConnection = onNewConnection;
3025
3046
  }
3026
3047
  connect(targetPeerDescriptor) {
3027
- logger$v.trace('connect() ' + toNodeId(targetPeerDescriptor));
3048
+ logger$w.trace('connect() ' + toNodeId(targetPeerDescriptor));
3028
3049
  const nodeId = toNodeId(targetPeerDescriptor);
3029
3050
  const existingConnection = this.connectingConnections.get(nodeId);
3030
3051
  if (existingConnection) {
@@ -3053,18 +3074,18 @@ class SimulatorConnector {
3053
3074
  // connection is incoming, so remotePeerDescriptor is localPeerDescriptor
3054
3075
  const remotePeerDescriptor = sourceConnection.localPeerDescriptor;
3055
3076
  const remoteNodeId = toNodeId(sourceConnection.localPeerDescriptor);
3056
- logger$v.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3077
+ logger$w.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
3057
3078
  if (this.stopped) {
3058
3079
  return;
3059
3080
  }
3060
3081
  const connection = new SimulatorConnection(this.localPeerDescriptor, remotePeerDescriptor, exports.ConnectionType.SIMULATOR_SERVER, this.simulator);
3061
3082
  const pendingConnection = new PendingConnection(remotePeerDescriptor);
3062
3083
  const handshaker = createIncomingHandshaker(this.localPeerDescriptor, pendingConnection, connection);
3063
- logger$v.trace('connected');
3084
+ logger$w.trace('connected');
3064
3085
  handshaker.once('handshakeRequest', () => {
3065
- logger$v.trace(remoteNodeId + ' incoming handshake request');
3086
+ logger$w.trace(remoteNodeId + ' incoming handshake request');
3066
3087
  if (this.onNewConnection(pendingConnection)) {
3067
- logger$v.trace(remoteNodeId + ' calling acceptHandshake');
3088
+ logger$w.trace(remoteNodeId + ' calling acceptHandshake');
3068
3089
  acceptHandshake(handshaker, pendingConnection, connection);
3069
3090
  }
3070
3091
  else {
@@ -3104,6 +3125,8 @@ class ListeningRpcCommunicator extends RoutingRpcCommunicator {
3104
3125
  }
3105
3126
  }
3106
3127
 
3128
+ const isWorkerEnvironment = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
3129
+
3107
3130
  var RtcDescription;
3108
3131
  (function (RtcDescription) {
3109
3132
  RtcDescription["OFFER"] = "offer";
@@ -3118,8 +3141,8 @@ var DisconnectedRtcPeerConnectionStateEnum;
3118
3141
  DisconnectedRtcPeerConnectionStateEnum["FAILED"] = "failed";
3119
3142
  DisconnectedRtcPeerConnectionStateEnum["CLOSED"] = "closed";
3120
3143
  })(DisconnectedRtcPeerConnectionStateEnum || (DisconnectedRtcPeerConnectionStateEnum = {}));
3121
- const logger$u = new utils.Logger('WebrtcConnection (browser)');
3122
- class WebrtcConnection extends eventemitter3.EventEmitter {
3144
+ const logger$v = new utils.Logger('DirectWebrtcConnection (browser)');
3145
+ class DirectWebrtcConnection extends eventemitter3.EventEmitter {
3123
3146
  connectionId;
3124
3147
  connectionType = exports.ConnectionType.WEBRTC;
3125
3148
  // We need to keep track of connection state ourselves because
@@ -3157,7 +3180,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3157
3180
  }
3158
3181
  };
3159
3182
  this.peerConnection.onicegatheringstatechange = () => {
3160
- logger$u.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3183
+ logger$v.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
3161
3184
  };
3162
3185
  this.peerConnection.onconnectionstatechange = () => this.onStateChange();
3163
3186
  if (isOffering) {
@@ -3168,7 +3191,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3168
3191
  await this.peerConnection.setLocalDescription();
3169
3192
  }
3170
3193
  catch (err) {
3171
- logger$u.warn('Failed to set local description', { err });
3194
+ logger$v.warn('Failed to set local description', { err });
3172
3195
  }
3173
3196
  if (this.peerConnection.localDescription !== null) {
3174
3197
  this.emit('localDescription', this.peerConnection.localDescription?.sdp, this.peerConnection.localDescription?.type);
@@ -3196,14 +3219,14 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3196
3219
  clearTimeout(this.earlyTimeout);
3197
3220
  }
3198
3221
  catch (err) {
3199
- logger$u.warn('Failed to set remote description', { err });
3222
+ logger$v.warn('Failed to set remote description', { err });
3200
3223
  }
3201
3224
  if ((type.toLowerCase() === RtcDescription.OFFER) && (this.peerConnection !== undefined)) {
3202
3225
  try {
3203
3226
  await this.peerConnection.setLocalDescription();
3204
3227
  }
3205
3228
  catch (err) {
3206
- logger$u.warn('Failed to set local description', { err });
3229
+ logger$v.warn('Failed to set local description', { err });
3207
3230
  }
3208
3231
  if (this.peerConnection.localDescription !== null) {
3209
3232
  this.emit('localDescription', this.peerConnection.localDescription.sdp, this.peerConnection.localDescription.type);
@@ -3213,7 +3236,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3213
3236
  addRemoteCandidate(candidate, mid) {
3214
3237
  this.peerConnection?.addIceCandidate({ candidate: candidate, sdpMid: mid })
3215
3238
  .catch((err) => {
3216
- logger$u.warn('Failed to add ICE candidate', { err });
3239
+ logger$v.warn('Failed to add ICE candidate', { err });
3217
3240
  });
3218
3241
  }
3219
3242
  isOpen() {
@@ -3236,7 +3259,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3236
3259
  this.dataChannel.close();
3237
3260
  }
3238
3261
  catch (err) {
3239
- logger$u.warn('Failed to close data channel', { err });
3262
+ logger$v.warn('Failed to close data channel', { err });
3240
3263
  }
3241
3264
  }
3242
3265
  this.dataChannel = undefined;
@@ -3245,7 +3268,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3245
3268
  this.peerConnection.close();
3246
3269
  }
3247
3270
  catch (err) {
3248
- logger$u.warn('Failed to close connection', { err });
3271
+ logger$v.warn('Failed to close connection', { err });
3249
3272
  }
3250
3273
  }
3251
3274
  this.peerConnection = undefined;
@@ -3265,7 +3288,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3265
3288
  }
3266
3289
  }
3267
3290
  else {
3268
- 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);
3269
3292
  }
3270
3293
  }
3271
3294
  setupDataChannel(dataChannel) {
@@ -3273,22 +3296,22 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3273
3296
  this.dataChannel.binaryType = 'arraybuffer';
3274
3297
  this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
3275
3298
  dataChannel.onopen = () => {
3276
- logger$u.trace('dc.onOpen');
3299
+ logger$v.trace('dc.onOpen');
3277
3300
  this.onDataChannelOpen();
3278
3301
  };
3279
3302
  dataChannel.onclose = () => {
3280
- logger$u.trace('dc.onClosed');
3303
+ logger$v.trace('dc.onClosed');
3281
3304
  this.doClose(false);
3282
3305
  };
3283
3306
  dataChannel.onerror = (err) => {
3284
- logger$u.warn('Data channel error', { err });
3307
+ logger$v.warn('Data channel error', { err });
3285
3308
  };
3286
3309
  dataChannel.onmessage = (msg) => {
3287
- logger$u.trace('dc.onmessage');
3310
+ logger$v.trace('dc.onmessage');
3288
3311
  this.emit('data', new Uint8Array(msg.data));
3289
3312
  };
3290
3313
  dataChannel.onbufferedamountlow = () => {
3291
- logger$u.trace('dc.onBufferedAmountLow');
3314
+ logger$v.trace('dc.onBufferedAmountLow');
3292
3315
  while (this.messageQueue.length > 0 && this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
3293
3316
  const data = this.messageQueue.shift();
3294
3317
  this.dataChannel.send(data);
@@ -3327,6 +3350,431 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
3327
3350
  }
3328
3351
  }
3329
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
+
3330
3778
  const logger$t = new utils.Logger('WebrtcConnectorRpcRemote');
3331
3779
  class WebrtcConnectorRpcRemote extends RpcRemote {
3332
3780
  requestConnection() {
@@ -7629,6 +8077,7 @@ exports.areEqualPeerDescriptors = areEqualPeerDescriptors;
7629
8077
  exports.createOutgoingHandshaker = createOutgoingHandshaker;
7630
8078
  exports.getRandomRegion = getRandomRegion;
7631
8079
  exports.getRegionDelayMatrix = getRegionDelayMatrix;
8080
+ exports.installWebrtcBridge = installWebrtcBridge;
7632
8081
  exports.randomDhtAddress = randomDhtAddress;
7633
8082
  exports.toDhtAddress = toDhtAddress;
7634
8083
  exports.toDhtAddressRaw = toDhtAddressRaw;