@streamr/dht 103.3.1 → 103.7.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exports-browser.cjs +621 -101
- package/dist/exports-browser.cjs.map +1 -1
- package/dist/exports-browser.d.ts +15 -8
- package/dist/exports-browser.js +600 -102
- package/dist/exports-browser.js.map +1 -1
- package/dist/exports-nodejs.cjs +74 -2
- package/dist/exports-nodejs.cjs.map +1 -1
- package/dist/exports-nodejs.d.ts +18 -8
- package/dist/exports-nodejs.js +72 -3
- package/dist/exports-nodejs.js.map +1 -1
- package/package.json +12 -8
package/dist/exports-browser.cjs
CHANGED
|
@@ -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
|
};
|
|
@@ -241,6 +261,65 @@ class SendFailed extends Err {
|
|
|
241
261
|
constructor(message, originalError) { super(ErrorCode.SEND_FAILED, message, originalError); }
|
|
242
262
|
}
|
|
243
263
|
|
|
264
|
+
let enabled = false;
|
|
265
|
+
function setGapDiagnosticsEnabled(val) {
|
|
266
|
+
enabled = val;
|
|
267
|
+
globalThis.__dhtGapDiagEnabled = val;
|
|
268
|
+
}
|
|
269
|
+
const SUMMARY_INTERVAL_MS = 2000;
|
|
270
|
+
const accumulators = new Map();
|
|
271
|
+
function logGapDiagnosticSampled(layer, opts = {}) {
|
|
272
|
+
if (!enabled)
|
|
273
|
+
return;
|
|
274
|
+
const now = performance.now();
|
|
275
|
+
const threshold = opts.outlierThresholdMs ?? 30;
|
|
276
|
+
let acc = accumulators.get(layer);
|
|
277
|
+
if (acc === undefined) {
|
|
278
|
+
acc = { count: 0, sumDeltaMs: 0, maxDeltaMs: 0, outlierCount: 0, lastReportMs: now, lastEventMs: now };
|
|
279
|
+
accumulators.set(layer, acc);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const deltaMs = now - acc.lastEventMs;
|
|
283
|
+
acc.lastEventMs = now;
|
|
284
|
+
acc.count++;
|
|
285
|
+
if (deltaMs > acc.maxDeltaMs)
|
|
286
|
+
acc.maxDeltaMs = deltaMs;
|
|
287
|
+
acc.sumDeltaMs += deltaMs;
|
|
288
|
+
if (deltaMs > threshold)
|
|
289
|
+
acc.outlierCount++;
|
|
290
|
+
if (deltaMs > threshold) {
|
|
291
|
+
const payload = {
|
|
292
|
+
layer,
|
|
293
|
+
timestampMs: now,
|
|
294
|
+
deltaMs: +deltaMs.toFixed(2),
|
|
295
|
+
detail: opts.detail,
|
|
296
|
+
};
|
|
297
|
+
// eslint-disable-next-line no-console
|
|
298
|
+
console.log('[gap-diagnostics]', JSON.stringify(payload));
|
|
299
|
+
}
|
|
300
|
+
if (now - acc.lastReportMs >= SUMMARY_INTERVAL_MS) {
|
|
301
|
+
const summaryDetail = {
|
|
302
|
+
count: acc.count,
|
|
303
|
+
meanDeltaMs: acc.count > 0 ? +(acc.sumDeltaMs / acc.count).toFixed(2) : 0,
|
|
304
|
+
maxDeltaMs: +acc.maxDeltaMs.toFixed(2),
|
|
305
|
+
outlierCount: acc.outlierCount,
|
|
306
|
+
periodMs: +(now - acc.lastReportMs).toFixed(1),
|
|
307
|
+
};
|
|
308
|
+
const summary = {
|
|
309
|
+
layer: `${layer}.summary`,
|
|
310
|
+
timestampMs: now,
|
|
311
|
+
detail: summaryDetail,
|
|
312
|
+
};
|
|
313
|
+
// eslint-disable-next-line no-console
|
|
314
|
+
console.log('[gap-diagnostics]', JSON.stringify(summary));
|
|
315
|
+
acc.count = 0;
|
|
316
|
+
acc.sumDeltaMs = 0;
|
|
317
|
+
acc.maxDeltaMs = 0;
|
|
318
|
+
acc.outlierCount = 0;
|
|
319
|
+
acc.lastReportMs = now;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
244
323
|
// @generated message type with reflection information, may provide speed optimized methods
|
|
245
324
|
class Empty$Type extends runtime.MessageType {
|
|
246
325
|
constructor() {
|
|
@@ -1800,7 +1879,7 @@ const createRandomConnectionId = () => {
|
|
|
1800
1879
|
return uuid.v4();
|
|
1801
1880
|
};
|
|
1802
1881
|
|
|
1803
|
-
const logger$
|
|
1882
|
+
const logger$D = new utils.Logger('ManagedConnection');
|
|
1804
1883
|
// ManagedConnection is a component used as a wrapper for IConnection after they have been successfully handshaked.
|
|
1805
1884
|
// Should only be used in the ConnectionManager.
|
|
1806
1885
|
class ManagedConnection extends eventemitter3.EventEmitter {
|
|
@@ -1830,7 +1909,7 @@ class ManagedConnection extends eventemitter3.EventEmitter {
|
|
|
1830
1909
|
this.remotePeerDescriptor = peerDescriptor;
|
|
1831
1910
|
}
|
|
1832
1911
|
onDisconnected(gracefulLeave) {
|
|
1833
|
-
logger$
|
|
1912
|
+
logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
|
|
1834
1913
|
if (!this.replacedAsDuplicate) {
|
|
1835
1914
|
this.emit('disconnected', gracefulLeave);
|
|
1836
1915
|
}
|
|
@@ -1839,7 +1918,7 @@ class ManagedConnection extends eventemitter3.EventEmitter {
|
|
|
1839
1918
|
// TODO: Can this be removed if ManagedConnections can never be duplicates?
|
|
1840
1919
|
// Handle duplicates in the ConncetorFacade and no longer have PendingConnections in ConnectionManager
|
|
1841
1920
|
replaceAsDuplicate() {
|
|
1842
|
-
logger$
|
|
1921
|
+
logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
|
|
1843
1922
|
this.replacedAsDuplicate = true;
|
|
1844
1923
|
}
|
|
1845
1924
|
send(data) {
|
|
@@ -1986,10 +2065,10 @@ class RpcRemote {
|
|
|
1986
2065
|
}
|
|
1987
2066
|
}
|
|
1988
2067
|
|
|
1989
|
-
const logger$
|
|
2068
|
+
const logger$C = new utils.Logger('ConnectionLockRpcRemote');
|
|
1990
2069
|
class ConnectionLockRpcRemote extends RpcRemote {
|
|
1991
2070
|
async lockRequest(lockId) {
|
|
1992
|
-
logger$
|
|
2071
|
+
logger$C.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
|
|
1993
2072
|
const request = {
|
|
1994
2073
|
lockId
|
|
1995
2074
|
};
|
|
@@ -1999,12 +2078,12 @@ class ConnectionLockRpcRemote extends RpcRemote {
|
|
|
1999
2078
|
return res.accepted;
|
|
2000
2079
|
}
|
|
2001
2080
|
catch (err) {
|
|
2002
|
-
logger$
|
|
2081
|
+
logger$C.debug('Connection lock rejected', { err });
|
|
2003
2082
|
return false;
|
|
2004
2083
|
}
|
|
2005
2084
|
}
|
|
2006
2085
|
unlockRequest(lockId) {
|
|
2007
|
-
logger$
|
|
2086
|
+
logger$C.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
|
|
2008
2087
|
const request = {
|
|
2009
2088
|
lockId
|
|
2010
2089
|
};
|
|
@@ -2012,11 +2091,11 @@ class ConnectionLockRpcRemote extends RpcRemote {
|
|
|
2012
2091
|
notification: true
|
|
2013
2092
|
});
|
|
2014
2093
|
this.getClient().unlockRequest(request, options).catch((_e) => {
|
|
2015
|
-
logger$
|
|
2094
|
+
logger$C.trace('failed to send unlockRequest');
|
|
2016
2095
|
});
|
|
2017
2096
|
}
|
|
2018
2097
|
async gracefulDisconnect(disconnectMode) {
|
|
2019
|
-
logger$
|
|
2098
|
+
logger$C.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
|
|
2020
2099
|
const request = {
|
|
2021
2100
|
disconnectMode
|
|
2022
2101
|
};
|
|
@@ -2028,7 +2107,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
|
|
|
2028
2107
|
await this.getClient().gracefulDisconnect(request, options);
|
|
2029
2108
|
}
|
|
2030
2109
|
async setPrivate(isPrivate) {
|
|
2031
|
-
logger$
|
|
2110
|
+
logger$C.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
|
|
2032
2111
|
const request = {
|
|
2033
2112
|
isPrivate
|
|
2034
2113
|
};
|
|
@@ -2040,7 +2119,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
|
|
|
2040
2119
|
}
|
|
2041
2120
|
}
|
|
2042
2121
|
|
|
2043
|
-
const logger$
|
|
2122
|
+
const logger$B = new utils.Logger('ConnectionLockRpcLocal');
|
|
2044
2123
|
class ConnectionLockRpcLocal {
|
|
2045
2124
|
options;
|
|
2046
2125
|
constructor(options) {
|
|
@@ -2069,7 +2148,7 @@ class ConnectionLockRpcLocal {
|
|
|
2069
2148
|
}
|
|
2070
2149
|
async gracefulDisconnect(disconnectNotice, context) {
|
|
2071
2150
|
const senderPeerDescriptor = context.incomingSourceDescriptor;
|
|
2072
|
-
logger$
|
|
2151
|
+
logger$B.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
|
|
2073
2152
|
if (disconnectNotice.disconnectMode === DisconnectMode.LEAVING) {
|
|
2074
2153
|
await this.options.closeConnection(senderPeerDescriptor, true, 'graceful leave notified');
|
|
2075
2154
|
}
|
|
@@ -2120,7 +2199,7 @@ var NatType;
|
|
|
2120
2199
|
NatType["OPEN_INTERNET"] = "open_internet";
|
|
2121
2200
|
NatType["UNKNOWN"] = "unknown";
|
|
2122
2201
|
})(NatType || (NatType = {}));
|
|
2123
|
-
const logger$
|
|
2202
|
+
const logger$A = new utils.Logger('ConnectionManager');
|
|
2124
2203
|
var ConnectionManagerState;
|
|
2125
2204
|
(function (ConnectionManagerState) {
|
|
2126
2205
|
ConnectionManagerState["IDLE"] = "idle";
|
|
@@ -2170,7 +2249,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2170
2249
|
getLocalPeerDescriptor: () => this.getLocalPeerDescriptor(),
|
|
2171
2250
|
setPrivate: (id, isPrivate) => {
|
|
2172
2251
|
if (!this.options.allowIncomingPrivateConnections) {
|
|
2173
|
-
logger$
|
|
2252
|
+
logger$A.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
|
|
2174
2253
|
return;
|
|
2175
2254
|
}
|
|
2176
2255
|
if (isPrivate) {
|
|
@@ -2204,7 +2283,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2204
2283
|
const connection = endpoint.connection;
|
|
2205
2284
|
const nodeId = connection.getNodeId();
|
|
2206
2285
|
if (!this.locks.isLocked(nodeId) && !this.locks.isPrivate(nodeId) && Date.now() - connection.getLastUsedTimestamp() > maxIdleTime) {
|
|
2207
|
-
logger$
|
|
2286
|
+
logger$A.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
|
|
2208
2287
|
disconnectionCandidates.addContact(connection);
|
|
2209
2288
|
}
|
|
2210
2289
|
}
|
|
@@ -2212,7 +2291,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2212
2291
|
const disconnectables = disconnectionCandidates.getFurthestContacts(this.endpoints.size - maxConnections);
|
|
2213
2292
|
for (const disconnectable of disconnectables) {
|
|
2214
2293
|
const peerDescriptor = disconnectable.getPeerDescriptor();
|
|
2215
|
-
logger$
|
|
2294
|
+
logger$A.trace('garbageCollecting ' + toNodeId(peerDescriptor));
|
|
2216
2295
|
this.gracefullyDisconnectAsync(peerDescriptor, DisconnectMode.NORMAL).catch((_e) => { });
|
|
2217
2296
|
}
|
|
2218
2297
|
}
|
|
@@ -2221,11 +2300,11 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2221
2300
|
throw new CouldNotStart(`Cannot start already ${this.state} module`);
|
|
2222
2301
|
}
|
|
2223
2302
|
this.state = ConnectionManagerState.RUNNING;
|
|
2224
|
-
logger$
|
|
2303
|
+
logger$A.trace(`Starting ConnectionManager...`);
|
|
2225
2304
|
await this.connectorFacade.start((connection) => this.onNewConnection(connection), (nodeId) => this.hasConnection(nodeId), this);
|
|
2226
2305
|
// Garbage collection of connections
|
|
2227
2306
|
this.disconnectorIntervalRef = setInterval(() => {
|
|
2228
|
-
logger$
|
|
2307
|
+
logger$A.trace('disconnectorInterval');
|
|
2229
2308
|
const LAST_USED_LIMIT = 20000;
|
|
2230
2309
|
this.garbageCollectConnections(this.options.maxConnections ?? 80, LAST_USED_LIMIT);
|
|
2231
2310
|
}, 5000); // TODO use options option or named constant?
|
|
@@ -2235,7 +2314,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2235
2314
|
return;
|
|
2236
2315
|
}
|
|
2237
2316
|
this.state = ConnectionManagerState.STOPPING;
|
|
2238
|
-
logger$
|
|
2317
|
+
logger$A.trace(`Stopping ConnectionManager`);
|
|
2239
2318
|
if (this.disconnectorIntervalRef) {
|
|
2240
2319
|
clearInterval(this.disconnectorIntervalRef);
|
|
2241
2320
|
}
|
|
@@ -2245,23 +2324,23 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2245
2324
|
await this.gracefullyDisconnectAsync(endpoint.connection.getPeerDescriptor(), DisconnectMode.LEAVING);
|
|
2246
2325
|
}
|
|
2247
2326
|
catch (e) {
|
|
2248
|
-
logger$
|
|
2327
|
+
logger$A.error(e);
|
|
2249
2328
|
}
|
|
2250
2329
|
}
|
|
2251
2330
|
else {
|
|
2252
2331
|
const connection = endpoint.connection;
|
|
2253
|
-
logger$
|
|
2332
|
+
logger$A.trace('handshake of connection not completed, force-closing');
|
|
2254
2333
|
// TODO use options option or named constant?
|
|
2255
2334
|
const eventReceived = utils.waitForEvent(connection, 'disconnected', 2000);
|
|
2256
2335
|
// TODO should we have some handling for this floating promise?
|
|
2257
2336
|
connection.close(true);
|
|
2258
2337
|
try {
|
|
2259
2338
|
await eventReceived;
|
|
2260
|
-
logger$
|
|
2339
|
+
logger$A.trace('resolving after receiving disconnected event from non-handshaked connection');
|
|
2261
2340
|
}
|
|
2262
2341
|
catch (e) {
|
|
2263
2342
|
endpoint.buffer.reject();
|
|
2264
|
-
logger$
|
|
2343
|
+
logger$A.trace('force-closing non-handshaked connection timed out ' + e);
|
|
2265
2344
|
}
|
|
2266
2345
|
}
|
|
2267
2346
|
}));
|
|
@@ -2285,12 +2364,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2285
2364
|
if ((this.state === ConnectionManagerState.STOPPED || this.state === ConnectionManagerState.STOPPING) && !opts.sendIfStopped) {
|
|
2286
2365
|
return;
|
|
2287
2366
|
}
|
|
2367
|
+
logGapDiagnosticSampled('dht.connMgr.send');
|
|
2288
2368
|
const peerDescriptor = message.targetDescriptor;
|
|
2289
2369
|
if (this.isConnectionToSelf(peerDescriptor)) {
|
|
2290
2370
|
throw new CannotConnectToSelf('Cannot send to self');
|
|
2291
2371
|
}
|
|
2292
2372
|
const nodeId = toNodeId(peerDescriptor);
|
|
2293
|
-
logger$
|
|
2373
|
+
logger$A.trace(`Sending message to: ${nodeId}`);
|
|
2294
2374
|
message = {
|
|
2295
2375
|
...message,
|
|
2296
2376
|
sourceDescriptor: this.getLocalPeerDescriptor()
|
|
@@ -2345,13 +2425,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2345
2425
|
}
|
|
2346
2426
|
handleMessage(message) {
|
|
2347
2427
|
const messageType = message.body.oneofKind;
|
|
2348
|
-
logger$
|
|
2428
|
+
logger$A.trace('Received message of type ' + messageType);
|
|
2349
2429
|
if (messageType !== 'rpcMessage') {
|
|
2350
|
-
logger$
|
|
2430
|
+
logger$A.trace('Filtered out non-RPC message of type ' + messageType);
|
|
2351
2431
|
return;
|
|
2352
2432
|
}
|
|
2353
2433
|
if (this.duplicateMessageDetector.isMostLikelyDuplicate(message.messageId)) {
|
|
2354
|
-
logger$
|
|
2434
|
+
logger$A.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
|
|
2355
2435
|
+ ' ' + message.serviceId + ' ' + message.messageId);
|
|
2356
2436
|
return;
|
|
2357
2437
|
}
|
|
@@ -2360,7 +2440,8 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2360
2440
|
this.rpcCommunicator?.handleMessageFromPeer(message);
|
|
2361
2441
|
}
|
|
2362
2442
|
else {
|
|
2363
|
-
|
|
2443
|
+
logGapDiagnosticSampled('dht.connMgr.emitMessage');
|
|
2444
|
+
logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
|
|
2364
2445
|
+ ' ' + message.serviceId + ' ' + message.messageId);
|
|
2365
2446
|
this.emit('message', message);
|
|
2366
2447
|
}
|
|
@@ -2369,6 +2450,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2369
2450
|
if (this.state === ConnectionManagerState.STOPPED) {
|
|
2370
2451
|
return;
|
|
2371
2452
|
}
|
|
2453
|
+
logGapDiagnosticSampled('dht.connMgr.onData');
|
|
2372
2454
|
this.metrics.receiveBytesPerSecond.record(data.byteLength);
|
|
2373
2455
|
this.metrics.receiveMessagesPerSecond.record(1);
|
|
2374
2456
|
let message;
|
|
@@ -2377,7 +2459,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2377
2459
|
}
|
|
2378
2460
|
catch (e) {
|
|
2379
2461
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
2380
|
-
logger$
|
|
2462
|
+
logger$A.debug(`Parsing incoming data into Message failed: ${e}`);
|
|
2381
2463
|
return;
|
|
2382
2464
|
}
|
|
2383
2465
|
message.sourceDescriptor = peerDescriptor;
|
|
@@ -2386,7 +2468,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2386
2468
|
}
|
|
2387
2469
|
catch (e) {
|
|
2388
2470
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
2389
|
-
logger$
|
|
2471
|
+
logger$A.debug(`Handling incoming data failed: ${e}`);
|
|
2390
2472
|
}
|
|
2391
2473
|
}
|
|
2392
2474
|
onConnected(peerDescriptor, connection) {
|
|
@@ -2399,7 +2481,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2399
2481
|
const pendingConnection = endpoint.connection;
|
|
2400
2482
|
const buffer = outputBuffer.getBuffer();
|
|
2401
2483
|
while (buffer.length > 0) {
|
|
2402
|
-
logger$
|
|
2484
|
+
logger$A.trace('emptying buffer');
|
|
2403
2485
|
managedConnection.send(buffer.shift());
|
|
2404
2486
|
}
|
|
2405
2487
|
outputBuffer.resolve();
|
|
@@ -2416,7 +2498,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2416
2498
|
}
|
|
2417
2499
|
onDisconnected(peerDescriptor, gracefulLeave) {
|
|
2418
2500
|
const nodeId = toNodeId(peerDescriptor);
|
|
2419
|
-
logger$
|
|
2501
|
+
logger$A.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
|
|
2420
2502
|
const endpoint = this.endpoints.get(nodeId);
|
|
2421
2503
|
if (endpoint) {
|
|
2422
2504
|
this.locks.clearAllLocks(nodeId);
|
|
@@ -2424,7 +2506,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2424
2506
|
endpoint.buffer.reject();
|
|
2425
2507
|
}
|
|
2426
2508
|
this.endpoints.delete(nodeId);
|
|
2427
|
-
logger$
|
|
2509
|
+
logger$A.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
|
|
2428
2510
|
this.emit('disconnected', peerDescriptor, gracefulLeave);
|
|
2429
2511
|
this.onConnectionCountChange();
|
|
2430
2512
|
}
|
|
@@ -2433,7 +2515,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2433
2515
|
if (this.state === ConnectionManagerState.STOPPED) {
|
|
2434
2516
|
return false;
|
|
2435
2517
|
}
|
|
2436
|
-
logger$
|
|
2518
|
+
logger$A.trace('onNewConnection()');
|
|
2437
2519
|
if (!this.acceptNewConnection(connection)) {
|
|
2438
2520
|
return false;
|
|
2439
2521
|
}
|
|
@@ -2443,7 +2525,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2443
2525
|
}
|
|
2444
2526
|
acceptNewConnection(newConnection) {
|
|
2445
2527
|
const nodeId = toNodeId(newConnection.getPeerDescriptor());
|
|
2446
|
-
logger$
|
|
2528
|
+
logger$A.trace(nodeId + ' acceptNewConnection()');
|
|
2447
2529
|
if (this.endpoints.has(nodeId)) {
|
|
2448
2530
|
if (getOfferer(toNodeId(this.getLocalPeerDescriptor()), nodeId) === 'remote') {
|
|
2449
2531
|
let buffer;
|
|
@@ -2452,14 +2534,14 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2452
2534
|
// Could be related to WS client connections not realizing that they have been disconnected.
|
|
2453
2535
|
// Makes refactoring duplicate connection handling to the connectors very difficult.
|
|
2454
2536
|
if (this.endpoints.get(nodeId).connected) {
|
|
2455
|
-
logger$
|
|
2537
|
+
logger$A.debug('replacing connected connection', { nodeId });
|
|
2456
2538
|
buffer = new OutputBuffer();
|
|
2457
2539
|
}
|
|
2458
2540
|
else {
|
|
2459
2541
|
buffer = endpoint.buffer;
|
|
2460
2542
|
}
|
|
2461
2543
|
const oldConnection = endpoint.connection;
|
|
2462
|
-
logger$
|
|
2544
|
+
logger$A.trace('replaced: ' + nodeId);
|
|
2463
2545
|
oldConnection.replaceAsDuplicate();
|
|
2464
2546
|
this.endpoints.set(nodeId, { connected: false, connection: newConnection, buffer: buffer });
|
|
2465
2547
|
return true;
|
|
@@ -2468,7 +2550,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2468
2550
|
return false;
|
|
2469
2551
|
}
|
|
2470
2552
|
}
|
|
2471
|
-
logger$
|
|
2553
|
+
logger$A.trace(nodeId + ' added to connections at acceptNewConnection');
|
|
2472
2554
|
this.endpoints.set(nodeId, {
|
|
2473
2555
|
connected: false,
|
|
2474
2556
|
buffer: new OutputBuffer(),
|
|
@@ -2478,14 +2560,14 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2478
2560
|
}
|
|
2479
2561
|
async closeConnection(peerDescriptor, gracefulLeave, reason) {
|
|
2480
2562
|
const nodeId = toNodeId(peerDescriptor);
|
|
2481
|
-
logger$
|
|
2563
|
+
logger$A.trace(nodeId + ' ' + 'closeConnection() ' + reason);
|
|
2482
2564
|
this.locks.clearAllLocks(nodeId);
|
|
2483
2565
|
if (this.endpoints.has(nodeId)) {
|
|
2484
2566
|
const connectionToClose = this.endpoints.get(nodeId).connection;
|
|
2485
2567
|
await connectionToClose.close(gracefulLeave);
|
|
2486
2568
|
}
|
|
2487
2569
|
else {
|
|
2488
|
-
logger$
|
|
2570
|
+
logger$A.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
|
|
2489
2571
|
this.emit('disconnected', peerDescriptor, false);
|
|
2490
2572
|
}
|
|
2491
2573
|
}
|
|
@@ -2497,8 +2579,8 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2497
2579
|
const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
|
|
2498
2580
|
this.locks.addLocalLocked(nodeId, lockId);
|
|
2499
2581
|
rpcRemote.lockRequest(lockId)
|
|
2500
|
-
.then((_accepted) => logger$
|
|
2501
|
-
.catch((err) => { logger$
|
|
2582
|
+
.then((_accepted) => logger$A.trace('LockRequest successful'))
|
|
2583
|
+
.catch((err) => { logger$A.debug(err); });
|
|
2502
2584
|
}
|
|
2503
2585
|
unlockConnection(targetDescriptor, lockId) {
|
|
2504
2586
|
if (this.state === ConnectionManagerState.STOPPED || areEqualPeerDescriptors(targetDescriptor, this.getLocalPeerDescriptor())) {
|
|
@@ -2550,7 +2632,7 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2550
2632
|
async gracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
|
|
2551
2633
|
const endpoint = this.endpoints.get(toNodeId(targetDescriptor));
|
|
2552
2634
|
if (!endpoint) {
|
|
2553
|
-
logger$
|
|
2635
|
+
logger$A.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
|
|
2554
2636
|
return;
|
|
2555
2637
|
}
|
|
2556
2638
|
if (endpoint.connected) {
|
|
@@ -2559,15 +2641,15 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2559
2641
|
// TODO use options option or named constant?
|
|
2560
2642
|
// eslint-disable-next-line promise/catch-or-return
|
|
2561
2643
|
utils.waitForEvent(connection, 'disconnected', 2000).then(() => {
|
|
2562
|
-
logger$
|
|
2644
|
+
logger$A.trace('disconnected event received in gracefullyDisconnectAsync()');
|
|
2563
2645
|
})
|
|
2564
2646
|
.catch((e) => {
|
|
2565
|
-
logger$
|
|
2647
|
+
logger$A.trace('force-closing connection after timeout ' + e);
|
|
2566
2648
|
// TODO should we have some handling for this floating promise?
|
|
2567
2649
|
connection.close(true);
|
|
2568
2650
|
})
|
|
2569
2651
|
.finally(() => {
|
|
2570
|
-
logger$
|
|
2652
|
+
logger$A.trace('resolving after receiving disconnected event');
|
|
2571
2653
|
resolve();
|
|
2572
2654
|
});
|
|
2573
2655
|
});
|
|
@@ -2582,13 +2664,13 @@ class ConnectionManager extends eventemitter3.EventEmitter {
|
|
|
2582
2664
|
}
|
|
2583
2665
|
async doGracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
|
|
2584
2666
|
const nodeId = toNodeId(targetDescriptor);
|
|
2585
|
-
logger$
|
|
2667
|
+
logger$A.trace(nodeId + ' gracefullyDisconnectAsync()');
|
|
2586
2668
|
const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
|
|
2587
2669
|
try {
|
|
2588
2670
|
await rpcRemote.gracefulDisconnect(disconnectMode);
|
|
2589
2671
|
}
|
|
2590
2672
|
catch (ex) {
|
|
2591
|
-
logger$
|
|
2673
|
+
logger$A.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
|
|
2592
2674
|
}
|
|
2593
2675
|
}
|
|
2594
2676
|
getConnections() {
|
|
@@ -2655,7 +2737,7 @@ function protoToString(protoObj, objectType) {
|
|
|
2655
2737
|
return ret;
|
|
2656
2738
|
}
|
|
2657
2739
|
|
|
2658
|
-
const logger$
|
|
2740
|
+
const logger$z = new utils.Logger('SimulatorConnection');
|
|
2659
2741
|
class SimulatorConnection extends Connection {
|
|
2660
2742
|
stopped = false;
|
|
2661
2743
|
localPeerDescriptor;
|
|
@@ -2677,46 +2759,46 @@ class SimulatorConnection extends Connection {
|
|
|
2677
2759
|
this.doDisconnect = this.doDisconnect.bind(this);
|
|
2678
2760
|
}
|
|
2679
2761
|
send(data) {
|
|
2680
|
-
logger$
|
|
2762
|
+
logger$z.trace('send()');
|
|
2681
2763
|
if (!this.stopped) {
|
|
2682
2764
|
this.simulator.send(this, data);
|
|
2683
2765
|
}
|
|
2684
2766
|
else {
|
|
2685
2767
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2686
2768
|
const targetNodeId = toNodeId(this.targetPeerDescriptor);
|
|
2687
|
-
logger$
|
|
2769
|
+
logger$z.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
|
|
2688
2770
|
}
|
|
2689
2771
|
}
|
|
2690
2772
|
async close(gracefulLeave) {
|
|
2691
2773
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2692
2774
|
const targetNodeId = toNodeId(this.targetPeerDescriptor);
|
|
2693
|
-
logger$
|
|
2775
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close()');
|
|
2694
2776
|
if (!this.stopped) {
|
|
2695
|
-
logger$
|
|
2777
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
|
|
2696
2778
|
this.stopped = true;
|
|
2697
2779
|
try {
|
|
2698
|
-
logger$
|
|
2780
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
|
|
2699
2781
|
this.simulator.close(this);
|
|
2700
|
-
logger$
|
|
2782
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
|
|
2701
2783
|
}
|
|
2702
2784
|
catch (e) {
|
|
2703
|
-
logger$
|
|
2785
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
|
|
2704
2786
|
}
|
|
2705
2787
|
finally {
|
|
2706
|
-
logger$
|
|
2788
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
|
|
2707
2789
|
this.doDisconnect(gracefulLeave);
|
|
2708
2790
|
}
|
|
2709
2791
|
}
|
|
2710
2792
|
else {
|
|
2711
|
-
logger$
|
|
2793
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
|
|
2712
2794
|
}
|
|
2713
2795
|
}
|
|
2714
2796
|
connect() {
|
|
2715
2797
|
if (!this.stopped) {
|
|
2716
|
-
logger$
|
|
2798
|
+
logger$z.trace('connect() called');
|
|
2717
2799
|
this.simulator.connect(this, this.targetPeerDescriptor, (error) => {
|
|
2718
2800
|
if (error !== undefined) {
|
|
2719
|
-
logger$
|
|
2801
|
+
logger$z.trace(error);
|
|
2720
2802
|
this.doDisconnect(false);
|
|
2721
2803
|
}
|
|
2722
2804
|
else {
|
|
@@ -2725,46 +2807,46 @@ class SimulatorConnection extends Connection {
|
|
|
2725
2807
|
});
|
|
2726
2808
|
}
|
|
2727
2809
|
else {
|
|
2728
|
-
logger$
|
|
2810
|
+
logger$z.trace('tried to connect() a stopped connection');
|
|
2729
2811
|
}
|
|
2730
2812
|
}
|
|
2731
2813
|
handleIncomingData(data) {
|
|
2732
2814
|
if (!this.stopped) {
|
|
2733
|
-
logger$
|
|
2815
|
+
logger$z.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
|
|
2734
2816
|
this.emit('data', data);
|
|
2735
2817
|
}
|
|
2736
2818
|
else {
|
|
2737
|
-
logger$
|
|
2819
|
+
logger$z.trace('tried to call handleIncomingData() a stopped connection');
|
|
2738
2820
|
}
|
|
2739
2821
|
}
|
|
2740
2822
|
handleIncomingDisconnection() {
|
|
2741
2823
|
if (!this.stopped) {
|
|
2742
2824
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2743
|
-
logger$
|
|
2825
|
+
logger$z.trace(localNodeId + ' handleIncomingDisconnection()');
|
|
2744
2826
|
this.stopped = true;
|
|
2745
2827
|
this.doDisconnect(false);
|
|
2746
2828
|
}
|
|
2747
2829
|
else {
|
|
2748
|
-
logger$
|
|
2830
|
+
logger$z.trace('tried to call handleIncomingDisconnection() a stopped connection');
|
|
2749
2831
|
}
|
|
2750
2832
|
}
|
|
2751
2833
|
destroy() {
|
|
2752
2834
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2753
2835
|
if (!this.stopped) {
|
|
2754
|
-
logger$
|
|
2836
|
+
logger$z.trace(localNodeId + ' destroy()');
|
|
2755
2837
|
this.removeAllListeners();
|
|
2756
2838
|
this.close(false).catch((_e) => { });
|
|
2757
2839
|
}
|
|
2758
2840
|
else {
|
|
2759
|
-
logger$
|
|
2841
|
+
logger$z.trace(localNodeId + ' tried to call destroy() a stopped connection');
|
|
2760
2842
|
}
|
|
2761
2843
|
}
|
|
2762
2844
|
doDisconnect(gracefulLeave) {
|
|
2763
2845
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2764
2846
|
const targetNodeId = toNodeId(this.targetPeerDescriptor);
|
|
2765
|
-
logger$
|
|
2847
|
+
logger$z.trace(localNodeId + ' doDisconnect()');
|
|
2766
2848
|
this.stopped = true;
|
|
2767
|
-
logger$
|
|
2849
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
|
|
2768
2850
|
this.emit('disconnected', gracefulLeave);
|
|
2769
2851
|
}
|
|
2770
2852
|
}
|
|
@@ -2802,9 +2884,9 @@ const parseVersion = (version) => {
|
|
|
2802
2884
|
}
|
|
2803
2885
|
};
|
|
2804
2886
|
|
|
2805
|
-
var version = "103.
|
|
2887
|
+
var version = "103.7.0-rc.2";
|
|
2806
2888
|
|
|
2807
|
-
const logger$
|
|
2889
|
+
const logger$y = new utils.Logger('Handshaker');
|
|
2808
2890
|
// Optimally the Outgoing and Incoming Handshakers could be their own separate classes
|
|
2809
2891
|
// However, in cases where the PeerDescriptor of the other end of the connection can be known
|
|
2810
2892
|
// only after a HandshakeRequest a base Handshaker class is needed as the IncomingHandshaker currently
|
|
@@ -2826,7 +2908,7 @@ const createOutgoingHandshaker = (localPeerDescriptor, pendingConnection, connec
|
|
|
2826
2908
|
}
|
|
2827
2909
|
};
|
|
2828
2910
|
const handshakeCompletedListener = (peerDescriptor) => {
|
|
2829
|
-
logger$
|
|
2911
|
+
logger$y.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
|
|
2830
2912
|
pendingConnection.onHandshakeCompleted(connection);
|
|
2831
2913
|
stopHandshaker();
|
|
2832
2914
|
};
|
|
@@ -2926,12 +3008,12 @@ class Handshaker extends eventemitter3.EventEmitter {
|
|
|
2926
3008
|
try {
|
|
2927
3009
|
const message = Message.fromBinary(data);
|
|
2928
3010
|
if (message.body.oneofKind === 'handshakeRequest') {
|
|
2929
|
-
logger$
|
|
3011
|
+
logger$y.trace('handshake request received');
|
|
2930
3012
|
const handshake = message.body.handshakeRequest;
|
|
2931
3013
|
this.emit('handshakeRequest', handshake.sourcePeerDescriptor, handshake.protocolVersion, handshake.targetPeerDescriptor);
|
|
2932
3014
|
}
|
|
2933
3015
|
if (message.body.oneofKind === 'handshakeResponse') {
|
|
2934
|
-
logger$
|
|
3016
|
+
logger$y.trace('handshake response received');
|
|
2935
3017
|
const handshake = message.body.handshakeResponse;
|
|
2936
3018
|
const error = !isMaybeSupportedProtocolVersion(handshake.protocolVersion)
|
|
2937
3019
|
? HandshakeError.UNSUPPORTED_PROTOCOL_VERSION : handshake.error;
|
|
@@ -2944,18 +3026,18 @@ class Handshaker extends eventemitter3.EventEmitter {
|
|
|
2944
3026
|
}
|
|
2945
3027
|
}
|
|
2946
3028
|
catch (err) {
|
|
2947
|
-
logger$
|
|
3029
|
+
logger$y.debug('error while parsing handshake message', err);
|
|
2948
3030
|
}
|
|
2949
3031
|
}
|
|
2950
3032
|
sendHandshakeRequest(remotePeerDescriptor) {
|
|
2951
3033
|
const msg = createHandshakeRequest(this.localPeerDescriptor, remotePeerDescriptor);
|
|
2952
3034
|
this.connection.send(Message.toBinary(msg));
|
|
2953
|
-
logger$
|
|
3035
|
+
logger$y.trace('handshake request sent');
|
|
2954
3036
|
}
|
|
2955
3037
|
sendHandshakeResponse(error) {
|
|
2956
3038
|
const msg = createHandshakeResponse(this.localPeerDescriptor, error);
|
|
2957
3039
|
this.connection.send(Message.toBinary(msg));
|
|
2958
|
-
logger$
|
|
3040
|
+
logger$y.trace('handshake response sent');
|
|
2959
3041
|
}
|
|
2960
3042
|
stop() {
|
|
2961
3043
|
this.connection.off('data', this.onDataListener);
|
|
@@ -2963,7 +3045,7 @@ class Handshaker extends eventemitter3.EventEmitter {
|
|
|
2963
3045
|
}
|
|
2964
3046
|
}
|
|
2965
3047
|
|
|
2966
|
-
const logger$
|
|
3048
|
+
const logger$x = new utils.Logger('PendingConnection');
|
|
2967
3049
|
// PendingConnection is used as a reference to a connection that should be opened and handshaked to a given PeerDescriptor
|
|
2968
3050
|
// It does not hold a connection internally. The public method onHandshakedCompleted should be called once a connection for the
|
|
2969
3051
|
// remotePeerDescriptor is opened and handshaked successfully.
|
|
@@ -2981,7 +3063,7 @@ class PendingConnection extends eventemitter3.EventEmitter {
|
|
|
2981
3063
|
}, timeout, this.connectingAbortController.signal);
|
|
2982
3064
|
}
|
|
2983
3065
|
replaceAsDuplicate() {
|
|
2984
|
-
logger$
|
|
3066
|
+
logger$x.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
|
|
2985
3067
|
this.replacedAsDuplicate = true;
|
|
2986
3068
|
}
|
|
2987
3069
|
onHandshakeCompleted(connection) {
|
|
@@ -3012,7 +3094,7 @@ class PendingConnection extends eventemitter3.EventEmitter {
|
|
|
3012
3094
|
}
|
|
3013
3095
|
}
|
|
3014
3096
|
|
|
3015
|
-
const logger$
|
|
3097
|
+
const logger$w = new utils.Logger('SimulatorConnector');
|
|
3016
3098
|
class SimulatorConnector {
|
|
3017
3099
|
connectingConnections = new Map();
|
|
3018
3100
|
stopped = false;
|
|
@@ -3025,7 +3107,7 @@ class SimulatorConnector {
|
|
|
3025
3107
|
this.onNewConnection = onNewConnection;
|
|
3026
3108
|
}
|
|
3027
3109
|
connect(targetPeerDescriptor) {
|
|
3028
|
-
logger$
|
|
3110
|
+
logger$w.trace('connect() ' + toNodeId(targetPeerDescriptor));
|
|
3029
3111
|
const nodeId = toNodeId(targetPeerDescriptor);
|
|
3030
3112
|
const existingConnection = this.connectingConnections.get(nodeId);
|
|
3031
3113
|
if (existingConnection) {
|
|
@@ -3054,18 +3136,18 @@ class SimulatorConnector {
|
|
|
3054
3136
|
// connection is incoming, so remotePeerDescriptor is localPeerDescriptor
|
|
3055
3137
|
const remotePeerDescriptor = sourceConnection.localPeerDescriptor;
|
|
3056
3138
|
const remoteNodeId = toNodeId(sourceConnection.localPeerDescriptor);
|
|
3057
|
-
logger$
|
|
3139
|
+
logger$w.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
|
|
3058
3140
|
if (this.stopped) {
|
|
3059
3141
|
return;
|
|
3060
3142
|
}
|
|
3061
3143
|
const connection = new SimulatorConnection(this.localPeerDescriptor, remotePeerDescriptor, exports.ConnectionType.SIMULATOR_SERVER, this.simulator);
|
|
3062
3144
|
const pendingConnection = new PendingConnection(remotePeerDescriptor);
|
|
3063
3145
|
const handshaker = createIncomingHandshaker(this.localPeerDescriptor, pendingConnection, connection);
|
|
3064
|
-
logger$
|
|
3146
|
+
logger$w.trace('connected');
|
|
3065
3147
|
handshaker.once('handshakeRequest', () => {
|
|
3066
|
-
logger$
|
|
3148
|
+
logger$w.trace(remoteNodeId + ' incoming handshake request');
|
|
3067
3149
|
if (this.onNewConnection(pendingConnection)) {
|
|
3068
|
-
logger$
|
|
3150
|
+
logger$w.trace(remoteNodeId + ' calling acceptHandshake');
|
|
3069
3151
|
acceptHandshake(handshaker, pendingConnection, connection);
|
|
3070
3152
|
}
|
|
3071
3153
|
else {
|
|
@@ -3105,6 +3187,8 @@ class ListeningRpcCommunicator extends RoutingRpcCommunicator {
|
|
|
3105
3187
|
}
|
|
3106
3188
|
}
|
|
3107
3189
|
|
|
3190
|
+
const isWorkerEnvironment = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
|
|
3191
|
+
|
|
3108
3192
|
var RtcDescription;
|
|
3109
3193
|
(function (RtcDescription) {
|
|
3110
3194
|
RtcDescription["OFFER"] = "offer";
|
|
@@ -3119,8 +3203,8 @@ var DisconnectedRtcPeerConnectionStateEnum;
|
|
|
3119
3203
|
DisconnectedRtcPeerConnectionStateEnum["FAILED"] = "failed";
|
|
3120
3204
|
DisconnectedRtcPeerConnectionStateEnum["CLOSED"] = "closed";
|
|
3121
3205
|
})(DisconnectedRtcPeerConnectionStateEnum || (DisconnectedRtcPeerConnectionStateEnum = {}));
|
|
3122
|
-
const logger$
|
|
3123
|
-
class
|
|
3206
|
+
const logger$v = new utils.Logger('DirectWebrtcConnection (browser)');
|
|
3207
|
+
class DirectWebrtcConnection extends eventemitter3.EventEmitter {
|
|
3124
3208
|
connectionId;
|
|
3125
3209
|
connectionType = exports.ConnectionType.WEBRTC;
|
|
3126
3210
|
// We need to keep track of connection state ourselves because
|
|
@@ -3158,7 +3242,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3158
3242
|
}
|
|
3159
3243
|
};
|
|
3160
3244
|
this.peerConnection.onicegatheringstatechange = () => {
|
|
3161
|
-
logger$
|
|
3245
|
+
logger$v.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
|
|
3162
3246
|
};
|
|
3163
3247
|
this.peerConnection.onconnectionstatechange = () => this.onStateChange();
|
|
3164
3248
|
if (isOffering) {
|
|
@@ -3169,7 +3253,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3169
3253
|
await this.peerConnection.setLocalDescription();
|
|
3170
3254
|
}
|
|
3171
3255
|
catch (err) {
|
|
3172
|
-
logger$
|
|
3256
|
+
logger$v.warn('Failed to set local description', { err });
|
|
3173
3257
|
}
|
|
3174
3258
|
if (this.peerConnection.localDescription !== null) {
|
|
3175
3259
|
this.emit('localDescription', this.peerConnection.localDescription?.sdp, this.peerConnection.localDescription?.type);
|
|
@@ -3197,14 +3281,14 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3197
3281
|
clearTimeout(this.earlyTimeout);
|
|
3198
3282
|
}
|
|
3199
3283
|
catch (err) {
|
|
3200
|
-
logger$
|
|
3284
|
+
logger$v.warn('Failed to set remote description', { err });
|
|
3201
3285
|
}
|
|
3202
3286
|
if ((type.toLowerCase() === RtcDescription.OFFER) && (this.peerConnection !== undefined)) {
|
|
3203
3287
|
try {
|
|
3204
3288
|
await this.peerConnection.setLocalDescription();
|
|
3205
3289
|
}
|
|
3206
3290
|
catch (err) {
|
|
3207
|
-
logger$
|
|
3291
|
+
logger$v.warn('Failed to set local description', { err });
|
|
3208
3292
|
}
|
|
3209
3293
|
if (this.peerConnection.localDescription !== null) {
|
|
3210
3294
|
this.emit('localDescription', this.peerConnection.localDescription.sdp, this.peerConnection.localDescription.type);
|
|
@@ -3214,7 +3298,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3214
3298
|
addRemoteCandidate(candidate, mid) {
|
|
3215
3299
|
this.peerConnection?.addIceCandidate({ candidate: candidate, sdpMid: mid })
|
|
3216
3300
|
.catch((err) => {
|
|
3217
|
-
logger$
|
|
3301
|
+
logger$v.warn('Failed to add ICE candidate', { err });
|
|
3218
3302
|
});
|
|
3219
3303
|
}
|
|
3220
3304
|
isOpen() {
|
|
@@ -3237,7 +3321,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3237
3321
|
this.dataChannel.close();
|
|
3238
3322
|
}
|
|
3239
3323
|
catch (err) {
|
|
3240
|
-
logger$
|
|
3324
|
+
logger$v.warn('Failed to close data channel', { err });
|
|
3241
3325
|
}
|
|
3242
3326
|
}
|
|
3243
3327
|
this.dataChannel = undefined;
|
|
@@ -3246,7 +3330,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3246
3330
|
this.peerConnection.close();
|
|
3247
3331
|
}
|
|
3248
3332
|
catch (err) {
|
|
3249
|
-
logger$
|
|
3333
|
+
logger$v.warn('Failed to close connection', { err });
|
|
3250
3334
|
}
|
|
3251
3335
|
}
|
|
3252
3336
|
this.peerConnection = undefined;
|
|
@@ -3258,6 +3342,9 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3258
3342
|
}
|
|
3259
3343
|
send(data) {
|
|
3260
3344
|
if (this.lastState === 'connected') {
|
|
3345
|
+
logGapDiagnosticSampled('dht.dc.send', {
|
|
3346
|
+
detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
|
|
3347
|
+
});
|
|
3261
3348
|
if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
|
|
3262
3349
|
this.messageQueue.push(data);
|
|
3263
3350
|
}
|
|
@@ -3266,7 +3353,7 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3266
3353
|
}
|
|
3267
3354
|
}
|
|
3268
3355
|
else {
|
|
3269
|
-
logger$
|
|
3356
|
+
logger$v.warn('Tried to send on a connection with last state ' + this.lastState);
|
|
3270
3357
|
}
|
|
3271
3358
|
}
|
|
3272
3359
|
setupDataChannel(dataChannel) {
|
|
@@ -3274,22 +3361,23 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3274
3361
|
this.dataChannel.binaryType = 'arraybuffer';
|
|
3275
3362
|
this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
|
|
3276
3363
|
dataChannel.onopen = () => {
|
|
3277
|
-
logger$
|
|
3364
|
+
logger$v.trace('dc.onOpen');
|
|
3278
3365
|
this.onDataChannelOpen();
|
|
3279
3366
|
};
|
|
3280
3367
|
dataChannel.onclose = () => {
|
|
3281
|
-
logger$
|
|
3368
|
+
logger$v.trace('dc.onClosed');
|
|
3282
3369
|
this.doClose(false);
|
|
3283
3370
|
};
|
|
3284
3371
|
dataChannel.onerror = (err) => {
|
|
3285
|
-
logger$
|
|
3372
|
+
logger$v.warn('Data channel error', { err });
|
|
3286
3373
|
};
|
|
3287
3374
|
dataChannel.onmessage = (msg) => {
|
|
3288
|
-
logger$
|
|
3375
|
+
logger$v.trace('dc.onmessage');
|
|
3376
|
+
logGapDiagnosticSampled('dht.dc.onmessage');
|
|
3289
3377
|
this.emit('data', new Uint8Array(msg.data));
|
|
3290
3378
|
};
|
|
3291
3379
|
dataChannel.onbufferedamountlow = () => {
|
|
3292
|
-
logger$
|
|
3380
|
+
logger$v.trace('dc.onBufferedAmountLow');
|
|
3293
3381
|
while (this.messageQueue.length > 0 && this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
|
|
3294
3382
|
const data = this.messageQueue.shift();
|
|
3295
3383
|
this.dataChannel.send(data);
|
|
@@ -3328,6 +3416,435 @@ class WebrtcConnection extends eventemitter3.EventEmitter {
|
|
|
3328
3416
|
}
|
|
3329
3417
|
}
|
|
3330
3418
|
|
|
3419
|
+
/**
|
|
3420
|
+
* WebrtcBridge — runs on the MAIN THREAD.
|
|
3421
|
+
*
|
|
3422
|
+
* Manages RTCPeerConnection instances on behalf of a worker that cannot
|
|
3423
|
+
* access them directly. Each logical connection is identified by a
|
|
3424
|
+
* `connectionId` string supplied by the worker.
|
|
3425
|
+
*
|
|
3426
|
+
* Signaling (ICE candidates, SDP offers/answers, connection-state changes)
|
|
3427
|
+
* is relayed to the worker through Comlink proxy callbacks.
|
|
3428
|
+
*
|
|
3429
|
+
* Once a DataChannel is created (offerer) or received (answerer) it is
|
|
3430
|
+
* **transferred** to the worker so that all data-path events fire inside
|
|
3431
|
+
* the worker's event loop — the main thread never touches data traffic.
|
|
3432
|
+
*/
|
|
3433
|
+
// ── Bridge implementation ───────────────────────────────────────────
|
|
3434
|
+
class WebrtcBridge {
|
|
3435
|
+
connections = new Map();
|
|
3436
|
+
async start(connectionId, iceServers, isOffering, callbacks) {
|
|
3437
|
+
const pc = new RTCPeerConnection({ iceServers });
|
|
3438
|
+
const conn = {
|
|
3439
|
+
pc,
|
|
3440
|
+
callbacks,
|
|
3441
|
+
isOffering,
|
|
3442
|
+
makingOffer: false,
|
|
3443
|
+
};
|
|
3444
|
+
this.connections.set(connectionId, conn);
|
|
3445
|
+
// ── ICE candidates ──────────────────────────────────────
|
|
3446
|
+
pc.onicecandidate = (event) => {
|
|
3447
|
+
if (event.candidate !== null && event.candidate.sdpMid !== null) {
|
|
3448
|
+
callbacks.onLocalCandidate(event.candidate.candidate, event.candidate.sdpMid);
|
|
3449
|
+
}
|
|
3450
|
+
};
|
|
3451
|
+
// ── Connection state → forwarded to worker ──────────────
|
|
3452
|
+
pc.onconnectionstatechange = () => {
|
|
3453
|
+
callbacks.onConnectionStateChange(pc.connectionState);
|
|
3454
|
+
};
|
|
3455
|
+
// ── Offerer path ────────────────────────────────────────
|
|
3456
|
+
if (isOffering) {
|
|
3457
|
+
pc.onnegotiationneeded = async () => {
|
|
3458
|
+
conn.makingOffer = true;
|
|
3459
|
+
try {
|
|
3460
|
+
await pc.setLocalDescription();
|
|
3461
|
+
}
|
|
3462
|
+
catch (_err) {
|
|
3463
|
+
// intentionally swallowed – mirrors DirectWebrtcConnection
|
|
3464
|
+
}
|
|
3465
|
+
if (pc.localDescription !== null) {
|
|
3466
|
+
callbacks.onLocalDescription(pc.localDescription.sdp, pc.localDescription.type);
|
|
3467
|
+
}
|
|
3468
|
+
conn.makingOffer = false;
|
|
3469
|
+
};
|
|
3470
|
+
const dc = pc.createDataChannel('streamrDataChannel');
|
|
3471
|
+
// Transfer DataChannel ownership to the worker immediately.
|
|
3472
|
+
// The worker will attach onopen/onclose/onmessage handlers.
|
|
3473
|
+
callbacks.onDataChannel(Comlink__namespace.transfer(dc, [dc]));
|
|
3474
|
+
}
|
|
3475
|
+
else {
|
|
3476
|
+
// ── Answerer path ───────────────────────────────────
|
|
3477
|
+
pc.ondatachannel = (event) => {
|
|
3478
|
+
callbacks.onDataChannel(Comlink__namespace.transfer(event.channel, [event.channel]));
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
async setRemoteDescription(connectionId, description, type) {
|
|
3483
|
+
const conn = this.connections.get(connectionId);
|
|
3484
|
+
if (!conn) {
|
|
3485
|
+
return false;
|
|
3486
|
+
}
|
|
3487
|
+
const lowerType = type.toLowerCase();
|
|
3488
|
+
// Perfect-negotiation collision detection
|
|
3489
|
+
const offerCollision = lowerType === 'offer' &&
|
|
3490
|
+
(conn.makingOffer || conn.pc.signalingState !== 'stable');
|
|
3491
|
+
if (conn.isOffering && offerCollision) {
|
|
3492
|
+
return false;
|
|
3493
|
+
}
|
|
3494
|
+
try {
|
|
3495
|
+
await conn.pc.setRemoteDescription({ sdp: description, type: lowerType });
|
|
3496
|
+
}
|
|
3497
|
+
catch (_err) {
|
|
3498
|
+
return false;
|
|
3499
|
+
}
|
|
3500
|
+
// If we received an offer, create an answer
|
|
3501
|
+
if (lowerType === 'offer') {
|
|
3502
|
+
try {
|
|
3503
|
+
await conn.pc.setLocalDescription();
|
|
3504
|
+
}
|
|
3505
|
+
catch (_err) {
|
|
3506
|
+
// intentionally swallowed
|
|
3507
|
+
}
|
|
3508
|
+
if (conn.pc.localDescription !== null) {
|
|
3509
|
+
conn.callbacks.onLocalDescription(conn.pc.localDescription.sdp, conn.pc.localDescription.type);
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
return true;
|
|
3513
|
+
}
|
|
3514
|
+
async addRemoteCandidate(connectionId, candidate, mid) {
|
|
3515
|
+
const conn = this.connections.get(connectionId);
|
|
3516
|
+
if (!conn) {
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
try {
|
|
3520
|
+
await conn.pc.addIceCandidate({ candidate, sdpMid: mid });
|
|
3521
|
+
}
|
|
3522
|
+
catch (_err) {
|
|
3523
|
+
// intentionally swallowed
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
async renameConnection(oldId, newId) {
|
|
3527
|
+
const conn = this.connections.get(oldId);
|
|
3528
|
+
if (conn) {
|
|
3529
|
+
this.connections.delete(oldId);
|
|
3530
|
+
this.connections.set(newId, conn);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
async close(connectionId) {
|
|
3534
|
+
const conn = this.connections.get(connectionId);
|
|
3535
|
+
if (!conn) {
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
this.connections.delete(connectionId);
|
|
3539
|
+
// Tear down event handlers
|
|
3540
|
+
conn.pc.onicecandidate = null;
|
|
3541
|
+
conn.pc.onconnectionstatechange = null;
|
|
3542
|
+
conn.pc.onnegotiationneeded = null;
|
|
3543
|
+
conn.pc.ondatachannel = null;
|
|
3544
|
+
try {
|
|
3545
|
+
conn.pc.close();
|
|
3546
|
+
}
|
|
3547
|
+
catch (_err) {
|
|
3548
|
+
// intentionally swallowed
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
/**
|
|
3554
|
+
* Call this function on the **main thread** before the worker starts using
|
|
3555
|
+
* WebRTC connections. It creates a dedicated `MessageChannel`, exposes a
|
|
3556
|
+
* {@link WebrtcBridge} instance on one port, and sends the other port to
|
|
3557
|
+
* the worker so that `WorkerWebrtcConnection` can reach the bridge.
|
|
3558
|
+
*
|
|
3559
|
+
* @example
|
|
3560
|
+
* ```ts
|
|
3561
|
+
* import { installWebrtcBridge } from '@streamr/dht'
|
|
3562
|
+
*
|
|
3563
|
+
* const worker = new Worker('./my-worker.ts', { type: 'module' })
|
|
3564
|
+
* installWebrtcBridge(worker)
|
|
3565
|
+
* ```
|
|
3566
|
+
*/
|
|
3567
|
+
const WEBRTC_BRIDGE_PORT_MESSAGE_TYPE = 'streamr-webrtc-bridge-port';
|
|
3568
|
+
function installWebrtcBridge(worker) {
|
|
3569
|
+
const bridge = new WebrtcBridge();
|
|
3570
|
+
const channel = new MessageChannel();
|
|
3571
|
+
// Expose the bridge API on port1 — the worker will Comlink.wrap(port2).
|
|
3572
|
+
Comlink__namespace.expose(bridge, channel.port1);
|
|
3573
|
+
// Send port2 to the worker. It is transferred (not cloned).
|
|
3574
|
+
worker.postMessage({ type: WEBRTC_BRIDGE_PORT_MESSAGE_TYPE, port: channel.port2 }, [channel.port2]);
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
/**
|
|
3578
|
+
* WorkerWebrtcConnection — runs inside a **Web Worker**.
|
|
3579
|
+
*
|
|
3580
|
+
* Implements the same IWebrtcConnection + IConnection interfaces as
|
|
3581
|
+
* DirectWebrtcConnection, but delegates RTCPeerConnection management to
|
|
3582
|
+
* the main-thread {@link WebrtcBridge} via Comlink.
|
|
3583
|
+
*
|
|
3584
|
+
* The RTCDataChannel is **transferred** from the main thread and lives
|
|
3585
|
+
* entirely in the worker — all data events (onmessage, onopen, onclose,
|
|
3586
|
+
* onbufferedamountlow) fire in the worker's event loop. The main thread
|
|
3587
|
+
* is never involved in the data path.
|
|
3588
|
+
*/
|
|
3589
|
+
// ── Module-level bridge client (initialized once per worker) ────────
|
|
3590
|
+
let resolveBridgeProxy;
|
|
3591
|
+
const bridgeProxyPromise = new Promise((resolve) => {
|
|
3592
|
+
resolveBridgeProxy = resolve;
|
|
3593
|
+
});
|
|
3594
|
+
// Listen for the bridge port message from the main thread.
|
|
3595
|
+
// This is guarded so it only runs inside a worker context.
|
|
3596
|
+
if (isWorkerEnvironment) {
|
|
3597
|
+
const handler = (e) => {
|
|
3598
|
+
if (e.data?.type === WEBRTC_BRIDGE_PORT_MESSAGE_TYPE && e.data.port) {
|
|
3599
|
+
const proxy = Comlink__namespace.wrap(e.data.port);
|
|
3600
|
+
resolveBridgeProxy(proxy);
|
|
3601
|
+
self.removeEventListener('message', handler);
|
|
3602
|
+
}
|
|
3603
|
+
};
|
|
3604
|
+
self.addEventListener('message', handler);
|
|
3605
|
+
}
|
|
3606
|
+
function getBridgeProxy() {
|
|
3607
|
+
return bridgeProxyPromise;
|
|
3608
|
+
}
|
|
3609
|
+
// ── Disconnection states ────────────────────────────────────────────
|
|
3610
|
+
var DisconnectedState;
|
|
3611
|
+
(function (DisconnectedState) {
|
|
3612
|
+
DisconnectedState["DISCONNECTED"] = "disconnected";
|
|
3613
|
+
DisconnectedState["FAILED"] = "failed";
|
|
3614
|
+
DisconnectedState["CLOSED"] = "closed";
|
|
3615
|
+
})(DisconnectedState || (DisconnectedState = {}));
|
|
3616
|
+
const logger$u = new utils.Logger('WorkerWebrtcConnection');
|
|
3617
|
+
// ── WorkerWebrtcConnection ──────────────────────────────────────────
|
|
3618
|
+
class WorkerWebrtcConnection extends eventemitter3.EventEmitter {
|
|
3619
|
+
connectionId;
|
|
3620
|
+
connectionType = exports.ConnectionType.WEBRTC;
|
|
3621
|
+
iceServers;
|
|
3622
|
+
bufferThresholdHigh;
|
|
3623
|
+
bufferThresholdLow;
|
|
3624
|
+
dataChannel;
|
|
3625
|
+
bridge;
|
|
3626
|
+
closed = false;
|
|
3627
|
+
connected = false;
|
|
3628
|
+
earlyTimeout;
|
|
3629
|
+
messageQueue = [];
|
|
3630
|
+
startPromise;
|
|
3631
|
+
constructor(params) {
|
|
3632
|
+
super();
|
|
3633
|
+
this.connectionId = createRandomConnectionId();
|
|
3634
|
+
this.iceServers = params.iceServers ?? [];
|
|
3635
|
+
this.bufferThresholdHigh = params.bufferThresholdHigh ?? 2 ** 17;
|
|
3636
|
+
this.bufferThresholdLow = params.bufferThresholdLow ?? 2 ** 15;
|
|
3637
|
+
this.earlyTimeout = setTimeout(() => {
|
|
3638
|
+
this.doClose(false, 'timed out due to remote descriptor not being set');
|
|
3639
|
+
}, EARLY_TIMEOUT);
|
|
3640
|
+
}
|
|
3641
|
+
// ── IWebrtcConnection ───────────────────────────────────────
|
|
3642
|
+
start(isOffering) {
|
|
3643
|
+
this.startPromise = this.doStart(isOffering);
|
|
3644
|
+
this.startPromise.catch((err) => {
|
|
3645
|
+
logger$u.warn('Failed to start worker WebRTC connection', { err });
|
|
3646
|
+
this.doClose(false, 'Failed to start');
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
async doStart(isOffering) {
|
|
3650
|
+
this.bridge = await getBridgeProxy();
|
|
3651
|
+
const iceServers = this.iceServers.map(({ url, port, username, password }) => ({
|
|
3652
|
+
urls: `${url}:${port}`,
|
|
3653
|
+
username,
|
|
3654
|
+
credential: password,
|
|
3655
|
+
}));
|
|
3656
|
+
await this.bridge.start(this.connectionId, iceServers, isOffering, Comlink__namespace.proxy({
|
|
3657
|
+
onLocalCandidate: (candidate, mid) => {
|
|
3658
|
+
if (!this.closed) {
|
|
3659
|
+
this.emit('localCandidate', candidate, mid);
|
|
3660
|
+
}
|
|
3661
|
+
},
|
|
3662
|
+
onLocalDescription: (description, type) => {
|
|
3663
|
+
if (!this.closed) {
|
|
3664
|
+
this.emit('localDescription', description, type);
|
|
3665
|
+
}
|
|
3666
|
+
},
|
|
3667
|
+
onConnectionStateChange: (state) => {
|
|
3668
|
+
if (state === DisconnectedState.CLOSED ||
|
|
3669
|
+
state === DisconnectedState.DISCONNECTED ||
|
|
3670
|
+
state === DisconnectedState.FAILED) {
|
|
3671
|
+
this.doClose(false, `pcState=${state}`);
|
|
3672
|
+
}
|
|
3673
|
+
},
|
|
3674
|
+
onDataChannel: (channel) => {
|
|
3675
|
+
if (!this.closed) {
|
|
3676
|
+
this.setupDataChannel(channel);
|
|
3677
|
+
// If the channel was already open at transfer time
|
|
3678
|
+
if (channel.readyState === 'open') {
|
|
3679
|
+
this.onDataChannelOpen();
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
},
|
|
3683
|
+
}));
|
|
3684
|
+
}
|
|
3685
|
+
async setRemoteDescription(description, type) {
|
|
3686
|
+
if (this.startPromise) {
|
|
3687
|
+
await this.startPromise;
|
|
3688
|
+
}
|
|
3689
|
+
if (!this.bridge || this.closed) {
|
|
3690
|
+
return;
|
|
3691
|
+
}
|
|
3692
|
+
const wasSet = await this.bridge.setRemoteDescription(this.connectionId, description, type);
|
|
3693
|
+
if (wasSet) {
|
|
3694
|
+
clearTimeout(this.earlyTimeout);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
addRemoteCandidate(candidate, mid) {
|
|
3698
|
+
this.doAddRemoteCandidate(candidate, mid).catch((err) => {
|
|
3699
|
+
logger$u.warn('Failed to add remote candidate via bridge', { err });
|
|
3700
|
+
});
|
|
3701
|
+
}
|
|
3702
|
+
async doAddRemoteCandidate(candidate, mid) {
|
|
3703
|
+
if (this.startPromise) {
|
|
3704
|
+
await this.startPromise;
|
|
3705
|
+
}
|
|
3706
|
+
if (!this.bridge || this.closed) {
|
|
3707
|
+
return;
|
|
3708
|
+
}
|
|
3709
|
+
await this.bridge.addRemoteCandidate(this.connectionId, candidate, mid);
|
|
3710
|
+
}
|
|
3711
|
+
isOpen() {
|
|
3712
|
+
return this.connected;
|
|
3713
|
+
}
|
|
3714
|
+
// ── IConnection ─────────────────────────────────────────────
|
|
3715
|
+
async close(gracefulLeave, reason) {
|
|
3716
|
+
this.doClose(gracefulLeave, reason);
|
|
3717
|
+
}
|
|
3718
|
+
destroy() {
|
|
3719
|
+
this.removeAllListeners();
|
|
3720
|
+
this.doClose(false);
|
|
3721
|
+
}
|
|
3722
|
+
send(data) {
|
|
3723
|
+
if (this.connected && this.dataChannel) {
|
|
3724
|
+
logGapDiagnosticSampled('dht.dc.send', {
|
|
3725
|
+
detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
|
|
3726
|
+
});
|
|
3727
|
+
if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
|
|
3728
|
+
this.messageQueue.push(data);
|
|
3729
|
+
}
|
|
3730
|
+
else {
|
|
3731
|
+
this.dataChannel.send(data);
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
else if (!this.closed) {
|
|
3735
|
+
this.messageQueue.push(data);
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
setConnectionId(connectionId) {
|
|
3739
|
+
const oldId = this.connectionId;
|
|
3740
|
+
this.connectionId = connectionId;
|
|
3741
|
+
if (this.bridge && oldId !== connectionId) {
|
|
3742
|
+
this.bridge.renameConnection(oldId, connectionId).catch(() => { });
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
// ── DataChannel handling (runs entirely in the worker) ──────
|
|
3746
|
+
setupDataChannel(dataChannel) {
|
|
3747
|
+
this.dataChannel = dataChannel;
|
|
3748
|
+
this.dataChannel.binaryType = 'arraybuffer';
|
|
3749
|
+
this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
|
|
3750
|
+
dataChannel.onopen = () => {
|
|
3751
|
+
logger$u.trace('dc.onOpen (worker)');
|
|
3752
|
+
this.onDataChannelOpen();
|
|
3753
|
+
};
|
|
3754
|
+
dataChannel.onclose = () => {
|
|
3755
|
+
logger$u.trace('dc.onClosed (worker)');
|
|
3756
|
+
this.doClose(false, 'dataChannel.onclose');
|
|
3757
|
+
};
|
|
3758
|
+
dataChannel.onerror = (err) => {
|
|
3759
|
+
logger$u.warn('Data channel error (worker)', { err });
|
|
3760
|
+
};
|
|
3761
|
+
dataChannel.onmessage = (msg) => {
|
|
3762
|
+
logger$u.trace('dc.onmessage (worker)');
|
|
3763
|
+
logGapDiagnosticSampled('dht.dc.onmessage');
|
|
3764
|
+
this.emit('data', new Uint8Array(msg.data));
|
|
3765
|
+
};
|
|
3766
|
+
dataChannel.onbufferedamountlow = () => {
|
|
3767
|
+
logger$u.trace('dc.onBufferedAmountLow (worker)');
|
|
3768
|
+
while (this.messageQueue.length > 0 &&
|
|
3769
|
+
this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
|
|
3770
|
+
const data = this.messageQueue.shift();
|
|
3771
|
+
this.dataChannel.send(data);
|
|
3772
|
+
}
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
onDataChannelOpen() {
|
|
3776
|
+
this.connected = true;
|
|
3777
|
+
this.flushMessageQueue();
|
|
3778
|
+
this.emit('connected');
|
|
3779
|
+
}
|
|
3780
|
+
flushMessageQueue() {
|
|
3781
|
+
while (this.messageQueue.length > 0 &&
|
|
3782
|
+
this.dataChannel &&
|
|
3783
|
+
this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
|
|
3784
|
+
const data = this.messageQueue.shift();
|
|
3785
|
+
this.dataChannel.send(data);
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
// ── Teardown ────────────────────────────────────────────────
|
|
3789
|
+
doClose(gracefulLeave, reason) {
|
|
3790
|
+
if (!this.closed) {
|
|
3791
|
+
this.closed = true;
|
|
3792
|
+
this.connected = false;
|
|
3793
|
+
this.messageQueue.length = 0;
|
|
3794
|
+
clearTimeout(this.earlyTimeout);
|
|
3795
|
+
this.stopListening();
|
|
3796
|
+
this.emit('disconnected', gracefulLeave, undefined, reason);
|
|
3797
|
+
this.removeAllListeners();
|
|
3798
|
+
if (this.dataChannel !== undefined) {
|
|
3799
|
+
try {
|
|
3800
|
+
this.dataChannel.close();
|
|
3801
|
+
}
|
|
3802
|
+
catch (err) {
|
|
3803
|
+
logger$u.warn('Failed to close data channel (worker)', { err });
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
this.dataChannel = undefined;
|
|
3807
|
+
// Tell the main-thread bridge to tear down the RTCPeerConnection.
|
|
3808
|
+
// Fire-and-forget — we don't block on this.
|
|
3809
|
+
this.bridge
|
|
3810
|
+
?.close(this.connectionId)
|
|
3811
|
+
.catch(() => {
|
|
3812
|
+
// intentionally swallowed
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
stopListening() {
|
|
3817
|
+
if (this.dataChannel !== undefined) {
|
|
3818
|
+
this.dataChannel.onopen = null;
|
|
3819
|
+
this.dataChannel.onclose = null;
|
|
3820
|
+
this.dataChannel.onerror = null;
|
|
3821
|
+
this.dataChannel.onbufferedamountlow = null;
|
|
3822
|
+
this.dataChannel.onmessage = null;
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
/**
|
|
3828
|
+
* Conditional re-export of the browser WebrtcConnection.
|
|
3829
|
+
*
|
|
3830
|
+
* At module-load time we detect whether we are running inside a Web Worker.
|
|
3831
|
+
* - **Main thread** → use {@link DirectWebrtcConnection} which owns the
|
|
3832
|
+
* `RTCPeerConnection` and `RTCDataChannel` directly.
|
|
3833
|
+
* - **Worker thread** → use {@link WorkerWebrtcConnection} which delegates
|
|
3834
|
+
* `RTCPeerConnection` signaling to the main thread via a Comlink bridge
|
|
3835
|
+
* and receives a transferred `RTCDataChannel` that lives entirely in the
|
|
3836
|
+
* worker.
|
|
3837
|
+
*
|
|
3838
|
+
* Both classes implement `IWebrtcConnection & IConnection` and expose the
|
|
3839
|
+
* same public API, so upstream code (WebrtcConnector, etc.) is unaffected.
|
|
3840
|
+
*/
|
|
3841
|
+
// The constructor — points to the right class based on the runtime
|
|
3842
|
+
// environment. The type assertion is safe because both implementations
|
|
3843
|
+
// share the same public interface surface.
|
|
3844
|
+
const WebrtcConnection = (isWorkerEnvironment
|
|
3845
|
+
? WorkerWebrtcConnection
|
|
3846
|
+
: DirectWebrtcConnection);
|
|
3847
|
+
|
|
3331
3848
|
const logger$t = new utils.Logger('WebrtcConnectorRpcRemote');
|
|
3332
3849
|
class WebrtcConnectorRpcRemote extends RpcRemote {
|
|
3333
3850
|
requestConnection() {
|
|
@@ -5513,7 +6030,7 @@ class PeerDiscovery {
|
|
|
5513
6030
|
logger$d.debug(`Ring join on ${this.options.serviceId} timed out`);
|
|
5514
6031
|
}
|
|
5515
6032
|
finally {
|
|
5516
|
-
sessions.forEach((session) => this.
|
|
6033
|
+
sessions.forEach((session) => this.ongoingRingDiscoverySessions.delete(session.id));
|
|
5517
6034
|
}
|
|
5518
6035
|
}
|
|
5519
6036
|
async rejoinDht(entryPoint, contactedPeers = new Set(), distantJoinContactPeers = new Set()) {
|
|
@@ -7630,7 +8147,10 @@ exports.areEqualPeerDescriptors = areEqualPeerDescriptors;
|
|
|
7630
8147
|
exports.createOutgoingHandshaker = createOutgoingHandshaker;
|
|
7631
8148
|
exports.getRandomRegion = getRandomRegion;
|
|
7632
8149
|
exports.getRegionDelayMatrix = getRegionDelayMatrix;
|
|
8150
|
+
exports.installWebrtcBridge = installWebrtcBridge;
|
|
8151
|
+
exports.logGapDiagnosticSampled = logGapDiagnosticSampled;
|
|
7633
8152
|
exports.randomDhtAddress = randomDhtAddress;
|
|
8153
|
+
exports.setGapDiagnosticsEnabled = setGapDiagnosticsEnabled;
|
|
7634
8154
|
exports.toDhtAddress = toDhtAddress;
|
|
7635
8155
|
exports.toDhtAddressRaw = toDhtAddressRaw;
|
|
7636
8156
|
exports.toNodeId = toNodeId;
|