@streamr/sdk 103.3.0 → 103.6.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exports-umd.js +865 -120
- package/dist/exports-umd.js.map +1 -1
- package/dist/exports-umd.min.js +8 -2
- package/dist/exports-umd.min.js.map +1 -1
- package/package.json +6 -6
package/dist/exports-umd.js
CHANGED
|
@@ -41288,6 +41288,22 @@
|
|
|
41288
41288
|
cb();
|
|
41289
41289
|
}, ms);
|
|
41290
41290
|
};
|
|
41291
|
+
/**
|
|
41292
|
+
* setInterval with AbortSignal support. Aborting will simply clear
|
|
41293
|
+
* the interval silently.
|
|
41294
|
+
*/
|
|
41295
|
+
const setAbortableInterval = (cb, ms, abortSignal) => {
|
|
41296
|
+
if (abortSignal.aborted) {
|
|
41297
|
+
return;
|
|
41298
|
+
}
|
|
41299
|
+
const abortListener = () => {
|
|
41300
|
+
clearInterval(timeoutRef);
|
|
41301
|
+
};
|
|
41302
|
+
abortSignal.addEventListener('abort', abortListener, { once: true });
|
|
41303
|
+
const timeoutRef = setInterval(() => {
|
|
41304
|
+
cb();
|
|
41305
|
+
}, ms);
|
|
41306
|
+
};
|
|
41291
41307
|
|
|
41292
41308
|
const noopExecutor = () => { };
|
|
41293
41309
|
/**
|
|
@@ -73321,7 +73337,7 @@
|
|
|
73321
73337
|
}
|
|
73322
73338
|
}
|
|
73323
73339
|
|
|
73324
|
-
const logger$
|
|
73340
|
+
const logger$E = new Logger('LoggingJsonRpcProvider');
|
|
73325
73341
|
class LoggingJsonRpcProvider extends JsonRpcProvider {
|
|
73326
73342
|
urlConfig;
|
|
73327
73343
|
constructor(urlConfig, network, options) {
|
|
@@ -73340,20 +73356,20 @@
|
|
|
73340
73356
|
timeout: this.urlConfig.timeout
|
|
73341
73357
|
}
|
|
73342
73358
|
};
|
|
73343
|
-
logger$
|
|
73359
|
+
logger$E.debug('Send request', logContext);
|
|
73344
73360
|
let result;
|
|
73345
73361
|
try {
|
|
73346
73362
|
result = await super.send(method, params);
|
|
73347
73363
|
}
|
|
73348
73364
|
catch (err) {
|
|
73349
|
-
logger$
|
|
73365
|
+
logger$E.debug('Encountered error while requesting', {
|
|
73350
73366
|
...logContext,
|
|
73351
73367
|
err,
|
|
73352
73368
|
elapsedTime: Date.now() - startTime
|
|
73353
73369
|
});
|
|
73354
73370
|
throw err;
|
|
73355
73371
|
}
|
|
73356
|
-
logger$
|
|
73372
|
+
logger$E.debug('Received response', {
|
|
73357
73373
|
...logContext,
|
|
73358
73374
|
elapsedTime: Date.now() - startTime
|
|
73359
73375
|
});
|
|
@@ -80246,7 +80262,7 @@
|
|
|
80246
80262
|
this.deferredPromises.trailer.resolve({});
|
|
80247
80263
|
}
|
|
80248
80264
|
catch (err) {
|
|
80249
|
-
logger$
|
|
80265
|
+
logger$z.debug(`Could not parse response, received message is likely `);
|
|
80250
80266
|
const error = new FailedToParse(`Failed to parse received response, network protocol version likely is likely incompatible`, err);
|
|
80251
80267
|
this.rejectDeferredPromises(error, StatusCode.SERVER_ERROR);
|
|
80252
80268
|
}
|
|
@@ -80267,7 +80283,7 @@
|
|
|
80267
80283
|
return this.callContext;
|
|
80268
80284
|
}
|
|
80269
80285
|
}
|
|
80270
|
-
const logger$
|
|
80286
|
+
const logger$z = new Logger('RpcCommunicator');
|
|
80271
80287
|
class RpcCommunicator {
|
|
80272
80288
|
stopped = false;
|
|
80273
80289
|
rpcClientTransport;
|
|
@@ -80321,7 +80337,7 @@
|
|
|
80321
80337
|
if (deferredPromises && (!callContext || !callContext.notification)) {
|
|
80322
80338
|
this.registerRequest(rpcMessage.requestId, deferredPromises, callContext, requestOptions.timeout);
|
|
80323
80339
|
}
|
|
80324
|
-
logger$
|
|
80340
|
+
logger$z.trace(`onOutGoingMessage, messageId: ${rpcMessage.requestId}`);
|
|
80325
80341
|
if (this.outgoingMessageListener) {
|
|
80326
80342
|
this.outgoingMessageListener(rpcMessage, rpcMessage.requestId, callContext)
|
|
80327
80343
|
.catch((clientSideException) => {
|
|
@@ -80352,7 +80368,7 @@
|
|
|
80352
80368
|
}
|
|
80353
80369
|
}
|
|
80354
80370
|
async onIncomingMessage(rpcMessage, callContext) {
|
|
80355
|
-
logger$
|
|
80371
|
+
logger$z.trace(`onIncomingMessage, requestId: ${rpcMessage.requestId}`);
|
|
80356
80372
|
if (rpcMessage.header.response && this.ongoingRequests.has(rpcMessage.requestId)) {
|
|
80357
80373
|
if (rpcMessage.errorType !== undefined) {
|
|
80358
80374
|
this.rejectOngoingRequest(rpcMessage);
|
|
@@ -80414,7 +80430,7 @@
|
|
|
80414
80430
|
await this.rpcServerRegistry.handleNotification(rpcMessage, callContext);
|
|
80415
80431
|
}
|
|
80416
80432
|
catch (err) {
|
|
80417
|
-
logger$
|
|
80433
|
+
logger$z.debug('error', { err });
|
|
80418
80434
|
}
|
|
80419
80435
|
}
|
|
80420
80436
|
registerRequest(requestId, deferredPromises, callContext, timeout = this.rpcRequestTimeout) {
|
|
@@ -81601,6 +81617,355 @@
|
|
|
81601
81617
|
var ipaddrExports = requireIpaddr();
|
|
81602
81618
|
var ipaddr = /*@__PURE__*/getDefaultExportFromCjs$1(ipaddrExports);
|
|
81603
81619
|
|
|
81620
|
+
/**
|
|
81621
|
+
* @license
|
|
81622
|
+
* Copyright 2019 Google LLC
|
|
81623
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
81624
|
+
*/
|
|
81625
|
+
const proxyMarker = Symbol("Comlink.proxy");
|
|
81626
|
+
const createEndpoint = Symbol("Comlink.endpoint");
|
|
81627
|
+
const releaseProxy = Symbol("Comlink.releaseProxy");
|
|
81628
|
+
const finalizer = Symbol("Comlink.finalizer");
|
|
81629
|
+
const throwMarker = Symbol("Comlink.thrown");
|
|
81630
|
+
const isObject$1 = (val) => (typeof val === "object" && val !== null) || typeof val === "function";
|
|
81631
|
+
/**
|
|
81632
|
+
* Internal transfer handle to handle objects marked to proxy.
|
|
81633
|
+
*/
|
|
81634
|
+
const proxyTransferHandler = {
|
|
81635
|
+
canHandle: (val) => isObject$1(val) && val[proxyMarker],
|
|
81636
|
+
serialize(obj) {
|
|
81637
|
+
const { port1, port2 } = new MessageChannel();
|
|
81638
|
+
expose(obj, port1);
|
|
81639
|
+
return [port2, [port2]];
|
|
81640
|
+
},
|
|
81641
|
+
deserialize(port) {
|
|
81642
|
+
port.start();
|
|
81643
|
+
return wrap$1(port);
|
|
81644
|
+
},
|
|
81645
|
+
};
|
|
81646
|
+
/**
|
|
81647
|
+
* Internal transfer handler to handle thrown exceptions.
|
|
81648
|
+
*/
|
|
81649
|
+
const throwTransferHandler = {
|
|
81650
|
+
canHandle: (value) => isObject$1(value) && throwMarker in value,
|
|
81651
|
+
serialize({ value }) {
|
|
81652
|
+
let serialized;
|
|
81653
|
+
if (value instanceof Error) {
|
|
81654
|
+
serialized = {
|
|
81655
|
+
isError: true,
|
|
81656
|
+
value: {
|
|
81657
|
+
message: value.message,
|
|
81658
|
+
name: value.name,
|
|
81659
|
+
stack: value.stack,
|
|
81660
|
+
},
|
|
81661
|
+
};
|
|
81662
|
+
}
|
|
81663
|
+
else {
|
|
81664
|
+
serialized = { isError: false, value };
|
|
81665
|
+
}
|
|
81666
|
+
return [serialized, []];
|
|
81667
|
+
},
|
|
81668
|
+
deserialize(serialized) {
|
|
81669
|
+
if (serialized.isError) {
|
|
81670
|
+
throw Object.assign(new Error(serialized.value.message), serialized.value);
|
|
81671
|
+
}
|
|
81672
|
+
throw serialized.value;
|
|
81673
|
+
},
|
|
81674
|
+
};
|
|
81675
|
+
/**
|
|
81676
|
+
* Allows customizing the serialization of certain values.
|
|
81677
|
+
*/
|
|
81678
|
+
const transferHandlers = new Map([
|
|
81679
|
+
["proxy", proxyTransferHandler],
|
|
81680
|
+
["throw", throwTransferHandler],
|
|
81681
|
+
]);
|
|
81682
|
+
function isAllowedOrigin(allowedOrigins, origin) {
|
|
81683
|
+
for (const allowedOrigin of allowedOrigins) {
|
|
81684
|
+
if (origin === allowedOrigin || allowedOrigin === "*") {
|
|
81685
|
+
return true;
|
|
81686
|
+
}
|
|
81687
|
+
if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {
|
|
81688
|
+
return true;
|
|
81689
|
+
}
|
|
81690
|
+
}
|
|
81691
|
+
return false;
|
|
81692
|
+
}
|
|
81693
|
+
function expose(obj, ep = globalThis, allowedOrigins = ["*"]) {
|
|
81694
|
+
ep.addEventListener("message", function callback(ev) {
|
|
81695
|
+
if (!ev || !ev.data) {
|
|
81696
|
+
return;
|
|
81697
|
+
}
|
|
81698
|
+
if (!isAllowedOrigin(allowedOrigins, ev.origin)) {
|
|
81699
|
+
console.warn(`Invalid origin '${ev.origin}' for comlink proxy`);
|
|
81700
|
+
return;
|
|
81701
|
+
}
|
|
81702
|
+
const { id, type, path } = Object.assign({ path: [] }, ev.data);
|
|
81703
|
+
const argumentList = (ev.data.argumentList || []).map(fromWireValue);
|
|
81704
|
+
let returnValue;
|
|
81705
|
+
try {
|
|
81706
|
+
const parent = path.slice(0, -1).reduce((obj, prop) => obj[prop], obj);
|
|
81707
|
+
const rawValue = path.reduce((obj, prop) => obj[prop], obj);
|
|
81708
|
+
switch (type) {
|
|
81709
|
+
case "GET" /* MessageType.GET */:
|
|
81710
|
+
{
|
|
81711
|
+
returnValue = rawValue;
|
|
81712
|
+
}
|
|
81713
|
+
break;
|
|
81714
|
+
case "SET" /* MessageType.SET */:
|
|
81715
|
+
{
|
|
81716
|
+
parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
|
|
81717
|
+
returnValue = true;
|
|
81718
|
+
}
|
|
81719
|
+
break;
|
|
81720
|
+
case "APPLY" /* MessageType.APPLY */:
|
|
81721
|
+
{
|
|
81722
|
+
returnValue = rawValue.apply(parent, argumentList);
|
|
81723
|
+
}
|
|
81724
|
+
break;
|
|
81725
|
+
case "CONSTRUCT" /* MessageType.CONSTRUCT */:
|
|
81726
|
+
{
|
|
81727
|
+
const value = new rawValue(...argumentList);
|
|
81728
|
+
returnValue = proxy(value);
|
|
81729
|
+
}
|
|
81730
|
+
break;
|
|
81731
|
+
case "ENDPOINT" /* MessageType.ENDPOINT */:
|
|
81732
|
+
{
|
|
81733
|
+
const { port1, port2 } = new MessageChannel();
|
|
81734
|
+
expose(obj, port2);
|
|
81735
|
+
returnValue = transfer(port1, [port1]);
|
|
81736
|
+
}
|
|
81737
|
+
break;
|
|
81738
|
+
case "RELEASE" /* MessageType.RELEASE */:
|
|
81739
|
+
{
|
|
81740
|
+
returnValue = undefined;
|
|
81741
|
+
}
|
|
81742
|
+
break;
|
|
81743
|
+
default:
|
|
81744
|
+
return;
|
|
81745
|
+
}
|
|
81746
|
+
}
|
|
81747
|
+
catch (value) {
|
|
81748
|
+
returnValue = { value, [throwMarker]: 0 };
|
|
81749
|
+
}
|
|
81750
|
+
Promise.resolve(returnValue)
|
|
81751
|
+
.catch((value) => {
|
|
81752
|
+
return { value, [throwMarker]: 0 };
|
|
81753
|
+
})
|
|
81754
|
+
.then((returnValue) => {
|
|
81755
|
+
const [wireValue, transferables] = toWireValue(returnValue);
|
|
81756
|
+
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
|
|
81757
|
+
if (type === "RELEASE" /* MessageType.RELEASE */) {
|
|
81758
|
+
// detach and deactive after sending release response above.
|
|
81759
|
+
ep.removeEventListener("message", callback);
|
|
81760
|
+
closeEndPoint(ep);
|
|
81761
|
+
if (finalizer in obj && typeof obj[finalizer] === "function") {
|
|
81762
|
+
obj[finalizer]();
|
|
81763
|
+
}
|
|
81764
|
+
}
|
|
81765
|
+
})
|
|
81766
|
+
.catch((error) => {
|
|
81767
|
+
// Send Serialization Error To Caller
|
|
81768
|
+
const [wireValue, transferables] = toWireValue({
|
|
81769
|
+
value: new TypeError("Unserializable return value"),
|
|
81770
|
+
[throwMarker]: 0,
|
|
81771
|
+
});
|
|
81772
|
+
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
|
|
81773
|
+
});
|
|
81774
|
+
});
|
|
81775
|
+
if (ep.start) {
|
|
81776
|
+
ep.start();
|
|
81777
|
+
}
|
|
81778
|
+
}
|
|
81779
|
+
function isMessagePort(endpoint) {
|
|
81780
|
+
return endpoint.constructor.name === "MessagePort";
|
|
81781
|
+
}
|
|
81782
|
+
function closeEndPoint(endpoint) {
|
|
81783
|
+
if (isMessagePort(endpoint))
|
|
81784
|
+
endpoint.close();
|
|
81785
|
+
}
|
|
81786
|
+
function wrap$1(ep, target) {
|
|
81787
|
+
const pendingListeners = new Map();
|
|
81788
|
+
ep.addEventListener("message", function handleMessage(ev) {
|
|
81789
|
+
const { data } = ev;
|
|
81790
|
+
if (!data || !data.id) {
|
|
81791
|
+
return;
|
|
81792
|
+
}
|
|
81793
|
+
const resolver = pendingListeners.get(data.id);
|
|
81794
|
+
if (!resolver) {
|
|
81795
|
+
return;
|
|
81796
|
+
}
|
|
81797
|
+
try {
|
|
81798
|
+
resolver(data);
|
|
81799
|
+
}
|
|
81800
|
+
finally {
|
|
81801
|
+
pendingListeners.delete(data.id);
|
|
81802
|
+
}
|
|
81803
|
+
});
|
|
81804
|
+
return createProxy(ep, pendingListeners, [], target);
|
|
81805
|
+
}
|
|
81806
|
+
function throwIfProxyReleased(isReleased) {
|
|
81807
|
+
if (isReleased) {
|
|
81808
|
+
throw new Error("Proxy has been released and is not useable");
|
|
81809
|
+
}
|
|
81810
|
+
}
|
|
81811
|
+
function releaseEndpoint(ep) {
|
|
81812
|
+
return requestResponseMessage(ep, new Map(), {
|
|
81813
|
+
type: "RELEASE" /* MessageType.RELEASE */,
|
|
81814
|
+
}).then(() => {
|
|
81815
|
+
closeEndPoint(ep);
|
|
81816
|
+
});
|
|
81817
|
+
}
|
|
81818
|
+
const proxyCounter = new WeakMap();
|
|
81819
|
+
const proxyFinalizers = "FinalizationRegistry" in globalThis &&
|
|
81820
|
+
new FinalizationRegistry((ep) => {
|
|
81821
|
+
const newCount = (proxyCounter.get(ep) || 0) - 1;
|
|
81822
|
+
proxyCounter.set(ep, newCount);
|
|
81823
|
+
if (newCount === 0) {
|
|
81824
|
+
releaseEndpoint(ep);
|
|
81825
|
+
}
|
|
81826
|
+
});
|
|
81827
|
+
function registerProxy(proxy, ep) {
|
|
81828
|
+
const newCount = (proxyCounter.get(ep) || 0) + 1;
|
|
81829
|
+
proxyCounter.set(ep, newCount);
|
|
81830
|
+
if (proxyFinalizers) {
|
|
81831
|
+
proxyFinalizers.register(proxy, ep, proxy);
|
|
81832
|
+
}
|
|
81833
|
+
}
|
|
81834
|
+
function unregisterProxy(proxy) {
|
|
81835
|
+
if (proxyFinalizers) {
|
|
81836
|
+
proxyFinalizers.unregister(proxy);
|
|
81837
|
+
}
|
|
81838
|
+
}
|
|
81839
|
+
function createProxy(ep, pendingListeners, path = [], target = function () { }) {
|
|
81840
|
+
let isProxyReleased = false;
|
|
81841
|
+
const proxy = new Proxy(target, {
|
|
81842
|
+
get(_target, prop) {
|
|
81843
|
+
throwIfProxyReleased(isProxyReleased);
|
|
81844
|
+
if (prop === releaseProxy) {
|
|
81845
|
+
return () => {
|
|
81846
|
+
unregisterProxy(proxy);
|
|
81847
|
+
releaseEndpoint(ep);
|
|
81848
|
+
pendingListeners.clear();
|
|
81849
|
+
isProxyReleased = true;
|
|
81850
|
+
};
|
|
81851
|
+
}
|
|
81852
|
+
if (prop === "then") {
|
|
81853
|
+
if (path.length === 0) {
|
|
81854
|
+
return { then: () => proxy };
|
|
81855
|
+
}
|
|
81856
|
+
const r = requestResponseMessage(ep, pendingListeners, {
|
|
81857
|
+
type: "GET" /* MessageType.GET */,
|
|
81858
|
+
path: path.map((p) => p.toString()),
|
|
81859
|
+
}).then(fromWireValue);
|
|
81860
|
+
return r.then.bind(r);
|
|
81861
|
+
}
|
|
81862
|
+
return createProxy(ep, pendingListeners, [...path, prop]);
|
|
81863
|
+
},
|
|
81864
|
+
set(_target, prop, rawValue) {
|
|
81865
|
+
throwIfProxyReleased(isProxyReleased);
|
|
81866
|
+
// FIXME: ES6 Proxy Handler `set` methods are supposed to return a
|
|
81867
|
+
// boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯
|
|
81868
|
+
const [value, transferables] = toWireValue(rawValue);
|
|
81869
|
+
return requestResponseMessage(ep, pendingListeners, {
|
|
81870
|
+
type: "SET" /* MessageType.SET */,
|
|
81871
|
+
path: [...path, prop].map((p) => p.toString()),
|
|
81872
|
+
value,
|
|
81873
|
+
}, transferables).then(fromWireValue);
|
|
81874
|
+
},
|
|
81875
|
+
apply(_target, _thisArg, rawArgumentList) {
|
|
81876
|
+
throwIfProxyReleased(isProxyReleased);
|
|
81877
|
+
const last = path[path.length - 1];
|
|
81878
|
+
if (last === createEndpoint) {
|
|
81879
|
+
return requestResponseMessage(ep, pendingListeners, {
|
|
81880
|
+
type: "ENDPOINT" /* MessageType.ENDPOINT */,
|
|
81881
|
+
}).then(fromWireValue);
|
|
81882
|
+
}
|
|
81883
|
+
// We just pretend that `bind()` didn’t happen.
|
|
81884
|
+
if (last === "bind") {
|
|
81885
|
+
return createProxy(ep, pendingListeners, path.slice(0, -1));
|
|
81886
|
+
}
|
|
81887
|
+
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
81888
|
+
return requestResponseMessage(ep, pendingListeners, {
|
|
81889
|
+
type: "APPLY" /* MessageType.APPLY */,
|
|
81890
|
+
path: path.map((p) => p.toString()),
|
|
81891
|
+
argumentList,
|
|
81892
|
+
}, transferables).then(fromWireValue);
|
|
81893
|
+
},
|
|
81894
|
+
construct(_target, rawArgumentList) {
|
|
81895
|
+
throwIfProxyReleased(isProxyReleased);
|
|
81896
|
+
const [argumentList, transferables] = processArguments(rawArgumentList);
|
|
81897
|
+
return requestResponseMessage(ep, pendingListeners, {
|
|
81898
|
+
type: "CONSTRUCT" /* MessageType.CONSTRUCT */,
|
|
81899
|
+
path: path.map((p) => p.toString()),
|
|
81900
|
+
argumentList,
|
|
81901
|
+
}, transferables).then(fromWireValue);
|
|
81902
|
+
},
|
|
81903
|
+
});
|
|
81904
|
+
registerProxy(proxy, ep);
|
|
81905
|
+
return proxy;
|
|
81906
|
+
}
|
|
81907
|
+
function myFlat(arr) {
|
|
81908
|
+
return Array.prototype.concat.apply([], arr);
|
|
81909
|
+
}
|
|
81910
|
+
function processArguments(argumentList) {
|
|
81911
|
+
const processed = argumentList.map(toWireValue);
|
|
81912
|
+
return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
|
|
81913
|
+
}
|
|
81914
|
+
const transferCache = new WeakMap();
|
|
81915
|
+
function transfer(obj, transfers) {
|
|
81916
|
+
transferCache.set(obj, transfers);
|
|
81917
|
+
return obj;
|
|
81918
|
+
}
|
|
81919
|
+
function proxy(obj) {
|
|
81920
|
+
return Object.assign(obj, { [proxyMarker]: true });
|
|
81921
|
+
}
|
|
81922
|
+
function toWireValue(value) {
|
|
81923
|
+
for (const [name, handler] of transferHandlers) {
|
|
81924
|
+
if (handler.canHandle(value)) {
|
|
81925
|
+
const [serializedValue, transferables] = handler.serialize(value);
|
|
81926
|
+
return [
|
|
81927
|
+
{
|
|
81928
|
+
type: "HANDLER" /* WireValueType.HANDLER */,
|
|
81929
|
+
name,
|
|
81930
|
+
value: serializedValue,
|
|
81931
|
+
},
|
|
81932
|
+
transferables,
|
|
81933
|
+
];
|
|
81934
|
+
}
|
|
81935
|
+
}
|
|
81936
|
+
return [
|
|
81937
|
+
{
|
|
81938
|
+
type: "RAW" /* WireValueType.RAW */,
|
|
81939
|
+
value,
|
|
81940
|
+
},
|
|
81941
|
+
transferCache.get(value) || [],
|
|
81942
|
+
];
|
|
81943
|
+
}
|
|
81944
|
+
function fromWireValue(value) {
|
|
81945
|
+
switch (value.type) {
|
|
81946
|
+
case "HANDLER" /* WireValueType.HANDLER */:
|
|
81947
|
+
return transferHandlers.get(value.name).deserialize(value.value);
|
|
81948
|
+
case "RAW" /* WireValueType.RAW */:
|
|
81949
|
+
return value.value;
|
|
81950
|
+
}
|
|
81951
|
+
}
|
|
81952
|
+
function requestResponseMessage(ep, pendingListeners, msg, transfers) {
|
|
81953
|
+
return new Promise((resolve) => {
|
|
81954
|
+
const id = generateUUID();
|
|
81955
|
+
pendingListeners.set(id, resolve);
|
|
81956
|
+
if (ep.start) {
|
|
81957
|
+
ep.start();
|
|
81958
|
+
}
|
|
81959
|
+
ep.postMessage(Object.assign({ id }, msg), transfers);
|
|
81960
|
+
});
|
|
81961
|
+
}
|
|
81962
|
+
function generateUUID() {
|
|
81963
|
+
return new Array(4)
|
|
81964
|
+
.fill(0)
|
|
81965
|
+
.map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16))
|
|
81966
|
+
.join("-");
|
|
81967
|
+
}
|
|
81968
|
+
|
|
81604
81969
|
var global$1;
|
|
81605
81970
|
var hasRequiredGlobal;
|
|
81606
81971
|
|
|
@@ -85073,7 +85438,7 @@
|
|
|
85073
85438
|
};
|
|
85074
85439
|
|
|
85075
85440
|
const DEFAULT_MAX_CACHE_AGE = 1000 * 60 * 60; // 1 hour
|
|
85076
|
-
const logger$
|
|
85441
|
+
const logger$w = new Logger('getLocalRegion');
|
|
85077
85442
|
let cachedLocalRegion = undefined;
|
|
85078
85443
|
let cachedLocalRegionFetchTime = undefined;
|
|
85079
85444
|
const getLocalAirportCode = async () => {
|
|
@@ -85093,7 +85458,7 @@
|
|
|
85093
85458
|
// indicate that the region is random by adding 99, the convention is
|
|
85094
85459
|
// that random region numbers end with 99
|
|
85095
85460
|
const randomRegion = airportCodeToRegion[randomAirportCode][0] + 99;
|
|
85096
|
-
logger$
|
|
85461
|
+
logger$w.warn(`Could not get airport code, using random region: ${randomRegion}`);
|
|
85097
85462
|
return randomRegion;
|
|
85098
85463
|
};
|
|
85099
85464
|
const getLocalRegionWithCache = async (maxCacheAge = DEFAULT_MAX_CACHE_AGE) => {
|
|
@@ -87288,7 +87653,7 @@
|
|
|
87288
87653
|
return v4();
|
|
87289
87654
|
};
|
|
87290
87655
|
|
|
87291
|
-
const logger$
|
|
87656
|
+
const logger$D = new Logger('ManagedConnection');
|
|
87292
87657
|
// ManagedConnection is a component used as a wrapper for IConnection after they have been successfully handshaked.
|
|
87293
87658
|
// Should only be used in the ConnectionManager.
|
|
87294
87659
|
class ManagedConnection extends EventEmitter {
|
|
@@ -87318,7 +87683,7 @@
|
|
|
87318
87683
|
this.remotePeerDescriptor = peerDescriptor;
|
|
87319
87684
|
}
|
|
87320
87685
|
onDisconnected(gracefulLeave) {
|
|
87321
|
-
logger$
|
|
87686
|
+
logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' onDisconnected() ' + gracefulLeave);
|
|
87322
87687
|
if (!this.replacedAsDuplicate) {
|
|
87323
87688
|
this.emit('disconnected', gracefulLeave);
|
|
87324
87689
|
}
|
|
@@ -87327,7 +87692,7 @@
|
|
|
87327
87692
|
// TODO: Can this be removed if ManagedConnections can never be duplicates?
|
|
87328
87693
|
// Handle duplicates in the ConncetorFacade and no longer have PendingConnections in ConnectionManager
|
|
87329
87694
|
replaceAsDuplicate() {
|
|
87330
|
-
logger$
|
|
87695
|
+
logger$D.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
|
|
87331
87696
|
this.replacedAsDuplicate = true;
|
|
87332
87697
|
}
|
|
87333
87698
|
send(data) {
|
|
@@ -87474,10 +87839,10 @@
|
|
|
87474
87839
|
}
|
|
87475
87840
|
}
|
|
87476
87841
|
|
|
87477
|
-
const logger$
|
|
87842
|
+
const logger$C = new Logger('ConnectionLockRpcRemote');
|
|
87478
87843
|
class ConnectionLockRpcRemote extends RpcRemote {
|
|
87479
87844
|
async lockRequest(lockId) {
|
|
87480
|
-
logger$
|
|
87845
|
+
logger$C.trace(`Requesting locked connection to ${toNodeId(this.getPeerDescriptor())}`);
|
|
87481
87846
|
const request = {
|
|
87482
87847
|
lockId
|
|
87483
87848
|
};
|
|
@@ -87487,12 +87852,12 @@
|
|
|
87487
87852
|
return res.accepted;
|
|
87488
87853
|
}
|
|
87489
87854
|
catch (err) {
|
|
87490
|
-
logger$
|
|
87855
|
+
logger$C.debug('Connection lock rejected', { err });
|
|
87491
87856
|
return false;
|
|
87492
87857
|
}
|
|
87493
87858
|
}
|
|
87494
87859
|
unlockRequest(lockId) {
|
|
87495
|
-
logger$
|
|
87860
|
+
logger$C.trace(`Requesting connection to be unlocked from ${toNodeId(this.getPeerDescriptor())}`);
|
|
87496
87861
|
const request = {
|
|
87497
87862
|
lockId
|
|
87498
87863
|
};
|
|
@@ -87500,11 +87865,11 @@
|
|
|
87500
87865
|
notification: true
|
|
87501
87866
|
});
|
|
87502
87867
|
this.getClient().unlockRequest(request, options).catch((_e) => {
|
|
87503
|
-
logger$
|
|
87868
|
+
logger$C.trace('failed to send unlockRequest');
|
|
87504
87869
|
});
|
|
87505
87870
|
}
|
|
87506
87871
|
async gracefulDisconnect(disconnectMode) {
|
|
87507
|
-
logger$
|
|
87872
|
+
logger$C.trace(`Notifying a graceful disconnect to ${toNodeId(this.getPeerDescriptor())}`);
|
|
87508
87873
|
const request = {
|
|
87509
87874
|
disconnectMode
|
|
87510
87875
|
};
|
|
@@ -87516,7 +87881,7 @@
|
|
|
87516
87881
|
await this.getClient().gracefulDisconnect(request, options);
|
|
87517
87882
|
}
|
|
87518
87883
|
async setPrivate(isPrivate) {
|
|
87519
|
-
logger$
|
|
87884
|
+
logger$C.trace(`Setting isPrivate: ${isPrivate} for ${toNodeId(this.getPeerDescriptor())}`);
|
|
87520
87885
|
const request = {
|
|
87521
87886
|
isPrivate
|
|
87522
87887
|
};
|
|
@@ -87528,7 +87893,7 @@
|
|
|
87528
87893
|
}
|
|
87529
87894
|
}
|
|
87530
87895
|
|
|
87531
|
-
const logger$
|
|
87896
|
+
const logger$B = new Logger('ConnectionLockRpcLocal');
|
|
87532
87897
|
class ConnectionLockRpcLocal {
|
|
87533
87898
|
options;
|
|
87534
87899
|
constructor(options) {
|
|
@@ -87557,7 +87922,7 @@
|
|
|
87557
87922
|
}
|
|
87558
87923
|
async gracefulDisconnect(disconnectNotice, context) {
|
|
87559
87924
|
const senderPeerDescriptor = context.incomingSourceDescriptor;
|
|
87560
|
-
logger$
|
|
87925
|
+
logger$B.trace(getNodeIdOrUnknownFromPeerDescriptor(senderPeerDescriptor) + ' received gracefulDisconnect notice');
|
|
87561
87926
|
if (disconnectNotice.disconnectMode === DisconnectMode$2.LEAVING) {
|
|
87562
87927
|
await this.options.closeConnection(senderPeerDescriptor, true, 'graceful leave notified');
|
|
87563
87928
|
}
|
|
@@ -87608,7 +87973,7 @@
|
|
|
87608
87973
|
NatType["OPEN_INTERNET"] = "open_internet";
|
|
87609
87974
|
NatType["UNKNOWN"] = "unknown";
|
|
87610
87975
|
})(NatType || (NatType = {}));
|
|
87611
|
-
const logger$
|
|
87976
|
+
const logger$A = new Logger('ConnectionManager');
|
|
87612
87977
|
var ConnectionManagerState;
|
|
87613
87978
|
(function (ConnectionManagerState) {
|
|
87614
87979
|
ConnectionManagerState["IDLE"] = "idle";
|
|
@@ -87658,7 +88023,7 @@
|
|
|
87658
88023
|
getLocalPeerDescriptor: () => this.getLocalPeerDescriptor(),
|
|
87659
88024
|
setPrivate: (id, isPrivate) => {
|
|
87660
88025
|
if (!this.options.allowIncomingPrivateConnections) {
|
|
87661
|
-
logger$
|
|
88026
|
+
logger$A.debug(`node ${id} attemted to set a connection as private, but it is not allowed`);
|
|
87662
88027
|
return;
|
|
87663
88028
|
}
|
|
87664
88029
|
if (isPrivate) {
|
|
@@ -87692,7 +88057,7 @@
|
|
|
87692
88057
|
const connection = endpoint.connection;
|
|
87693
88058
|
const nodeId = connection.getNodeId();
|
|
87694
88059
|
if (!this.locks.isLocked(nodeId) && !this.locks.isPrivate(nodeId) && Date.now() - connection.getLastUsedTimestamp() > maxIdleTime) {
|
|
87695
|
-
logger$
|
|
88060
|
+
logger$A.trace('disconnecting in timeout interval: ' + getNodeIdOrUnknownFromPeerDescriptor(connection.getPeerDescriptor()));
|
|
87696
88061
|
disconnectionCandidates.addContact(connection);
|
|
87697
88062
|
}
|
|
87698
88063
|
}
|
|
@@ -87700,7 +88065,7 @@
|
|
|
87700
88065
|
const disconnectables = disconnectionCandidates.getFurthestContacts(this.endpoints.size - maxConnections);
|
|
87701
88066
|
for (const disconnectable of disconnectables) {
|
|
87702
88067
|
const peerDescriptor = disconnectable.getPeerDescriptor();
|
|
87703
|
-
logger$
|
|
88068
|
+
logger$A.trace('garbageCollecting ' + toNodeId(peerDescriptor));
|
|
87704
88069
|
this.gracefullyDisconnectAsync(peerDescriptor, DisconnectMode$2.NORMAL).catch((_e) => { });
|
|
87705
88070
|
}
|
|
87706
88071
|
}
|
|
@@ -87709,11 +88074,11 @@
|
|
|
87709
88074
|
throw new CouldNotStart(`Cannot start already ${this.state} module`);
|
|
87710
88075
|
}
|
|
87711
88076
|
this.state = ConnectionManagerState.RUNNING;
|
|
87712
|
-
logger$
|
|
88077
|
+
logger$A.trace(`Starting ConnectionManager...`);
|
|
87713
88078
|
await this.connectorFacade.start((connection) => this.onNewConnection(connection), (nodeId) => this.hasConnection(nodeId), this);
|
|
87714
88079
|
// Garbage collection of connections
|
|
87715
88080
|
this.disconnectorIntervalRef = setInterval(() => {
|
|
87716
|
-
logger$
|
|
88081
|
+
logger$A.trace('disconnectorInterval');
|
|
87717
88082
|
const LAST_USED_LIMIT = 20000;
|
|
87718
88083
|
this.garbageCollectConnections(this.options.maxConnections ?? 80, LAST_USED_LIMIT);
|
|
87719
88084
|
}, 5000); // TODO use options option or named constant?
|
|
@@ -87723,7 +88088,7 @@
|
|
|
87723
88088
|
return;
|
|
87724
88089
|
}
|
|
87725
88090
|
this.state = ConnectionManagerState.STOPPING;
|
|
87726
|
-
logger$
|
|
88091
|
+
logger$A.trace(`Stopping ConnectionManager`);
|
|
87727
88092
|
if (this.disconnectorIntervalRef) {
|
|
87728
88093
|
clearInterval(this.disconnectorIntervalRef);
|
|
87729
88094
|
}
|
|
@@ -87733,23 +88098,23 @@
|
|
|
87733
88098
|
await this.gracefullyDisconnectAsync(endpoint.connection.getPeerDescriptor(), DisconnectMode$2.LEAVING);
|
|
87734
88099
|
}
|
|
87735
88100
|
catch (e) {
|
|
87736
|
-
logger$
|
|
88101
|
+
logger$A.error(e);
|
|
87737
88102
|
}
|
|
87738
88103
|
}
|
|
87739
88104
|
else {
|
|
87740
88105
|
const connection = endpoint.connection;
|
|
87741
|
-
logger$
|
|
88106
|
+
logger$A.trace('handshake of connection not completed, force-closing');
|
|
87742
88107
|
// TODO use options option or named constant?
|
|
87743
88108
|
const eventReceived = waitForEvent(connection, 'disconnected', 2000);
|
|
87744
88109
|
// TODO should we have some handling for this floating promise?
|
|
87745
88110
|
connection.close(true);
|
|
87746
88111
|
try {
|
|
87747
88112
|
await eventReceived;
|
|
87748
|
-
logger$
|
|
88113
|
+
logger$A.trace('resolving after receiving disconnected event from non-handshaked connection');
|
|
87749
88114
|
}
|
|
87750
88115
|
catch (e) {
|
|
87751
88116
|
endpoint.buffer.reject();
|
|
87752
|
-
logger$
|
|
88117
|
+
logger$A.trace('force-closing non-handshaked connection timed out ' + e);
|
|
87753
88118
|
}
|
|
87754
88119
|
}
|
|
87755
88120
|
}));
|
|
@@ -87778,7 +88143,7 @@
|
|
|
87778
88143
|
throw new CannotConnectToSelf('Cannot send to self');
|
|
87779
88144
|
}
|
|
87780
88145
|
const nodeId = toNodeId(peerDescriptor);
|
|
87781
|
-
logger$
|
|
88146
|
+
logger$A.trace(`Sending message to: ${nodeId}`);
|
|
87782
88147
|
message = {
|
|
87783
88148
|
...message,
|
|
87784
88149
|
sourceDescriptor: this.getLocalPeerDescriptor()
|
|
@@ -87833,13 +88198,13 @@
|
|
|
87833
88198
|
}
|
|
87834
88199
|
handleMessage(message) {
|
|
87835
88200
|
const messageType = message.body.oneofKind;
|
|
87836
|
-
logger$
|
|
88201
|
+
logger$A.trace('Received message of type ' + messageType);
|
|
87837
88202
|
if (messageType !== 'rpcMessage') {
|
|
87838
|
-
logger$
|
|
88203
|
+
logger$A.trace('Filtered out non-RPC message of type ' + messageType);
|
|
87839
88204
|
return;
|
|
87840
88205
|
}
|
|
87841
88206
|
if (this.duplicateMessageDetector.isMostLikelyDuplicate(message.messageId)) {
|
|
87842
|
-
logger$
|
|
88207
|
+
logger$A.trace('handleMessage filtered duplicate ' + toNodeId(message.sourceDescriptor)
|
|
87843
88208
|
+ ' ' + message.serviceId + ' ' + message.messageId);
|
|
87844
88209
|
return;
|
|
87845
88210
|
}
|
|
@@ -87848,7 +88213,7 @@
|
|
|
87848
88213
|
this.rpcCommunicator?.handleMessageFromPeer(message);
|
|
87849
88214
|
}
|
|
87850
88215
|
else {
|
|
87851
|
-
logger$
|
|
88216
|
+
logger$A.trace('emit "message" ' + toNodeId(message.sourceDescriptor)
|
|
87852
88217
|
+ ' ' + message.serviceId + ' ' + message.messageId);
|
|
87853
88218
|
this.emit('message', message);
|
|
87854
88219
|
}
|
|
@@ -87865,7 +88230,7 @@
|
|
|
87865
88230
|
}
|
|
87866
88231
|
catch (e) {
|
|
87867
88232
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
87868
|
-
logger$
|
|
88233
|
+
logger$A.debug(`Parsing incoming data into Message failed: ${e}`);
|
|
87869
88234
|
return;
|
|
87870
88235
|
}
|
|
87871
88236
|
message.sourceDescriptor = peerDescriptor;
|
|
@@ -87874,7 +88239,7 @@
|
|
|
87874
88239
|
}
|
|
87875
88240
|
catch (e) {
|
|
87876
88241
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
87877
|
-
logger$
|
|
88242
|
+
logger$A.debug(`Handling incoming data failed: ${e}`);
|
|
87878
88243
|
}
|
|
87879
88244
|
}
|
|
87880
88245
|
onConnected(peerDescriptor, connection) {
|
|
@@ -87887,7 +88252,7 @@
|
|
|
87887
88252
|
const pendingConnection = endpoint.connection;
|
|
87888
88253
|
const buffer = outputBuffer.getBuffer();
|
|
87889
88254
|
while (buffer.length > 0) {
|
|
87890
|
-
logger$
|
|
88255
|
+
logger$A.trace('emptying buffer');
|
|
87891
88256
|
managedConnection.send(buffer.shift());
|
|
87892
88257
|
}
|
|
87893
88258
|
outputBuffer.resolve();
|
|
@@ -87904,7 +88269,7 @@
|
|
|
87904
88269
|
}
|
|
87905
88270
|
onDisconnected(peerDescriptor, gracefulLeave) {
|
|
87906
88271
|
const nodeId = toNodeId(peerDescriptor);
|
|
87907
|
-
logger$
|
|
88272
|
+
logger$A.trace(nodeId + ' onDisconnected() gracefulLeave: ' + gracefulLeave);
|
|
87908
88273
|
const endpoint = this.endpoints.get(nodeId);
|
|
87909
88274
|
if (endpoint) {
|
|
87910
88275
|
this.locks.clearAllLocks(nodeId);
|
|
@@ -87912,7 +88277,7 @@
|
|
|
87912
88277
|
endpoint.buffer.reject();
|
|
87913
88278
|
}
|
|
87914
88279
|
this.endpoints.delete(nodeId);
|
|
87915
|
-
logger$
|
|
88280
|
+
logger$A.trace(nodeId + ' deleted connection in onDisconnected() gracefulLeave: ' + gracefulLeave);
|
|
87916
88281
|
this.emit('disconnected', peerDescriptor, gracefulLeave);
|
|
87917
88282
|
this.onConnectionCountChange();
|
|
87918
88283
|
}
|
|
@@ -87921,7 +88286,7 @@
|
|
|
87921
88286
|
if (this.state === ConnectionManagerState.STOPPED) {
|
|
87922
88287
|
return false;
|
|
87923
88288
|
}
|
|
87924
|
-
logger$
|
|
88289
|
+
logger$A.trace('onNewConnection()');
|
|
87925
88290
|
if (!this.acceptNewConnection(connection)) {
|
|
87926
88291
|
return false;
|
|
87927
88292
|
}
|
|
@@ -87931,7 +88296,7 @@
|
|
|
87931
88296
|
}
|
|
87932
88297
|
acceptNewConnection(newConnection) {
|
|
87933
88298
|
const nodeId = toNodeId(newConnection.getPeerDescriptor());
|
|
87934
|
-
logger$
|
|
88299
|
+
logger$A.trace(nodeId + ' acceptNewConnection()');
|
|
87935
88300
|
if (this.endpoints.has(nodeId)) {
|
|
87936
88301
|
if (getOfferer(toNodeId(this.getLocalPeerDescriptor()), nodeId) === 'remote') {
|
|
87937
88302
|
let buffer;
|
|
@@ -87940,14 +88305,14 @@
|
|
|
87940
88305
|
// Could be related to WS client connections not realizing that they have been disconnected.
|
|
87941
88306
|
// Makes refactoring duplicate connection handling to the connectors very difficult.
|
|
87942
88307
|
if (this.endpoints.get(nodeId).connected) {
|
|
87943
|
-
logger$
|
|
88308
|
+
logger$A.debug('replacing connected connection', { nodeId });
|
|
87944
88309
|
buffer = new OutputBuffer();
|
|
87945
88310
|
}
|
|
87946
88311
|
else {
|
|
87947
88312
|
buffer = endpoint.buffer;
|
|
87948
88313
|
}
|
|
87949
88314
|
const oldConnection = endpoint.connection;
|
|
87950
|
-
logger$
|
|
88315
|
+
logger$A.trace('replaced: ' + nodeId);
|
|
87951
88316
|
oldConnection.replaceAsDuplicate();
|
|
87952
88317
|
this.endpoints.set(nodeId, { connected: false, connection: newConnection, buffer: buffer });
|
|
87953
88318
|
return true;
|
|
@@ -87956,7 +88321,7 @@
|
|
|
87956
88321
|
return false;
|
|
87957
88322
|
}
|
|
87958
88323
|
}
|
|
87959
|
-
logger$
|
|
88324
|
+
logger$A.trace(nodeId + ' added to connections at acceptNewConnection');
|
|
87960
88325
|
this.endpoints.set(nodeId, {
|
|
87961
88326
|
connected: false,
|
|
87962
88327
|
buffer: new OutputBuffer(),
|
|
@@ -87966,14 +88331,14 @@
|
|
|
87966
88331
|
}
|
|
87967
88332
|
async closeConnection(peerDescriptor, gracefulLeave, reason) {
|
|
87968
88333
|
const nodeId = toNodeId(peerDescriptor);
|
|
87969
|
-
logger$
|
|
88334
|
+
logger$A.trace(nodeId + ' ' + 'closeConnection() ' + reason);
|
|
87970
88335
|
this.locks.clearAllLocks(nodeId);
|
|
87971
88336
|
if (this.endpoints.has(nodeId)) {
|
|
87972
88337
|
const connectionToClose = this.endpoints.get(nodeId).connection;
|
|
87973
88338
|
await connectionToClose.close(gracefulLeave);
|
|
87974
88339
|
}
|
|
87975
88340
|
else {
|
|
87976
|
-
logger$
|
|
88341
|
+
logger$A.trace(nodeId + ' ' + 'closeConnection() this.endpoints did not have the id');
|
|
87977
88342
|
this.emit('disconnected', peerDescriptor, false);
|
|
87978
88343
|
}
|
|
87979
88344
|
}
|
|
@@ -87985,8 +88350,8 @@
|
|
|
87985
88350
|
const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
|
|
87986
88351
|
this.locks.addLocalLocked(nodeId, lockId);
|
|
87987
88352
|
rpcRemote.lockRequest(lockId)
|
|
87988
|
-
.then((_accepted) => logger$
|
|
87989
|
-
.catch((err) => { logger$
|
|
88353
|
+
.then((_accepted) => logger$A.trace('LockRequest successful'))
|
|
88354
|
+
.catch((err) => { logger$A.debug(err); });
|
|
87990
88355
|
}
|
|
87991
88356
|
unlockConnection(targetDescriptor, lockId) {
|
|
87992
88357
|
if (this.state === ConnectionManagerState.STOPPED || areEqualPeerDescriptors(targetDescriptor, this.getLocalPeerDescriptor())) {
|
|
@@ -88038,7 +88403,7 @@
|
|
|
88038
88403
|
async gracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
|
|
88039
88404
|
const endpoint = this.endpoints.get(toNodeId(targetDescriptor));
|
|
88040
88405
|
if (!endpoint) {
|
|
88041
|
-
logger$
|
|
88406
|
+
logger$A.debug('gracefullyDisconnectedAsync() tried on a non-existing connection');
|
|
88042
88407
|
return;
|
|
88043
88408
|
}
|
|
88044
88409
|
if (endpoint.connected) {
|
|
@@ -88047,15 +88412,15 @@
|
|
|
88047
88412
|
// TODO use options option or named constant?
|
|
88048
88413
|
// eslint-disable-next-line promise/catch-or-return
|
|
88049
88414
|
waitForEvent(connection, 'disconnected', 2000).then(() => {
|
|
88050
|
-
logger$
|
|
88415
|
+
logger$A.trace('disconnected event received in gracefullyDisconnectAsync()');
|
|
88051
88416
|
})
|
|
88052
88417
|
.catch((e) => {
|
|
88053
|
-
logger$
|
|
88418
|
+
logger$A.trace('force-closing connection after timeout ' + e);
|
|
88054
88419
|
// TODO should we have some handling for this floating promise?
|
|
88055
88420
|
connection.close(true);
|
|
88056
88421
|
})
|
|
88057
88422
|
.finally(() => {
|
|
88058
|
-
logger$
|
|
88423
|
+
logger$A.trace('resolving after receiving disconnected event');
|
|
88059
88424
|
resolve();
|
|
88060
88425
|
});
|
|
88061
88426
|
});
|
|
@@ -88070,13 +88435,13 @@
|
|
|
88070
88435
|
}
|
|
88071
88436
|
async doGracefullyDisconnectAsync(targetDescriptor, disconnectMode) {
|
|
88072
88437
|
const nodeId = toNodeId(targetDescriptor);
|
|
88073
|
-
logger$
|
|
88438
|
+
logger$A.trace(nodeId + ' gracefullyDisconnectAsync()');
|
|
88074
88439
|
const rpcRemote = new ConnectionLockRpcRemote(this.getLocalPeerDescriptor(), targetDescriptor, this.rpcCommunicator, ConnectionLockRpcClient);
|
|
88075
88440
|
try {
|
|
88076
88441
|
await rpcRemote.gracefulDisconnect(disconnectMode);
|
|
88077
88442
|
}
|
|
88078
88443
|
catch (ex) {
|
|
88079
|
-
logger$
|
|
88444
|
+
logger$A.trace(nodeId + ' remote.gracefulDisconnect() failed' + ex);
|
|
88080
88445
|
}
|
|
88081
88446
|
}
|
|
88082
88447
|
getConnections() {
|
|
@@ -88136,9 +88501,9 @@
|
|
|
88136
88501
|
}
|
|
88137
88502
|
};
|
|
88138
88503
|
|
|
88139
|
-
var version$2 = "103.
|
|
88504
|
+
var version$2 = "103.6.0-rc.0";
|
|
88140
88505
|
|
|
88141
|
-
const logger$
|
|
88506
|
+
const logger$y = new Logger('Handshaker');
|
|
88142
88507
|
// Optimally the Outgoing and Incoming Handshakers could be their own separate classes
|
|
88143
88508
|
// However, in cases where the PeerDescriptor of the other end of the connection can be known
|
|
88144
88509
|
// only after a HandshakeRequest a base Handshaker class is needed as the IncomingHandshaker currently
|
|
@@ -88160,7 +88525,7 @@
|
|
|
88160
88525
|
}
|
|
88161
88526
|
};
|
|
88162
88527
|
const handshakeCompletedListener = (peerDescriptor) => {
|
|
88163
|
-
logger$
|
|
88528
|
+
logger$y.trace('handshake completed for outgoing connection, ' + toNodeId(peerDescriptor));
|
|
88164
88529
|
pendingConnection.onHandshakeCompleted(connection);
|
|
88165
88530
|
stopHandshaker();
|
|
88166
88531
|
};
|
|
@@ -88260,12 +88625,12 @@
|
|
|
88260
88625
|
try {
|
|
88261
88626
|
const message = Message$2.fromBinary(data);
|
|
88262
88627
|
if (message.body.oneofKind === 'handshakeRequest') {
|
|
88263
|
-
logger$
|
|
88628
|
+
logger$y.trace('handshake request received');
|
|
88264
88629
|
const handshake = message.body.handshakeRequest;
|
|
88265
88630
|
this.emit('handshakeRequest', handshake.sourcePeerDescriptor, handshake.protocolVersion, handshake.targetPeerDescriptor);
|
|
88266
88631
|
}
|
|
88267
88632
|
if (message.body.oneofKind === 'handshakeResponse') {
|
|
88268
|
-
logger$
|
|
88633
|
+
logger$y.trace('handshake response received');
|
|
88269
88634
|
const handshake = message.body.handshakeResponse;
|
|
88270
88635
|
const error = !isMaybeSupportedProtocolVersion(handshake.protocolVersion)
|
|
88271
88636
|
? HandshakeError$2.UNSUPPORTED_PROTOCOL_VERSION : handshake.error;
|
|
@@ -88278,18 +88643,18 @@
|
|
|
88278
88643
|
}
|
|
88279
88644
|
}
|
|
88280
88645
|
catch (err) {
|
|
88281
|
-
logger$
|
|
88646
|
+
logger$y.debug('error while parsing handshake message', err);
|
|
88282
88647
|
}
|
|
88283
88648
|
}
|
|
88284
88649
|
sendHandshakeRequest(remotePeerDescriptor) {
|
|
88285
88650
|
const msg = createHandshakeRequest(this.localPeerDescriptor, remotePeerDescriptor);
|
|
88286
88651
|
this.connection.send(Message$2.toBinary(msg));
|
|
88287
|
-
logger$
|
|
88652
|
+
logger$y.trace('handshake request sent');
|
|
88288
88653
|
}
|
|
88289
88654
|
sendHandshakeResponse(error) {
|
|
88290
88655
|
const msg = createHandshakeResponse(this.localPeerDescriptor, error);
|
|
88291
88656
|
this.connection.send(Message$2.toBinary(msg));
|
|
88292
|
-
logger$
|
|
88657
|
+
logger$y.trace('handshake response sent');
|
|
88293
88658
|
}
|
|
88294
88659
|
stop() {
|
|
88295
88660
|
this.connection.off('data', this.onDataListener);
|
|
@@ -88297,7 +88662,7 @@
|
|
|
88297
88662
|
}
|
|
88298
88663
|
};
|
|
88299
88664
|
|
|
88300
|
-
const logger$
|
|
88665
|
+
const logger$x = new Logger('PendingConnection');
|
|
88301
88666
|
// PendingConnection is used as a reference to a connection that should be opened and handshaked to a given PeerDescriptor
|
|
88302
88667
|
// It does not hold a connection internally. The public method onHandshakedCompleted should be called once a connection for the
|
|
88303
88668
|
// remotePeerDescriptor is opened and handshaked successfully.
|
|
@@ -88315,7 +88680,7 @@
|
|
|
88315
88680
|
}, timeout, this.connectingAbortController.signal);
|
|
88316
88681
|
}
|
|
88317
88682
|
replaceAsDuplicate() {
|
|
88318
|
-
logger$
|
|
88683
|
+
logger$x.trace(getNodeIdOrUnknownFromPeerDescriptor(this.remotePeerDescriptor) + ' replaceAsDuplicate');
|
|
88319
88684
|
this.replacedAsDuplicate = true;
|
|
88320
88685
|
}
|
|
88321
88686
|
onHandshakeCompleted(connection) {
|
|
@@ -88372,6 +88737,8 @@
|
|
|
88372
88737
|
}
|
|
88373
88738
|
}
|
|
88374
88739
|
|
|
88740
|
+
const isWorkerEnvironment = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
|
|
88741
|
+
|
|
88375
88742
|
var RtcDescription;
|
|
88376
88743
|
(function (RtcDescription) {
|
|
88377
88744
|
RtcDescription["OFFER"] = "offer";
|
|
@@ -88386,8 +88753,8 @@
|
|
|
88386
88753
|
DisconnectedRtcPeerConnectionStateEnum["FAILED"] = "failed";
|
|
88387
88754
|
DisconnectedRtcPeerConnectionStateEnum["CLOSED"] = "closed";
|
|
88388
88755
|
})(DisconnectedRtcPeerConnectionStateEnum || (DisconnectedRtcPeerConnectionStateEnum = {}));
|
|
88389
|
-
const logger$
|
|
88390
|
-
class
|
|
88756
|
+
const logger$v = new Logger('DirectWebrtcConnection (browser)');
|
|
88757
|
+
class DirectWebrtcConnection extends EventEmitter {
|
|
88391
88758
|
connectionId;
|
|
88392
88759
|
connectionType = ConnectionType.WEBRTC;
|
|
88393
88760
|
// We need to keep track of connection state ourselves because
|
|
@@ -88425,7 +88792,7 @@
|
|
|
88425
88792
|
}
|
|
88426
88793
|
};
|
|
88427
88794
|
this.peerConnection.onicegatheringstatechange = () => {
|
|
88428
|
-
logger$
|
|
88795
|
+
logger$v.trace(`conn.onGatheringStateChange: ${this.peerConnection?.iceGatheringState}`);
|
|
88429
88796
|
};
|
|
88430
88797
|
this.peerConnection.onconnectionstatechange = () => this.onStateChange();
|
|
88431
88798
|
if (isOffering) {
|
|
@@ -88436,7 +88803,7 @@
|
|
|
88436
88803
|
await this.peerConnection.setLocalDescription();
|
|
88437
88804
|
}
|
|
88438
88805
|
catch (err) {
|
|
88439
|
-
logger$
|
|
88806
|
+
logger$v.warn('Failed to set local description', { err });
|
|
88440
88807
|
}
|
|
88441
88808
|
if (this.peerConnection.localDescription !== null) {
|
|
88442
88809
|
this.emit('localDescription', this.peerConnection.localDescription?.sdp, this.peerConnection.localDescription?.type);
|
|
@@ -88464,14 +88831,14 @@
|
|
|
88464
88831
|
clearTimeout(this.earlyTimeout);
|
|
88465
88832
|
}
|
|
88466
88833
|
catch (err) {
|
|
88467
|
-
logger$
|
|
88834
|
+
logger$v.warn('Failed to set remote description', { err });
|
|
88468
88835
|
}
|
|
88469
88836
|
if ((type.toLowerCase() === RtcDescription.OFFER) && (this.peerConnection !== undefined)) {
|
|
88470
88837
|
try {
|
|
88471
88838
|
await this.peerConnection.setLocalDescription();
|
|
88472
88839
|
}
|
|
88473
88840
|
catch (err) {
|
|
88474
|
-
logger$
|
|
88841
|
+
logger$v.warn('Failed to set local description', { err });
|
|
88475
88842
|
}
|
|
88476
88843
|
if (this.peerConnection.localDescription !== null) {
|
|
88477
88844
|
this.emit('localDescription', this.peerConnection.localDescription.sdp, this.peerConnection.localDescription.type);
|
|
@@ -88481,7 +88848,7 @@
|
|
|
88481
88848
|
addRemoteCandidate(candidate, mid) {
|
|
88482
88849
|
this.peerConnection?.addIceCandidate({ candidate: candidate, sdpMid: mid })
|
|
88483
88850
|
.catch((err) => {
|
|
88484
|
-
logger$
|
|
88851
|
+
logger$v.warn('Failed to add ICE candidate', { err });
|
|
88485
88852
|
});
|
|
88486
88853
|
}
|
|
88487
88854
|
isOpen() {
|
|
@@ -88504,7 +88871,7 @@
|
|
|
88504
88871
|
this.dataChannel.close();
|
|
88505
88872
|
}
|
|
88506
88873
|
catch (err) {
|
|
88507
|
-
logger$
|
|
88874
|
+
logger$v.warn('Failed to close data channel', { err });
|
|
88508
88875
|
}
|
|
88509
88876
|
}
|
|
88510
88877
|
this.dataChannel = undefined;
|
|
@@ -88513,7 +88880,7 @@
|
|
|
88513
88880
|
this.peerConnection.close();
|
|
88514
88881
|
}
|
|
88515
88882
|
catch (err) {
|
|
88516
|
-
logger$
|
|
88883
|
+
logger$v.warn('Failed to close connection', { err });
|
|
88517
88884
|
}
|
|
88518
88885
|
}
|
|
88519
88886
|
this.peerConnection = undefined;
|
|
@@ -88533,7 +88900,7 @@
|
|
|
88533
88900
|
}
|
|
88534
88901
|
}
|
|
88535
88902
|
else {
|
|
88536
|
-
logger$
|
|
88903
|
+
logger$v.warn('Tried to send on a connection with last state ' + this.lastState);
|
|
88537
88904
|
}
|
|
88538
88905
|
}
|
|
88539
88906
|
setupDataChannel(dataChannel) {
|
|
@@ -88541,22 +88908,22 @@
|
|
|
88541
88908
|
this.dataChannel.binaryType = 'arraybuffer';
|
|
88542
88909
|
this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
|
|
88543
88910
|
dataChannel.onopen = () => {
|
|
88544
|
-
logger$
|
|
88911
|
+
logger$v.trace('dc.onOpen');
|
|
88545
88912
|
this.onDataChannelOpen();
|
|
88546
88913
|
};
|
|
88547
88914
|
dataChannel.onclose = () => {
|
|
88548
|
-
logger$
|
|
88915
|
+
logger$v.trace('dc.onClosed');
|
|
88549
88916
|
this.doClose(false);
|
|
88550
88917
|
};
|
|
88551
88918
|
dataChannel.onerror = (err) => {
|
|
88552
|
-
logger$
|
|
88919
|
+
logger$v.warn('Data channel error', { err });
|
|
88553
88920
|
};
|
|
88554
88921
|
dataChannel.onmessage = (msg) => {
|
|
88555
|
-
logger$
|
|
88922
|
+
logger$v.trace('dc.onmessage');
|
|
88556
88923
|
this.emit('data', new Uint8Array(msg.data));
|
|
88557
88924
|
};
|
|
88558
88925
|
dataChannel.onbufferedamountlow = () => {
|
|
88559
|
-
logger$
|
|
88926
|
+
logger$v.trace('dc.onBufferedAmountLow');
|
|
88560
88927
|
while (this.messageQueue.length > 0 && this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
|
|
88561
88928
|
const data = this.messageQueue.shift();
|
|
88562
88929
|
this.dataChannel.send(data);
|
|
@@ -88595,6 +88962,289 @@
|
|
|
88595
88962
|
}
|
|
88596
88963
|
}
|
|
88597
88964
|
|
|
88965
|
+
/**
|
|
88966
|
+
* Call this function on the **main thread** before the worker starts using
|
|
88967
|
+
* WebRTC connections. It creates a dedicated `MessageChannel`, exposes a
|
|
88968
|
+
* {@link WebrtcBridge} instance on one port, and sends the other port to
|
|
88969
|
+
* the worker so that `WorkerWebrtcConnection` can reach the bridge.
|
|
88970
|
+
*
|
|
88971
|
+
* @example
|
|
88972
|
+
* ```ts
|
|
88973
|
+
* import { installWebrtcBridge } from '@streamr/dht'
|
|
88974
|
+
*
|
|
88975
|
+
* const worker = new Worker('./my-worker.ts', { type: 'module' })
|
|
88976
|
+
* installWebrtcBridge(worker)
|
|
88977
|
+
* ```
|
|
88978
|
+
*/
|
|
88979
|
+
const WEBRTC_BRIDGE_PORT_MESSAGE_TYPE = 'streamr-webrtc-bridge-port';
|
|
88980
|
+
|
|
88981
|
+
/**
|
|
88982
|
+
* WorkerWebrtcConnection — runs inside a **Web Worker**.
|
|
88983
|
+
*
|
|
88984
|
+
* Implements the same IWebrtcConnection + IConnection interfaces as
|
|
88985
|
+
* DirectWebrtcConnection, but delegates RTCPeerConnection management to
|
|
88986
|
+
* the main-thread {@link WebrtcBridge} via Comlink.
|
|
88987
|
+
*
|
|
88988
|
+
* The RTCDataChannel is **transferred** from the main thread and lives
|
|
88989
|
+
* entirely in the worker — all data events (onmessage, onopen, onclose,
|
|
88990
|
+
* onbufferedamountlow) fire in the worker's event loop. The main thread
|
|
88991
|
+
* is never involved in the data path.
|
|
88992
|
+
*/
|
|
88993
|
+
// ── Module-level bridge client (initialized once per worker) ────────
|
|
88994
|
+
let resolveBridgeProxy;
|
|
88995
|
+
const bridgeProxyPromise = new Promise((resolve) => {
|
|
88996
|
+
resolveBridgeProxy = resolve;
|
|
88997
|
+
});
|
|
88998
|
+
// Listen for the bridge port message from the main thread.
|
|
88999
|
+
// This is guarded so it only runs inside a worker context.
|
|
89000
|
+
if (isWorkerEnvironment) {
|
|
89001
|
+
const handler = (e) => {
|
|
89002
|
+
if (e.data?.type === WEBRTC_BRIDGE_PORT_MESSAGE_TYPE && e.data.port) {
|
|
89003
|
+
const proxy = wrap$1(e.data.port);
|
|
89004
|
+
resolveBridgeProxy(proxy);
|
|
89005
|
+
self.removeEventListener('message', handler);
|
|
89006
|
+
}
|
|
89007
|
+
};
|
|
89008
|
+
self.addEventListener('message', handler);
|
|
89009
|
+
}
|
|
89010
|
+
function getBridgeProxy() {
|
|
89011
|
+
return bridgeProxyPromise;
|
|
89012
|
+
}
|
|
89013
|
+
// ── Disconnection states ────────────────────────────────────────────
|
|
89014
|
+
var DisconnectedState;
|
|
89015
|
+
(function (DisconnectedState) {
|
|
89016
|
+
DisconnectedState["DISCONNECTED"] = "disconnected";
|
|
89017
|
+
DisconnectedState["FAILED"] = "failed";
|
|
89018
|
+
DisconnectedState["CLOSED"] = "closed";
|
|
89019
|
+
})(DisconnectedState || (DisconnectedState = {}));
|
|
89020
|
+
const logger$u = new Logger('WorkerWebrtcConnection');
|
|
89021
|
+
// ── WorkerWebrtcConnection ──────────────────────────────────────────
|
|
89022
|
+
class WorkerWebrtcConnection extends EventEmitter {
|
|
89023
|
+
connectionId;
|
|
89024
|
+
connectionType = ConnectionType.WEBRTC;
|
|
89025
|
+
iceServers;
|
|
89026
|
+
bufferThresholdHigh;
|
|
89027
|
+
bufferThresholdLow;
|
|
89028
|
+
dataChannel;
|
|
89029
|
+
bridge;
|
|
89030
|
+
closed = false;
|
|
89031
|
+
connected = false;
|
|
89032
|
+
earlyTimeout;
|
|
89033
|
+
messageQueue = [];
|
|
89034
|
+
startPromise;
|
|
89035
|
+
constructor(params) {
|
|
89036
|
+
super();
|
|
89037
|
+
this.connectionId = createRandomConnectionId();
|
|
89038
|
+
this.iceServers = params.iceServers ?? [];
|
|
89039
|
+
this.bufferThresholdHigh = params.bufferThresholdHigh ?? 2 ** 17;
|
|
89040
|
+
this.bufferThresholdLow = params.bufferThresholdLow ?? 2 ** 15;
|
|
89041
|
+
this.earlyTimeout = setTimeout(() => {
|
|
89042
|
+
this.doClose(false, 'timed out due to remote descriptor not being set');
|
|
89043
|
+
}, EARLY_TIMEOUT);
|
|
89044
|
+
}
|
|
89045
|
+
// ── IWebrtcConnection ───────────────────────────────────────
|
|
89046
|
+
start(isOffering) {
|
|
89047
|
+
this.startPromise = this.doStart(isOffering);
|
|
89048
|
+
this.startPromise.catch((err) => {
|
|
89049
|
+
logger$u.warn('Failed to start worker WebRTC connection', { err });
|
|
89050
|
+
this.doClose(false, 'Failed to start');
|
|
89051
|
+
});
|
|
89052
|
+
}
|
|
89053
|
+
async doStart(isOffering) {
|
|
89054
|
+
this.bridge = await getBridgeProxy();
|
|
89055
|
+
const iceServers = this.iceServers.map(({ url, port, username, password }) => ({
|
|
89056
|
+
urls: `${url}:${port}`,
|
|
89057
|
+
username,
|
|
89058
|
+
credential: password,
|
|
89059
|
+
}));
|
|
89060
|
+
await this.bridge.start(this.connectionId, iceServers, isOffering, proxy({
|
|
89061
|
+
onLocalCandidate: (candidate, mid) => {
|
|
89062
|
+
if (!this.closed) {
|
|
89063
|
+
this.emit('localCandidate', candidate, mid);
|
|
89064
|
+
}
|
|
89065
|
+
},
|
|
89066
|
+
onLocalDescription: (description, type) => {
|
|
89067
|
+
if (!this.closed) {
|
|
89068
|
+
this.emit('localDescription', description, type);
|
|
89069
|
+
}
|
|
89070
|
+
},
|
|
89071
|
+
onConnectionStateChange: (state) => {
|
|
89072
|
+
if (state === DisconnectedState.CLOSED ||
|
|
89073
|
+
state === DisconnectedState.DISCONNECTED ||
|
|
89074
|
+
state === DisconnectedState.FAILED) {
|
|
89075
|
+
this.doClose(false);
|
|
89076
|
+
}
|
|
89077
|
+
},
|
|
89078
|
+
onDataChannel: (channel) => {
|
|
89079
|
+
if (!this.closed) {
|
|
89080
|
+
this.setupDataChannel(channel);
|
|
89081
|
+
// If the channel was already open at transfer time
|
|
89082
|
+
if (channel.readyState === 'open') {
|
|
89083
|
+
this.onDataChannelOpen();
|
|
89084
|
+
}
|
|
89085
|
+
}
|
|
89086
|
+
},
|
|
89087
|
+
}));
|
|
89088
|
+
}
|
|
89089
|
+
async setRemoteDescription(description, type) {
|
|
89090
|
+
if (this.startPromise) {
|
|
89091
|
+
await this.startPromise;
|
|
89092
|
+
}
|
|
89093
|
+
if (!this.bridge || this.closed) {
|
|
89094
|
+
return;
|
|
89095
|
+
}
|
|
89096
|
+
const wasSet = await this.bridge.setRemoteDescription(this.connectionId, description, type);
|
|
89097
|
+
if (wasSet) {
|
|
89098
|
+
clearTimeout(this.earlyTimeout);
|
|
89099
|
+
}
|
|
89100
|
+
}
|
|
89101
|
+
addRemoteCandidate(candidate, mid) {
|
|
89102
|
+
this.doAddRemoteCandidate(candidate, mid).catch((err) => {
|
|
89103
|
+
logger$u.warn('Failed to add remote candidate via bridge', { err });
|
|
89104
|
+
});
|
|
89105
|
+
}
|
|
89106
|
+
async doAddRemoteCandidate(candidate, mid) {
|
|
89107
|
+
if (this.startPromise) {
|
|
89108
|
+
await this.startPromise;
|
|
89109
|
+
}
|
|
89110
|
+
if (!this.bridge || this.closed) {
|
|
89111
|
+
return;
|
|
89112
|
+
}
|
|
89113
|
+
await this.bridge.addRemoteCandidate(this.connectionId, candidate, mid);
|
|
89114
|
+
}
|
|
89115
|
+
isOpen() {
|
|
89116
|
+
return this.connected;
|
|
89117
|
+
}
|
|
89118
|
+
// ── IConnection ─────────────────────────────────────────────
|
|
89119
|
+
async close(gracefulLeave, reason) {
|
|
89120
|
+
this.doClose(gracefulLeave, reason);
|
|
89121
|
+
}
|
|
89122
|
+
destroy() {
|
|
89123
|
+
this.removeAllListeners();
|
|
89124
|
+
this.doClose(false);
|
|
89125
|
+
}
|
|
89126
|
+
send(data) {
|
|
89127
|
+
if (this.connected && this.dataChannel) {
|
|
89128
|
+
if (this.dataChannel.bufferedAmount > this.bufferThresholdHigh) {
|
|
89129
|
+
this.messageQueue.push(data);
|
|
89130
|
+
}
|
|
89131
|
+
else {
|
|
89132
|
+
this.dataChannel.send(data);
|
|
89133
|
+
}
|
|
89134
|
+
}
|
|
89135
|
+
else if (!this.closed) {
|
|
89136
|
+
this.messageQueue.push(data);
|
|
89137
|
+
}
|
|
89138
|
+
}
|
|
89139
|
+
setConnectionId(connectionId) {
|
|
89140
|
+
const oldId = this.connectionId;
|
|
89141
|
+
this.connectionId = connectionId;
|
|
89142
|
+
if (this.bridge && oldId !== connectionId) {
|
|
89143
|
+
this.bridge.renameConnection(oldId, connectionId).catch(() => { });
|
|
89144
|
+
}
|
|
89145
|
+
}
|
|
89146
|
+
// ── DataChannel handling (runs entirely in the worker) ──────
|
|
89147
|
+
setupDataChannel(dataChannel) {
|
|
89148
|
+
this.dataChannel = dataChannel;
|
|
89149
|
+
this.dataChannel.binaryType = 'arraybuffer';
|
|
89150
|
+
this.dataChannel.bufferedAmountLowThreshold = this.bufferThresholdLow;
|
|
89151
|
+
dataChannel.onopen = () => {
|
|
89152
|
+
logger$u.trace('dc.onOpen (worker)');
|
|
89153
|
+
this.onDataChannelOpen();
|
|
89154
|
+
};
|
|
89155
|
+
dataChannel.onclose = () => {
|
|
89156
|
+
logger$u.trace('dc.onClosed (worker)');
|
|
89157
|
+
this.doClose(false);
|
|
89158
|
+
};
|
|
89159
|
+
dataChannel.onerror = (err) => {
|
|
89160
|
+
logger$u.warn('Data channel error (worker)', { err });
|
|
89161
|
+
};
|
|
89162
|
+
dataChannel.onmessage = (msg) => {
|
|
89163
|
+
logger$u.trace('dc.onmessage (worker)');
|
|
89164
|
+
this.emit('data', new Uint8Array(msg.data));
|
|
89165
|
+
};
|
|
89166
|
+
dataChannel.onbufferedamountlow = () => {
|
|
89167
|
+
logger$u.trace('dc.onBufferedAmountLow (worker)');
|
|
89168
|
+
while (this.messageQueue.length > 0 &&
|
|
89169
|
+
this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
|
|
89170
|
+
const data = this.messageQueue.shift();
|
|
89171
|
+
this.dataChannel.send(data);
|
|
89172
|
+
}
|
|
89173
|
+
};
|
|
89174
|
+
}
|
|
89175
|
+
onDataChannelOpen() {
|
|
89176
|
+
this.connected = true;
|
|
89177
|
+
this.flushMessageQueue();
|
|
89178
|
+
this.emit('connected');
|
|
89179
|
+
}
|
|
89180
|
+
flushMessageQueue() {
|
|
89181
|
+
while (this.messageQueue.length > 0 &&
|
|
89182
|
+
this.dataChannel &&
|
|
89183
|
+
this.dataChannel.bufferedAmount < this.bufferThresholdHigh) {
|
|
89184
|
+
const data = this.messageQueue.shift();
|
|
89185
|
+
this.dataChannel.send(data);
|
|
89186
|
+
}
|
|
89187
|
+
}
|
|
89188
|
+
// ── Teardown ────────────────────────────────────────────────
|
|
89189
|
+
doClose(gracefulLeave, reason) {
|
|
89190
|
+
if (!this.closed) {
|
|
89191
|
+
this.closed = true;
|
|
89192
|
+
this.connected = false;
|
|
89193
|
+
this.messageQueue.length = 0;
|
|
89194
|
+
clearTimeout(this.earlyTimeout);
|
|
89195
|
+
this.stopListening();
|
|
89196
|
+
this.emit('disconnected', gracefulLeave, undefined, reason);
|
|
89197
|
+
this.removeAllListeners();
|
|
89198
|
+
if (this.dataChannel !== undefined) {
|
|
89199
|
+
try {
|
|
89200
|
+
this.dataChannel.close();
|
|
89201
|
+
}
|
|
89202
|
+
catch (err) {
|
|
89203
|
+
logger$u.warn('Failed to close data channel (worker)', { err });
|
|
89204
|
+
}
|
|
89205
|
+
}
|
|
89206
|
+
this.dataChannel = undefined;
|
|
89207
|
+
// Tell the main-thread bridge to tear down the RTCPeerConnection.
|
|
89208
|
+
// Fire-and-forget — we don't block on this.
|
|
89209
|
+
this.bridge
|
|
89210
|
+
?.close(this.connectionId)
|
|
89211
|
+
.catch(() => {
|
|
89212
|
+
// intentionally swallowed
|
|
89213
|
+
});
|
|
89214
|
+
}
|
|
89215
|
+
}
|
|
89216
|
+
stopListening() {
|
|
89217
|
+
if (this.dataChannel !== undefined) {
|
|
89218
|
+
this.dataChannel.onopen = null;
|
|
89219
|
+
this.dataChannel.onclose = null;
|
|
89220
|
+
this.dataChannel.onerror = null;
|
|
89221
|
+
this.dataChannel.onbufferedamountlow = null;
|
|
89222
|
+
this.dataChannel.onmessage = null;
|
|
89223
|
+
}
|
|
89224
|
+
}
|
|
89225
|
+
}
|
|
89226
|
+
|
|
89227
|
+
/**
|
|
89228
|
+
* Conditional re-export of the browser WebrtcConnection.
|
|
89229
|
+
*
|
|
89230
|
+
* At module-load time we detect whether we are running inside a Web Worker.
|
|
89231
|
+
* - **Main thread** → use {@link DirectWebrtcConnection} which owns the
|
|
89232
|
+
* `RTCPeerConnection` and `RTCDataChannel` directly.
|
|
89233
|
+
* - **Worker thread** → use {@link WorkerWebrtcConnection} which delegates
|
|
89234
|
+
* `RTCPeerConnection` signaling to the main thread via a Comlink bridge
|
|
89235
|
+
* and receives a transferred `RTCDataChannel` that lives entirely in the
|
|
89236
|
+
* worker.
|
|
89237
|
+
*
|
|
89238
|
+
* Both classes implement `IWebrtcConnection & IConnection` and expose the
|
|
89239
|
+
* same public API, so upstream code (WebrtcConnector, etc.) is unaffected.
|
|
89240
|
+
*/
|
|
89241
|
+
// The constructor — points to the right class based on the runtime
|
|
89242
|
+
// environment. The type assertion is safe because both implementations
|
|
89243
|
+
// share the same public interface surface.
|
|
89244
|
+
const WebrtcConnection = (isWorkerEnvironment
|
|
89245
|
+
? WorkerWebrtcConnection
|
|
89246
|
+
: DirectWebrtcConnection);
|
|
89247
|
+
|
|
88598
89248
|
const logger$t = new Logger('WebrtcConnectorRpcRemote');
|
|
88599
89249
|
class WebrtcConnectorRpcRemote extends RpcRemote {
|
|
88600
89250
|
requestConnection() {
|
|
@@ -93073,7 +93723,7 @@
|
|
|
93073
93723
|
}
|
|
93074
93724
|
}
|
|
93075
93725
|
|
|
93076
|
-
var version$1 = "103.
|
|
93726
|
+
var version$1 = "103.6.0-rc.0";
|
|
93077
93727
|
|
|
93078
93728
|
// @generated message type with reflection information, may provide speed optimized methods
|
|
93079
93729
|
let Any$Type$1 = class Any$Type extends MessageType {
|
|
@@ -94720,6 +95370,18 @@
|
|
|
94720
95370
|
*/
|
|
94721
95371
|
const PauseNeighborRequest = new PauseNeighborRequest$Type();
|
|
94722
95372
|
// @generated message type with reflection information, may provide speed optimized methods
|
|
95373
|
+
class PauseNeighborResponse$Type extends MessageType {
|
|
95374
|
+
constructor() {
|
|
95375
|
+
super("PauseNeighborResponse", [
|
|
95376
|
+
{ no: 1, name: "accepted", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }
|
|
95377
|
+
]);
|
|
95378
|
+
}
|
|
95379
|
+
}
|
|
95380
|
+
/**
|
|
95381
|
+
* @generated MessageType for protobuf message PauseNeighborResponse
|
|
95382
|
+
*/
|
|
95383
|
+
const PauseNeighborResponse = new PauseNeighborResponse$Type();
|
|
95384
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
94723
95385
|
class ResumeNeighborRequest$Type extends MessageType {
|
|
94724
95386
|
constructor() {
|
|
94725
95387
|
super("ResumeNeighborRequest", [
|
|
@@ -94775,7 +95437,7 @@
|
|
|
94775
95437
|
* @generated ServiceType for protobuf service PlumtreeRpc
|
|
94776
95438
|
*/
|
|
94777
95439
|
const PlumtreeRpc = new ServiceType("PlumtreeRpc", [
|
|
94778
|
-
{ name: "pauseNeighbor", options: {}, I: PauseNeighborRequest, O:
|
|
95440
|
+
{ name: "pauseNeighbor", options: {}, I: PauseNeighborRequest, O: PauseNeighborResponse },
|
|
94779
95441
|
{ name: "resumeNeighbor", options: {}, I: ResumeNeighborRequest, O: Empty$1 },
|
|
94780
95442
|
{ name: "sendMetadata", options: {}, I: MessageID$1, O: Empty$1 }
|
|
94781
95443
|
]);
|
|
@@ -96377,14 +97039,17 @@
|
|
|
96377
97039
|
async pauseNeighbor(request, context) {
|
|
96378
97040
|
const sender = toNodeId(context.incomingSourceDescriptor);
|
|
96379
97041
|
if (this.neighbors.has(sender)) {
|
|
96380
|
-
this.pausedNodes.add(sender, request.messageChainId);
|
|
97042
|
+
const accepted = this.pausedNodes.add(sender, request.messageChainId);
|
|
97043
|
+
return { accepted };
|
|
96381
97044
|
}
|
|
96382
|
-
return
|
|
97045
|
+
return { accepted: false };
|
|
96383
97046
|
}
|
|
96384
97047
|
async resumeNeighbor(request, context) {
|
|
96385
97048
|
const sender = context.incomingSourceDescriptor;
|
|
96386
|
-
this.
|
|
96387
|
-
|
|
97049
|
+
if (this.neighbors.has(toNodeId(sender))) {
|
|
97050
|
+
this.pausedNodes.delete(toNodeId(sender), request.messageChainId);
|
|
97051
|
+
await this.sendBuffer(request.fromTimestamp, request.messageChainId, sender);
|
|
97052
|
+
}
|
|
96388
97053
|
return Empty$1;
|
|
96389
97054
|
}
|
|
96390
97055
|
}
|
|
@@ -96397,10 +97062,9 @@
|
|
|
96397
97062
|
await this.getClient().sendMetadata(msg, options);
|
|
96398
97063
|
}
|
|
96399
97064
|
async pauseNeighbor(messageChainId) {
|
|
96400
|
-
const options = this.formDhtRpcOptions(
|
|
96401
|
-
|
|
96402
|
-
|
|
96403
|
-
await this.getClient().pauseNeighbor({ messageChainId }, options);
|
|
97065
|
+
const options = this.formDhtRpcOptions();
|
|
97066
|
+
const response = await this.getClient().pauseNeighbor({ messageChainId }, options);
|
|
97067
|
+
return response.accepted;
|
|
96404
97068
|
}
|
|
96405
97069
|
async resumeNeighbor(fromTimestamp, messageChainId) {
|
|
96406
97070
|
const options = this.formDhtRpcOptions({
|
|
@@ -96422,9 +97086,10 @@
|
|
|
96422
97086
|
this.pausedNeighbors.set(msgChainId, new Set());
|
|
96423
97087
|
}
|
|
96424
97088
|
if (this.pausedNeighbors.get(msgChainId).size >= this.limit) {
|
|
96425
|
-
return;
|
|
97089
|
+
return false;
|
|
96426
97090
|
}
|
|
96427
97091
|
this.pausedNeighbors.get(msgChainId).add(node);
|
|
97092
|
+
return true;
|
|
96428
97093
|
}
|
|
96429
97094
|
delete(node, msgChainId) {
|
|
96430
97095
|
this.pausedNeighbors.get(msgChainId)?.delete(node);
|
|
@@ -96457,19 +97122,24 @@
|
|
|
96457
97122
|
}
|
|
96458
97123
|
|
|
96459
97124
|
const MAX_PAUSED_NEIGHBORS_DEFAULT = 3;
|
|
97125
|
+
const DEFAULT_RECOVERY_TIMEOUT = 500;
|
|
97126
|
+
const DEFAULT_RECOVERY_CHECK_INTERVAL = 200;
|
|
97127
|
+
const DEFAULT_RECOVERY_COOLDOWN = 2500;
|
|
96460
97128
|
const logger$4$1 = new Logger('PlumtreeManager');
|
|
96461
97129
|
class PlumtreeManager extends EventEmitter {
|
|
96462
97130
|
neighbors;
|
|
96463
97131
|
localPeerDescriptor;
|
|
96464
|
-
// We have paused sending real data to these neighbrs and only send metadata
|
|
96465
97132
|
localPausedNeighbors;
|
|
96466
|
-
// We have asked these nodes to pause sending real data to us, used to limit sending of pausing and resuming requests
|
|
96467
97133
|
remotePausedNeighbors;
|
|
96468
97134
|
rpcLocal;
|
|
96469
97135
|
latestMessages = new Map();
|
|
96470
97136
|
rpcCommunicator;
|
|
96471
|
-
metadataTimestampsAheadOfRealData = new Map();
|
|
96472
97137
|
maxPausedNeighbors;
|
|
97138
|
+
recoveryState = new Map();
|
|
97139
|
+
recoveryCooldownUntil = new Map();
|
|
97140
|
+
recoveryTimeout;
|
|
97141
|
+
recoveryCooldown;
|
|
97142
|
+
abortController = new AbortController();
|
|
96473
97143
|
constructor(options) {
|
|
96474
97144
|
super();
|
|
96475
97145
|
this.neighbors = options.neighbors;
|
|
@@ -96477,12 +97147,22 @@
|
|
|
96477
97147
|
this.localPeerDescriptor = options.localPeerDescriptor;
|
|
96478
97148
|
this.localPausedNeighbors = new PausedNeighbors(options.maxPausedNeighbors ?? MAX_PAUSED_NEIGHBORS_DEFAULT);
|
|
96479
97149
|
this.remotePausedNeighbors = new PausedNeighbors(options.maxPausedNeighbors ?? MAX_PAUSED_NEIGHBORS_DEFAULT);
|
|
97150
|
+
this.recoveryTimeout = options.recoveryTimeout ?? DEFAULT_RECOVERY_TIMEOUT;
|
|
97151
|
+
this.recoveryCooldown = options.recoveryCooldown ?? DEFAULT_RECOVERY_COOLDOWN;
|
|
96480
97152
|
this.rpcLocal = new PlumtreeRpcLocal(this.neighbors, this.localPausedNeighbors, (metadata, previousNode) => this.onMetadata(metadata, previousNode), (fromTimestamp, msgChainId, remotePeerDescriptor) => this.sendBuffer(fromTimestamp, msgChainId, remotePeerDescriptor));
|
|
96481
|
-
this.neighbors.on('nodeRemoved',
|
|
97153
|
+
this.neighbors.on('nodeRemoved', this.onNeighborRemoved);
|
|
96482
97154
|
this.rpcCommunicator = options.rpcCommunicator;
|
|
96483
97155
|
this.rpcCommunicator.registerRpcNotification(MessageID$1, 'sendMetadata', (msg, context) => this.rpcLocal.sendMetadata(msg, context));
|
|
96484
|
-
this.rpcCommunicator.
|
|
97156
|
+
this.rpcCommunicator.registerRpcMethod(PauseNeighborRequest, PauseNeighborResponse, 'pauseNeighbor', (msg, context) => this.rpcLocal.pauseNeighbor(msg, context));
|
|
96485
97157
|
this.rpcCommunicator.registerRpcNotification(ResumeNeighborRequest, 'resumeNeighbor', (msg, context) => this.rpcLocal.resumeNeighbor(msg, context));
|
|
97158
|
+
setAbortableInterval(() => {
|
|
97159
|
+
const now = performance.now();
|
|
97160
|
+
for (const [chainId, state] of this.recoveryState) {
|
|
97161
|
+
if (now - state.metadataAheadSince >= this.recoveryTimeout && !state.resumeInProgress) {
|
|
97162
|
+
this.attemptRecovery(chainId, state, this.getLatestMessageTimestamp(chainId));
|
|
97163
|
+
}
|
|
97164
|
+
}
|
|
97165
|
+
}, options.recoveryCheckInterval ?? DEFAULT_RECOVERY_CHECK_INTERVAL, this.abortController.signal);
|
|
96486
97166
|
}
|
|
96487
97167
|
async pauseNeighbor(node, msgChainId) {
|
|
96488
97168
|
if (this.neighbors.has(toNodeId(node))
|
|
@@ -96490,8 +97170,16 @@
|
|
|
96490
97170
|
&& this.remotePausedNeighbors.size(msgChainId) < this.maxPausedNeighbors) {
|
|
96491
97171
|
logger$4$1.debug(`Pausing neighbor ${toNodeId(node)}`);
|
|
96492
97172
|
this.remotePausedNeighbors.add(toNodeId(node), msgChainId);
|
|
96493
|
-
|
|
96494
|
-
|
|
97173
|
+
try {
|
|
97174
|
+
const remote = this.createRemote(node);
|
|
97175
|
+
const accepted = await remote.pauseNeighbor(msgChainId);
|
|
97176
|
+
if (!accepted) {
|
|
97177
|
+
this.remotePausedNeighbors.delete(toNodeId(node), msgChainId);
|
|
97178
|
+
}
|
|
97179
|
+
}
|
|
97180
|
+
catch (_e) {
|
|
97181
|
+
this.remotePausedNeighbors.delete(toNodeId(node), msgChainId);
|
|
97182
|
+
}
|
|
96495
97183
|
}
|
|
96496
97184
|
}
|
|
96497
97185
|
async resumeNeighbor(node, msgChainId, fromTimestamp) {
|
|
@@ -96502,9 +97190,15 @@
|
|
|
96502
97190
|
await remote.resumeNeighbor(fromTimestamp, msgChainId);
|
|
96503
97191
|
}
|
|
96504
97192
|
}
|
|
96505
|
-
onNeighborRemoved(nodeId) {
|
|
97193
|
+
onNeighborRemoved = (nodeId) => {
|
|
96506
97194
|
this.localPausedNeighbors.deleteAll(nodeId);
|
|
96507
97195
|
this.remotePausedNeighbors.deleteAll(nodeId);
|
|
97196
|
+
for (const [_chainId, state] of this.recoveryState) {
|
|
97197
|
+
state.candidates = state.candidates.filter((c) => toNodeId(c) !== nodeId);
|
|
97198
|
+
if (state.lastAttemptedNode !== null && toNodeId(state.lastAttemptedNode) === nodeId) {
|
|
97199
|
+
state.lastAttemptedNode = null;
|
|
97200
|
+
}
|
|
97201
|
+
}
|
|
96508
97202
|
if (this.neighbors.size() > 0) {
|
|
96509
97203
|
this.remotePausedNeighbors.forEach((pausedNeighbors, msgChainId) => {
|
|
96510
97204
|
if (pausedNeighbors.size >= this.neighbors.size()) {
|
|
@@ -96514,7 +97208,7 @@
|
|
|
96514
97208
|
}
|
|
96515
97209
|
});
|
|
96516
97210
|
}
|
|
96517
|
-
}
|
|
97211
|
+
};
|
|
96518
97212
|
getLatestMessageTimestamp(msgChainId) {
|
|
96519
97213
|
if (!this.latestMessages.has(msgChainId) || this.latestMessages.get(msgChainId).length === 0) {
|
|
96520
97214
|
return 0;
|
|
@@ -96524,22 +97218,61 @@
|
|
|
96524
97218
|
async sendBuffer(fromTimestamp, msgChainId, neighbor) {
|
|
96525
97219
|
const remote = new ContentDeliveryRpcRemote(this.localPeerDescriptor, neighbor, this.rpcCommunicator, ContentDeliveryRpcClient);
|
|
96526
97220
|
const messages = this.latestMessages.get(msgChainId)?.filter((msg) => msg.messageId.timestamp > fromTimestamp) ?? [];
|
|
96527
|
-
|
|
97221
|
+
for (const msg of messages) {
|
|
97222
|
+
await remote.sendStreamMessage(msg);
|
|
97223
|
+
}
|
|
96528
97224
|
}
|
|
96529
97225
|
async onMetadata(msg, previousNode) {
|
|
96530
|
-
|
|
96531
|
-
|
|
96532
|
-
|
|
96533
|
-
|
|
96534
|
-
|
|
96535
|
-
|
|
96536
|
-
|
|
96537
|
-
|
|
96538
|
-
|
|
96539
|
-
|
|
96540
|
-
|
|
96541
|
-
|
|
96542
|
-
|
|
97226
|
+
const latestTs = this.getLatestMessageTimestamp(msg.messageChainId);
|
|
97227
|
+
if (latestTs >= msg.timestamp) {
|
|
97228
|
+
return;
|
|
97229
|
+
}
|
|
97230
|
+
const chainId = msg.messageChainId;
|
|
97231
|
+
const cooldownUntil = this.recoveryCooldownUntil.get(chainId);
|
|
97232
|
+
if (cooldownUntil !== undefined && performance.now() < cooldownUntil) {
|
|
97233
|
+
return;
|
|
97234
|
+
}
|
|
97235
|
+
let state = this.recoveryState.get(chainId);
|
|
97236
|
+
if (!state) {
|
|
97237
|
+
state = {
|
|
97238
|
+
timestampsAhead: new Set(),
|
|
97239
|
+
metadataAheadSince: performance.now(),
|
|
97240
|
+
candidates: [],
|
|
97241
|
+
lastAttemptedNode: null,
|
|
97242
|
+
resumeInProgress: false
|
|
97243
|
+
};
|
|
97244
|
+
this.recoveryState.set(chainId, state);
|
|
97245
|
+
}
|
|
97246
|
+
state.timestampsAhead.add(msg.timestamp);
|
|
97247
|
+
const nodeId = toNodeId(previousNode);
|
|
97248
|
+
const isLastAttempted = state.lastAttemptedNode !== null && toNodeId(state.lastAttemptedNode) === nodeId;
|
|
97249
|
+
if (!isLastAttempted && !state.candidates.some((c) => toNodeId(c) === nodeId)) {
|
|
97250
|
+
state.candidates.push(previousNode);
|
|
97251
|
+
}
|
|
97252
|
+
if (state.timestampsAhead.size > 1 && !state.resumeInProgress) {
|
|
97253
|
+
await this.attemptRecovery(chainId, state, latestTs);
|
|
97254
|
+
}
|
|
97255
|
+
}
|
|
97256
|
+
async attemptRecovery(chainId, state, latestTs) {
|
|
97257
|
+
const candidate = state.candidates.shift();
|
|
97258
|
+
if (!candidate) {
|
|
97259
|
+
state.metadataAheadSince = performance.now();
|
|
97260
|
+
return;
|
|
97261
|
+
}
|
|
97262
|
+
state.resumeInProgress = true;
|
|
97263
|
+
state.lastAttemptedNode = candidate;
|
|
97264
|
+
state.candidates = [];
|
|
97265
|
+
state.timestampsAhead.clear();
|
|
97266
|
+
state.metadataAheadSince = performance.now();
|
|
97267
|
+
try {
|
|
97268
|
+
const remote = this.createRemote(candidate);
|
|
97269
|
+
await remote.resumeNeighbor(latestTs, chainId);
|
|
97270
|
+
}
|
|
97271
|
+
catch (_e) {
|
|
97272
|
+
logger$4$1.debug('Recovery resume failed, will retry with next candidate');
|
|
97273
|
+
}
|
|
97274
|
+
finally {
|
|
97275
|
+
state.resumeInProgress = false;
|
|
96543
97276
|
}
|
|
96544
97277
|
}
|
|
96545
97278
|
createRemote(neighbor) {
|
|
@@ -96557,8 +97290,13 @@
|
|
|
96557
97290
|
this.latestMessages.get(messageChainId).shift();
|
|
96558
97291
|
this.latestMessages.get(messageChainId).push(msg);
|
|
96559
97292
|
}
|
|
96560
|
-
|
|
96561
|
-
|
|
97293
|
+
const state = this.recoveryState.get(messageChainId);
|
|
97294
|
+
if (state) {
|
|
97295
|
+
if (state.lastAttemptedNode) {
|
|
97296
|
+
this.remotePausedNeighbors.delete(toNodeId(state.lastAttemptedNode), messageChainId);
|
|
97297
|
+
}
|
|
97298
|
+
this.recoveryState.delete(messageChainId);
|
|
97299
|
+
this.recoveryCooldownUntil.set(messageChainId, performance.now() + this.recoveryCooldown);
|
|
96562
97300
|
}
|
|
96563
97301
|
this.emit('message', msg);
|
|
96564
97302
|
const neighbors = this.neighbors.getAll().filter((neighbor) => toNodeId(neighbor.getPeerDescriptor()) !== previousNode);
|
|
@@ -96576,7 +97314,14 @@
|
|
|
96576
97314
|
return this.localPausedNeighbors.isPaused(toNodeId(node), msgChainId)
|
|
96577
97315
|
|| this.remotePausedNeighbors.isPaused(toNodeId(node), msgChainId);
|
|
96578
97316
|
}
|
|
97317
|
+
getLocalPausedNeighbors() {
|
|
97318
|
+
return this.localPausedNeighbors;
|
|
97319
|
+
}
|
|
97320
|
+
getRemotePausedNeighbors() {
|
|
97321
|
+
return this.remotePausedNeighbors;
|
|
97322
|
+
}
|
|
96579
97323
|
stop() {
|
|
97324
|
+
this.abortController.abort();
|
|
96580
97325
|
this.neighbors.off('nodeRemoved', this.onNeighborRemoved);
|
|
96581
97326
|
}
|
|
96582
97327
|
}
|