@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.js
CHANGED
|
@@ -9,6 +9,7 @@ import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
|
|
|
9
9
|
import { v4 } from 'uuid';
|
|
10
10
|
import { ProtoCallContext, RpcCommunicator, toProtoRpcClient, protoClasses as protoClasses$1, RpcError } from '@streamr/proto-rpc';
|
|
11
11
|
import ipaddr from 'ipaddr.js';
|
|
12
|
+
import * as Comlink from 'comlink';
|
|
12
13
|
import { w3cwebsocket } from 'websocket';
|
|
13
14
|
import { SERVICE_ID } from '@streamr/autocertifier-client';
|
|
14
15
|
import shuffle from 'lodash/shuffle';
|
|
@@ -239,6 +240,65 @@ class SendFailed extends Err {
|
|
|
239
240
|
constructor(message, originalError) { super(ErrorCode.SEND_FAILED, message, originalError); }
|
|
240
241
|
}
|
|
241
242
|
|
|
243
|
+
let enabled = false;
|
|
244
|
+
function setGapDiagnosticsEnabled(val) {
|
|
245
|
+
enabled = val;
|
|
246
|
+
globalThis.__dhtGapDiagEnabled = val;
|
|
247
|
+
}
|
|
248
|
+
const SUMMARY_INTERVAL_MS = 2000;
|
|
249
|
+
const accumulators = new Map();
|
|
250
|
+
function logGapDiagnosticSampled(layer, opts = {}) {
|
|
251
|
+
if (!enabled)
|
|
252
|
+
return;
|
|
253
|
+
const now = performance.now();
|
|
254
|
+
const threshold = opts.outlierThresholdMs ?? 30;
|
|
255
|
+
let acc = accumulators.get(layer);
|
|
256
|
+
if (acc === undefined) {
|
|
257
|
+
acc = { count: 0, sumDeltaMs: 0, maxDeltaMs: 0, outlierCount: 0, lastReportMs: now, lastEventMs: now };
|
|
258
|
+
accumulators.set(layer, acc);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const deltaMs = now - acc.lastEventMs;
|
|
262
|
+
acc.lastEventMs = now;
|
|
263
|
+
acc.count++;
|
|
264
|
+
if (deltaMs > acc.maxDeltaMs)
|
|
265
|
+
acc.maxDeltaMs = deltaMs;
|
|
266
|
+
acc.sumDeltaMs += deltaMs;
|
|
267
|
+
if (deltaMs > threshold)
|
|
268
|
+
acc.outlierCount++;
|
|
269
|
+
if (deltaMs > threshold) {
|
|
270
|
+
const payload = {
|
|
271
|
+
layer,
|
|
272
|
+
timestampMs: now,
|
|
273
|
+
deltaMs: +deltaMs.toFixed(2),
|
|
274
|
+
detail: opts.detail,
|
|
275
|
+
};
|
|
276
|
+
// eslint-disable-next-line no-console
|
|
277
|
+
console.log('[gap-diagnostics]', JSON.stringify(payload));
|
|
278
|
+
}
|
|
279
|
+
if (now - acc.lastReportMs >= SUMMARY_INTERVAL_MS) {
|
|
280
|
+
const summaryDetail = {
|
|
281
|
+
count: acc.count,
|
|
282
|
+
meanDeltaMs: acc.count > 0 ? +(acc.sumDeltaMs / acc.count).toFixed(2) : 0,
|
|
283
|
+
maxDeltaMs: +acc.maxDeltaMs.toFixed(2),
|
|
284
|
+
outlierCount: acc.outlierCount,
|
|
285
|
+
periodMs: +(now - acc.lastReportMs).toFixed(1),
|
|
286
|
+
};
|
|
287
|
+
const summary = {
|
|
288
|
+
layer: `${layer}.summary`,
|
|
289
|
+
timestampMs: now,
|
|
290
|
+
detail: summaryDetail,
|
|
291
|
+
};
|
|
292
|
+
// eslint-disable-next-line no-console
|
|
293
|
+
console.log('[gap-diagnostics]', JSON.stringify(summary));
|
|
294
|
+
acc.count = 0;
|
|
295
|
+
acc.sumDeltaMs = 0;
|
|
296
|
+
acc.maxDeltaMs = 0;
|
|
297
|
+
acc.outlierCount = 0;
|
|
298
|
+
acc.lastReportMs = now;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
242
302
|
// @generated message type with reflection information, may provide speed optimized methods
|
|
243
303
|
class Empty$Type extends MessageType {
|
|
244
304
|
constructor() {
|
|
@@ -1798,7 +1858,7 @@ const createRandomConnectionId = () => {
|
|
|
1798
1858
|
return v4();
|
|
1799
1859
|
};
|
|
1800
1860
|
|
|
1801
|
-
const logger$
|
|
1861
|
+
const logger$D = new Logger('ManagedConnection');
|
|
1802
1862
|
// ManagedConnection is a component used as a wrapper for IConnection after they have been successfully handshaked.
|
|
1803
1863
|
// Should only be used in the ConnectionManager.
|
|
1804
1864
|
class ManagedConnection extends EventEmitter {
|
|
@@ -1828,7 +1888,7 @@ class ManagedConnection extends EventEmitter {
|
|
|
1828
1888
|
this.remotePeerDescriptor = peerDescriptor;
|
|
1829
1889
|
}
|
|
1830
1890
|
onDisconnected(gracefulLeave) {
|
|
1831
|
-
logger$
|
|
1891
|
+
logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
|
|
1832
1892
|
if (!this.replacedAsDuplicate) {
|
|
1833
1893
|
this.emit('disconnected', gracefulLeave);
|
|
1834
1894
|
}
|
|
@@ -1837,7 +1897,7 @@ class ManagedConnection extends EventEmitter {
|
|
|
1837
1897
|
// TODO: Can this be removed if ManagedConnections can never be duplicates?
|
|
1838
1898
|
// Handle duplicates in the ConncetorFacade and no longer have PendingConnections in ConnectionManager
|
|
1839
1899
|
replaceAsDuplicate() {
|
|
1840
|
-
logger$
|
|
1900
|
+
logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
|
|
1841
1901
|
this.replacedAsDuplicate = true;
|
|
1842
1902
|
}
|
|
1843
1903
|
send(data) {
|
|
@@ -1984,10 +2044,10 @@ class RpcRemote {
|
|
|
1984
2044
|
}
|
|
1985
2045
|
}
|
|
1986
2046
|
|
|
1987
|
-
const logger$
|
|
2047
|
+
const logger$C = new Logger('ConnectionLockRpcRemote');
|
|
1988
2048
|
class ConnectionLockRpcRemote extends RpcRemote {
|
|
1989
2049
|
async lockRequest(lockId) {
|
|
1990
|
-
logger$
|
|
2050
|
+
logger$C.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
|
|
1991
2051
|
const request = {
|
|
1992
2052
|
lockId
|
|
1993
2053
|
};
|
|
@@ -1997,12 +2057,12 @@ class ConnectionLockRpcRemote extends RpcRemote {
|
|
|
1997
2057
|
return res.accepted;
|
|
1998
2058
|
}
|
|
1999
2059
|
catch (err) {
|
|
2000
|
-
logger$
|
|
2060
|
+
logger$C.debug('Connection lock rejected', { err });
|
|
2001
2061
|
return false;
|
|
2002
2062
|
}
|
|
2003
2063
|
}
|
|
2004
2064
|
unlockRequest(lockId) {
|
|
2005
|
-
logger$
|
|
2065
|
+
logger$C.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
|
|
2006
2066
|
const request = {
|
|
2007
2067
|
lockId
|
|
2008
2068
|
};
|
|
@@ -2010,11 +2070,11 @@ class ConnectionLockRpcRemote extends RpcRemote {
|
|
|
2010
2070
|
notification: true
|
|
2011
2071
|
});
|
|
2012
2072
|
this.getClient().unlockRequest(request, options).catch((_e) => {
|
|
2013
|
-
logger$
|
|
2073
|
+
logger$C.trace('failed to send unlockRequest');
|
|
2014
2074
|
});
|
|
2015
2075
|
}
|
|
2016
2076
|
async gracefulDisconnect(disconnectMode) {
|
|
2017
|
-
logger$
|
|
2077
|
+
logger$C.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
|
|
2018
2078
|
const request = {
|
|
2019
2079
|
disconnectMode
|
|
2020
2080
|
};
|
|
@@ -2026,7 +2086,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
|
|
|
2026
2086
|
await this.getClient().gracefulDisconnect(request, options);
|
|
2027
2087
|
}
|
|
2028
2088
|
async setPrivate(isPrivate) {
|
|
2029
|
-
logger$
|
|
2089
|
+
logger$C.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
|
|
2030
2090
|
const request = {
|
|
2031
2091
|
isPrivate
|
|
2032
2092
|
};
|
|
@@ -2038,7 +2098,7 @@ class ConnectionLockRpcRemote extends RpcRemote {
|
|
|
2038
2098
|
}
|
|
2039
2099
|
}
|
|
2040
2100
|
|
|
2041
|
-
const logger$
|
|
2101
|
+
const logger$B = new Logger('ConnectionLockRpcLocal');
|
|
2042
2102
|
class ConnectionLockRpcLocal {
|
|
2043
2103
|
options;
|
|
2044
2104
|
constructor(options) {
|
|
@@ -2067,7 +2127,7 @@ class ConnectionLockRpcLocal {
|
|
|
2067
2127
|
}
|
|
2068
2128
|
async gracefulDisconnect(disconnectNotice, context) {
|
|
2069
2129
|
const senderPeerDescriptor = context.incomingSourceDescriptor;
|
|
2070
|
-
logger$
|
|
2130
|
+
logger$B.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
|
|
2071
2131
|
if (disconnectNotice.disconnectMode === DisconnectMode.LEAVING) {
|
|
2072
2132
|
await this.options.closeConnection(senderPeerDescriptor, true, 'graceful leave notified');
|
|
2073
2133
|
}
|
|
@@ -2118,7 +2178,7 @@ var NatType;
|
|
|
2118
2178
|
NatType["OPEN_INTERNET"] = "open_internet";
|
|
2119
2179
|
NatType["UNKNOWN"] = "unknown";
|
|
2120
2180
|
})(NatType || (NatType = {}));
|
|
2121
|
-
const logger$
|
|
2181
|
+
const logger$A = new Logger('ConnectionManager');
|
|
2122
2182
|
var ConnectionManagerState;
|
|
2123
2183
|
(function (ConnectionManagerState) {
|
|
2124
2184
|
ConnectionManagerState["IDLE"] = "idle";
|
|
@@ -2168,7 +2228,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2168
2228
|
getLocalPeerDescriptor: () => this.getLocalPeerDescriptor(),
|
|
2169
2229
|
setPrivate: (id, isPrivate) => {
|
|
2170
2230
|
if (!this.options.allowIncomingPrivateConnections) {
|
|
2171
|
-
logger$
|
|
2231
|
+
logger$A.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
|
|
2172
2232
|
return;
|
|
2173
2233
|
}
|
|
2174
2234
|
if (isPrivate) {
|
|
@@ -2202,7 +2262,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2202
2262
|
const connection = endpoint.connection;
|
|
2203
2263
|
const nodeId = connection.getNodeId();
|
|
2204
2264
|
if (!this.locks.isLocked(nodeId) && !this.locks.isPrivate(nodeId) && Date.now() - connection.getLastUsedTimestamp() > maxIdleTime) {
|
|
2205
|
-
logger$
|
|
2265
|
+
logger$A.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
|
|
2206
2266
|
disconnectionCandidates.addContact(connection);
|
|
2207
2267
|
}
|
|
2208
2268
|
}
|
|
@@ -2210,7 +2270,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2210
2270
|
const disconnectables = disconnectionCandidates.getFurthestContacts(this.endpoints.size - maxConnections);
|
|
2211
2271
|
for (const disconnectable of disconnectables) {
|
|
2212
2272
|
const peerDescriptor = disconnectable.getPeerDescriptor();
|
|
2213
|
-
logger$
|
|
2273
|
+
logger$A.trace('garbageCollecting ' + toNodeId(peerDescriptor));
|
|
2214
2274
|
this.gracefullyDisconnectAsync(peerDescriptor, DisconnectMode.NORMAL).catch((_e) => { });
|
|
2215
2275
|
}
|
|
2216
2276
|
}
|
|
@@ -2219,11 +2279,11 @@ class ConnectionManager extends EventEmitter {
|
|
|
2219
2279
|
throw new CouldNotStart(`Cannot start already ${this.state} module`);
|
|
2220
2280
|
}
|
|
2221
2281
|
this.state = ConnectionManagerState.RUNNING;
|
|
2222
|
-
logger$
|
|
2282
|
+
logger$A.trace(`Starting ConnectionManager...`);
|
|
2223
2283
|
await this.connectorFacade.start((connection) => this.onNewConnection(connection), (nodeId) => this.hasConnection(nodeId), this);
|
|
2224
2284
|
// Garbage collection of connections
|
|
2225
2285
|
this.disconnectorIntervalRef = setInterval(() => {
|
|
2226
|
-
logger$
|
|
2286
|
+
logger$A.trace('disconnectorInterval');
|
|
2227
2287
|
const LAST_USED_LIMIT = 20000;
|
|
2228
2288
|
this.garbageCollectConnections(this.options.maxConnections ?? 80, LAST_USED_LIMIT);
|
|
2229
2289
|
}, 5000); // TODO use options option or named constant?
|
|
@@ -2233,7 +2293,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2233
2293
|
return;
|
|
2234
2294
|
}
|
|
2235
2295
|
this.state = ConnectionManagerState.STOPPING;
|
|
2236
|
-
logger$
|
|
2296
|
+
logger$A.trace(`Stopping ConnectionManager`);
|
|
2237
2297
|
if (this.disconnectorIntervalRef) {
|
|
2238
2298
|
clearInterval(this.disconnectorIntervalRef);
|
|
2239
2299
|
}
|
|
@@ -2243,23 +2303,23 @@ class ConnectionManager extends EventEmitter {
|
|
|
2243
2303
|
await this.gracefullyDisconnectAsync(endpoint.connection.getPeerDescriptor(), DisconnectMode.LEAVING);
|
|
2244
2304
|
}
|
|
2245
2305
|
catch (e) {
|
|
2246
|
-
logger$
|
|
2306
|
+
logger$A.error(e);
|
|
2247
2307
|
}
|
|
2248
2308
|
}
|
|
2249
2309
|
else {
|
|
2250
2310
|
const connection = endpoint.connection;
|
|
2251
|
-
logger$
|
|
2311
|
+
logger$A.trace('handshake of connection not completed, force-closing');
|
|
2252
2312
|
// TODO use options option or named constant?
|
|
2253
2313
|
const eventReceived = waitForEvent(connection, 'disconnected', 2000);
|
|
2254
2314
|
// TODO should we have some handling for this floating promise?
|
|
2255
2315
|
connection.close(true);
|
|
2256
2316
|
try {
|
|
2257
2317
|
await eventReceived;
|
|
2258
|
-
logger$
|
|
2318
|
+
logger$A.trace('resolving after receiving disconnected event from non-handshaked connection');
|
|
2259
2319
|
}
|
|
2260
2320
|
catch (e) {
|
|
2261
2321
|
endpoint.buffer.reject();
|
|
2262
|
-
logger$
|
|
2322
|
+
logger$A.trace('force-closing non-handshaked connection timed out ' + e);
|
|
2263
2323
|
}
|
|
2264
2324
|
}
|
|
2265
2325
|
}));
|
|
@@ -2283,12 +2343,13 @@ class ConnectionManager extends EventEmitter {
|
|
|
2283
2343
|
if ((this.state === ConnectionManagerState.STOPPED || this.state === ConnectionManagerState.STOPPING) && !opts.sendIfStopped) {
|
|
2284
2344
|
return;
|
|
2285
2345
|
}
|
|
2346
|
+
logGapDiagnosticSampled('dht.connMgr.send');
|
|
2286
2347
|
const peerDescriptor = message.targetDescriptor;
|
|
2287
2348
|
if (this.isConnectionToSelf(peerDescriptor)) {
|
|
2288
2349
|
throw new CannotConnectToSelf('Cannot send to self');
|
|
2289
2350
|
}
|
|
2290
2351
|
const nodeId = toNodeId(peerDescriptor);
|
|
2291
|
-
logger$
|
|
2352
|
+
logger$A.trace(`Sending message to: ${nodeId}`);
|
|
2292
2353
|
message = {
|
|
2293
2354
|
...message,
|
|
2294
2355
|
sourceDescriptor: this.getLocalPeerDescriptor()
|
|
@@ -2343,13 +2404,13 @@ class ConnectionManager extends EventEmitter {
|
|
|
2343
2404
|
}
|
|
2344
2405
|
handleMessage(message) {
|
|
2345
2406
|
const messageType = message.body.oneofKind;
|
|
2346
|
-
logger$
|
|
2407
|
+
logger$A.trace('Received message of type ' + messageType);
|
|
2347
2408
|
if (messageType !== 'rpcMessage') {
|
|
2348
|
-
logger$
|
|
2409
|
+
logger$A.trace('Filtered out non-RPC message of type ' + messageType);
|
|
2349
2410
|
return;
|
|
2350
2411
|
}
|
|
2351
2412
|
if (this.duplicateMessageDetector.isMostLikelyDuplicate(message.messageId)) {
|
|
2352
|
-
logger$
|
|
2413
|
+
logger$A.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
|
|
2353
2414
|
+ ' ' + message.serviceId + ' ' + message.messageId);
|
|
2354
2415
|
return;
|
|
2355
2416
|
}
|
|
@@ -2358,7 +2419,8 @@ class ConnectionManager extends EventEmitter {
|
|
|
2358
2419
|
this.rpcCommunicator?.handleMessageFromPeer(message);
|
|
2359
2420
|
}
|
|
2360
2421
|
else {
|
|
2361
|
-
|
|
2422
|
+
logGapDiagnosticSampled('dht.connMgr.emitMessage');
|
|
2423
|
+
logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
|
|
2362
2424
|
+ ' ' + message.serviceId + ' ' + message.messageId);
|
|
2363
2425
|
this.emit('message', message);
|
|
2364
2426
|
}
|
|
@@ -2367,6 +2429,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2367
2429
|
if (this.state === ConnectionManagerState.STOPPED) {
|
|
2368
2430
|
return;
|
|
2369
2431
|
}
|
|
2432
|
+
logGapDiagnosticSampled('dht.connMgr.onData');
|
|
2370
2433
|
this.metrics.receiveBytesPerSecond.record(data.byteLength);
|
|
2371
2434
|
this.metrics.receiveMessagesPerSecond.record(1);
|
|
2372
2435
|
let message;
|
|
@@ -2375,7 +2438,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2375
2438
|
}
|
|
2376
2439
|
catch (e) {
|
|
2377
2440
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
2378
|
-
logger$
|
|
2441
|
+
logger$A.debug(`Parsing incoming data into Message failed: ${e}`);
|
|
2379
2442
|
return;
|
|
2380
2443
|
}
|
|
2381
2444
|
message.sourceDescriptor = peerDescriptor;
|
|
@@ -2384,7 +2447,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2384
2447
|
}
|
|
2385
2448
|
catch (e) {
|
|
2386
2449
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
2387
|
-
logger$
|
|
2450
|
+
logger$A.debug(`Handling incoming data failed: ${e}`);
|
|
2388
2451
|
}
|
|
2389
2452
|
}
|
|
2390
2453
|
onConnected(peerDescriptor, connection) {
|
|
@@ -2397,7 +2460,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2397
2460
|
const pendingConnection = endpoint.connection;
|
|
2398
2461
|
const buffer = outputBuffer.getBuffer();
|
|
2399
2462
|
while (buffer.length > 0) {
|
|
2400
|
-
logger$
|
|
2463
|
+
logger$A.trace('emptying buffer');
|
|
2401
2464
|
managedConnection.send(buffer.shift());
|
|
2402
2465
|
}
|
|
2403
2466
|
outputBuffer.resolve();
|
|
@@ -2414,7 +2477,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2414
2477
|
}
|
|
2415
2478
|
onDisconnected(peerDescriptor, gracefulLeave) {
|
|
2416
2479
|
const nodeId = toNodeId(peerDescriptor);
|
|
2417
|
-
logger$
|
|
2480
|
+
logger$A.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
|
|
2418
2481
|
const endpoint = this.endpoints.get(nodeId);
|
|
2419
2482
|
if (endpoint) {
|
|
2420
2483
|
this.locks.clearAllLocks(nodeId);
|
|
@@ -2422,7 +2485,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2422
2485
|
endpoint.buffer.reject();
|
|
2423
2486
|
}
|
|
2424
2487
|
this.endpoints.delete(nodeId);
|
|
2425
|
-
logger$
|
|
2488
|
+
logger$A.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
|
|
2426
2489
|
this.emit('disconnected', peerDescriptor, gracefulLeave);
|
|
2427
2490
|
this.onConnectionCountChange();
|
|
2428
2491
|
}
|
|
@@ -2431,7 +2494,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2431
2494
|
if (this.state === ConnectionManagerState.STOPPED) {
|
|
2432
2495
|
return false;
|
|
2433
2496
|
}
|
|
2434
|
-
logger$
|
|
2497
|
+
logger$A.trace('onNewConnection()');
|
|
2435
2498
|
if (!this.acceptNewConnection(connection)) {
|
|
2436
2499
|
return false;
|
|
2437
2500
|
}
|
|
@@ -2441,7 +2504,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2441
2504
|
}
|
|
2442
2505
|
acceptNewConnection(newConnection) {
|
|
2443
2506
|
const nodeId = toNodeId(newConnection.getPeerDescriptor());
|
|
2444
|
-
logger$
|
|
2507
|
+
logger$A.trace(nodeId + ' acceptNewConnection()');
|
|
2445
2508
|
if (this.endpoints.has(nodeId)) {
|
|
2446
2509
|
if (getOfferer(toNodeId(this.getLocalPeerDescriptor()), nodeId) === 'remote') {
|
|
2447
2510
|
let buffer;
|
|
@@ -2450,14 +2513,14 @@ class ConnectionManager extends EventEmitter {
|
|
|
2450
2513
|
// Could be related to WS client connections not realizing that they have been disconnected.
|
|
2451
2514
|
// Makes refactoring duplicate connection handling to the connectors very difficult.
|
|
2452
2515
|
if (this.endpoints.get(nodeId).connected) {
|
|
2453
|
-
logger$
|
|
2516
|
+
logger$A.debug('replacing connected connection', { nodeId });
|
|
2454
2517
|
buffer = new OutputBuffer();
|
|
2455
2518
|
}
|
|
2456
2519
|
else {
|
|
2457
2520
|
buffer = endpoint.buffer;
|
|
2458
2521
|
}
|
|
2459
2522
|
const oldConnection = endpoint.connection;
|
|
2460
|
-
logger$
|
|
2523
|
+
logger$A.trace('replaced: ' + nodeId);
|
|
2461
2524
|
oldConnection.replaceAsDuplicate();
|
|
2462
2525
|
this.endpoints.set(nodeId, { connected: false, connection: newConnection, buffer: buffer });
|
|
2463
2526
|
return true;
|
|
@@ -2466,7 +2529,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2466
2529
|
return false;
|
|
2467
2530
|
}
|
|
2468
2531
|
}
|
|
2469
|
-
logger$
|
|
2532
|
+
logger$A.trace(nodeId + ' added to connections at acceptNewConnection');
|
|
2470
2533
|
this.endpoints.set(nodeId, {
|
|
2471
2534
|
connected: false,
|
|
2472
2535
|
buffer: new OutputBuffer(),
|
|
@@ -2476,14 +2539,14 @@ class ConnectionManager extends EventEmitter {
|
|
|
2476
2539
|
}
|
|
2477
2540
|
async closeConnection(peerDescriptor, gracefulLeave, reason) {
|
|
2478
2541
|
const nodeId = toNodeId(peerDescriptor);
|
|
2479
|
-
logger$
|
|
2542
|
+
logger$A.trace(nodeId + ' ' + 'closeConnection() ' + reason);
|
|
2480
2543
|
this.locks.clearAllLocks(nodeId);
|
|
2481
2544
|
if (this.endpoints.has(nodeId)) {
|
|
2482
2545
|
const connectionToClose = this.endpoints.get(nodeId).connection;
|
|
2483
2546
|
await connectionToClose.close(gracefulLeave);
|
|
2484
2547
|
}
|
|
2485
2548
|
else {
|
|
2486
|
-
logger$
|
|
2549
|
+
logger$A.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
|
|
2487
2550
|
this.emit('disconnected', peerDescriptor, false);
|
|
2488
2551
|
}
|
|
2489
2552
|
}
|
|
@@ -2495,8 +2558,8 @@ class ConnectionManager extends EventEmitter {
|
|
|
2495
2558
|
const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
|
|
2496
2559
|
this.locks.addLocalLocked(nodeId, lockId);
|
|
2497
2560
|
rpcRemote.lockRequest(lockId)
|
|
2498
|
-
.then((_accepted) => logger$
|
|
2499
|
-
.catch((err) => { logger$
|
|
2561
|
+
.then((_accepted) => logger$A.trace('LockRequest successful'))
|
|
2562
|
+
.catch((err) => { logger$A.debug(err); });
|
|
2500
2563
|
}
|
|
2501
2564
|
unlockConnection(targetDescriptor, lockId) {
|
|
2502
2565
|
if (this.state === ConnectionManagerState.STOPPED || areEqualPeerDescriptors(targetDescriptor, this.getLocalPeerDescriptor())) {
|
|
@@ -2548,7 +2611,7 @@ class ConnectionManager extends EventEmitter {
|
|
|
2548
2611
|
async gracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
|
|
2549
2612
|
const endpoint = this.endpoints.get(toNodeId(targetDescriptor));
|
|
2550
2613
|
if (!endpoint) {
|
|
2551
|
-
logger$
|
|
2614
|
+
logger$A.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
|
|
2552
2615
|
return;
|
|
2553
2616
|
}
|
|
2554
2617
|
if (endpoint.connected) {
|
|
@@ -2557,15 +2620,15 @@ class ConnectionManager extends EventEmitter {
|
|
|
2557
2620
|
// TODO use options option or named constant?
|
|
2558
2621
|
// eslint-disable-next-line promise/catch-or-return
|
|
2559
2622
|
waitForEvent(connection, 'disconnected', 2000).then(() => {
|
|
2560
|
-
logger$
|
|
2623
|
+
logger$A.trace('disconnected event received in gracefullyDisconnectAsync()');
|
|
2561
2624
|
})
|
|
2562
2625
|
.catch((e) => {
|
|
2563
|
-
logger$
|
|
2626
|
+
logger$A.trace('force-closing connection after timeout ' + e);
|
|
2564
2627
|
// TODO should we have some handling for this floating promise?
|
|
2565
2628
|
connection.close(true);
|
|
2566
2629
|
})
|
|
2567
2630
|
.finally(() => {
|
|
2568
|
-
logger$
|
|
2631
|
+
logger$A.trace('resolving after receiving disconnected event');
|
|
2569
2632
|
resolve();
|
|
2570
2633
|
});
|
|
2571
2634
|
});
|
|
@@ -2580,13 +2643,13 @@ class ConnectionManager extends EventEmitter {
|
|
|
2580
2643
|
}
|
|
2581
2644
|
async doGracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
|
|
2582
2645
|
const nodeId = toNodeId(targetDescriptor);
|
|
2583
|
-
logger$
|
|
2646
|
+
logger$A.trace(nodeId + ' gracefullyDisconnectAsync()');
|
|
2584
2647
|
const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
|
|
2585
2648
|
try {
|
|
2586
2649
|
await rpcRemote.gracefulDisconnect(disconnectMode);
|
|
2587
2650
|
}
|
|
2588
2651
|
catch (ex) {
|
|
2589
|
-
logger$
|
|
2652
|
+
logger$A.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
|
|
2590
2653
|
}
|
|
2591
2654
|
}
|
|
2592
2655
|
getConnections() {
|
|
@@ -2653,7 +2716,7 @@ function protoToString(protoObj, objectType) {
|
|
|
2653
2716
|
return ret;
|
|
2654
2717
|
}
|
|
2655
2718
|
|
|
2656
|
-
const logger$
|
|
2719
|
+
const logger$z = new Logger('SimulatorConnection');
|
|
2657
2720
|
class SimulatorConnection extends Connection {
|
|
2658
2721
|
stopped = false;
|
|
2659
2722
|
localPeerDescriptor;
|
|
@@ -2675,46 +2738,46 @@ class SimulatorConnection extends Connection {
|
|
|
2675
2738
|
this.doDisconnect = this.doDisconnect.bind(this);
|
|
2676
2739
|
}
|
|
2677
2740
|
send(data) {
|
|
2678
|
-
logger$
|
|
2741
|
+
logger$z.trace('send()');
|
|
2679
2742
|
if (!this.stopped) {
|
|
2680
2743
|
this.simulator.send(this, data);
|
|
2681
2744
|
}
|
|
2682
2745
|
else {
|
|
2683
2746
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2684
2747
|
const targetNodeId = toNodeId(this.targetPeerDescriptor);
|
|
2685
|
-
logger$
|
|
2748
|
+
logger$z.error(localNodeId + ', ' + targetNodeId + 'tried to send() on a stopped connection');
|
|
2686
2749
|
}
|
|
2687
2750
|
}
|
|
2688
2751
|
async close(gracefulLeave) {
|
|
2689
2752
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2690
2753
|
const targetNodeId = toNodeId(this.targetPeerDescriptor);
|
|
2691
|
-
logger$
|
|
2754
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close()');
|
|
2692
2755
|
if (!this.stopped) {
|
|
2693
|
-
logger$
|
|
2756
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() not stopped');
|
|
2694
2757
|
this.stopped = true;
|
|
2695
2758
|
try {
|
|
2696
|
-
logger$
|
|
2759
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() calling simulator.disconnect()');
|
|
2697
2760
|
this.simulator.close(this);
|
|
2698
|
-
logger$
|
|
2761
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() simulator.disconnect returned');
|
|
2699
2762
|
}
|
|
2700
2763
|
catch (e) {
|
|
2701
|
-
logger$
|
|
2764
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + 'close aborted' + e);
|
|
2702
2765
|
}
|
|
2703
2766
|
finally {
|
|
2704
|
-
logger$
|
|
2767
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' calling this.doDisconnect');
|
|
2705
2768
|
this.doDisconnect(gracefulLeave);
|
|
2706
2769
|
}
|
|
2707
2770
|
}
|
|
2708
2771
|
else {
|
|
2709
|
-
logger$
|
|
2772
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' close() tried to close a stopped connection');
|
|
2710
2773
|
}
|
|
2711
2774
|
}
|
|
2712
2775
|
connect() {
|
|
2713
2776
|
if (!this.stopped) {
|
|
2714
|
-
logger$
|
|
2777
|
+
logger$z.trace('connect() called');
|
|
2715
2778
|
this.simulator.connect(this, this.targetPeerDescriptor, (error) => {
|
|
2716
2779
|
if (error !== undefined) {
|
|
2717
|
-
logger$
|
|
2780
|
+
logger$z.trace(error);
|
|
2718
2781
|
this.doDisconnect(false);
|
|
2719
2782
|
}
|
|
2720
2783
|
else {
|
|
@@ -2723,46 +2786,46 @@ class SimulatorConnection extends Connection {
|
|
|
2723
2786
|
});
|
|
2724
2787
|
}
|
|
2725
2788
|
else {
|
|
2726
|
-
logger$
|
|
2789
|
+
logger$z.trace('tried to connect() a stopped connection');
|
|
2727
2790
|
}
|
|
2728
2791
|
}
|
|
2729
2792
|
handleIncomingData(data) {
|
|
2730
2793
|
if (!this.stopped) {
|
|
2731
|
-
logger$
|
|
2794
|
+
logger$z.trace('handleIncomingData() ' + protoToString(Message.fromBinary(data), Message));
|
|
2732
2795
|
this.emit('data', data);
|
|
2733
2796
|
}
|
|
2734
2797
|
else {
|
|
2735
|
-
logger$
|
|
2798
|
+
logger$z.trace('tried to call handleIncomingData() a stopped connection');
|
|
2736
2799
|
}
|
|
2737
2800
|
}
|
|
2738
2801
|
handleIncomingDisconnection() {
|
|
2739
2802
|
if (!this.stopped) {
|
|
2740
2803
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2741
|
-
logger$
|
|
2804
|
+
logger$z.trace(localNodeId + ' handleIncomingDisconnection()');
|
|
2742
2805
|
this.stopped = true;
|
|
2743
2806
|
this.doDisconnect(false);
|
|
2744
2807
|
}
|
|
2745
2808
|
else {
|
|
2746
|
-
logger$
|
|
2809
|
+
logger$z.trace('tried to call handleIncomingDisconnection() a stopped connection');
|
|
2747
2810
|
}
|
|
2748
2811
|
}
|
|
2749
2812
|
destroy() {
|
|
2750
2813
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2751
2814
|
if (!this.stopped) {
|
|
2752
|
-
logger$
|
|
2815
|
+
logger$z.trace(localNodeId + ' destroy()');
|
|
2753
2816
|
this.removeAllListeners();
|
|
2754
2817
|
this.close(false).catch((_e) => { });
|
|
2755
2818
|
}
|
|
2756
2819
|
else {
|
|
2757
|
-
logger$
|
|
2820
|
+
logger$z.trace(localNodeId + ' tried to call destroy() a stopped connection');
|
|
2758
2821
|
}
|
|
2759
2822
|
}
|
|
2760
2823
|
doDisconnect(gracefulLeave) {
|
|
2761
2824
|
const localNodeId = toNodeId(this.localPeerDescriptor);
|
|
2762
2825
|
const targetNodeId = toNodeId(this.targetPeerDescriptor);
|
|
2763
|
-
logger$
|
|
2826
|
+
logger$z.trace(localNodeId + ' doDisconnect()');
|
|
2764
2827
|
this.stopped = true;
|
|
2765
|
-
logger$
|
|
2828
|
+
logger$z.trace(localNodeId + ', ' + targetNodeId + ' doDisconnect emitting');
|
|
2766
2829
|
this.emit('disconnected', gracefulLeave);
|
|
2767
2830
|
}
|
|
2768
2831
|
}
|
|
@@ -2800,9 +2863,9 @@ const parseVersion = (version) => {
|
|
|
2800
2863
|
}
|
|
2801
2864
|
};
|
|
2802
2865
|
|
|
2803
|
-
var version = "103.
|
|
2866
|
+
var version = "103.7.0-rc.2";
|
|
2804
2867
|
|
|
2805
|
-
const logger$
|
|
2868
|
+
const logger$y = new Logger('Handshaker');
|
|
2806
2869
|
// Optimally the Outgoing and Incoming Handshakers could be their own separate classes
|
|
2807
2870
|
// However, in cases where the PeerDescriptor of the other end of the connection can be known
|
|
2808
2871
|
// only after a HandshakeRequest a base Handshaker class is needed as the IncomingHandshaker currently
|
|
@@ -2824,7 +2887,7 @@ const createOutgoingHandshaker = (localPeerDescriptor, pendingConnection, connec
|
|
|
2824
2887
|
}
|
|
2825
2888
|
};
|
|
2826
2889
|
const handshakeCompletedListener = (peerDescriptor) => {
|
|
2827
|
-
logger$
|
|
2890
|
+
logger$y.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
|
|
2828
2891
|
pendingConnection.onHandshakeCompleted(connection);
|
|
2829
2892
|
stopHandshaker();
|
|
2830
2893
|
};
|
|
@@ -2924,12 +2987,12 @@ class Handshaker extends EventEmitter {
|
|
|
2924
2987
|
try {
|
|
2925
2988
|
const message = Message.fromBinary(data);
|
|
2926
2989
|
if (message.body.oneofKind === 'handshakeRequest') {
|
|
2927
|
-
logger$
|
|
2990
|
+
logger$y.trace('handshake request received');
|
|
2928
2991
|
const handshake = message.body.handshakeRequest;
|
|
2929
2992
|
this.emit('handshakeRequest', handshake.sourcePeerDescriptor, handshake.protocolVersion, handshake.targetPeerDescriptor);
|
|
2930
2993
|
}
|
|
2931
2994
|
if (message.body.oneofKind === 'handshakeResponse') {
|
|
2932
|
-
logger$
|
|
2995
|
+
logger$y.trace('handshake response received');
|
|
2933
2996
|
const handshake = message.body.handshakeResponse;
|
|
2934
2997
|
const error = !isMaybeSupportedProtocolVersion(handshake.protocolVersion)
|
|
2935
2998
|
? HandshakeError.UNSUPPORTED_PROTOCOL_VERSION : handshake.error;
|
|
@@ -2942,18 +3005,18 @@ class Handshaker extends EventEmitter {
|
|
|
2942
3005
|
}
|
|
2943
3006
|
}
|
|
2944
3007
|
catch (err) {
|
|
2945
|
-
logger$
|
|
3008
|
+
logger$y.debug('error while parsing handshake message', err);
|
|
2946
3009
|
}
|
|
2947
3010
|
}
|
|
2948
3011
|
sendHandshakeRequest(remotePeerDescriptor) {
|
|
2949
3012
|
const msg = createHandshakeRequest(this.localPeerDescriptor, remotePeerDescriptor);
|
|
2950
3013
|
this.connection.send(Message.toBinary(msg));
|
|
2951
|
-
logger$
|
|
3014
|
+
logger$y.trace('handshake request sent');
|
|
2952
3015
|
}
|
|
2953
3016
|
sendHandshakeResponse(error) {
|
|
2954
3017
|
const msg = createHandshakeResponse(this.localPeerDescriptor, error);
|
|
2955
3018
|
this.connection.send(Message.toBinary(msg));
|
|
2956
|
-
logger$
|
|
3019
|
+
logger$y.trace('handshake response sent');
|
|
2957
3020
|
}
|
|
2958
3021
|
stop() {
|
|
2959
3022
|
this.connection.off('data', this.onDataListener);
|
|
@@ -2961,7 +3024,7 @@ class Handshaker extends EventEmitter {
|
|
|
2961
3024
|
}
|
|
2962
3025
|
}
|
|
2963
3026
|
|
|
2964
|
-
const logger$
|
|
3027
|
+
const logger$x = new Logger('PendingConnection');
|
|
2965
3028
|
// PendingConnection is used as a reference to a connection that should be opened and handshaked to a given PeerDescriptor
|
|
2966
3029
|
// It does not hold a connection internally. The public method onHandshakedCompleted should be called once a connection for the
|
|
2967
3030
|
// remotePeerDescriptor is opened and handshaked successfully.
|
|
@@ -2979,7 +3042,7 @@ class PendingConnection extends EventEmitter {
|
|
|
2979
3042
|
}, timeout, this.connectingAbortController.signal);
|
|
2980
3043
|
}
|
|
2981
3044
|
replaceAsDuplicate() {
|
|
2982
|
-
logger$
|
|
3045
|
+
logger$x.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
|
|
2983
3046
|
this.replacedAsDuplicate = true;
|
|
2984
3047
|
}
|
|
2985
3048
|
onHandshakeCompleted(connection) {
|
|
@@ -3010,7 +3073,7 @@ class PendingConnection extends EventEmitter {
|
|
|
3010
3073
|
}
|
|
3011
3074
|
}
|
|
3012
3075
|
|
|
3013
|
-
const logger$
|
|
3076
|
+
const logger$w = new Logger('SimulatorConnector');
|
|
3014
3077
|
class SimulatorConnector {
|
|
3015
3078
|
connectingConnections = new Map();
|
|
3016
3079
|
stopped = false;
|
|
@@ -3023,7 +3086,7 @@ class SimulatorConnector {
|
|
|
3023
3086
|
this.onNewConnection = onNewConnection;
|
|
3024
3087
|
}
|
|
3025
3088
|
connect(targetPeerDescriptor) {
|
|
3026
|
-
logger$
|
|
3089
|
+
logger$w.trace('connect() ' + toNodeId(targetPeerDescriptor));
|
|
3027
3090
|
const nodeId = toNodeId(targetPeerDescriptor);
|
|
3028
3091
|
const existingConnection = this.connectingConnections.get(nodeId);
|
|
3029
3092
|
if (existingConnection) {
|
|
@@ -3052,18 +3115,18 @@ class SimulatorConnector {
|
|
|
3052
3115
|
// connection is incoming, so remotePeerDescriptor is localPeerDescriptor
|
|
3053
3116
|
const remotePeerDescriptor = sourceConnection.localPeerDescriptor;
|
|
3054
3117
|
const remoteNodeId = toNodeId(sourceConnection.localPeerDescriptor);
|
|
3055
|
-
logger$
|
|
3118
|
+
logger$w.trace(remoteNodeId + ' incoming connection, stopped: ' + this.stopped);
|
|
3056
3119
|
if (this.stopped) {
|
|
3057
3120
|
return;
|
|
3058
3121
|
}
|
|
3059
3122
|
const connection = new SimulatorConnection(this.localPeerDescriptor, remotePeerDescriptor, ConnectionType.SIMULATOR_SERVER, this.simulator);
|
|
3060
3123
|
const pendingConnection = new PendingConnection(remotePeerDescriptor);
|
|
3061
3124
|
const handshaker = createIncomingHandshaker(this.localPeerDescriptor, pendingConnection, connection);
|
|
3062
|
-
logger$
|
|
3125
|
+
logger$w.trace('connected');
|
|
3063
3126
|
handshaker.once('handshakeRequest', () => {
|
|
3064
|
-
logger$
|
|
3127
|
+
logger$w.trace(remoteNodeId + ' incoming handshake request');
|
|
3065
3128
|
if (this.onNewConnection(pendingConnection)) {
|
|
3066
|
-
logger$
|
|
3129
|
+
logger$w.trace(remoteNodeId + ' calling acceptHandshake');
|
|
3067
3130
|
acceptHandshake(handshaker, pendingConnection, connection);
|
|
3068
3131
|
}
|
|
3069
3132
|
else {
|
|
@@ -3103,6 +3166,8 @@ class ListeningRpcCommunicator extends RoutingRpcCommunicator {
|
|
|
3103
3166
|
}
|
|
3104
3167
|
}
|
|
3105
3168
|
|
|
3169
|
+
const isWorkerEnvironment = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
|
|
3170
|
+
|
|
3106
3171
|
var RtcDescription;
|
|
3107
3172
|
(function (RtcDescription) {
|
|
3108
3173
|
RtcDescription["OFFER"] = "offer";
|
|
@@ -3117,8 +3182,8 @@ var DisconnectedRtcPeerConnectionStateEnum;
|
|
|
3117
3182
|
DisconnectedRtcPeerConnectionStateEnum["FAILED"] = "failed";
|
|
3118
3183
|
DisconnectedRtcPeerConnectionStateEnum["CLOSED"] = "closed";
|
|
3119
3184
|
})(DisconnectedRtcPeerConnectionStateEnum || (DisconnectedRtcPeerConnectionStateEnum = {}));
|
|
3120
|
-
const logger$
|
|
3121
|
-
class
|
|
3185
|
+
const logger$v = new Logger('DirectWebrtcConnection (browser)');
|
|
3186
|
+
class DirectWebrtcConnection extends EventEmitter {
|
|
3122
3187
|
connectionId;
|
|
3123
3188
|
connectionType = ConnectionType.WEBRTC;
|
|
3124
3189
|
// We need to keep track of connection state ourselves because
|
|
@@ -3156,7 +3221,7 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3156
3221
|
}
|
|
3157
3222
|
};
|
|
3158
3223
|
this.peerConnection.onicegatheringstatechange = () => {
|
|
3159
|
-
logger$
|
|
3224
|
+
logger$v.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
|
|
3160
3225
|
};
|
|
3161
3226
|
this.peerConnection.onconnectionstatechange = () => this.onStateChange();
|
|
3162
3227
|
if (isOffering) {
|
|
@@ -3167,7 +3232,7 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3167
3232
|
await this.peerConnection.setLocalDescription();
|
|
3168
3233
|
}
|
|
3169
3234
|
catch (err) {
|
|
3170
|
-
logger$
|
|
3235
|
+
logger$v.warn('Failed to set local description', { err });
|
|
3171
3236
|
}
|
|
3172
3237
|
if (this.peerConnection.localDescription !== null) {
|
|
3173
3238
|
this.emit('localDescription', this.peerConnection.localDescription?.sdp, this.peerConnection.localDescription?.type);
|
|
@@ -3195,14 +3260,14 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3195
3260
|
clearTimeout(this.earlyTimeout);
|
|
3196
3261
|
}
|
|
3197
3262
|
catch (err) {
|
|
3198
|
-
logger$
|
|
3263
|
+
logger$v.warn('Failed to set remote description', { err });
|
|
3199
3264
|
}
|
|
3200
3265
|
if ((type.toLowerCase() === RtcDescription.OFFER) && (this.peerConnection !== undefined)) {
|
|
3201
3266
|
try {
|
|
3202
3267
|
await this.peerConnection.setLocalDescription();
|
|
3203
3268
|
}
|
|
3204
3269
|
catch (err) {
|
|
3205
|
-
logger$
|
|
3270
|
+
logger$v.warn('Failed to set local description', { err });
|
|
3206
3271
|
}
|
|
3207
3272
|
if (this.peerConnection.localDescription !== null) {
|
|
3208
3273
|
this.emit('localDescription', this.peerConnection.localDescription.sdp, this.peerConnection.localDescription.type);
|
|
@@ -3212,7 +3277,7 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3212
3277
|
addRemoteCandidate(candidate, mid) {
|
|
3213
3278
|
this.peerConnection?.addIceCandidate({ candidate: candidate, sdpMid: mid })
|
|
3214
3279
|
.catch((err) => {
|
|
3215
|
-
logger$
|
|
3280
|
+
logger$v.warn('Failed to add ICE candidate', { err });
|
|
3216
3281
|
});
|
|
3217
3282
|
}
|
|
3218
3283
|
isOpen() {
|
|
@@ -3235,7 +3300,7 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3235
3300
|
this.dataChannel.close();
|
|
3236
3301
|
}
|
|
3237
3302
|
catch (err) {
|
|
3238
|
-
logger$
|
|
3303
|
+
logger$v.warn('Failed to close data channel', { err });
|
|
3239
3304
|
}
|
|
3240
3305
|
}
|
|
3241
3306
|
this.dataChannel = undefined;
|
|
@@ -3244,7 +3309,7 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3244
3309
|
this.peerConnection.close();
|
|
3245
3310
|
}
|
|
3246
3311
|
catch (err) {
|
|
3247
|
-
logger$
|
|
3312
|
+
logger$v.warn('Failed to close connection', { err });
|
|
3248
3313
|
}
|
|
3249
3314
|
}
|
|
3250
3315
|
this.peerConnection = undefined;
|
|
@@ -3256,6 +3321,9 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3256
3321
|
}
|
|
3257
3322
|
send(data) {
|
|
3258
3323
|
if (this.lastState === 'connected') {
|
|
3324
|
+
logGapDiagnosticSampled('dht.dc.send', {
|
|
3325
|
+
detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
|
|
3326
|
+
});
|
|
3259
3327
|
if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
|
|
3260
3328
|
this.messageQueue.push(data);
|
|
3261
3329
|
}
|
|
@@ -3264,7 +3332,7 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3264
3332
|
}
|
|
3265
3333
|
}
|
|
3266
3334
|
else {
|
|
3267
|
-
logger$
|
|
3335
|
+
logger$v.warn('Tried to send on a connection with last state ' + this.lastState);
|
|
3268
3336
|
}
|
|
3269
3337
|
}
|
|
3270
3338
|
setupDataChannel(dataChannel) {
|
|
@@ -3272,22 +3340,23 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3272
3340
|
this.dataChannel.binaryType = 'arraybuffer';
|
|
3273
3341
|
this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
|
|
3274
3342
|
dataChannel.onopen = () => {
|
|
3275
|
-
logger$
|
|
3343
|
+
logger$v.trace('dc.onOpen');
|
|
3276
3344
|
this.onDataChannelOpen();
|
|
3277
3345
|
};
|
|
3278
3346
|
dataChannel.onclose = () => {
|
|
3279
|
-
logger$
|
|
3347
|
+
logger$v.trace('dc.onClosed');
|
|
3280
3348
|
this.doClose(false);
|
|
3281
3349
|
};
|
|
3282
3350
|
dataChannel.onerror = (err) => {
|
|
3283
|
-
logger$
|
|
3351
|
+
logger$v.warn('Data channel error', { err });
|
|
3284
3352
|
};
|
|
3285
3353
|
dataChannel.onmessage = (msg) => {
|
|
3286
|
-
logger$
|
|
3354
|
+
logger$v.trace('dc.onmessage');
|
|
3355
|
+
logGapDiagnosticSampled('dht.dc.onmessage');
|
|
3287
3356
|
this.emit('data', new Uint8Array(msg.data));
|
|
3288
3357
|
};
|
|
3289
3358
|
dataChannel.onbufferedamountlow = () => {
|
|
3290
|
-
logger$
|
|
3359
|
+
logger$v.trace('dc.onBufferedAmountLow');
|
|
3291
3360
|
while (this.messageQueue.length > 0 && this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
|
|
3292
3361
|
const data = this.messageQueue.shift();
|
|
3293
3362
|
this.dataChannel.send(data);
|
|
@@ -3326,6 +3395,435 @@ class WebrtcConnection extends EventEmitter {
|
|
|
3326
3395
|
}
|
|
3327
3396
|
}
|
|
3328
3397
|
|
|
3398
|
+
/**
|
|
3399
|
+
* WebrtcBridge — runs on the MAIN THREAD.
|
|
3400
|
+
*
|
|
3401
|
+
* Manages RTCPeerConnection instances on behalf of a worker that cannot
|
|
3402
|
+
* access them directly. Each logical connection is identified by a
|
|
3403
|
+
* `connectionId` string supplied by the worker.
|
|
3404
|
+
*
|
|
3405
|
+
* Signaling (ICE candidates, SDP offers/answers, connection-state changes)
|
|
3406
|
+
* is relayed to the worker through Comlink proxy callbacks.
|
|
3407
|
+
*
|
|
3408
|
+
* Once a DataChannel is created (offerer) or received (answerer) it is
|
|
3409
|
+
* **transferred** to the worker so that all data-path events fire inside
|
|
3410
|
+
* the worker's event loop — the main thread never touches data traffic.
|
|
3411
|
+
*/
|
|
3412
|
+
// ── Bridge implementation ───────────────────────────────────────────
|
|
3413
|
+
class WebrtcBridge {
|
|
3414
|
+
connections = new Map();
|
|
3415
|
+
async start(connectionId, iceServers, isOffering, callbacks) {
|
|
3416
|
+
const pc = new RTCPeerConnection({ iceServers });
|
|
3417
|
+
const conn = {
|
|
3418
|
+
pc,
|
|
3419
|
+
callbacks,
|
|
3420
|
+
isOffering,
|
|
3421
|
+
makingOffer: false,
|
|
3422
|
+
};
|
|
3423
|
+
this.connections.set(connectionId, conn);
|
|
3424
|
+
// ── ICE candidates ──────────────────────────────────────
|
|
3425
|
+
pc.onicecandidate = (event) => {
|
|
3426
|
+
if (event.candidate !== null && event.candidate.sdpMid !== null) {
|
|
3427
|
+
callbacks.onLocalCandidate(event.candidate.candidate, event.candidate.sdpMid);
|
|
3428
|
+
}
|
|
3429
|
+
};
|
|
3430
|
+
// ── Connection state → forwarded to worker ──────────────
|
|
3431
|
+
pc.onconnectionstatechange = () => {
|
|
3432
|
+
callbacks.onConnectionStateChange(pc.connectionState);
|
|
3433
|
+
};
|
|
3434
|
+
// ── Offerer path ────────────────────────────────────────
|
|
3435
|
+
if (isOffering) {
|
|
3436
|
+
pc.onnegotiationneeded = async () => {
|
|
3437
|
+
conn.makingOffer = true;
|
|
3438
|
+
try {
|
|
3439
|
+
await pc.setLocalDescription();
|
|
3440
|
+
}
|
|
3441
|
+
catch (_err) {
|
|
3442
|
+
// intentionally swallowed – mirrors DirectWebrtcConnection
|
|
3443
|
+
}
|
|
3444
|
+
if (pc.localDescription !== null) {
|
|
3445
|
+
callbacks.onLocalDescription(pc.localDescription.sdp, pc.localDescription.type);
|
|
3446
|
+
}
|
|
3447
|
+
conn.makingOffer = false;
|
|
3448
|
+
};
|
|
3449
|
+
const dc = pc.createDataChannel('streamrDataChannel');
|
|
3450
|
+
// Transfer DataChannel ownership to the worker immediately.
|
|
3451
|
+
// The worker will attach onopen/onclose/onmessage handlers.
|
|
3452
|
+
callbacks.onDataChannel(Comlink.transfer(dc, [dc]));
|
|
3453
|
+
}
|
|
3454
|
+
else {
|
|
3455
|
+
// ── Answerer path ───────────────────────────────────
|
|
3456
|
+
pc.ondatachannel = (event) => {
|
|
3457
|
+
callbacks.onDataChannel(Comlink.transfer(event.channel, [event.channel]));
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
async setRemoteDescription(connectionId, description, type) {
|
|
3462
|
+
const conn = this.connections.get(connectionId);
|
|
3463
|
+
if (!conn) {
|
|
3464
|
+
return false;
|
|
3465
|
+
}
|
|
3466
|
+
const lowerType = type.toLowerCase();
|
|
3467
|
+
// Perfect-negotiation collision detection
|
|
3468
|
+
const offerCollision = lowerType === 'offer' &&
|
|
3469
|
+
(conn.makingOffer || conn.pc.signalingState !== 'stable');
|
|
3470
|
+
if (conn.isOffering && offerCollision) {
|
|
3471
|
+
return false;
|
|
3472
|
+
}
|
|
3473
|
+
try {
|
|
3474
|
+
await conn.pc.setRemoteDescription({ sdp: description, type: lowerType });
|
|
3475
|
+
}
|
|
3476
|
+
catch (_err) {
|
|
3477
|
+
return false;
|
|
3478
|
+
}
|
|
3479
|
+
// If we received an offer, create an answer
|
|
3480
|
+
if (lowerType === 'offer') {
|
|
3481
|
+
try {
|
|
3482
|
+
await conn.pc.setLocalDescription();
|
|
3483
|
+
}
|
|
3484
|
+
catch (_err) {
|
|
3485
|
+
// intentionally swallowed
|
|
3486
|
+
}
|
|
3487
|
+
if (conn.pc.localDescription !== null) {
|
|
3488
|
+
conn.callbacks.onLocalDescription(conn.pc.localDescription.sdp, conn.pc.localDescription.type);
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
return true;
|
|
3492
|
+
}
|
|
3493
|
+
async addRemoteCandidate(connectionId, candidate, mid) {
|
|
3494
|
+
const conn = this.connections.get(connectionId);
|
|
3495
|
+
if (!conn) {
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
try {
|
|
3499
|
+
await conn.pc.addIceCandidate({ candidate, sdpMid: mid });
|
|
3500
|
+
}
|
|
3501
|
+
catch (_err) {
|
|
3502
|
+
// intentionally swallowed
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
async renameConnection(oldId, newId) {
|
|
3506
|
+
const conn = this.connections.get(oldId);
|
|
3507
|
+
if (conn) {
|
|
3508
|
+
this.connections.delete(oldId);
|
|
3509
|
+
this.connections.set(newId, conn);
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
async close(connectionId) {
|
|
3513
|
+
const conn = this.connections.get(connectionId);
|
|
3514
|
+
if (!conn) {
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
this.connections.delete(connectionId);
|
|
3518
|
+
// Tear down event handlers
|
|
3519
|
+
conn.pc.onicecandidate = null;
|
|
3520
|
+
conn.pc.onconnectionstatechange = null;
|
|
3521
|
+
conn.pc.onnegotiationneeded = null;
|
|
3522
|
+
conn.pc.ondatachannel = null;
|
|
3523
|
+
try {
|
|
3524
|
+
conn.pc.close();
|
|
3525
|
+
}
|
|
3526
|
+
catch (_err) {
|
|
3527
|
+
// intentionally swallowed
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
/**
|
|
3533
|
+
* Call this function on the **main thread** before the worker starts using
|
|
3534
|
+
* WebRTC connections. It creates a dedicated `MessageChannel`, exposes a
|
|
3535
|
+
* {@link WebrtcBridge} instance on one port, and sends the other port to
|
|
3536
|
+
* the worker so that `WorkerWebrtcConnection` can reach the bridge.
|
|
3537
|
+
*
|
|
3538
|
+
* @example
|
|
3539
|
+
* ```ts
|
|
3540
|
+
* import { installWebrtcBridge } from '@streamr/dht'
|
|
3541
|
+
*
|
|
3542
|
+
* const worker = new Worker('./my-worker.ts', { type: 'module' })
|
|
3543
|
+
* installWebrtcBridge(worker)
|
|
3544
|
+
* ```
|
|
3545
|
+
*/
|
|
3546
|
+
const WEBRTC_BRIDGE_PORT_MESSAGE_TYPE = 'streamr-webrtc-bridge-port';
|
|
3547
|
+
function installWebrtcBridge(worker) {
|
|
3548
|
+
const bridge = new WebrtcBridge();
|
|
3549
|
+
const channel = new MessageChannel();
|
|
3550
|
+
// Expose the bridge API on port1 — the worker will Comlink.wrap(port2).
|
|
3551
|
+
Comlink.expose(bridge, channel.port1);
|
|
3552
|
+
// Send port2 to the worker. It is transferred (not cloned).
|
|
3553
|
+
worker.postMessage({ type: WEBRTC_BRIDGE_PORT_MESSAGE_TYPE, port: channel.port2 }, [channel.port2]);
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
/**
|
|
3557
|
+
* WorkerWebrtcConnection — runs inside a **Web Worker**.
|
|
3558
|
+
*
|
|
3559
|
+
* Implements the same IWebrtcConnection + IConnection interfaces as
|
|
3560
|
+
* DirectWebrtcConnection, but delegates RTCPeerConnection management to
|
|
3561
|
+
* the main-thread {@link WebrtcBridge} via Comlink.
|
|
3562
|
+
*
|
|
3563
|
+
* The RTCDataChannel is **transferred** from the main thread and lives
|
|
3564
|
+
* entirely in the worker — all data events (onmessage, onopen, onclose,
|
|
3565
|
+
* onbufferedamountlow) fire in the worker's event loop. The main thread
|
|
3566
|
+
* is never involved in the data path.
|
|
3567
|
+
*/
|
|
3568
|
+
// ── Module-level bridge client (initialized once per worker) ────────
|
|
3569
|
+
let resolveBridgeProxy;
|
|
3570
|
+
const bridgeProxyPromise = new Promise((resolve) => {
|
|
3571
|
+
resolveBridgeProxy = resolve;
|
|
3572
|
+
});
|
|
3573
|
+
// Listen for the bridge port message from the main thread.
|
|
3574
|
+
// This is guarded so it only runs inside a worker context.
|
|
3575
|
+
if (isWorkerEnvironment) {
|
|
3576
|
+
const handler = (e) => {
|
|
3577
|
+
if (e.data?.type === WEBRTC_BRIDGE_PORT_MESSAGE_TYPE && e.data.port) {
|
|
3578
|
+
const proxy = Comlink.wrap(e.data.port);
|
|
3579
|
+
resolveBridgeProxy(proxy);
|
|
3580
|
+
self.removeEventListener('message', handler);
|
|
3581
|
+
}
|
|
3582
|
+
};
|
|
3583
|
+
self.addEventListener('message', handler);
|
|
3584
|
+
}
|
|
3585
|
+
function getBridgeProxy() {
|
|
3586
|
+
return bridgeProxyPromise;
|
|
3587
|
+
}
|
|
3588
|
+
// ── Disconnection states ────────────────────────────────────────────
|
|
3589
|
+
var DisconnectedState;
|
|
3590
|
+
(function (DisconnectedState) {
|
|
3591
|
+
DisconnectedState["DISCONNECTED"] = "disconnected";
|
|
3592
|
+
DisconnectedState["FAILED"] = "failed";
|
|
3593
|
+
DisconnectedState["CLOSED"] = "closed";
|
|
3594
|
+
})(DisconnectedState || (DisconnectedState = {}));
|
|
3595
|
+
const logger$u = new Logger('WorkerWebrtcConnection');
|
|
3596
|
+
// ── WorkerWebrtcConnection ──────────────────────────────────────────
|
|
3597
|
+
class WorkerWebrtcConnection extends EventEmitter {
|
|
3598
|
+
connectionId;
|
|
3599
|
+
connectionType = ConnectionType.WEBRTC;
|
|
3600
|
+
iceServers;
|
|
3601
|
+
bufferThresholdHigh;
|
|
3602
|
+
bufferThresholdLow;
|
|
3603
|
+
dataChannel;
|
|
3604
|
+
bridge;
|
|
3605
|
+
closed = false;
|
|
3606
|
+
connected = false;
|
|
3607
|
+
earlyTimeout;
|
|
3608
|
+
messageQueue = [];
|
|
3609
|
+
startPromise;
|
|
3610
|
+
constructor(params) {
|
|
3611
|
+
super();
|
|
3612
|
+
this.connectionId = createRandomConnectionId();
|
|
3613
|
+
this.iceServers = params.iceServers ?? [];
|
|
3614
|
+
this.bufferThresholdHigh = params.bufferThresholdHigh ?? 2 ** 17;
|
|
3615
|
+
this.bufferThresholdLow = params.bufferThresholdLow ?? 2 ** 15;
|
|
3616
|
+
this.earlyTimeout = setTimeout(() => {
|
|
3617
|
+
this.doClose(false, 'timed out due to remote descriptor not being set');
|
|
3618
|
+
}, EARLY_TIMEOUT);
|
|
3619
|
+
}
|
|
3620
|
+
// ── IWebrtcConnection ───────────────────────────────────────
|
|
3621
|
+
start(isOffering) {
|
|
3622
|
+
this.startPromise = this.doStart(isOffering);
|
|
3623
|
+
this.startPromise.catch((err) => {
|
|
3624
|
+
logger$u.warn('Failed to start worker WebRTC connection', { err });
|
|
3625
|
+
this.doClose(false, 'Failed to start');
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
async doStart(isOffering) {
|
|
3629
|
+
this.bridge = await getBridgeProxy();
|
|
3630
|
+
const iceServers = this.iceServers.map(({ url, port, username, password }) => ({
|
|
3631
|
+
urls: `${url}:${port}`,
|
|
3632
|
+
username,
|
|
3633
|
+
credential: password,
|
|
3634
|
+
}));
|
|
3635
|
+
await this.bridge.start(this.connectionId, iceServers, isOffering, Comlink.proxy({
|
|
3636
|
+
onLocalCandidate: (candidate, mid) => {
|
|
3637
|
+
if (!this.closed) {
|
|
3638
|
+
this.emit('localCandidate', candidate, mid);
|
|
3639
|
+
}
|
|
3640
|
+
},
|
|
3641
|
+
onLocalDescription: (description, type) => {
|
|
3642
|
+
if (!this.closed) {
|
|
3643
|
+
this.emit('localDescription', description, type);
|
|
3644
|
+
}
|
|
3645
|
+
},
|
|
3646
|
+
onConnectionStateChange: (state) => {
|
|
3647
|
+
if (state === DisconnectedState.CLOSED ||
|
|
3648
|
+
state === DisconnectedState.DISCONNECTED ||
|
|
3649
|
+
state === DisconnectedState.FAILED) {
|
|
3650
|
+
this.doClose(false, `pcState=${state}`);
|
|
3651
|
+
}
|
|
3652
|
+
},
|
|
3653
|
+
onDataChannel: (channel) => {
|
|
3654
|
+
if (!this.closed) {
|
|
3655
|
+
this.setupDataChannel(channel);
|
|
3656
|
+
// If the channel was already open at transfer time
|
|
3657
|
+
if (channel.readyState === 'open') {
|
|
3658
|
+
this.onDataChannelOpen();
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
},
|
|
3662
|
+
}));
|
|
3663
|
+
}
|
|
3664
|
+
async setRemoteDescription(description, type) {
|
|
3665
|
+
if (this.startPromise) {
|
|
3666
|
+
await this.startPromise;
|
|
3667
|
+
}
|
|
3668
|
+
if (!this.bridge || this.closed) {
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
const wasSet = await this.bridge.setRemoteDescription(this.connectionId, description, type);
|
|
3672
|
+
if (wasSet) {
|
|
3673
|
+
clearTimeout(this.earlyTimeout);
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
addRemoteCandidate(candidate, mid) {
|
|
3677
|
+
this.doAddRemoteCandidate(candidate, mid).catch((err) => {
|
|
3678
|
+
logger$u.warn('Failed to add remote candidate via bridge', { err });
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
async doAddRemoteCandidate(candidate, mid) {
|
|
3682
|
+
if (this.startPromise) {
|
|
3683
|
+
await this.startPromise;
|
|
3684
|
+
}
|
|
3685
|
+
if (!this.bridge || this.closed) {
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
await this.bridge.addRemoteCandidate(this.connectionId, candidate, mid);
|
|
3689
|
+
}
|
|
3690
|
+
isOpen() {
|
|
3691
|
+
return this.connected;
|
|
3692
|
+
}
|
|
3693
|
+
// ── IConnection ─────────────────────────────────────────────
|
|
3694
|
+
async close(gracefulLeave, reason) {
|
|
3695
|
+
this.doClose(gracefulLeave, reason);
|
|
3696
|
+
}
|
|
3697
|
+
destroy() {
|
|
3698
|
+
this.removeAllListeners();
|
|
3699
|
+
this.doClose(false);
|
|
3700
|
+
}
|
|
3701
|
+
send(data) {
|
|
3702
|
+
if (this.connected && this.dataChannel) {
|
|
3703
|
+
logGapDiagnosticSampled('dht.dc.send', {
|
|
3704
|
+
detail: { bufferedAmount: this.dataChannel.bufferedAmount, queueLen: this.messageQueue.length }
|
|
3705
|
+
});
|
|
3706
|
+
if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
|
|
3707
|
+
this.messageQueue.push(data);
|
|
3708
|
+
}
|
|
3709
|
+
else {
|
|
3710
|
+
this.dataChannel.send(data);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
else if (!this.closed) {
|
|
3714
|
+
this.messageQueue.push(data);
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
setConnectionId(connectionId) {
|
|
3718
|
+
const oldId = this.connectionId;
|
|
3719
|
+
this.connectionId = connectionId;
|
|
3720
|
+
if (this.bridge && oldId !== connectionId) {
|
|
3721
|
+
this.bridge.renameConnection(oldId, connectionId).catch(() => { });
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
// ── DataChannel handling (runs entirely in the worker) ──────
|
|
3725
|
+
setupDataChannel(dataChannel) {
|
|
3726
|
+
this.dataChannel = dataChannel;
|
|
3727
|
+
this.dataChannel.binaryType = 'arraybuffer';
|
|
3728
|
+
this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
|
|
3729
|
+
dataChannel.onopen = () => {
|
|
3730
|
+
logger$u.trace('dc.onOpen (worker)');
|
|
3731
|
+
this.onDataChannelOpen();
|
|
3732
|
+
};
|
|
3733
|
+
dataChannel.onclose = () => {
|
|
3734
|
+
logger$u.trace('dc.onClosed (worker)');
|
|
3735
|
+
this.doClose(false, 'dataChannel.onclose');
|
|
3736
|
+
};
|
|
3737
|
+
dataChannel.onerror = (err) => {
|
|
3738
|
+
logger$u.warn('Data channel error (worker)', { err });
|
|
3739
|
+
};
|
|
3740
|
+
dataChannel.onmessage = (msg) => {
|
|
3741
|
+
logger$u.trace('dc.onmessage (worker)');
|
|
3742
|
+
logGapDiagnosticSampled('dht.dc.onmessage');
|
|
3743
|
+
this.emit('data', new Uint8Array(msg.data));
|
|
3744
|
+
};
|
|
3745
|
+
dataChannel.onbufferedamountlow = () => {
|
|
3746
|
+
logger$u.trace('dc.onBufferedAmountLow (worker)');
|
|
3747
|
+
while (this.messageQueue.length > 0 &&
|
|
3748
|
+
this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
|
|
3749
|
+
const data = this.messageQueue.shift();
|
|
3750
|
+
this.dataChannel.send(data);
|
|
3751
|
+
}
|
|
3752
|
+
};
|
|
3753
|
+
}
|
|
3754
|
+
onDataChannelOpen() {
|
|
3755
|
+
this.connected = true;
|
|
3756
|
+
this.flushMessageQueue();
|
|
3757
|
+
this.emit('connected');
|
|
3758
|
+
}
|
|
3759
|
+
flushMessageQueue() {
|
|
3760
|
+
while (this.messageQueue.length > 0 &&
|
|
3761
|
+
this.dataChannel &&
|
|
3762
|
+
this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
|
|
3763
|
+
const data = this.messageQueue.shift();
|
|
3764
|
+
this.dataChannel.send(data);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
// ── Teardown ────────────────────────────────────────────────
|
|
3768
|
+
doClose(gracefulLeave, reason) {
|
|
3769
|
+
if (!this.closed) {
|
|
3770
|
+
this.closed = true;
|
|
3771
|
+
this.connected = false;
|
|
3772
|
+
this.messageQueue.length = 0;
|
|
3773
|
+
clearTimeout(this.earlyTimeout);
|
|
3774
|
+
this.stopListening();
|
|
3775
|
+
this.emit('disconnected', gracefulLeave, undefined, reason);
|
|
3776
|
+
this.removeAllListeners();
|
|
3777
|
+
if (this.dataChannel !== undefined) {
|
|
3778
|
+
try {
|
|
3779
|
+
this.dataChannel.close();
|
|
3780
|
+
}
|
|
3781
|
+
catch (err) {
|
|
3782
|
+
logger$u.warn('Failed to close data channel (worker)', { err });
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
this.dataChannel = undefined;
|
|
3786
|
+
// Tell the main-thread bridge to tear down the RTCPeerConnection.
|
|
3787
|
+
// Fire-and-forget — we don't block on this.
|
|
3788
|
+
this.bridge
|
|
3789
|
+
?.close(this.connectionId)
|
|
3790
|
+
.catch(() => {
|
|
3791
|
+
// intentionally swallowed
|
|
3792
|
+
});
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
stopListening() {
|
|
3796
|
+
if (this.dataChannel !== undefined) {
|
|
3797
|
+
this.dataChannel.onopen = null;
|
|
3798
|
+
this.dataChannel.onclose = null;
|
|
3799
|
+
this.dataChannel.onerror = null;
|
|
3800
|
+
this.dataChannel.onbufferedamountlow = null;
|
|
3801
|
+
this.dataChannel.onmessage = null;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
/**
|
|
3807
|
+
* Conditional re-export of the browser WebrtcConnection.
|
|
3808
|
+
*
|
|
3809
|
+
* At module-load time we detect whether we are running inside a Web Worker.
|
|
3810
|
+
* - **Main thread** → use {@link DirectWebrtcConnection} which owns the
|
|
3811
|
+
* `RTCPeerConnection` and `RTCDataChannel` directly.
|
|
3812
|
+
* - **Worker thread** → use {@link WorkerWebrtcConnection} which delegates
|
|
3813
|
+
* `RTCPeerConnection` signaling to the main thread via a Comlink bridge
|
|
3814
|
+
* and receives a transferred `RTCDataChannel` that lives entirely in the
|
|
3815
|
+
* worker.
|
|
3816
|
+
*
|
|
3817
|
+
* Both classes implement `IWebrtcConnection & IConnection` and expose the
|
|
3818
|
+
* same public API, so upstream code (WebrtcConnector, etc.) is unaffected.
|
|
3819
|
+
*/
|
|
3820
|
+
// The constructor — points to the right class based on the runtime
|
|
3821
|
+
// environment. The type assertion is safe because both implementations
|
|
3822
|
+
// share the same public interface surface.
|
|
3823
|
+
const WebrtcConnection = (isWorkerEnvironment
|
|
3824
|
+
? WorkerWebrtcConnection
|
|
3825
|
+
: DirectWebrtcConnection);
|
|
3826
|
+
|
|
3329
3827
|
const logger$t = new Logger('WebrtcConnectorRpcRemote');
|
|
3330
3828
|
class WebrtcConnectorRpcRemote extends RpcRemote {
|
|
3331
3829
|
requestConnection() {
|
|
@@ -5511,7 +6009,7 @@ class PeerDiscovery {
|
|
|
5511
6009
|
logger$d.debug(`Ring join on ${this.options.serviceId} timed out`);
|
|
5512
6010
|
}
|
|
5513
6011
|
finally {
|
|
5514
|
-
sessions.forEach((session) => this.
|
|
6012
|
+
sessions.forEach((session) => this.ongoingRingDiscoverySessions.delete(session.id));
|
|
5515
6013
|
}
|
|
5516
6014
|
}
|
|
5517
6015
|
async rejoinDht(entryPoint, contactedPeers = new Set(), distantJoinContactPeers = new Set()) {
|
|
@@ -7608,5 +8106,5 @@ class SimulatorTransport extends ConnectionManager {
|
|
|
7608
8106
|
}
|
|
7609
8107
|
}
|
|
7610
8108
|
|
|
7611
|
-
export { ConnectionManager, ConnectionType, DataEntry, DefaultConnectorFacade, DhtCallContext, DhtNode, EXISTING_CONNECTION_TIMEOUT, LatencyType, ListeningRpcCommunicator, ManagedConnection, Message, NodeType, PeerDescriptor, PendingConnection, RoutingRpcCommunicator, RpcRemote, Simulator, SimulatorTransport, WebsocketClientConnection, areEqualPeerDescriptors, createOutgoingHandshaker, getRandomRegion, getRegionDelayMatrix, randomDhtAddress, toDhtAddress, toDhtAddressRaw, toNodeId };
|
|
8109
|
+
export { ConnectionManager, ConnectionType, DataEntry, DefaultConnectorFacade, DhtCallContext, DhtNode, EXISTING_CONNECTION_TIMEOUT, LatencyType, ListeningRpcCommunicator, ManagedConnection, Message, NodeType, PeerDescriptor, PendingConnection, RoutingRpcCommunicator, RpcRemote, Simulator, SimulatorTransport, WebsocketClientConnection, areEqualPeerDescriptors, createOutgoingHandshaker, getRandomRegion, getRegionDelayMatrix, installWebrtcBridge, logGapDiagnosticSampled, randomDhtAddress, setGapDiagnosticsEnabled, toDhtAddress, toDhtAddressRaw, toNodeId };
|
|
7612
8110
|
//# sourceMappingURL=exports-browser.js.map
|