webpeerjs 0.0.10 → 0.1.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/README.md +12 -18
- package/dist/esm/webpeerjs.js +35 -23
- package/dist/umd/webpeerjs.js +2868 -723
- package/package.json +3 -1
- package/src/config.js +1 -0
- package/src/webpeerjs.js +33 -22
package/dist/umd/webpeerjs.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
const CONFIG_PEER_DISCOVERY_GLOBAL = '_peer-discovery._p2p._pubsub';
|
|
16
16
|
const CONFIG_PEER_DISCOVERY_WEBPEERJS= prefix$1+'-peer-discovery';
|
|
17
17
|
const CONFIG_PUBSUB_PEER_DISCOVERY = [CONFIG_PEER_DISCOVERY_GLOBAL, CONFIG_PEER_DISCOVERY_UNIVERSAL_CONNECTIVITY, CONFIG_PEER_DISCOVERY_WEBPEERJS];
|
|
18
|
+
const CONFIG_PUPSUB_PEER_DATA = ['_'+prefix$1+'-peer-data_'];
|
|
18
19
|
const CONFIG_DELEGATED_API = 'https://delegated-ipfs.dev';
|
|
19
20
|
const CONFIG_DNS_RESOLVER = 'https://dns.google/resolve';
|
|
20
21
|
const CONFIG_KNOWN_BOOTSTRAP_DNS = '_dnsaddr.bootstrap.libp2p.io';
|
|
@@ -3417,6 +3418,7 @@
|
|
|
3417
3418
|
}
|
|
3418
3419
|
// Error codes
|
|
3419
3420
|
const ERR_TIMEOUT = 'ERR_TIMEOUT';
|
|
3421
|
+
const ERR_INVALID_MESSAGE = 'ERR_INVALID_MESSAGE';
|
|
3420
3422
|
|
|
3421
3423
|
/** Noop for browser compatibility */
|
|
3422
3424
|
function setMaxListeners$1() { }
|
|
@@ -3572,6 +3574,14 @@
|
|
|
3572
3574
|
* value should be a string array of provided capabilities.
|
|
3573
3575
|
*/
|
|
3574
3576
|
const serviceCapabilities = Symbol.for('@libp2p/service-capabilities');
|
|
3577
|
+
/**
|
|
3578
|
+
* This symbol is used by libp2p services to define the capabilities they
|
|
3579
|
+
* require from other libp2p services.
|
|
3580
|
+
*
|
|
3581
|
+
* The service should define a property with this symbol as the key and the
|
|
3582
|
+
* value should be a string array of required capabilities.
|
|
3583
|
+
*/
|
|
3584
|
+
const serviceDependencies = Symbol.for('@libp2p/service-dependencies');
|
|
3575
3585
|
|
|
3576
3586
|
/**
|
|
3577
3587
|
* Returns true if the two passed Uint8Arrays have the same content
|
|
@@ -3946,7 +3956,7 @@
|
|
|
3946
3956
|
|
|
3947
3957
|
const V = -1;
|
|
3948
3958
|
const names = {};
|
|
3949
|
-
const codes$
|
|
3959
|
+
const codes$4 = {};
|
|
3950
3960
|
const table = [
|
|
3951
3961
|
[4, 32, 'ip4'],
|
|
3952
3962
|
[6, 16, 'tcp'],
|
|
@@ -3995,7 +4005,7 @@
|
|
|
3995
4005
|
// populate tables
|
|
3996
4006
|
table.forEach(row => {
|
|
3997
4007
|
const proto = createProtocol(...row);
|
|
3998
|
-
codes$
|
|
4008
|
+
codes$4[proto.code] = proto;
|
|
3999
4009
|
names[proto.name] = proto;
|
|
4000
4010
|
});
|
|
4001
4011
|
function createProtocol(code, size, name, resolvable, path) {
|
|
@@ -4021,8 +4031,8 @@
|
|
|
4021
4031
|
*/
|
|
4022
4032
|
function getProtocol(proto) {
|
|
4023
4033
|
if (typeof proto === 'number') {
|
|
4024
|
-
if (codes$
|
|
4025
|
-
return codes$
|
|
4034
|
+
if (codes$4[proto] != null) {
|
|
4035
|
+
return codes$4[proto];
|
|
4026
4036
|
}
|
|
4027
4037
|
throw new Error(`no protocol with code: ${proto}`);
|
|
4028
4038
|
}
|
|
@@ -6719,7 +6729,7 @@
|
|
|
6719
6729
|
}
|
|
6720
6730
|
}
|
|
6721
6731
|
|
|
6722
|
-
var browser = {exports: {}};
|
|
6732
|
+
var browser$1 = {exports: {}};
|
|
6723
6733
|
|
|
6724
6734
|
/**
|
|
6725
6735
|
* Helpers.
|
|
@@ -7436,9 +7446,9 @@
|
|
|
7436
7446
|
return '[UnexpectedJSONParseError]: ' + error.message;
|
|
7437
7447
|
}
|
|
7438
7448
|
};
|
|
7439
|
-
} (browser, browser.exports));
|
|
7449
|
+
} (browser$1, browser$1.exports));
|
|
7440
7450
|
|
|
7441
|
-
var browserExports = browser.exports;
|
|
7451
|
+
var browserExports = browser$1.exports;
|
|
7442
7452
|
var debug = /*@__PURE__*/getDefaultExportFromCjs(browserExports);
|
|
7443
7453
|
|
|
7444
7454
|
/**
|
|
@@ -19198,7 +19208,7 @@
|
|
|
19198
19208
|
}
|
|
19199
19209
|
|
|
19200
19210
|
const log$1 = logger('delegated-routing-v1-http-api-client');
|
|
19201
|
-
const defaultValues$
|
|
19211
|
+
const defaultValues$2 = {
|
|
19202
19212
|
concurrentRequests: 4,
|
|
19203
19213
|
timeout: 30e3
|
|
19204
19214
|
};
|
|
@@ -19218,10 +19228,10 @@
|
|
|
19218
19228
|
this.shutDownController = new AbortController();
|
|
19219
19229
|
setMaxListeners(Infinity, this.shutDownController.signal);
|
|
19220
19230
|
this.httpQueue = new PQueue({
|
|
19221
|
-
concurrency: init.concurrentRequests ?? defaultValues$
|
|
19231
|
+
concurrency: init.concurrentRequests ?? defaultValues$2.concurrentRequests
|
|
19222
19232
|
});
|
|
19223
19233
|
this.clientUrl = url instanceof URL ? url : new URL(url);
|
|
19224
|
-
this.timeout = init.timeout ?? defaultValues$
|
|
19234
|
+
this.timeout = init.timeout ?? defaultValues$2.timeout;
|
|
19225
19235
|
this.contentRouting = new DelegatedRoutingV1HttpApiClientContentRouting(this);
|
|
19226
19236
|
this.peerRouting = new DelegatedRoutingV1HttpApiClientPeerRouting(this);
|
|
19227
19237
|
}
|
|
@@ -19728,7 +19738,7 @@
|
|
|
19728
19738
|
return peerIdFromKeys(marshalPublicKey(privateKey.public), marshalPrivateKey(privateKey));
|
|
19729
19739
|
}
|
|
19730
19740
|
|
|
19731
|
-
const codes$
|
|
19741
|
+
const codes$3 = {
|
|
19732
19742
|
ERR_SIGNATURE_NOT_VALID: 'ERR_SIGNATURE_NOT_VALID'
|
|
19733
19743
|
};
|
|
19734
19744
|
|
|
@@ -19849,7 +19859,7 @@
|
|
|
19849
19859
|
const envelope = await RecordEnvelope.createFromProtobuf(data);
|
|
19850
19860
|
const valid = await envelope.validate(domain);
|
|
19851
19861
|
if (!valid) {
|
|
19852
|
-
throw new CodeError$2('envelope signature is not valid for the given domain', codes$
|
|
19862
|
+
throw new CodeError$2('envelope signature is not valid for the given domain', codes$3.ERR_SIGNATURE_NOT_VALID);
|
|
19853
19863
|
}
|
|
19854
19864
|
return envelope;
|
|
19855
19865
|
};
|
|
@@ -20538,7 +20548,7 @@
|
|
|
20538
20548
|
return mutexes[opts.name];
|
|
20539
20549
|
}
|
|
20540
20550
|
|
|
20541
|
-
const codes$
|
|
20551
|
+
const codes$2 = {
|
|
20542
20552
|
ERR_INVALID_PARAMETERS: 'ERR_INVALID_PARAMETERS'
|
|
20543
20553
|
};
|
|
20544
20554
|
|
|
@@ -20880,7 +20890,7 @@
|
|
|
20880
20890
|
const NAMESPACE_COMMON = '/peers/';
|
|
20881
20891
|
function peerIdToDatastoreKey(peerId) {
|
|
20882
20892
|
if (!isPeerId(peerId) || peerId.type == null) {
|
|
20883
|
-
throw new CodeError$2('Invalid PeerId', codes$
|
|
20893
|
+
throw new CodeError$2('Invalid PeerId', codes$2.ERR_INVALID_PARAMETERS);
|
|
20884
20894
|
}
|
|
20885
20895
|
const b32key = peerId.toCID().toString();
|
|
20886
20896
|
return new Key(`${NAMESPACE_COMMON}${b32key}`);
|
|
@@ -20896,7 +20906,7 @@
|
|
|
20896
20906
|
addr.multiaddr = multiaddr(addr.multiaddr);
|
|
20897
20907
|
}
|
|
20898
20908
|
if (!isMultiaddr(addr.multiaddr)) {
|
|
20899
|
-
throw new CodeError$2('Multiaddr was invalid', codes$
|
|
20909
|
+
throw new CodeError$2('Multiaddr was invalid', codes$2.ERR_INVALID_PARAMETERS);
|
|
20900
20910
|
}
|
|
20901
20911
|
if (!(await filter(peerId, addr.multiaddr))) {
|
|
20902
20912
|
continue;
|
|
@@ -20926,14 +20936,14 @@
|
|
|
20926
20936
|
|
|
20927
20937
|
async function toPeerPB(peerId, data, strategy, options) {
|
|
20928
20938
|
if (data == null) {
|
|
20929
|
-
throw new CodeError$2('Invalid PeerData', codes$
|
|
20939
|
+
throw new CodeError$2('Invalid PeerData', codes$2.ERR_INVALID_PARAMETERS);
|
|
20930
20940
|
}
|
|
20931
20941
|
if (data.publicKey != null && peerId.publicKey != null && !equals(data.publicKey, peerId.publicKey)) {
|
|
20932
|
-
throw new CodeError$2('publicKey bytes do not match peer id publicKey bytes', codes$
|
|
20942
|
+
throw new CodeError$2('publicKey bytes do not match peer id publicKey bytes', codes$2.ERR_INVALID_PARAMETERS);
|
|
20933
20943
|
}
|
|
20934
20944
|
const existingPeer = options.existingPeer;
|
|
20935
20945
|
if (existingPeer != null && !peerId.equals(existingPeer.id)) {
|
|
20936
|
-
throw new CodeError$2('peer id did not match existing peer id', codes$
|
|
20946
|
+
throw new CodeError$2('peer id did not match existing peer id', codes$2.ERR_INVALID_PARAMETERS);
|
|
20937
20947
|
}
|
|
20938
20948
|
let addresses = existingPeer?.addresses ?? [];
|
|
20939
20949
|
let protocols = new Set(existingPeer?.protocols ?? []);
|
|
@@ -21061,30 +21071,30 @@
|
|
|
21061
21071
|
}
|
|
21062
21072
|
function validateMetadata(key, value) {
|
|
21063
21073
|
if (typeof key !== 'string') {
|
|
21064
|
-
throw new CodeError$2('Metadata key must be a string', codes$
|
|
21074
|
+
throw new CodeError$2('Metadata key must be a string', codes$2.ERR_INVALID_PARAMETERS);
|
|
21065
21075
|
}
|
|
21066
21076
|
if (!(value instanceof Uint8Array)) {
|
|
21067
|
-
throw new CodeError$2('Metadata value must be a Uint8Array', codes$
|
|
21077
|
+
throw new CodeError$2('Metadata value must be a Uint8Array', codes$2.ERR_INVALID_PARAMETERS);
|
|
21068
21078
|
}
|
|
21069
21079
|
}
|
|
21070
21080
|
function validateTag(key, tag) {
|
|
21071
21081
|
if (typeof key !== 'string') {
|
|
21072
|
-
throw new CodeError$2('Tag name must be a string', codes$
|
|
21082
|
+
throw new CodeError$2('Tag name must be a string', codes$2.ERR_INVALID_PARAMETERS);
|
|
21073
21083
|
}
|
|
21074
21084
|
if (tag.value != null) {
|
|
21075
21085
|
if (parseInt(`${tag.value}`, 10) !== tag.value) {
|
|
21076
|
-
throw new CodeError$2('Tag value must be an integer', codes$
|
|
21086
|
+
throw new CodeError$2('Tag value must be an integer', codes$2.ERR_INVALID_PARAMETERS);
|
|
21077
21087
|
}
|
|
21078
21088
|
if (tag.value < 0 || tag.value > 100) {
|
|
21079
|
-
throw new CodeError$2('Tag value must be between 0-100', codes$
|
|
21089
|
+
throw new CodeError$2('Tag value must be between 0-100', codes$2.ERR_INVALID_PARAMETERS);
|
|
21080
21090
|
}
|
|
21081
21091
|
}
|
|
21082
21092
|
if (tag.ttl != null) {
|
|
21083
21093
|
if (parseInt(`${tag.ttl}`, 10) !== tag.ttl) {
|
|
21084
|
-
throw new CodeError$2('Tag ttl must be an integer', codes$
|
|
21094
|
+
throw new CodeError$2('Tag ttl must be an integer', codes$2.ERR_INVALID_PARAMETERS);
|
|
21085
21095
|
}
|
|
21086
21096
|
if (tag.ttl < 0) {
|
|
21087
|
-
throw new CodeError$2('Tag ttl must be between greater than 0', codes$
|
|
21097
|
+
throw new CodeError$2('Tag ttl must be between greater than 0', codes$2.ERR_INVALID_PARAMETERS);
|
|
21088
21098
|
}
|
|
21089
21099
|
}
|
|
21090
21100
|
}
|
|
@@ -21148,7 +21158,7 @@
|
|
|
21148
21158
|
}
|
|
21149
21159
|
async delete(peerId) {
|
|
21150
21160
|
if (this.peerId.equals(peerId)) {
|
|
21151
|
-
throw new CodeError$2('Cannot delete self peer', codes$
|
|
21161
|
+
throw new CodeError$2('Cannot delete self peer', codes$2.ERR_INVALID_PARAMETERS);
|
|
21152
21162
|
}
|
|
21153
21163
|
await this.datastore.delete(peerIdToDatastoreKey(peerId));
|
|
21154
21164
|
}
|
|
@@ -22568,7 +22578,7 @@
|
|
|
22568
22578
|
* DNS.matches(multiaddr('/dns6/example.org')) // true
|
|
22569
22579
|
* ```
|
|
22570
22580
|
*/
|
|
22571
|
-
fmt(or$1(_DNS, _DNSADDR, _DNS4, _DNS6));
|
|
22581
|
+
const DNS$2 = fmt(or$1(_DNS, _DNSADDR, _DNS4, _DNS6));
|
|
22572
22582
|
const _IP4 = and$1(literal('ip4'), func(isIPv4));
|
|
22573
22583
|
const _IP6 = and$1(literal('ip6'), func(isIPv6));
|
|
22574
22584
|
const _IP = or$1(_IP4, _IP6);
|
|
@@ -22588,6 +22598,20 @@
|
|
|
22588
22598
|
* ```
|
|
22589
22599
|
*/
|
|
22590
22600
|
const IP_OR_DOMAIN = fmt(_IP_OR_DOMAIN);
|
|
22601
|
+
/**
|
|
22602
|
+
* Matches ip4 or ip6 addresses.
|
|
22603
|
+
*
|
|
22604
|
+
* @example
|
|
22605
|
+
*
|
|
22606
|
+
* ```ts
|
|
22607
|
+
* import { multiaddr } from '@multiformats/multiaddr'
|
|
22608
|
+
* import { IP } from '@multiformats/multiaddr-matcher'
|
|
22609
|
+
*
|
|
22610
|
+
* IP.matches(multiaddr('/ip4/123.123.123.123')) // true
|
|
22611
|
+
* IP.matches(multiaddr('/ip6/fe80::1cc1:a3b8:322f:cf22')) // true
|
|
22612
|
+
* ```
|
|
22613
|
+
*/
|
|
22614
|
+
const IP$1 = fmt(_IP);
|
|
22591
22615
|
const _TCP = and$1(_IP_OR_DOMAIN, literal('tcp'), number$1());
|
|
22592
22616
|
const _UDP = and$1(_IP_OR_DOMAIN, literal('udp'), number$1());
|
|
22593
22617
|
const TCP_OR_UDP = or$1(_TCP, _UDP);
|
|
@@ -22627,7 +22651,20 @@
|
|
|
22627
22651
|
* ```
|
|
22628
22652
|
*/
|
|
22629
22653
|
const Circuit$1 = fmt(_Circuit$1);
|
|
22630
|
-
or$1(and$1(_P2P$1, literal('p2p-circuit'), literal('webrtc'), peerId()), and$1(_P2P$1, literal('webrtc'), optional(peerId())), literal('webrtc'));
|
|
22654
|
+
const _WebRTC = or$1(and$1(_P2P$1, literal('p2p-circuit'), literal('webrtc'), peerId()), and$1(_P2P$1, literal('webrtc'), optional(peerId())), literal('webrtc'));
|
|
22655
|
+
/**
|
|
22656
|
+
* Matches WebRTC addresses
|
|
22657
|
+
*
|
|
22658
|
+
* @example
|
|
22659
|
+
*
|
|
22660
|
+
* ```ts
|
|
22661
|
+
* import { multiaddr } from '@multiformats/multiaddr'
|
|
22662
|
+
* import { WebRTC } from '@multiformats/multiaddr-matcher'
|
|
22663
|
+
*
|
|
22664
|
+
* WebRTC.matches(multiaddr('/ip4/123.123.123.123/tcp/1234/p2p/QmRelay/p2p-circuit/webrtc/p2p/QmTarget')) // true
|
|
22665
|
+
* ```
|
|
22666
|
+
*/
|
|
22667
|
+
const WebRTC = fmt(_WebRTC);
|
|
22631
22668
|
or$1(and$1(_IP_OR_DOMAIN, literal('tcp'), number$1(), literal('http'), optional(peerId())), and$1(_IP_OR_DOMAIN, literal('http'), optional(peerId())));
|
|
22632
22669
|
or$1(and$1(_IP_OR_DOMAIN, literal('tcp'), or$1(and$1(literal('443'), literal('http')), and$1(number$1(), literal('https'))), optional(peerId())), and$1(_IP_OR_DOMAIN, literal('tls'), literal('http'), optional(peerId())), and$1(_IP_OR_DOMAIN, literal('https'), optional(peerId())));
|
|
22633
22670
|
|
|
@@ -23360,7 +23397,7 @@
|
|
|
23360
23397
|
messages["ERR_PROTECTOR_REQUIRED"] = "Private network is enforced, but no protector was provided";
|
|
23361
23398
|
messages["NOT_FOUND"] = "Not found";
|
|
23362
23399
|
})(messages || (messages = {}));
|
|
23363
|
-
var codes;
|
|
23400
|
+
var codes$1;
|
|
23364
23401
|
(function (codes) {
|
|
23365
23402
|
codes["ERR_PROTECTOR_REQUIRED"] = "ERR_PROTECTOR_REQUIRED";
|
|
23366
23403
|
codes["ERR_PEER_DIAL_INTERCEPTED"] = "ERR_PEER_DIAL_INTERCEPTED";
|
|
@@ -23421,7 +23458,7 @@
|
|
|
23421
23458
|
codes["ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS"] = "ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS";
|
|
23422
23459
|
codes["ERR_CONNECTION_DENIED"] = "ERR_CONNECTION_DENIED";
|
|
23423
23460
|
codes["ERR_TRANSFER_LIMIT_EXCEEDED"] = "ERR_TRANSFER_LIMIT_EXCEEDED";
|
|
23424
|
-
})(codes || (codes = {}));
|
|
23461
|
+
})(codes$1 || (codes$1 = {}));
|
|
23425
23462
|
|
|
23426
23463
|
const DefaultConfig = {
|
|
23427
23464
|
addresses: {
|
|
@@ -23443,10 +23480,10 @@
|
|
|
23443
23480
|
async function validateConfig(opts) {
|
|
23444
23481
|
const resultingOptions = mergeOptions$1(DefaultConfig, opts);
|
|
23445
23482
|
if (resultingOptions.connectionProtector === null && globalThis.process?.env?.LIBP2P_FORCE_PNET != null) { // eslint-disable-line no-undef
|
|
23446
|
-
throw new CodeError$2(messages.ERR_PROTECTOR_REQUIRED, codes.ERR_PROTECTOR_REQUIRED);
|
|
23483
|
+
throw new CodeError$2(messages.ERR_PROTECTOR_REQUIRED, codes$1.ERR_PROTECTOR_REQUIRED);
|
|
23447
23484
|
}
|
|
23448
23485
|
if (!(await peerIdFromKeys(resultingOptions.privateKey.public.bytes, resultingOptions.privateKey.bytes)).equals(resultingOptions.peerId)) {
|
|
23449
|
-
throw new CodeError$2('Private key doesn\'t match peer id', codes.ERR_INVALID_KEY);
|
|
23486
|
+
throw new CodeError$2('Private key doesn\'t match peer id', codes$1.ERR_INVALID_KEY);
|
|
23450
23487
|
}
|
|
23451
23488
|
return resultingOptions;
|
|
23452
23489
|
}
|
|
@@ -23710,18 +23747,18 @@
|
|
|
23710
23747
|
// ensure PeerId is either not set or is consistent
|
|
23711
23748
|
peer.forEach(ma => {
|
|
23712
23749
|
if (!isMultiaddr(ma)) {
|
|
23713
|
-
throw new CodeError$2('Invalid Multiaddr', codes.ERR_INVALID_MULTIADDR);
|
|
23750
|
+
throw new CodeError$2('Invalid Multiaddr', codes$1.ERR_INVALID_MULTIADDR);
|
|
23714
23751
|
}
|
|
23715
23752
|
const maPeerIdStr = ma.getPeerId();
|
|
23716
23753
|
if (maPeerIdStr == null) {
|
|
23717
23754
|
if (peerId != null) {
|
|
23718
|
-
throw new CodeError$2('Multiaddrs must all have the same peer id or have no peer id', codes.ERR_INVALID_PARAMETERS);
|
|
23755
|
+
throw new CodeError$2('Multiaddrs must all have the same peer id or have no peer id', codes$1.ERR_INVALID_PARAMETERS);
|
|
23719
23756
|
}
|
|
23720
23757
|
}
|
|
23721
23758
|
else {
|
|
23722
23759
|
const maPeerId = peerIdFromString(maPeerIdStr);
|
|
23723
23760
|
if (peerId == null || !peerId.equals(maPeerId)) {
|
|
23724
|
-
throw new CodeError$2('Multiaddrs must all have the same peer id or have no peer id', codes.ERR_INVALID_PARAMETERS);
|
|
23761
|
+
throw new CodeError$2('Multiaddrs must all have the same peer id or have no peer id', codes$1.ERR_INVALID_PARAMETERS);
|
|
23725
23762
|
}
|
|
23726
23763
|
}
|
|
23727
23764
|
});
|
|
@@ -24911,7 +24948,7 @@
|
|
|
24911
24948
|
for (const address of addrsToDial) {
|
|
24912
24949
|
if (dialed === this.maxPeerAddrsToDial) {
|
|
24913
24950
|
this.log('dialed maxPeerAddrsToDial (%d) addresses for %p, not trying any others', dialed, peerId);
|
|
24914
|
-
throw new CodeError$2('Peer had more than maxPeerAddrsToDial', codes.ERR_TOO_MANY_ADDRESSES);
|
|
24951
|
+
throw new CodeError$2('Peer had more than maxPeerAddrsToDial', codes$1.ERR_TOO_MANY_ADDRESSES);
|
|
24915
24952
|
}
|
|
24916
24953
|
dialed++;
|
|
24917
24954
|
try {
|
|
@@ -24947,7 +24984,7 @@
|
|
|
24947
24984
|
if (errors.length === 1) {
|
|
24948
24985
|
throw errors[0];
|
|
24949
24986
|
}
|
|
24950
|
-
throw new AggregateCodeError(errors, 'All multiaddr dials failed', codes.ERR_TRANSPORT_DIAL_FAILED);
|
|
24987
|
+
throw new AggregateCodeError(errors, 'All multiaddr dials failed', codes$1.ERR_TRANSPORT_DIAL_FAILED);
|
|
24951
24988
|
}
|
|
24952
24989
|
finally {
|
|
24953
24990
|
// clean up abort signals/controllers
|
|
@@ -24980,10 +25017,10 @@
|
|
|
24980
25017
|
// if a peer id or multiaddr(s) with a peer id, make sure it isn't our peer id and that we are allowed to dial it
|
|
24981
25018
|
if (peerId != null) {
|
|
24982
25019
|
if (this.components.peerId.equals(peerId)) {
|
|
24983
|
-
throw new CodeError$2('Tried to dial self', codes.ERR_DIALED_SELF);
|
|
25020
|
+
throw new CodeError$2('Tried to dial self', codes$1.ERR_DIALED_SELF);
|
|
24984
25021
|
}
|
|
24985
25022
|
if ((await this.components.connectionGater.denyDialPeer?.(peerId)) === true) {
|
|
24986
|
-
throw new CodeError$2('The dial request is blocked by gater.allowDialPeer', codes.ERR_PEER_DIAL_INTERCEPTED);
|
|
25023
|
+
throw new CodeError$2('The dial request is blocked by gater.allowDialPeer', codes$1.ERR_PEER_DIAL_INTERCEPTED);
|
|
24987
25024
|
}
|
|
24988
25025
|
// if just a peer id was passed, load available multiaddrs for this peer
|
|
24989
25026
|
// from the peer store
|
|
@@ -24995,7 +25032,7 @@
|
|
|
24995
25032
|
this.log('loaded multiaddrs for %p', peerId, addrs.map(({ multiaddr }) => multiaddr.toString()));
|
|
24996
25033
|
}
|
|
24997
25034
|
catch (err) {
|
|
24998
|
-
if (err.code !== codes.ERR_NOT_FOUND) {
|
|
25035
|
+
if (err.code !== codes$1.ERR_NOT_FOUND) {
|
|
24999
25036
|
throw err;
|
|
25000
25037
|
}
|
|
25001
25038
|
}
|
|
@@ -25013,7 +25050,7 @@
|
|
|
25013
25050
|
})));
|
|
25014
25051
|
}
|
|
25015
25052
|
catch (err) {
|
|
25016
|
-
if (err.code !== codes.ERR_NO_ROUTERS_AVAILABLE) {
|
|
25053
|
+
if (err.code !== codes$1.ERR_NO_ROUTERS_AVAILABLE) {
|
|
25017
25054
|
this.log.error('looking up multiaddrs for %p in the peer routing failed', peerId, err);
|
|
25018
25055
|
}
|
|
25019
25056
|
}
|
|
@@ -25083,7 +25120,7 @@
|
|
|
25083
25120
|
const dedupedMultiaddrs = [...dedupedAddrs.values()];
|
|
25084
25121
|
// make sure we actually have some addresses to dial
|
|
25085
25122
|
if (dedupedMultiaddrs.length === 0) {
|
|
25086
|
-
throw new CodeError$2('The dial request has no valid addresses', codes.ERR_NO_VALID_ADDRESSES);
|
|
25123
|
+
throw new CodeError$2('The dial request has no valid addresses', codes$1.ERR_NO_VALID_ADDRESSES);
|
|
25087
25124
|
}
|
|
25088
25125
|
const gatedAdrs = [];
|
|
25089
25126
|
for (const addr of dedupedMultiaddrs) {
|
|
@@ -25095,7 +25132,7 @@
|
|
|
25095
25132
|
const sortedGatedAddrs = gatedAdrs.sort(this.addressSorter);
|
|
25096
25133
|
// make sure we actually have some addresses to dial
|
|
25097
25134
|
if (sortedGatedAddrs.length === 0) {
|
|
25098
|
-
throw new CodeError$2('The connection gater denied all addresses in the dial request', codes.ERR_NO_VALID_ADDRESSES);
|
|
25135
|
+
throw new CodeError$2('The connection gater denied all addresses in the dial request', codes$1.ERR_NO_VALID_ADDRESSES);
|
|
25099
25136
|
}
|
|
25100
25137
|
this.log.trace('addresses for %p before filtering', peerId ?? 'unknown peer', resolvedAddresses.map(({ multiaddr }) => multiaddr.toString()));
|
|
25101
25138
|
this.log.trace('addresses for %p after filtering', peerId ?? 'unknown peer', sortedGatedAddrs.map(({ multiaddr }) => multiaddr.toString()));
|
|
@@ -25157,7 +25194,7 @@
|
|
|
25157
25194
|
this.maxConnections = init.maxConnections ?? defaultOptions.maxConnections;
|
|
25158
25195
|
const minConnections = init.minConnections ?? defaultOptions.minConnections;
|
|
25159
25196
|
if (this.maxConnections < minConnections) {
|
|
25160
|
-
throw new CodeError$2('Connection Manager maxConnections must be greater than minConnections', codes.ERR_INVALID_PARAMETERS);
|
|
25197
|
+
throw new CodeError$2('Connection Manager maxConnections must be greater than minConnections', codes$1.ERR_INVALID_PARAMETERS);
|
|
25161
25198
|
}
|
|
25162
25199
|
/**
|
|
25163
25200
|
* Map of connections per peer
|
|
@@ -25410,7 +25447,7 @@
|
|
|
25410
25447
|
}
|
|
25411
25448
|
async openConnection(peerIdOrMultiaddr, options = {}) {
|
|
25412
25449
|
if (!this.isStarted()) {
|
|
25413
|
-
throw new CodeError$2('Not started', codes.ERR_NODE_NOT_STARTED);
|
|
25450
|
+
throw new CodeError$2('Not started', codes$1.ERR_NODE_NOT_STARTED);
|
|
25414
25451
|
}
|
|
25415
25452
|
options.signal?.throwIfAborted();
|
|
25416
25453
|
const { peerId } = getPeerAddress(peerIdOrMultiaddr);
|
|
@@ -25543,7 +25580,7 @@
|
|
|
25543
25580
|
*/
|
|
25544
25581
|
async *findProviders(key, options = {}) {
|
|
25545
25582
|
if (this.routers.length === 0) {
|
|
25546
|
-
throw new CodeError$2('No content routers available', codes.ERR_NO_ROUTERS_AVAILABLE);
|
|
25583
|
+
throw new CodeError$2('No content routers available', codes$1.ERR_NO_ROUTERS_AVAILABLE);
|
|
25547
25584
|
}
|
|
25548
25585
|
const self = this;
|
|
25549
25586
|
const seen = new PeerSet();
|
|
@@ -25573,7 +25610,7 @@
|
|
|
25573
25610
|
*/
|
|
25574
25611
|
async provide(key, options = {}) {
|
|
25575
25612
|
if (this.routers.length === 0) {
|
|
25576
|
-
throw new CodeError$2('No content routers available', codes.ERR_NO_ROUTERS_AVAILABLE);
|
|
25613
|
+
throw new CodeError$2('No content routers available', codes$1.ERR_NO_ROUTERS_AVAILABLE);
|
|
25577
25614
|
}
|
|
25578
25615
|
await Promise.all(this.routers.map(async (router) => {
|
|
25579
25616
|
await router.provide(key, options);
|
|
@@ -25584,7 +25621,7 @@
|
|
|
25584
25621
|
*/
|
|
25585
25622
|
async put(key, value, options) {
|
|
25586
25623
|
if (!this.isStarted()) {
|
|
25587
|
-
throw new CodeError$2(messages.NOT_STARTED_YET, codes.ERR_NODE_NOT_STARTED);
|
|
25624
|
+
throw new CodeError$2(messages.NOT_STARTED_YET, codes$1.ERR_NODE_NOT_STARTED);
|
|
25588
25625
|
}
|
|
25589
25626
|
await Promise.all(this.routers.map(async (router) => {
|
|
25590
25627
|
await router.put(key, value, options);
|
|
@@ -25596,7 +25633,7 @@
|
|
|
25596
25633
|
*/
|
|
25597
25634
|
async get(key, options) {
|
|
25598
25635
|
if (!this.isStarted()) {
|
|
25599
|
-
throw new CodeError$2(messages.NOT_STARTED_YET, codes.ERR_NODE_NOT_STARTED);
|
|
25636
|
+
throw new CodeError$2(messages.NOT_STARTED_YET, codes$1.ERR_NODE_NOT_STARTED);
|
|
25600
25637
|
}
|
|
25601
25638
|
return Promise.any(this.routers.map(async (router) => {
|
|
25602
25639
|
return router.get(key, options);
|
|
@@ -25813,10 +25850,10 @@
|
|
|
25813
25850
|
*/
|
|
25814
25851
|
async findPeer(id, options) {
|
|
25815
25852
|
if (this.routers.length === 0) {
|
|
25816
|
-
throw new CodeError$2('No peer routers available', codes.ERR_NO_ROUTERS_AVAILABLE);
|
|
25853
|
+
throw new CodeError$2('No peer routers available', codes$1.ERR_NO_ROUTERS_AVAILABLE);
|
|
25817
25854
|
}
|
|
25818
25855
|
if (id.toString() === this.peerId.toString()) {
|
|
25819
|
-
throw new CodeError$2('Should not try to find self', codes.ERR_FIND_SELF);
|
|
25856
|
+
throw new CodeError$2('Should not try to find self', codes$1.ERR_FIND_SELF);
|
|
25820
25857
|
}
|
|
25821
25858
|
const self = this;
|
|
25822
25859
|
const source = merge$1(...this.routers.map(router => (async function* () {
|
|
@@ -25839,14 +25876,14 @@
|
|
|
25839
25876
|
}
|
|
25840
25877
|
return peer;
|
|
25841
25878
|
}
|
|
25842
|
-
throw new CodeError$2(messages.NOT_FOUND, codes.ERR_NOT_FOUND);
|
|
25879
|
+
throw new CodeError$2(messages.NOT_FOUND, codes$1.ERR_NOT_FOUND);
|
|
25843
25880
|
}
|
|
25844
25881
|
/**
|
|
25845
25882
|
* Attempt to find the closest peers on the network to the given key
|
|
25846
25883
|
*/
|
|
25847
25884
|
async *getClosestPeers(key, options = {}) {
|
|
25848
25885
|
if (this.routers.length === 0) {
|
|
25849
|
-
throw new CodeError$2('No peer routers available', codes.ERR_NO_ROUTERS_AVAILABLE);
|
|
25886
|
+
throw new CodeError$2('No peer routers available', codes$1.ERR_NO_ROUTERS_AVAILABLE);
|
|
25850
25887
|
}
|
|
25851
25888
|
const self = this;
|
|
25852
25889
|
const seen = new PeerSet();
|
|
@@ -26022,7 +26059,7 @@
|
|
|
26022
26059
|
getHandler(protocol) {
|
|
26023
26060
|
const handler = this.handlers.get(protocol);
|
|
26024
26061
|
if (handler == null) {
|
|
26025
|
-
throw new CodeError$2(`No handler registered for protocol ${protocol}`, codes.ERR_NO_HANDLER_FOR_PROTOCOL);
|
|
26062
|
+
throw new CodeError$2(`No handler registered for protocol ${protocol}`, codes$1.ERR_NO_HANDLER_FOR_PROTOCOL);
|
|
26026
26063
|
}
|
|
26027
26064
|
return handler;
|
|
26028
26065
|
}
|
|
@@ -26040,7 +26077,7 @@
|
|
|
26040
26077
|
*/
|
|
26041
26078
|
async handle(protocol, handler, opts) {
|
|
26042
26079
|
if (this.handlers.has(protocol)) {
|
|
26043
|
-
throw new CodeError$2(`Handler already registered for protocol ${protocol}`, codes.ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED);
|
|
26080
|
+
throw new CodeError$2(`Handler already registered for protocol ${protocol}`, codes$1.ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED);
|
|
26044
26081
|
}
|
|
26045
26082
|
const options = mergeOptions$1.bind({ ignoreUndefined: true })({
|
|
26046
26083
|
maxInboundStreams: DEFAULT_MAX_INBOUND_STREAMS$1,
|
|
@@ -26074,7 +26111,7 @@
|
|
|
26074
26111
|
*/
|
|
26075
26112
|
async register(protocol, topology) {
|
|
26076
26113
|
if (topology == null) {
|
|
26077
|
-
throw new CodeError$2('invalid topology', codes.ERR_INVALID_PARAMETERS);
|
|
26114
|
+
throw new CodeError$2('invalid topology', codes$1.ERR_INVALID_PARAMETERS);
|
|
26078
26115
|
}
|
|
26079
26116
|
// Create topology
|
|
26080
26117
|
const id = `${(Math.random() * 1e9).toString(36)}${Date.now()}`;
|
|
@@ -26122,7 +26159,7 @@
|
|
|
26122
26159
|
}
|
|
26123
26160
|
})
|
|
26124
26161
|
.catch(err => {
|
|
26125
|
-
if (err.code === codes.ERR_NOT_FOUND) {
|
|
26162
|
+
if (err.code === codes$1.ERR_NOT_FOUND) {
|
|
26126
26163
|
// peer has not completed identify so they are not in the peer store
|
|
26127
26164
|
return;
|
|
26128
26165
|
}
|
|
@@ -26241,10 +26278,10 @@
|
|
|
26241
26278
|
add(transport) {
|
|
26242
26279
|
const tag = transport[Symbol.toStringTag];
|
|
26243
26280
|
if (tag == null) {
|
|
26244
|
-
throw new CodeError$2('Transport must have a valid tag', codes.ERR_INVALID_KEY);
|
|
26281
|
+
throw new CodeError$2('Transport must have a valid tag', codes$1.ERR_INVALID_KEY);
|
|
26245
26282
|
}
|
|
26246
26283
|
if (this.transports.has(tag)) {
|
|
26247
|
-
throw new CodeError$2(`There is already a transport with the tag ${tag}`, codes.ERR_DUPLICATE_TRANSPORT);
|
|
26284
|
+
throw new CodeError$2(`There is already a transport with the tag ${tag}`, codes$1.ERR_DUPLICATE_TRANSPORT);
|
|
26248
26285
|
}
|
|
26249
26286
|
this.log('adding transport %s', tag);
|
|
26250
26287
|
this.transports.set(tag, transport);
|
|
@@ -26291,7 +26328,7 @@
|
|
|
26291
26328
|
async dial(ma, options) {
|
|
26292
26329
|
const transport = this.dialTransportForMultiaddr(ma);
|
|
26293
26330
|
if (transport == null) {
|
|
26294
|
-
throw new CodeError$2(`No transport available for address ${String(ma)}`, codes.ERR_TRANSPORT_UNAVAILABLE);
|
|
26331
|
+
throw new CodeError$2(`No transport available for address ${String(ma)}`, codes$1.ERR_TRANSPORT_UNAVAILABLE);
|
|
26295
26332
|
}
|
|
26296
26333
|
try {
|
|
26297
26334
|
return await transport.dial(ma, {
|
|
@@ -26301,7 +26338,7 @@
|
|
|
26301
26338
|
}
|
|
26302
26339
|
catch (err) {
|
|
26303
26340
|
if (err.code == null) {
|
|
26304
|
-
err.code = codes.ERR_TRANSPORT_DIAL_FAILED;
|
|
26341
|
+
err.code = codes$1.ERR_TRANSPORT_DIAL_FAILED;
|
|
26305
26342
|
}
|
|
26306
26343
|
throw err;
|
|
26307
26344
|
}
|
|
@@ -26357,7 +26394,7 @@
|
|
|
26357
26394
|
*/
|
|
26358
26395
|
async listen(addrs) {
|
|
26359
26396
|
if (!this.isStarted()) {
|
|
26360
|
-
throw new CodeError$2('Not started', codes.ERR_NODE_NOT_STARTED);
|
|
26397
|
+
throw new CodeError$2('Not started', codes$1.ERR_NODE_NOT_STARTED);
|
|
26361
26398
|
}
|
|
26362
26399
|
if (addrs == null || addrs.length === 0) {
|
|
26363
26400
|
this.log('no addresses were provided for listening, this node is dial only');
|
|
@@ -26408,7 +26445,7 @@
|
|
|
26408
26445
|
// just wait for any (`p-any`) listener to succeed on each transport before returning
|
|
26409
26446
|
const isListening = results.find(r => r.status === 'fulfilled');
|
|
26410
26447
|
if ((isListening == null) && this.faultTolerance !== FaultTolerance.NO_FATAL) {
|
|
26411
|
-
throw new CodeError$2(`Transport (${key}) could not listen on any available address`, codes.ERR_NO_VALID_ADDRESSES);
|
|
26448
|
+
throw new CodeError$2(`Transport (${key}) could not listen on any available address`, codes$1.ERR_NO_VALID_ADDRESSES);
|
|
26412
26449
|
}
|
|
26413
26450
|
}
|
|
26414
26451
|
// If no transports were able to listen, throw an error. This likely
|
|
@@ -26416,7 +26453,7 @@
|
|
|
26416
26453
|
if (couldNotListen.length === this.transports.size) {
|
|
26417
26454
|
const message = `no valid addresses were provided for transports [${couldNotListen.join(', ')}]`;
|
|
26418
26455
|
if (this.faultTolerance === FaultTolerance.FATAL_ALL) {
|
|
26419
|
-
throw new CodeError$2(message, codes.ERR_NO_VALID_ADDRESSES);
|
|
26456
|
+
throw new CodeError$2(message, codes$1.ERR_NO_VALID_ADDRESSES);
|
|
26420
26457
|
}
|
|
26421
26458
|
this.log(`libp2p in dial mode only: ${message}`);
|
|
26422
26459
|
}
|
|
@@ -27016,7 +27053,7 @@
|
|
|
27016
27053
|
return options.maxInboundStreams;
|
|
27017
27054
|
}
|
|
27018
27055
|
catch (err) {
|
|
27019
|
-
if (err.code !== codes.ERR_NO_HANDLER_FOR_PROTOCOL) {
|
|
27056
|
+
if (err.code !== codes$1.ERR_NO_HANDLER_FOR_PROTOCOL) {
|
|
27020
27057
|
throw err;
|
|
27021
27058
|
}
|
|
27022
27059
|
}
|
|
@@ -27030,7 +27067,7 @@
|
|
|
27030
27067
|
}
|
|
27031
27068
|
}
|
|
27032
27069
|
catch (err) {
|
|
27033
|
-
if (err.code !== codes.ERR_NO_HANDLER_FOR_PROTOCOL) {
|
|
27070
|
+
if (err.code !== codes$1.ERR_NO_HANDLER_FOR_PROTOCOL) {
|
|
27034
27071
|
throw err;
|
|
27035
27072
|
}
|
|
27036
27073
|
}
|
|
@@ -27068,7 +27105,7 @@
|
|
|
27068
27105
|
const connectionGater = this.components.connectionGater[connectionType];
|
|
27069
27106
|
if (connectionGater !== undefined) {
|
|
27070
27107
|
if (await connectionGater(remotePeer, maConn)) {
|
|
27071
|
-
throw new CodeError$2(`The multiaddr connection is blocked by gater.${connectionType}`, codes.ERR_CONNECTION_INTERCEPTED);
|
|
27108
|
+
throw new CodeError$2(`The multiaddr connection is blocked by gater.${connectionType}`, codes$1.ERR_CONNECTION_INTERCEPTED);
|
|
27072
27109
|
}
|
|
27073
27110
|
}
|
|
27074
27111
|
}
|
|
@@ -27078,7 +27115,7 @@
|
|
|
27078
27115
|
async upgradeInbound(maConn, opts) {
|
|
27079
27116
|
const accept = await this.components.connectionManager.acceptIncomingConnection(maConn);
|
|
27080
27117
|
if (!accept) {
|
|
27081
|
-
throw new CodeError$2('connection denied', codes.ERR_CONNECTION_DENIED);
|
|
27118
|
+
throw new CodeError$2('connection denied', codes$1.ERR_CONNECTION_DENIED);
|
|
27082
27119
|
}
|
|
27083
27120
|
let encryptedConn;
|
|
27084
27121
|
let remotePeer;
|
|
@@ -27093,7 +27130,7 @@
|
|
|
27093
27130
|
setMaxListeners(Infinity, signal);
|
|
27094
27131
|
try {
|
|
27095
27132
|
if ((await this.components.connectionGater.denyInboundConnection?.(maConn)) === true) {
|
|
27096
|
-
throw new CodeError$2('The multiaddr connection is blocked by gater.acceptConnection', codes.ERR_CONNECTION_INTERCEPTED);
|
|
27133
|
+
throw new CodeError$2('The multiaddr connection is blocked by gater.acceptConnection', codes$1.ERR_CONNECTION_INTERCEPTED);
|
|
27097
27134
|
}
|
|
27098
27135
|
this.components.metrics?.trackMultiaddrConnection(maConn);
|
|
27099
27136
|
maConn.log('starting the inbound connection upgrade');
|
|
@@ -27124,7 +27161,7 @@
|
|
|
27124
27161
|
else {
|
|
27125
27162
|
const idStr = maConn.remoteAddr.getPeerId();
|
|
27126
27163
|
if (idStr == null) {
|
|
27127
|
-
throw new CodeError$2('inbound connection that skipped encryption must have a peer id', codes.ERR_INVALID_MULTIADDR);
|
|
27164
|
+
throw new CodeError$2('inbound connection that skipped encryption must have a peer id', codes$1.ERR_INVALID_MULTIADDR);
|
|
27128
27165
|
}
|
|
27129
27166
|
const remotePeerId = peerIdFromString(idStr);
|
|
27130
27167
|
cryptoProtocol = 'native';
|
|
@@ -27209,7 +27246,7 @@
|
|
|
27209
27246
|
}
|
|
27210
27247
|
else {
|
|
27211
27248
|
if (remotePeerId == null) {
|
|
27212
|
-
throw new CodeError$2('Encryption was skipped but no peer id was passed', codes.ERR_INVALID_PEER);
|
|
27249
|
+
throw new CodeError$2('Encryption was skipped but no peer id was passed', codes$1.ERR_INVALID_PEER);
|
|
27213
27250
|
}
|
|
27214
27251
|
cryptoProtocol = 'native';
|
|
27215
27252
|
remotePeer = remotePeerId;
|
|
@@ -27276,7 +27313,7 @@
|
|
|
27276
27313
|
const incomingLimit = findIncomingStreamLimit(protocol, this.components.registrar);
|
|
27277
27314
|
const streamCount = countStreams(protocol, 'inbound', connection);
|
|
27278
27315
|
if (streamCount === incomingLimit) {
|
|
27279
|
-
const err = new CodeError$2(`Too many inbound protocol streams for protocol "${protocol}" - limit ${incomingLimit}`, codes.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS);
|
|
27316
|
+
const err = new CodeError$2(`Too many inbound protocol streams for protocol "${protocol}" - limit ${incomingLimit}`, codes$1.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS);
|
|
27280
27317
|
muxedStream.abort(err);
|
|
27281
27318
|
throw err;
|
|
27282
27319
|
}
|
|
@@ -27315,7 +27352,7 @@
|
|
|
27315
27352
|
});
|
|
27316
27353
|
newStream = async (protocols, options = {}) => {
|
|
27317
27354
|
if (muxer == null) {
|
|
27318
|
-
throw new CodeError$2('Stream is not multiplexed', codes.ERR_MUXER_UNAVAILABLE);
|
|
27355
|
+
throw new CodeError$2('Stream is not multiplexed', codes$1.ERR_MUXER_UNAVAILABLE);
|
|
27319
27356
|
}
|
|
27320
27357
|
connection.log('starting new stream for protocols %s', protocols);
|
|
27321
27358
|
const muxedStream = await muxer.newStream();
|
|
@@ -27340,7 +27377,7 @@
|
|
|
27340
27377
|
const outgoingLimit = findOutgoingStreamLimit(protocol, this.components.registrar, options);
|
|
27341
27378
|
const streamCount = countStreams(protocol, 'outbound', connection);
|
|
27342
27379
|
if (streamCount >= outgoingLimit) {
|
|
27343
|
-
const err = new CodeError$2(`Too many outbound protocol streams for protocol "${protocol}" - ${streamCount}/${outgoingLimit}`, codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS);
|
|
27380
|
+
const err = new CodeError$2(`Too many outbound protocol streams for protocol "${protocol}" - ${streamCount}/${outgoingLimit}`, codes$1.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS);
|
|
27344
27381
|
muxedStream.abort(err);
|
|
27345
27382
|
throw err;
|
|
27346
27383
|
}
|
|
@@ -27377,7 +27414,7 @@
|
|
|
27377
27414
|
if (err.code != null) {
|
|
27378
27415
|
throw err;
|
|
27379
27416
|
}
|
|
27380
|
-
throw new CodeError$2(String(err), codes.ERR_UNSUPPORTED_PROTOCOL);
|
|
27417
|
+
throw new CodeError$2(String(err), codes$1.ERR_UNSUPPORTED_PROTOCOL);
|
|
27381
27418
|
}
|
|
27382
27419
|
};
|
|
27383
27420
|
// Pipe all data through the muxer
|
|
@@ -27416,7 +27453,7 @@
|
|
|
27416
27453
|
});
|
|
27417
27454
|
maConn.timeline.upgraded = Date.now();
|
|
27418
27455
|
const errConnectionNotMultiplexed = () => {
|
|
27419
|
-
throw new CodeError$2('connection is not multiplexed', codes.ERR_CONNECTION_NOT_MULTIPLEXED);
|
|
27456
|
+
throw new CodeError$2('connection is not multiplexed', codes$1.ERR_CONNECTION_NOT_MULTIPLEXED);
|
|
27420
27457
|
};
|
|
27421
27458
|
// Create the connection
|
|
27422
27459
|
connection = createConnection({
|
|
@@ -27493,7 +27530,7 @@
|
|
|
27493
27530
|
}
|
|
27494
27531
|
catch (err) {
|
|
27495
27532
|
connection.log.error('encrypting inbound connection failed', err);
|
|
27496
|
-
throw new CodeError$2(err.message, codes.ERR_ENCRYPTION_FAILED);
|
|
27533
|
+
throw new CodeError$2(err.message, codes$1.ERR_ENCRYPTION_FAILED);
|
|
27497
27534
|
}
|
|
27498
27535
|
}
|
|
27499
27536
|
/**
|
|
@@ -27521,7 +27558,7 @@
|
|
|
27521
27558
|
}
|
|
27522
27559
|
catch (err) {
|
|
27523
27560
|
connection.log.error('encrypting outbound connection to %p failed', remotePeerId, err);
|
|
27524
|
-
throw new CodeError$2(err.message, codes.ERR_ENCRYPTION_FAILED);
|
|
27561
|
+
throw new CodeError$2(err.message, codes$1.ERR_ENCRYPTION_FAILED);
|
|
27525
27562
|
}
|
|
27526
27563
|
}
|
|
27527
27564
|
/**
|
|
@@ -27543,7 +27580,7 @@
|
|
|
27543
27580
|
}
|
|
27544
27581
|
catch (err) {
|
|
27545
27582
|
connection.log.error('error multiplexing outbound connection', err);
|
|
27546
|
-
throw new CodeError$2(String(err), codes.ERR_MUXER_UNAVAILABLE);
|
|
27583
|
+
throw new CodeError$2(String(err), codes$1.ERR_MUXER_UNAVAILABLE);
|
|
27547
27584
|
}
|
|
27548
27585
|
}
|
|
27549
27586
|
/**
|
|
@@ -27562,7 +27599,7 @@
|
|
|
27562
27599
|
}
|
|
27563
27600
|
catch (err) {
|
|
27564
27601
|
connection.log.error('error multiplexing inbound connection', err);
|
|
27565
|
-
throw new CodeError$2(String(err), codes.ERR_MUXER_UNAVAILABLE);
|
|
27602
|
+
throw new CodeError$2(String(err), codes$1.ERR_MUXER_UNAVAILABLE);
|
|
27566
27603
|
}
|
|
27567
27604
|
}
|
|
27568
27605
|
}
|
|
@@ -27769,11 +27806,11 @@
|
|
|
27769
27806
|
}
|
|
27770
27807
|
async dialProtocol(peer, protocols, options = {}) {
|
|
27771
27808
|
if (protocols == null) {
|
|
27772
|
-
throw new CodeError$2('no protocols were provided to open a stream', codes.ERR_INVALID_PROTOCOLS_FOR_STREAM);
|
|
27809
|
+
throw new CodeError$2('no protocols were provided to open a stream', codes$1.ERR_INVALID_PROTOCOLS_FOR_STREAM);
|
|
27773
27810
|
}
|
|
27774
27811
|
protocols = Array.isArray(protocols) ? protocols : [protocols];
|
|
27775
27812
|
if (protocols.length === 0) {
|
|
27776
|
-
throw new CodeError$2('no protocols were provided to open a stream', codes.ERR_INVALID_PROTOCOLS_FOR_STREAM);
|
|
27813
|
+
throw new CodeError$2('no protocols were provided to open a stream', codes$1.ERR_INVALID_PROTOCOLS_FOR_STREAM);
|
|
27777
27814
|
}
|
|
27778
27815
|
const connection = await this.dial(peer, options);
|
|
27779
27816
|
return connection.newStream(protocols, options);
|
|
@@ -27805,7 +27842,7 @@
|
|
|
27805
27842
|
}
|
|
27806
27843
|
}
|
|
27807
27844
|
catch (err) {
|
|
27808
|
-
if (err.code !== codes.ERR_NOT_FOUND) {
|
|
27845
|
+
if (err.code !== codes$1.ERR_NOT_FOUND) {
|
|
27809
27846
|
throw err;
|
|
27810
27847
|
}
|
|
27811
27848
|
}
|
|
@@ -27854,7 +27891,7 @@
|
|
|
27854
27891
|
#onDiscoveryPeer(evt) {
|
|
27855
27892
|
const { detail: peer } = evt;
|
|
27856
27893
|
if (peer.id.toString() === this.peerId.toString()) {
|
|
27857
|
-
this.log.error(new Error(codes.ERR_DISCOVERED_SELF));
|
|
27894
|
+
this.log.error(new Error(codes$1.ERR_DISCOVERED_SELF));
|
|
27858
27895
|
return;
|
|
27859
27896
|
}
|
|
27860
27897
|
void this.components.peerStore.merge(peer.id, {
|
|
@@ -32067,484 +32104,2755 @@
|
|
|
32067
32104
|
};
|
|
32068
32105
|
}
|
|
32069
32106
|
|
|
32070
|
-
|
|
32071
|
-
|
|
32072
|
-
|
|
32073
|
-
|
|
32074
|
-
|
|
32075
|
-
|
|
32076
|
-
|
|
32077
|
-
|
|
32078
|
-
|
|
32079
|
-
|
|
32080
|
-
|
|
32081
|
-
|
|
32082
|
-
|
|
32083
|
-
ERR_DECODE_INVALID_VERSION,
|
|
32084
|
-
ERR_BOTH_CLIENTS,
|
|
32085
|
-
ERR_RECV_WINDOW_EXCEEDED
|
|
32086
|
-
]);
|
|
32087
|
-
// local errors
|
|
32088
|
-
const ERR_INVALID_CONFIG = 'ERR_INVALID_CONFIG';
|
|
32089
|
-
const ERR_MUXER_LOCAL_CLOSED = 'ERR_MUXER_LOCAL_CLOSED';
|
|
32090
|
-
const ERR_MUXER_REMOTE_CLOSED = 'ERR_MUXER_REMOTE_CLOSED';
|
|
32091
|
-
const ERR_STREAM_ABORT = 'ERR_STREAM_ABORT';
|
|
32092
|
-
const ERR_MAX_OUTBOUND_STREAMS_EXCEEDED = 'ERROR_MAX_OUTBOUND_STREAMS_EXCEEDED';
|
|
32093
|
-
const ERR_DECODE_IN_PROGRESS = 'ERR_DECODE_IN_PROGRESS';
|
|
32094
|
-
/**
|
|
32095
|
-
* INITIAL_STREAM_WINDOW is the initial stream window size.
|
|
32096
|
-
*
|
|
32097
|
-
* Not an implementation choice, this is defined in the specification
|
|
32098
|
-
*/
|
|
32099
|
-
const INITIAL_STREAM_WINDOW = 256 * 1024;
|
|
32100
|
-
/**
|
|
32101
|
-
* Default max stream window
|
|
32102
|
-
*/
|
|
32103
|
-
const MAX_STREAM_WINDOW = 16 * 1024 * 1024;
|
|
32107
|
+
var codes;
|
|
32108
|
+
(function (codes) {
|
|
32109
|
+
codes["ERR_ALREADY_ABORTED"] = "ERR_ALREADY_ABORTED";
|
|
32110
|
+
codes["ERR_DATA_CHANNEL"] = "ERR_DATA_CHANNEL";
|
|
32111
|
+
codes["ERR_CONNECTION_CLOSED"] = "ERR_CONNECTION_CLOSED";
|
|
32112
|
+
codes["ERR_HASH_NOT_SUPPORTED"] = "ERR_HASH_NOT_SUPPORTED";
|
|
32113
|
+
codes["ERR_INVALID_MULTIADDR"] = "ERR_INVALID_MULTIADDR";
|
|
32114
|
+
codes["ERR_INVALID_FINGERPRINT"] = "ERR_INVALID_FINGERPRINT";
|
|
32115
|
+
codes["ERR_INVALID_PARAMETERS"] = "ERR_INVALID_PARAMETERS";
|
|
32116
|
+
codes["ERR_NOT_IMPLEMENTED"] = "ERR_NOT_IMPLEMENTED";
|
|
32117
|
+
codes["ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS"] = "ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS";
|
|
32118
|
+
codes["ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS"] = "ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS";
|
|
32119
|
+
})(codes || (codes = {}));
|
|
32104
32120
|
|
|
32105
|
-
|
|
32106
|
-
|
|
32107
|
-
|
|
32108
|
-
|
|
32109
|
-
|
|
32110
|
-
|
|
32111
|
-
maxStreamWindowSize: MAX_STREAM_WINDOW,
|
|
32112
|
-
maxMessageSize: 64 * 1024
|
|
32113
|
-
};
|
|
32114
|
-
function verifyConfig(config) {
|
|
32115
|
-
if (config.keepAliveInterval <= 0) {
|
|
32116
|
-
throw new CodeError$2('keep-alive interval must be positive', ERR_INVALID_CONFIG);
|
|
32117
|
-
}
|
|
32118
|
-
if (config.maxInboundStreams < 0) {
|
|
32119
|
-
throw new CodeError$2('max inbound streams must be larger or equal 0', ERR_INVALID_CONFIG);
|
|
32121
|
+
var __spreadArray = (undefined && undefined.__spreadArray) || function (to, from, pack) {
|
|
32122
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
32123
|
+
if (ar || !(i in from)) {
|
|
32124
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
32125
|
+
ar[i] = from[i];
|
|
32126
|
+
}
|
|
32120
32127
|
}
|
|
32121
|
-
|
|
32122
|
-
|
|
32128
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
32129
|
+
};
|
|
32130
|
+
var BrowserInfo = /** @class */ (function () {
|
|
32131
|
+
function BrowserInfo(name, version, os) {
|
|
32132
|
+
this.name = name;
|
|
32133
|
+
this.version = version;
|
|
32134
|
+
this.os = os;
|
|
32135
|
+
this.type = 'browser';
|
|
32123
32136
|
}
|
|
32124
|
-
|
|
32125
|
-
|
|
32137
|
+
return BrowserInfo;
|
|
32138
|
+
}());
|
|
32139
|
+
var NodeInfo = /** @class */ (function () {
|
|
32140
|
+
function NodeInfo(version) {
|
|
32141
|
+
this.version = version;
|
|
32142
|
+
this.type = 'node';
|
|
32143
|
+
this.name = 'node';
|
|
32144
|
+
this.os = process.platform;
|
|
32145
|
+
}
|
|
32146
|
+
return NodeInfo;
|
|
32147
|
+
}());
|
|
32148
|
+
var SearchBotDeviceInfo = /** @class */ (function () {
|
|
32149
|
+
function SearchBotDeviceInfo(name, version, os, bot) {
|
|
32150
|
+
this.name = name;
|
|
32151
|
+
this.version = version;
|
|
32152
|
+
this.os = os;
|
|
32153
|
+
this.bot = bot;
|
|
32154
|
+
this.type = 'bot-device';
|
|
32155
|
+
}
|
|
32156
|
+
return SearchBotDeviceInfo;
|
|
32157
|
+
}());
|
|
32158
|
+
var BotInfo = /** @class */ (function () {
|
|
32159
|
+
function BotInfo() {
|
|
32160
|
+
this.type = 'bot';
|
|
32161
|
+
this.bot = true; // NOTE: deprecated test name instead
|
|
32162
|
+
this.name = 'bot';
|
|
32163
|
+
this.version = null;
|
|
32164
|
+
this.os = null;
|
|
32165
|
+
}
|
|
32166
|
+
return BotInfo;
|
|
32167
|
+
}());
|
|
32168
|
+
var ReactNativeInfo = /** @class */ (function () {
|
|
32169
|
+
function ReactNativeInfo() {
|
|
32170
|
+
this.type = 'react-native';
|
|
32171
|
+
this.name = 'react-native';
|
|
32172
|
+
this.version = null;
|
|
32173
|
+
this.os = null;
|
|
32174
|
+
}
|
|
32175
|
+
return ReactNativeInfo;
|
|
32176
|
+
}());
|
|
32177
|
+
// tslint:disable-next-line:max-line-length
|
|
32178
|
+
var SEARCHBOX_UA_REGEX = /alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/;
|
|
32179
|
+
var SEARCHBOT_OS_REGEX = /(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/;
|
|
32180
|
+
var REQUIRED_VERSION_PARTS = 3;
|
|
32181
|
+
var userAgentRules = [
|
|
32182
|
+
['aol', /AOLShield\/([0-9\._]+)/],
|
|
32183
|
+
['edge', /Edge\/([0-9\._]+)/],
|
|
32184
|
+
['edge-ios', /EdgiOS\/([0-9\._]+)/],
|
|
32185
|
+
['yandexbrowser', /YaBrowser\/([0-9\._]+)/],
|
|
32186
|
+
['kakaotalk', /KAKAOTALK\s([0-9\.]+)/],
|
|
32187
|
+
['samsung', /SamsungBrowser\/([0-9\.]+)/],
|
|
32188
|
+
['silk', /\bSilk\/([0-9._-]+)\b/],
|
|
32189
|
+
['miui', /MiuiBrowser\/([0-9\.]+)$/],
|
|
32190
|
+
['beaker', /BeakerBrowser\/([0-9\.]+)/],
|
|
32191
|
+
['edge-chromium', /EdgA?\/([0-9\.]+)/],
|
|
32192
|
+
[
|
|
32193
|
+
'chromium-webview',
|
|
32194
|
+
/(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/,
|
|
32195
|
+
],
|
|
32196
|
+
['chrome', /(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/],
|
|
32197
|
+
['phantomjs', /PhantomJS\/([0-9\.]+)(:?\s|$)/],
|
|
32198
|
+
['crios', /CriOS\/([0-9\.]+)(:?\s|$)/],
|
|
32199
|
+
['firefox', /Firefox\/([0-9\.]+)(?:\s|$)/],
|
|
32200
|
+
['fxios', /FxiOS\/([0-9\.]+)/],
|
|
32201
|
+
['opera-mini', /Opera Mini.*Version\/([0-9\.]+)/],
|
|
32202
|
+
['opera', /Opera\/([0-9\.]+)(?:\s|$)/],
|
|
32203
|
+
['opera', /OPR\/([0-9\.]+)(:?\s|$)/],
|
|
32204
|
+
['pie', /^Microsoft Pocket Internet Explorer\/(\d+\.\d+)$/],
|
|
32205
|
+
['pie', /^Mozilla\/\d\.\d+\s\(compatible;\s(?:MSP?IE|MSInternet Explorer) (\d+\.\d+);.*Windows CE.*\)$/],
|
|
32206
|
+
['netfront', /^Mozilla\/\d\.\d+.*NetFront\/(\d.\d)/],
|
|
32207
|
+
['ie', /Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/],
|
|
32208
|
+
['ie', /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/],
|
|
32209
|
+
['ie', /MSIE\s(7\.0)/],
|
|
32210
|
+
['bb10', /BB10;\sTouch.*Version\/([0-9\.]+)/],
|
|
32211
|
+
['android', /Android\s([0-9\.]+)/],
|
|
32212
|
+
['ios', /Version\/([0-9\._]+).*Mobile.*Safari.*/],
|
|
32213
|
+
['safari', /Version\/([0-9\._]+).*Safari/],
|
|
32214
|
+
['facebook', /FB[AS]V\/([0-9\.]+)/],
|
|
32215
|
+
['instagram', /Instagram\s([0-9\.]+)/],
|
|
32216
|
+
['ios-webview', /AppleWebKit\/([0-9\.]+).*Mobile/],
|
|
32217
|
+
['ios-webview', /AppleWebKit\/([0-9\.]+).*Gecko\)$/],
|
|
32218
|
+
['curl', /^curl\/([0-9\.]+)$/],
|
|
32219
|
+
['searchbot', SEARCHBOX_UA_REGEX],
|
|
32220
|
+
];
|
|
32221
|
+
var operatingSystemRules = [
|
|
32222
|
+
['iOS', /iP(hone|od|ad)/],
|
|
32223
|
+
['Android OS', /Android/],
|
|
32224
|
+
['BlackBerry OS', /BlackBerry|BB10/],
|
|
32225
|
+
['Windows Mobile', /IEMobile/],
|
|
32226
|
+
['Amazon OS', /Kindle/],
|
|
32227
|
+
['Windows 3.11', /Win16/],
|
|
32228
|
+
['Windows 95', /(Windows 95)|(Win95)|(Windows_95)/],
|
|
32229
|
+
['Windows 98', /(Windows 98)|(Win98)/],
|
|
32230
|
+
['Windows 2000', /(Windows NT 5.0)|(Windows 2000)/],
|
|
32231
|
+
['Windows XP', /(Windows NT 5.1)|(Windows XP)/],
|
|
32232
|
+
['Windows Server 2003', /(Windows NT 5.2)/],
|
|
32233
|
+
['Windows Vista', /(Windows NT 6.0)/],
|
|
32234
|
+
['Windows 7', /(Windows NT 6.1)/],
|
|
32235
|
+
['Windows 8', /(Windows NT 6.2)/],
|
|
32236
|
+
['Windows 8.1', /(Windows NT 6.3)/],
|
|
32237
|
+
['Windows 10', /(Windows NT 10.0)/],
|
|
32238
|
+
['Windows ME', /Windows ME/],
|
|
32239
|
+
['Windows CE', /Windows CE|WinCE|Microsoft Pocket Internet Explorer/],
|
|
32240
|
+
['Open BSD', /OpenBSD/],
|
|
32241
|
+
['Sun OS', /SunOS/],
|
|
32242
|
+
['Chrome OS', /CrOS/],
|
|
32243
|
+
['Linux', /(Linux)|(X11)/],
|
|
32244
|
+
['Mac OS', /(Mac_PowerPC)|(Macintosh)/],
|
|
32245
|
+
['QNX', /QNX/],
|
|
32246
|
+
['BeOS', /BeOS/],
|
|
32247
|
+
['OS/2', /OS\/2/],
|
|
32248
|
+
];
|
|
32249
|
+
function detect(userAgent) {
|
|
32250
|
+
if (typeof document === 'undefined' &&
|
|
32251
|
+
typeof navigator !== 'undefined' &&
|
|
32252
|
+
navigator.product === 'ReactNative') {
|
|
32253
|
+
return new ReactNativeInfo();
|
|
32254
|
+
}
|
|
32255
|
+
if (typeof navigator !== 'undefined') {
|
|
32256
|
+
return parseUserAgent(navigator.userAgent);
|
|
32257
|
+
}
|
|
32258
|
+
return getNodeVersion();
|
|
32259
|
+
}
|
|
32260
|
+
function matchUserAgent(ua) {
|
|
32261
|
+
// opted for using reduce here rather than Array#first with a regex.test call
|
|
32262
|
+
// this is primarily because using the reduce we only perform the regex
|
|
32263
|
+
// execution once rather than once for the test and for the exec again below
|
|
32264
|
+
// probably something that needs to be benchmarked though
|
|
32265
|
+
return (ua !== '' &&
|
|
32266
|
+
userAgentRules.reduce(function (matched, _a) {
|
|
32267
|
+
var browser = _a[0], regex = _a[1];
|
|
32268
|
+
if (matched) {
|
|
32269
|
+
return matched;
|
|
32270
|
+
}
|
|
32271
|
+
var uaMatch = regex.exec(ua);
|
|
32272
|
+
return !!uaMatch && [browser, uaMatch];
|
|
32273
|
+
}, false));
|
|
32274
|
+
}
|
|
32275
|
+
function parseUserAgent(ua) {
|
|
32276
|
+
var matchedRule = matchUserAgent(ua);
|
|
32277
|
+
if (!matchedRule) {
|
|
32278
|
+
return null;
|
|
32126
32279
|
}
|
|
32127
|
-
|
|
32128
|
-
|
|
32280
|
+
var name = matchedRule[0], match = matchedRule[1];
|
|
32281
|
+
if (name === 'searchbot') {
|
|
32282
|
+
return new BotInfo();
|
|
32129
32283
|
}
|
|
32130
|
-
|
|
32131
|
-
|
|
32284
|
+
// Do not use RegExp for split operation as some browser do not support it (See: http://blog.stevenlevithan.com/archives/cross-browser-split)
|
|
32285
|
+
var versionParts = match[1] && match[1].split('.').join('_').split('_').slice(0, 3);
|
|
32286
|
+
if (versionParts) {
|
|
32287
|
+
if (versionParts.length < REQUIRED_VERSION_PARTS) {
|
|
32288
|
+
versionParts = __spreadArray(__spreadArray([], versionParts, true), createVersionParts(REQUIRED_VERSION_PARTS - versionParts.length), true);
|
|
32289
|
+
}
|
|
32132
32290
|
}
|
|
32133
|
-
|
|
32134
|
-
|
|
32291
|
+
else {
|
|
32292
|
+
versionParts = [];
|
|
32135
32293
|
}
|
|
32136
|
-
|
|
32137
|
-
|
|
32138
|
-
|
|
32139
|
-
|
|
32140
|
-
|
|
32141
|
-
FrameType[FrameType["Data"] = 0] = "Data";
|
|
32142
|
-
/** Used to updated the senders receive window size. This is used to implement per-session flow control. */
|
|
32143
|
-
FrameType[FrameType["WindowUpdate"] = 1] = "WindowUpdate";
|
|
32144
|
-
/** Used to measure RTT. It can also be used to heart-beat and do keep-alives over TCP. */
|
|
32145
|
-
FrameType[FrameType["Ping"] = 2] = "Ping";
|
|
32146
|
-
/** Used to close a session. */
|
|
32147
|
-
FrameType[FrameType["GoAway"] = 3] = "GoAway";
|
|
32148
|
-
})(FrameType || (FrameType = {}));
|
|
32149
|
-
var Flag;
|
|
32150
|
-
(function (Flag) {
|
|
32151
|
-
/** Signals the start of a new stream. May be sent with a data or window update message. Also sent with a ping to indicate outbound. */
|
|
32152
|
-
Flag[Flag["SYN"] = 1] = "SYN";
|
|
32153
|
-
/** Acknowledges the start of a new stream. May be sent with a data or window update message. Also sent with a ping to indicate response. */
|
|
32154
|
-
Flag[Flag["ACK"] = 2] = "ACK";
|
|
32155
|
-
/** Performs a half-close of a stream. May be sent with a data message or window update. */
|
|
32156
|
-
Flag[Flag["FIN"] = 4] = "FIN";
|
|
32157
|
-
/** Reset a stream immediately. May be sent with a data or window update message. */
|
|
32158
|
-
Flag[Flag["RST"] = 8] = "RST";
|
|
32159
|
-
})(Flag || (Flag = {}));
|
|
32160
|
-
Object.values(Flag).filter((x) => typeof x !== 'string');
|
|
32161
|
-
const YAMUX_VERSION = 0;
|
|
32162
|
-
var GoAwayCode;
|
|
32163
|
-
(function (GoAwayCode) {
|
|
32164
|
-
GoAwayCode[GoAwayCode["NormalTermination"] = 0] = "NormalTermination";
|
|
32165
|
-
GoAwayCode[GoAwayCode["ProtocolError"] = 1] = "ProtocolError";
|
|
32166
|
-
GoAwayCode[GoAwayCode["InternalError"] = 2] = "InternalError";
|
|
32167
|
-
})(GoAwayCode || (GoAwayCode = {}));
|
|
32168
|
-
const HEADER_LENGTH = 12;
|
|
32169
|
-
|
|
32170
|
-
// used to bitshift in decoding
|
|
32171
|
-
// native bitshift can overflow into a negative number, so we bitshift by multiplying by a power of 2
|
|
32172
|
-
const twoPow24 = 2 ** 24;
|
|
32173
|
-
/**
|
|
32174
|
-
* Decode a header from the front of a buffer
|
|
32175
|
-
*
|
|
32176
|
-
* @param data - Assumed to have enough bytes for a header
|
|
32177
|
-
*/
|
|
32178
|
-
function decodeHeader(data) {
|
|
32179
|
-
if (data[0] !== YAMUX_VERSION) {
|
|
32180
|
-
throw new CodeError$2('Invalid frame version', ERR_DECODE_INVALID_VERSION);
|
|
32294
|
+
var version = versionParts.join('.');
|
|
32295
|
+
var os = detectOS(ua);
|
|
32296
|
+
var searchBotMatch = SEARCHBOT_OS_REGEX.exec(ua);
|
|
32297
|
+
if (searchBotMatch && searchBotMatch[1]) {
|
|
32298
|
+
return new SearchBotDeviceInfo(name, version, os, searchBotMatch[1]);
|
|
32181
32299
|
}
|
|
32182
|
-
return
|
|
32183
|
-
type: data[1],
|
|
32184
|
-
flag: (data[2] << 8) + data[3],
|
|
32185
|
-
streamID: (data[4] * twoPow24) + (data[5] << 16) + (data[6] << 8) + data[7],
|
|
32186
|
-
length: (data[8] * twoPow24) + (data[9] << 16) + (data[10] << 8) + data[11]
|
|
32187
|
-
};
|
|
32300
|
+
return new BrowserInfo(name, version, os);
|
|
32188
32301
|
}
|
|
32189
|
-
|
|
32190
|
-
|
|
32191
|
-
|
|
32192
|
-
|
|
32193
|
-
|
|
32194
|
-
|
|
32195
|
-
buffer;
|
|
32196
|
-
/** Used to sanity check against decoding while in an inconsistent state */
|
|
32197
|
-
frameInProgress;
|
|
32198
|
-
constructor(source) {
|
|
32199
|
-
// Normally, when entering a for-await loop with an iterable/async iterable, the only ways to exit the loop are:
|
|
32200
|
-
// 1. exhaust the iterable
|
|
32201
|
-
// 2. throw an error - slow, undesirable if there's not actually an error
|
|
32202
|
-
// 3. break or return - calls the iterable's `return` method, finalizing the iterable, no more iteration possible
|
|
32203
|
-
//
|
|
32204
|
-
// In this case, we want to enter (and exit) a for-await loop per chunked data frame and continue processing the iterable.
|
|
32205
|
-
// To do this, we strip the `return` method from the iterator and can now `break` early and continue iterating.
|
|
32206
|
-
// Exiting the main for-await is still possible via 1. and 2.
|
|
32207
|
-
this.source = returnlessSource(source);
|
|
32208
|
-
this.buffer = new Uint8ArrayList();
|
|
32209
|
-
this.frameInProgress = false;
|
|
32210
|
-
}
|
|
32211
|
-
/**
|
|
32212
|
-
* Emits frames from the decoder source.
|
|
32213
|
-
*
|
|
32214
|
-
* Note: If `readData` is emitted, it _must_ be called before the next iteration
|
|
32215
|
-
* Otherwise an error is thrown
|
|
32216
|
-
*/
|
|
32217
|
-
async *emitFrames() {
|
|
32218
|
-
for await (const chunk of this.source) {
|
|
32219
|
-
this.buffer.append(chunk);
|
|
32220
|
-
// Loop to consume as many bytes from the buffer as possible
|
|
32221
|
-
// Eg: when a single chunk contains several frames
|
|
32222
|
-
while (true) {
|
|
32223
|
-
const header = this.readHeader();
|
|
32224
|
-
if (header === undefined) {
|
|
32225
|
-
break;
|
|
32226
|
-
}
|
|
32227
|
-
const { type, length } = header;
|
|
32228
|
-
if (type === FrameType.Data) {
|
|
32229
|
-
// This is a data frame, the frame body must still be read
|
|
32230
|
-
// `readData` must be called before the next iteration here
|
|
32231
|
-
this.frameInProgress = true;
|
|
32232
|
-
yield {
|
|
32233
|
-
header,
|
|
32234
|
-
readData: this.readBytes.bind(this, length)
|
|
32235
|
-
};
|
|
32236
|
-
}
|
|
32237
|
-
else {
|
|
32238
|
-
yield { header };
|
|
32239
|
-
}
|
|
32240
|
-
}
|
|
32241
|
-
}
|
|
32242
|
-
}
|
|
32243
|
-
readHeader() {
|
|
32244
|
-
// Sanity check to ensure a header isn't read when another frame is partially decoded
|
|
32245
|
-
// In practice this shouldn't happen
|
|
32246
|
-
if (this.frameInProgress) {
|
|
32247
|
-
throw new CodeError$2('decoding frame already in progress', ERR_DECODE_IN_PROGRESS);
|
|
32248
|
-
}
|
|
32249
|
-
if (this.buffer.length < HEADER_LENGTH) {
|
|
32250
|
-
// not enough data yet
|
|
32251
|
-
return;
|
|
32252
|
-
}
|
|
32253
|
-
const header = decodeHeader(this.buffer.subarray(0, HEADER_LENGTH));
|
|
32254
|
-
this.buffer.consume(HEADER_LENGTH);
|
|
32255
|
-
return header;
|
|
32256
|
-
}
|
|
32257
|
-
async readBytes(length) {
|
|
32258
|
-
if (this.buffer.length < length) {
|
|
32259
|
-
for await (const chunk of this.source) {
|
|
32260
|
-
this.buffer.append(chunk);
|
|
32261
|
-
if (this.buffer.length >= length) {
|
|
32262
|
-
// see note above, the iterator is not `return`ed here
|
|
32263
|
-
break;
|
|
32264
|
-
}
|
|
32265
|
-
}
|
|
32302
|
+
function detectOS(ua) {
|
|
32303
|
+
for (var ii = 0, count = operatingSystemRules.length; ii < count; ii++) {
|
|
32304
|
+
var _a = operatingSystemRules[ii], os = _a[0], regex = _a[1];
|
|
32305
|
+
var match = regex.exec(ua);
|
|
32306
|
+
if (match) {
|
|
32307
|
+
return os;
|
|
32266
32308
|
}
|
|
32267
|
-
const out = this.buffer.sublist(0, length);
|
|
32268
|
-
this.buffer.consume(length);
|
|
32269
|
-
// The next frame can now be decoded
|
|
32270
|
-
this.frameInProgress = false;
|
|
32271
|
-
return out;
|
|
32272
32309
|
}
|
|
32310
|
+
return null;
|
|
32273
32311
|
}
|
|
32274
|
-
|
|
32275
|
-
|
|
32276
|
-
|
|
32277
|
-
function returnlessSource(source) {
|
|
32278
|
-
if (source[Symbol.iterator] !== undefined) {
|
|
32279
|
-
const iterator = source[Symbol.iterator]();
|
|
32280
|
-
iterator.return = undefined;
|
|
32281
|
-
return {
|
|
32282
|
-
[Symbol.iterator]() { return iterator; }
|
|
32283
|
-
};
|
|
32284
|
-
}
|
|
32285
|
-
else if (source[Symbol.asyncIterator] !== undefined) {
|
|
32286
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
32287
|
-
iterator.return = undefined;
|
|
32288
|
-
return {
|
|
32289
|
-
[Symbol.asyncIterator]() { return iterator; }
|
|
32290
|
-
};
|
|
32291
|
-
}
|
|
32292
|
-
else {
|
|
32293
|
-
throw new Error('a source must be either an iterable or an async iterable');
|
|
32294
|
-
}
|
|
32312
|
+
function getNodeVersion() {
|
|
32313
|
+
var isNode = typeof process !== 'undefined' && process.version;
|
|
32314
|
+
return isNode ? new NodeInfo(process.version.slice(1)) : null;
|
|
32295
32315
|
}
|
|
32296
|
-
|
|
32297
|
-
|
|
32298
|
-
|
|
32299
|
-
|
|
32300
|
-
|
|
32301
|
-
|
|
32302
|
-
frame[2] = header.flag >>> 8;
|
|
32303
|
-
frame[3] = header.flag;
|
|
32304
|
-
frame[4] = header.streamID >>> 24;
|
|
32305
|
-
frame[5] = header.streamID >>> 16;
|
|
32306
|
-
frame[6] = header.streamID >>> 8;
|
|
32307
|
-
frame[7] = header.streamID;
|
|
32308
|
-
frame[8] = header.length >>> 24;
|
|
32309
|
-
frame[9] = header.length >>> 16;
|
|
32310
|
-
frame[10] = header.length >>> 8;
|
|
32311
|
-
frame[11] = header.length;
|
|
32312
|
-
return frame;
|
|
32316
|
+
function createVersionParts(count) {
|
|
32317
|
+
var output = [];
|
|
32318
|
+
for (var ii = 0; ii < count; ii++) {
|
|
32319
|
+
output.push('0');
|
|
32320
|
+
}
|
|
32321
|
+
return output;
|
|
32313
32322
|
}
|
|
32314
32323
|
|
|
32315
|
-
|
|
32316
|
-
|
|
32317
|
-
|
|
32318
|
-
|
|
32319
|
-
|
|
32320
|
-
|
|
32321
|
-
|
|
32322
|
-
|
|
32323
|
-
*
|
|
32324
|
-
* @example
|
|
32325
|
-
*
|
|
32326
|
-
* ```javascript
|
|
32327
|
-
* import each from 'it-foreach'
|
|
32328
|
-
* import drain from 'it-drain'
|
|
32329
|
-
*
|
|
32330
|
-
* // This can also be an iterator, generator, etc
|
|
32331
|
-
* const values = [0, 1, 2, 3, 4]
|
|
32332
|
-
*
|
|
32333
|
-
* // prints [0, 0], [1, 1], [2, 2], [3, 3], [4, 4]
|
|
32334
|
-
* const arr = drain(
|
|
32335
|
-
* each(values, console.info)
|
|
32336
|
-
* )
|
|
32337
|
-
* ```
|
|
32338
|
-
*
|
|
32339
|
-
* Async sources and callbacks must be awaited:
|
|
32340
|
-
*
|
|
32341
|
-
* ```javascript
|
|
32342
|
-
* import each from 'it-foreach'
|
|
32343
|
-
* import drain from 'it-drain'
|
|
32344
|
-
*
|
|
32345
|
-
* const values = async function * () {
|
|
32346
|
-
* yield * [0, 1, 2, 3, 4]
|
|
32347
|
-
* }
|
|
32348
|
-
*
|
|
32349
|
-
* // prints [0, 0], [1, 1], [2, 2], [3, 3], [4, 4]
|
|
32350
|
-
* const arr = await drain(
|
|
32351
|
-
* each(values(), console.info)
|
|
32352
|
-
* )
|
|
32353
|
-
* ```
|
|
32354
|
-
*/
|
|
32355
|
-
function isAsyncIterable$1(thing) {
|
|
32356
|
-
return thing[Symbol.asyncIterator] != null;
|
|
32357
|
-
}
|
|
32358
|
-
function isPromise$1(thing) {
|
|
32359
|
-
return thing?.then != null;
|
|
32360
|
-
}
|
|
32361
|
-
function forEach(source, fn) {
|
|
32362
|
-
let index = 0;
|
|
32363
|
-
if (isAsyncIterable$1(source)) {
|
|
32364
|
-
return (async function* () {
|
|
32365
|
-
for await (const val of source) {
|
|
32366
|
-
const res = fn(val, index++);
|
|
32367
|
-
if (isPromise$1(res)) {
|
|
32368
|
-
await res;
|
|
32369
|
-
}
|
|
32370
|
-
yield val;
|
|
32371
|
-
}
|
|
32372
|
-
})();
|
|
32373
|
-
}
|
|
32374
|
-
// if fn function returns a promise we have to return an async generator
|
|
32375
|
-
const peekable$1 = peekable(source);
|
|
32376
|
-
const { value, done } = peekable$1.next();
|
|
32377
|
-
if (done === true) {
|
|
32378
|
-
return (function* () { }());
|
|
32324
|
+
const browser = detect();
|
|
32325
|
+
const isFirefox = ((browser != null) && browser.name === 'firefox');
|
|
32326
|
+
const nopSource = async function* nop() { };
|
|
32327
|
+
const nopSink = async (_) => { };
|
|
32328
|
+
const DATA_CHANNEL_DRAIN_TIMEOUT = 30 * 1000;
|
|
32329
|
+
function drainAndClose(channel, direction, drainTimeout = DATA_CHANNEL_DRAIN_TIMEOUT, options) {
|
|
32330
|
+
if (channel.readyState !== 'open') {
|
|
32331
|
+
return;
|
|
32379
32332
|
}
|
|
32380
|
-
|
|
32381
|
-
|
|
32382
|
-
|
|
32383
|
-
|
|
32384
|
-
|
|
32385
|
-
|
|
32386
|
-
|
|
32387
|
-
|
|
32333
|
+
void Promise.resolve()
|
|
32334
|
+
.then(async () => {
|
|
32335
|
+
// wait for bufferedAmount to become zero
|
|
32336
|
+
if (channel.bufferedAmount > 0) {
|
|
32337
|
+
options.log('%s drain channel with %d buffered bytes', direction, channel.bufferedAmount);
|
|
32338
|
+
const deferred = pDefer();
|
|
32339
|
+
let drained = false;
|
|
32340
|
+
channel.bufferedAmountLowThreshold = 0;
|
|
32341
|
+
const closeListener = () => {
|
|
32342
|
+
if (!drained) {
|
|
32343
|
+
options.log('%s drain channel closed before drain', direction);
|
|
32344
|
+
deferred.resolve();
|
|
32388
32345
|
}
|
|
32389
|
-
|
|
32390
|
-
|
|
32391
|
-
|
|
32392
|
-
|
|
32393
|
-
|
|
32394
|
-
|
|
32395
|
-
|
|
32396
|
-
|
|
32397
|
-
|
|
32398
|
-
|
|
32346
|
+
};
|
|
32347
|
+
channel.addEventListener('close', closeListener, {
|
|
32348
|
+
once: true
|
|
32349
|
+
});
|
|
32350
|
+
channel.addEventListener('bufferedamountlow', () => {
|
|
32351
|
+
drained = true;
|
|
32352
|
+
channel.removeEventListener('close', closeListener);
|
|
32353
|
+
deferred.resolve();
|
|
32354
|
+
});
|
|
32355
|
+
await pTimeout(deferred.promise, {
|
|
32356
|
+
milliseconds: drainTimeout
|
|
32357
|
+
});
|
|
32399
32358
|
}
|
|
32400
|
-
})
|
|
32359
|
+
})
|
|
32360
|
+
.then(async () => {
|
|
32361
|
+
// only close if the channel is still open
|
|
32362
|
+
if (channel.readyState === 'open') {
|
|
32363
|
+
channel.close();
|
|
32364
|
+
}
|
|
32365
|
+
})
|
|
32366
|
+
.catch(err => {
|
|
32367
|
+
options.log.error('error closing outbound stream', err);
|
|
32368
|
+
});
|
|
32401
32369
|
}
|
|
32402
32370
|
|
|
32403
|
-
|
|
32404
|
-
|
|
32405
|
-
StreamState[StreamState["Init"] = 0] = "Init";
|
|
32406
|
-
StreamState[StreamState["SYNSent"] = 1] = "SYNSent";
|
|
32407
|
-
StreamState[StreamState["SYNReceived"] = 2] = "SYNReceived";
|
|
32408
|
-
StreamState[StreamState["Established"] = 3] = "Established";
|
|
32409
|
-
StreamState[StreamState["Finished"] = 4] = "Finished";
|
|
32410
|
-
})(StreamState || (StreamState = {}));
|
|
32411
|
-
/** YamuxStream is used to represent a logical stream within a session */
|
|
32412
|
-
class YamuxStream extends AbstractStream {
|
|
32413
|
-
name;
|
|
32414
|
-
state;
|
|
32415
|
-
config;
|
|
32416
|
-
_id;
|
|
32417
|
-
/** The number of available bytes to send */
|
|
32418
|
-
sendWindowCapacity;
|
|
32419
|
-
/** Callback to notify that the sendWindowCapacity has been updated */
|
|
32420
|
-
sendWindowCapacityUpdate;
|
|
32421
|
-
/** The number of bytes available to receive in a full window */
|
|
32422
|
-
recvWindow;
|
|
32423
|
-
/** The number of available bytes to receive */
|
|
32424
|
-
recvWindowCapacity;
|
|
32425
|
-
/**
|
|
32426
|
-
* An 'epoch' is the time it takes to process and read data
|
|
32427
|
-
*
|
|
32428
|
-
* Used in conjunction with RTT to determine whether to increase the recvWindow
|
|
32429
|
-
*/
|
|
32430
|
-
epochStart;
|
|
32431
|
-
getRTT;
|
|
32432
|
-
sendFrame;
|
|
32433
|
-
constructor(init) {
|
|
32434
|
-
super({
|
|
32435
|
-
...init,
|
|
32436
|
-
onEnd: (err) => {
|
|
32437
|
-
this.state = StreamState.Finished;
|
|
32438
|
-
init.onEnd?.(err);
|
|
32439
|
-
}
|
|
32440
|
-
});
|
|
32441
|
-
this.config = init.config;
|
|
32442
|
-
this._id = parseInt(init.id, 10);
|
|
32443
|
-
this.name = init.name;
|
|
32444
|
-
this.state = init.state;
|
|
32445
|
-
this.sendWindowCapacity = INITIAL_STREAM_WINDOW;
|
|
32446
|
-
this.recvWindow = this.config.initialStreamWindowSize;
|
|
32447
|
-
this.recvWindowCapacity = this.recvWindow;
|
|
32448
|
-
this.epochStart = Date.now();
|
|
32449
|
-
this.getRTT = init.getRTT;
|
|
32450
|
-
this.sendFrame = init.sendFrame;
|
|
32451
|
-
this.source = forEach(this.source, () => {
|
|
32452
|
-
this.sendWindowUpdate();
|
|
32453
|
-
});
|
|
32454
|
-
}
|
|
32371
|
+
class WebRTCMultiaddrConnection {
|
|
32372
|
+
log;
|
|
32455
32373
|
/**
|
|
32456
|
-
*
|
|
32457
|
-
* opened.
|
|
32458
|
-
*
|
|
32459
|
-
* This is a noop for Yamux because the first window update is sent when
|
|
32460
|
-
* .newStream is called on the muxer which opens the stream on the remote.
|
|
32374
|
+
* WebRTC Peer Connection
|
|
32461
32375
|
*/
|
|
32462
|
-
|
|
32463
|
-
}
|
|
32376
|
+
peerConnection;
|
|
32464
32377
|
/**
|
|
32465
|
-
*
|
|
32378
|
+
* The multiaddr address used to communicate with the remote peer
|
|
32466
32379
|
*/
|
|
32467
|
-
|
|
32468
|
-
buf = buf.sublist();
|
|
32469
|
-
// send in chunks, waiting for window updates
|
|
32470
|
-
while (buf.byteLength !== 0) {
|
|
32471
|
-
// wait for the send window to refill
|
|
32472
|
-
if (this.sendWindowCapacity === 0) {
|
|
32473
|
-
this.log?.trace('wait for send window capacity, status %s', this.status);
|
|
32474
|
-
await this.waitForSendWindowCapacity(options);
|
|
32475
|
-
// check we didn't close while waiting for send window capacity
|
|
32476
|
-
if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
|
|
32477
|
-
this.log?.trace('%s while waiting for send window capacity', this.status);
|
|
32478
|
-
return;
|
|
32479
|
-
}
|
|
32480
|
-
}
|
|
32481
|
-
// send as much as we can
|
|
32482
|
-
const toSend = Math.min(this.sendWindowCapacity, this.config.maxMessageSize - HEADER_LENGTH, buf.length);
|
|
32483
|
-
const flags = this.getSendFlags();
|
|
32484
|
-
this.sendFrame({
|
|
32485
|
-
type: FrameType.Data,
|
|
32486
|
-
flag: flags,
|
|
32487
|
-
streamID: this._id,
|
|
32488
|
-
length: toSend
|
|
32489
|
-
}, buf.sublist(0, toSend));
|
|
32490
|
-
this.sendWindowCapacity -= toSend;
|
|
32491
|
-
buf.consume(toSend);
|
|
32492
|
-
}
|
|
32493
|
-
}
|
|
32380
|
+
remoteAddr;
|
|
32494
32381
|
/**
|
|
32495
|
-
*
|
|
32382
|
+
* Holds the lifecycle times of the connection
|
|
32496
32383
|
*/
|
|
32497
|
-
|
|
32498
|
-
this.sendFrame({
|
|
32499
|
-
type: FrameType.WindowUpdate,
|
|
32500
|
-
flag: Flag.RST,
|
|
32501
|
-
streamID: this._id,
|
|
32502
|
-
length: 0
|
|
32503
|
-
});
|
|
32504
|
-
}
|
|
32384
|
+
timeline;
|
|
32505
32385
|
/**
|
|
32506
|
-
*
|
|
32507
|
-
* will be sent by this end of the stream
|
|
32386
|
+
* Optional metrics counter group for this connection
|
|
32508
32387
|
*/
|
|
32509
|
-
|
|
32510
|
-
const flags = this.getSendFlags() | Flag.FIN;
|
|
32511
|
-
this.sendFrame({
|
|
32512
|
-
type: FrameType.WindowUpdate,
|
|
32513
|
-
flag: flags,
|
|
32514
|
-
streamID: this._id,
|
|
32515
|
-
length: 0
|
|
32516
|
-
});
|
|
32517
|
-
}
|
|
32388
|
+
metrics;
|
|
32518
32389
|
/**
|
|
32519
|
-
*
|
|
32520
|
-
* will be read by this end of the stream
|
|
32390
|
+
* The stream source, a no-op as the transport natively supports multiplexing
|
|
32521
32391
|
*/
|
|
32522
|
-
|
|
32523
|
-
}
|
|
32392
|
+
source = nopSource();
|
|
32524
32393
|
/**
|
|
32525
|
-
*
|
|
32526
|
-
*
|
|
32527
|
-
* Will throw with ERR_STREAM_ABORT if the stream gets aborted
|
|
32394
|
+
* The stream destination, a no-op as the transport natively supports multiplexing
|
|
32528
32395
|
*/
|
|
32529
|
-
|
|
32530
|
-
|
|
32531
|
-
|
|
32532
|
-
|
|
32533
|
-
|
|
32534
|
-
|
|
32535
|
-
const
|
|
32536
|
-
|
|
32537
|
-
|
|
32538
|
-
|
|
32539
|
-
|
|
32540
|
-
|
|
32541
|
-
resolve();
|
|
32396
|
+
sink = nopSink;
|
|
32397
|
+
constructor(components, init) {
|
|
32398
|
+
this.log = components.logger.forComponent('libp2p:webrtc:maconn');
|
|
32399
|
+
this.remoteAddr = init.remoteAddr;
|
|
32400
|
+
this.timeline = init.timeline;
|
|
32401
|
+
this.peerConnection = init.peerConnection;
|
|
32402
|
+
const initialState = this.peerConnection.connectionState;
|
|
32403
|
+
this.peerConnection.onconnectionstatechange = () => {
|
|
32404
|
+
this.log.trace('peer connection state change', this.peerConnection.connectionState, 'initial state', initialState);
|
|
32405
|
+
if (this.peerConnection.connectionState === 'disconnected' || this.peerConnection.connectionState === 'failed' || this.peerConnection.connectionState === 'closed') {
|
|
32406
|
+
// nothing else to do but close the connection
|
|
32407
|
+
this.timeline.close = Date.now();
|
|
32542
32408
|
}
|
|
32543
32409
|
};
|
|
32544
|
-
|
|
32545
|
-
|
|
32546
|
-
|
|
32547
|
-
|
|
32410
|
+
}
|
|
32411
|
+
async close(options) {
|
|
32412
|
+
this.log.trace('closing connection');
|
|
32413
|
+
this.peerConnection.close();
|
|
32414
|
+
this.timeline.close = Date.now();
|
|
32415
|
+
this.metrics?.increment({ close: true });
|
|
32416
|
+
}
|
|
32417
|
+
abort(err) {
|
|
32418
|
+
this.log.error('closing connection due to error', err);
|
|
32419
|
+
this.peerConnection.close();
|
|
32420
|
+
this.timeline.close = Date.now();
|
|
32421
|
+
this.metrics?.increment({ abort: true });
|
|
32422
|
+
}
|
|
32423
|
+
}
|
|
32424
|
+
|
|
32425
|
+
const normalizeEmitter = emitter => {
|
|
32426
|
+
const addListener = emitter.addEventListener || emitter.on || emitter.addListener;
|
|
32427
|
+
const removeListener = emitter.removeEventListener || emitter.off || emitter.removeListener;
|
|
32428
|
+
|
|
32429
|
+
if (!addListener || !removeListener) {
|
|
32430
|
+
throw new TypeError('Emitter is not compatible');
|
|
32431
|
+
}
|
|
32432
|
+
|
|
32433
|
+
return {
|
|
32434
|
+
addListener: addListener.bind(emitter),
|
|
32435
|
+
removeListener: removeListener.bind(emitter),
|
|
32436
|
+
};
|
|
32437
|
+
};
|
|
32438
|
+
|
|
32439
|
+
function pEventMultiple(emitter, event, options) {
|
|
32440
|
+
let cancel;
|
|
32441
|
+
const returnValue = new Promise((resolve, reject) => {
|
|
32442
|
+
options = {
|
|
32443
|
+
rejectionEvents: ['error'],
|
|
32444
|
+
multiArgs: false,
|
|
32445
|
+
resolveImmediately: false,
|
|
32446
|
+
...options,
|
|
32447
|
+
};
|
|
32448
|
+
|
|
32449
|
+
if (!(options.count >= 0 && (options.count === Number.POSITIVE_INFINITY || Number.isInteger(options.count)))) {
|
|
32450
|
+
throw new TypeError('The `count` option should be at least 0 or more');
|
|
32451
|
+
}
|
|
32452
|
+
|
|
32453
|
+
options.signal?.throwIfAborted();
|
|
32454
|
+
|
|
32455
|
+
// Allow multiple events
|
|
32456
|
+
const events = [event].flat();
|
|
32457
|
+
|
|
32458
|
+
const items = [];
|
|
32459
|
+
const {addListener, removeListener} = normalizeEmitter(emitter);
|
|
32460
|
+
|
|
32461
|
+
const onItem = (...arguments_) => {
|
|
32462
|
+
const value = options.multiArgs ? arguments_ : arguments_[0];
|
|
32463
|
+
|
|
32464
|
+
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
32465
|
+
if (options.filter && !options.filter(value)) {
|
|
32466
|
+
return;
|
|
32467
|
+
}
|
|
32468
|
+
|
|
32469
|
+
items.push(value);
|
|
32470
|
+
|
|
32471
|
+
if (options.count === items.length) {
|
|
32472
|
+
cancel();
|
|
32473
|
+
resolve(items);
|
|
32474
|
+
}
|
|
32475
|
+
};
|
|
32476
|
+
|
|
32477
|
+
const rejectHandler = error => {
|
|
32478
|
+
cancel();
|
|
32479
|
+
reject(error);
|
|
32480
|
+
};
|
|
32481
|
+
|
|
32482
|
+
cancel = () => {
|
|
32483
|
+
for (const event of events) {
|
|
32484
|
+
removeListener(event, onItem);
|
|
32485
|
+
}
|
|
32486
|
+
|
|
32487
|
+
for (const rejectionEvent of options.rejectionEvents) {
|
|
32488
|
+
removeListener(rejectionEvent, rejectHandler);
|
|
32489
|
+
}
|
|
32490
|
+
};
|
|
32491
|
+
|
|
32492
|
+
for (const event of events) {
|
|
32493
|
+
addListener(event, onItem);
|
|
32494
|
+
}
|
|
32495
|
+
|
|
32496
|
+
for (const rejectionEvent of options.rejectionEvents) {
|
|
32497
|
+
addListener(rejectionEvent, rejectHandler);
|
|
32498
|
+
}
|
|
32499
|
+
|
|
32500
|
+
if (options.signal) {
|
|
32501
|
+
options.signal.addEventListener('abort', () => {
|
|
32502
|
+
rejectHandler(options.signal.reason);
|
|
32503
|
+
}, {once: true});
|
|
32504
|
+
}
|
|
32505
|
+
|
|
32506
|
+
if (options.resolveImmediately) {
|
|
32507
|
+
resolve(items);
|
|
32508
|
+
}
|
|
32509
|
+
});
|
|
32510
|
+
|
|
32511
|
+
returnValue.cancel = cancel;
|
|
32512
|
+
|
|
32513
|
+
if (typeof options.timeout === 'number') {
|
|
32514
|
+
const timeout = pTimeout(returnValue, {milliseconds: options.timeout});
|
|
32515
|
+
timeout.cancel = cancel;
|
|
32516
|
+
return timeout;
|
|
32517
|
+
}
|
|
32518
|
+
|
|
32519
|
+
return returnValue;
|
|
32520
|
+
}
|
|
32521
|
+
|
|
32522
|
+
function pEvent(emitter, event, options) {
|
|
32523
|
+
if (typeof options === 'function') {
|
|
32524
|
+
options = {filter: options};
|
|
32525
|
+
}
|
|
32526
|
+
|
|
32527
|
+
options = {
|
|
32528
|
+
...options,
|
|
32529
|
+
count: 1,
|
|
32530
|
+
resolveImmediately: false,
|
|
32531
|
+
};
|
|
32532
|
+
|
|
32533
|
+
const arrayPromise = pEventMultiple(emitter, event, options);
|
|
32534
|
+
const promise = arrayPromise.then(array => array[0]);
|
|
32535
|
+
promise.cancel = arrayPromise.cancel;
|
|
32536
|
+
|
|
32537
|
+
return promise;
|
|
32538
|
+
}
|
|
32539
|
+
|
|
32540
|
+
/* eslint-disable import/export */
|
|
32541
|
+
/* eslint-disable complexity */
|
|
32542
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
32543
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
|
|
32544
|
+
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
32545
|
+
var Message$2;
|
|
32546
|
+
(function (Message) {
|
|
32547
|
+
(function (Flag) {
|
|
32548
|
+
Flag["FIN"] = "FIN";
|
|
32549
|
+
Flag["STOP_SENDING"] = "STOP_SENDING";
|
|
32550
|
+
Flag["RESET"] = "RESET";
|
|
32551
|
+
Flag["FIN_ACK"] = "FIN_ACK";
|
|
32552
|
+
})(Message.Flag || (Message.Flag = {}));
|
|
32553
|
+
let __FlagValues;
|
|
32554
|
+
(function (__FlagValues) {
|
|
32555
|
+
__FlagValues[__FlagValues["FIN"] = 0] = "FIN";
|
|
32556
|
+
__FlagValues[__FlagValues["STOP_SENDING"] = 1] = "STOP_SENDING";
|
|
32557
|
+
__FlagValues[__FlagValues["RESET"] = 2] = "RESET";
|
|
32558
|
+
__FlagValues[__FlagValues["FIN_ACK"] = 3] = "FIN_ACK";
|
|
32559
|
+
})(__FlagValues || (__FlagValues = {}));
|
|
32560
|
+
(function (Flag) {
|
|
32561
|
+
Flag.codec = () => {
|
|
32562
|
+
return enumeration(__FlagValues);
|
|
32563
|
+
};
|
|
32564
|
+
})(Message.Flag || (Message.Flag = {}));
|
|
32565
|
+
let _codec;
|
|
32566
|
+
Message.codec = () => {
|
|
32567
|
+
if (_codec == null) {
|
|
32568
|
+
_codec = message((obj, w, opts = {}) => {
|
|
32569
|
+
if (opts.lengthDelimited !== false) {
|
|
32570
|
+
w.fork();
|
|
32571
|
+
}
|
|
32572
|
+
if (obj.flag != null) {
|
|
32573
|
+
w.uint32(8);
|
|
32574
|
+
Message.Flag.codec().encode(obj.flag, w);
|
|
32575
|
+
}
|
|
32576
|
+
if (obj.message != null) {
|
|
32577
|
+
w.uint32(18);
|
|
32578
|
+
w.bytes(obj.message);
|
|
32579
|
+
}
|
|
32580
|
+
if (opts.lengthDelimited !== false) {
|
|
32581
|
+
w.ldelim();
|
|
32582
|
+
}
|
|
32583
|
+
}, (reader, length) => {
|
|
32584
|
+
const obj = {};
|
|
32585
|
+
const end = length == null ? reader.len : reader.pos + length;
|
|
32586
|
+
while (reader.pos < end) {
|
|
32587
|
+
const tag = reader.uint32();
|
|
32588
|
+
switch (tag >>> 3) {
|
|
32589
|
+
case 1:
|
|
32590
|
+
obj.flag = Message.Flag.codec().decode(reader);
|
|
32591
|
+
break;
|
|
32592
|
+
case 2:
|
|
32593
|
+
obj.message = reader.bytes();
|
|
32594
|
+
break;
|
|
32595
|
+
default:
|
|
32596
|
+
reader.skipType(tag & 7);
|
|
32597
|
+
break;
|
|
32598
|
+
}
|
|
32599
|
+
}
|
|
32600
|
+
return obj;
|
|
32601
|
+
});
|
|
32602
|
+
}
|
|
32603
|
+
return _codec;
|
|
32604
|
+
};
|
|
32605
|
+
Message.encode = (obj) => {
|
|
32606
|
+
return encodeMessage(obj, Message.codec());
|
|
32607
|
+
};
|
|
32608
|
+
Message.decode = (buf) => {
|
|
32609
|
+
return decodeMessage(buf, Message.codec());
|
|
32610
|
+
};
|
|
32611
|
+
})(Message$2 || (Message$2 = {}));
|
|
32612
|
+
|
|
32613
|
+
/**
|
|
32614
|
+
* How much can be buffered to the DataChannel at once
|
|
32615
|
+
*/
|
|
32616
|
+
const MAX_BUFFERED_AMOUNT = 16 * 1024 * 1024;
|
|
32617
|
+
/**
|
|
32618
|
+
* How long time we wait for the 'bufferedamountlow' event to be emitted
|
|
32619
|
+
*/
|
|
32620
|
+
const BUFFERED_AMOUNT_LOW_TIMEOUT = 30 * 1000;
|
|
32621
|
+
/**
|
|
32622
|
+
* protobuf field definition overhead
|
|
32623
|
+
*/
|
|
32624
|
+
const PROTOBUF_OVERHEAD = 5;
|
|
32625
|
+
/**
|
|
32626
|
+
* Length of varint, in bytes
|
|
32627
|
+
*/
|
|
32628
|
+
const VARINT_LENGTH = 2;
|
|
32629
|
+
/**
|
|
32630
|
+
* Max message size that can be sent to the DataChannel
|
|
32631
|
+
*/
|
|
32632
|
+
const MAX_MESSAGE_SIZE = 16 * 1024;
|
|
32633
|
+
/**
|
|
32634
|
+
* When closing streams we send a FIN then wait for the remote to
|
|
32635
|
+
* reply with a FIN_ACK. If that does not happen within this timeout
|
|
32636
|
+
* we close the stream anyway.
|
|
32637
|
+
*/
|
|
32638
|
+
const FIN_ACK_TIMEOUT = 5000;
|
|
32639
|
+
/**
|
|
32640
|
+
* When sending data messages, if the channel is not in the "open" state, wait
|
|
32641
|
+
* this long for the "open" event to fire.
|
|
32642
|
+
*/
|
|
32643
|
+
const OPEN_TIMEOUT = 5000;
|
|
32644
|
+
class WebRTCStream extends AbstractStream {
|
|
32645
|
+
/**
|
|
32646
|
+
* The data channel used to send and receive data
|
|
32647
|
+
*/
|
|
32648
|
+
channel;
|
|
32649
|
+
/**
|
|
32650
|
+
* push data from the underlying datachannel to the length prefix decoder
|
|
32651
|
+
* and then the protobuf decoder.
|
|
32652
|
+
*/
|
|
32653
|
+
incomingData;
|
|
32654
|
+
maxBufferedAmount;
|
|
32655
|
+
bufferedAmountLowEventTimeout;
|
|
32656
|
+
/**
|
|
32657
|
+
* The maximum size of a message in bytes
|
|
32658
|
+
*/
|
|
32659
|
+
maxMessageSize;
|
|
32660
|
+
/**
|
|
32661
|
+
* When this promise is resolved, the remote has sent us a FIN flag
|
|
32662
|
+
*/
|
|
32663
|
+
receiveFinAck;
|
|
32664
|
+
finAckTimeout;
|
|
32665
|
+
openTimeout;
|
|
32666
|
+
constructor(init) {
|
|
32667
|
+
// override onEnd to send/receive FIN_ACK before closing the stream
|
|
32668
|
+
const originalOnEnd = init.onEnd;
|
|
32669
|
+
init.onEnd = (err) => {
|
|
32670
|
+
this.log.trace('readable and writeable ends closed', this.status);
|
|
32671
|
+
void Promise.resolve(async () => {
|
|
32672
|
+
if (this.timeline.abort != null || this.timeline.reset !== null) {
|
|
32673
|
+
return;
|
|
32674
|
+
}
|
|
32675
|
+
// wait for FIN_ACK if we haven't received it already
|
|
32676
|
+
try {
|
|
32677
|
+
await pTimeout(this.receiveFinAck.promise, {
|
|
32678
|
+
milliseconds: this.finAckTimeout
|
|
32679
|
+
});
|
|
32680
|
+
}
|
|
32681
|
+
catch (err) {
|
|
32682
|
+
this.log.error('error receiving FIN_ACK', err);
|
|
32683
|
+
}
|
|
32684
|
+
})
|
|
32685
|
+
.then(() => {
|
|
32686
|
+
// stop processing incoming messages
|
|
32687
|
+
this.incomingData.end();
|
|
32688
|
+
// final cleanup
|
|
32689
|
+
originalOnEnd?.(err);
|
|
32690
|
+
})
|
|
32691
|
+
.catch(err => {
|
|
32692
|
+
this.log.error('error ending stream', err);
|
|
32693
|
+
});
|
|
32694
|
+
};
|
|
32695
|
+
super(init);
|
|
32696
|
+
this.channel = init.channel;
|
|
32697
|
+
this.channel.binaryType = 'arraybuffer';
|
|
32698
|
+
this.incomingData = pushable$1();
|
|
32699
|
+
this.bufferedAmountLowEventTimeout = init.bufferedAmountLowEventTimeout ?? BUFFERED_AMOUNT_LOW_TIMEOUT;
|
|
32700
|
+
this.maxBufferedAmount = init.maxBufferedAmount ?? MAX_BUFFERED_AMOUNT;
|
|
32701
|
+
this.maxMessageSize = (init.maxMessageSize ?? MAX_MESSAGE_SIZE) - PROTOBUF_OVERHEAD - VARINT_LENGTH;
|
|
32702
|
+
this.receiveFinAck = pDefer();
|
|
32703
|
+
this.finAckTimeout = init.closeTimeout ?? FIN_ACK_TIMEOUT;
|
|
32704
|
+
this.openTimeout = init.openTimeout ?? OPEN_TIMEOUT;
|
|
32705
|
+
// set up initial state
|
|
32706
|
+
switch (this.channel.readyState) {
|
|
32707
|
+
case 'open':
|
|
32708
|
+
this.timeline.open = new Date().getTime();
|
|
32709
|
+
break;
|
|
32710
|
+
case 'closed':
|
|
32711
|
+
case 'closing':
|
|
32712
|
+
if (this.timeline.close === undefined || this.timeline.close === 0) {
|
|
32713
|
+
this.timeline.close = Date.now();
|
|
32714
|
+
}
|
|
32715
|
+
break;
|
|
32716
|
+
case 'connecting':
|
|
32717
|
+
// noop
|
|
32718
|
+
break;
|
|
32719
|
+
default:
|
|
32720
|
+
this.log.error('unknown datachannel state %s', this.channel.readyState);
|
|
32721
|
+
throw new CodeError$2('Unknown datachannel state', 'ERR_INVALID_STATE');
|
|
32722
|
+
}
|
|
32723
|
+
// handle RTCDataChannel events
|
|
32724
|
+
this.channel.onopen = (_evt) => {
|
|
32725
|
+
this.timeline.open = new Date().getTime();
|
|
32726
|
+
};
|
|
32727
|
+
this.channel.onclose = (_evt) => {
|
|
32728
|
+
// if the channel has closed we'll never receive a FIN_ACK so resolve the
|
|
32729
|
+
// promise so we don't try to wait later
|
|
32730
|
+
this.receiveFinAck.resolve();
|
|
32731
|
+
void this.close().catch(err => {
|
|
32732
|
+
this.log.error('error closing stream after channel closed', err);
|
|
32733
|
+
});
|
|
32734
|
+
};
|
|
32735
|
+
this.channel.onerror = (evt) => {
|
|
32736
|
+
const err = evt.error;
|
|
32737
|
+
this.abort(err);
|
|
32738
|
+
};
|
|
32739
|
+
this.channel.onmessage = async (event) => {
|
|
32740
|
+
const { data } = event;
|
|
32741
|
+
if (data === null || data.byteLength === 0) {
|
|
32742
|
+
return;
|
|
32743
|
+
}
|
|
32744
|
+
this.incomingData.push(new Uint8Array(data, 0, data.byteLength));
|
|
32745
|
+
};
|
|
32746
|
+
const self = this;
|
|
32747
|
+
// pipe framed protobuf messages through a length prefixed decoder, and
|
|
32748
|
+
// surface data from the `Message.message` field through a source.
|
|
32749
|
+
Promise.resolve().then(async () => {
|
|
32750
|
+
for await (const buf of decode$1(this.incomingData)) {
|
|
32751
|
+
const message = self.processIncomingProtobuf(buf);
|
|
32752
|
+
if (message != null) {
|
|
32753
|
+
self.sourcePush(new Uint8ArrayList(message));
|
|
32754
|
+
}
|
|
32755
|
+
}
|
|
32756
|
+
})
|
|
32757
|
+
.catch(err => {
|
|
32758
|
+
this.log.error('error processing incoming data channel messages', err);
|
|
32759
|
+
});
|
|
32760
|
+
}
|
|
32761
|
+
sendNewStream() {
|
|
32762
|
+
// opening new streams is handled by WebRTC so this is a noop
|
|
32763
|
+
}
|
|
32764
|
+
async _sendMessage(data, checkBuffer = true) {
|
|
32765
|
+
if (checkBuffer && this.channel.bufferedAmount > this.maxBufferedAmount) {
|
|
32766
|
+
try {
|
|
32767
|
+
this.log('channel buffer is %d, wait for "bufferedamountlow" event', this.channel.bufferedAmount);
|
|
32768
|
+
await pEvent(this.channel, 'bufferedamountlow', { timeout: this.bufferedAmountLowEventTimeout });
|
|
32769
|
+
}
|
|
32770
|
+
catch (err) {
|
|
32771
|
+
if (err instanceof TimeoutError) {
|
|
32772
|
+
throw new CodeError$2(`Timed out waiting for DataChannel buffer to clear after ${this.bufferedAmountLowEventTimeout}ms`, 'ERR_BUFFER_CLEAR_TIMEOUT');
|
|
32773
|
+
}
|
|
32774
|
+
throw err;
|
|
32775
|
+
}
|
|
32776
|
+
}
|
|
32777
|
+
if (this.channel.readyState === 'closed' || this.channel.readyState === 'closing') {
|
|
32778
|
+
throw new CodeError$2(`Invalid datachannel state - ${this.channel.readyState}`, 'ERR_INVALID_STATE');
|
|
32779
|
+
}
|
|
32780
|
+
if (this.channel.readyState !== 'open') {
|
|
32781
|
+
this.log('channel state is "%s" and not "open", waiting for "open" event before sending data', this.channel.readyState);
|
|
32782
|
+
await pEvent(this.channel, 'open', { timeout: this.openTimeout });
|
|
32783
|
+
this.log('channel state is now "%s", sending data', this.channel.readyState);
|
|
32784
|
+
}
|
|
32785
|
+
// send message without copying data
|
|
32786
|
+
this.channel.send(data.subarray());
|
|
32787
|
+
}
|
|
32788
|
+
async sendData(data) {
|
|
32789
|
+
// sending messages is an async operation so use a copy of the list as it
|
|
32790
|
+
// may be changed beneath us
|
|
32791
|
+
data = data.sublist();
|
|
32792
|
+
while (data.byteLength > 0) {
|
|
32793
|
+
const toSend = Math.min(data.byteLength, this.maxMessageSize);
|
|
32794
|
+
const buf = data.subarray(0, toSend);
|
|
32795
|
+
const msgbuf = Message$2.encode({ message: buf });
|
|
32796
|
+
const sendbuf = encode.single(msgbuf);
|
|
32797
|
+
await this._sendMessage(sendbuf);
|
|
32798
|
+
data.consume(toSend);
|
|
32799
|
+
}
|
|
32800
|
+
}
|
|
32801
|
+
async sendReset() {
|
|
32802
|
+
await this._sendFlag(Message$2.Flag.RESET);
|
|
32803
|
+
}
|
|
32804
|
+
async sendCloseWrite(options) {
|
|
32805
|
+
const sent = await this._sendFlag(Message$2.Flag.FIN);
|
|
32806
|
+
if (sent) {
|
|
32807
|
+
this.log.trace('awaiting FIN_ACK');
|
|
32808
|
+
try {
|
|
32809
|
+
await raceSignal(this.receiveFinAck.promise, options?.signal, {
|
|
32810
|
+
errorMessage: 'sending close-write was aborted before FIN_ACK was received',
|
|
32811
|
+
errorCode: 'ERR_FIN_ACK_NOT_RECEIVED'
|
|
32812
|
+
});
|
|
32813
|
+
}
|
|
32814
|
+
catch (err) {
|
|
32815
|
+
this.log.error('failed to await FIN_ACK', err);
|
|
32816
|
+
}
|
|
32817
|
+
}
|
|
32818
|
+
else {
|
|
32819
|
+
this.log.trace('sending FIN failed, not awaiting FIN_ACK');
|
|
32820
|
+
}
|
|
32821
|
+
// if we've attempted to receive a FIN_ACK, do not try again
|
|
32822
|
+
this.receiveFinAck.resolve();
|
|
32823
|
+
}
|
|
32824
|
+
async sendCloseRead() {
|
|
32825
|
+
await this._sendFlag(Message$2.Flag.STOP_SENDING);
|
|
32826
|
+
}
|
|
32827
|
+
/**
|
|
32828
|
+
* Handle incoming
|
|
32829
|
+
*/
|
|
32830
|
+
processIncomingProtobuf(buffer) {
|
|
32831
|
+
const message = Message$2.decode(buffer);
|
|
32832
|
+
if (message.flag !== undefined) {
|
|
32833
|
+
this.log.trace('incoming flag %s, write status "%s", read status "%s"', message.flag, this.writeStatus, this.readStatus);
|
|
32834
|
+
if (message.flag === Message$2.Flag.FIN) {
|
|
32835
|
+
// We should expect no more data from the remote, stop reading
|
|
32836
|
+
this.remoteCloseWrite();
|
|
32837
|
+
this.log.trace('sending FIN_ACK');
|
|
32838
|
+
void this._sendFlag(Message$2.Flag.FIN_ACK)
|
|
32839
|
+
.catch(err => {
|
|
32840
|
+
this.log.error('error sending FIN_ACK immediately', err);
|
|
32841
|
+
});
|
|
32842
|
+
}
|
|
32843
|
+
if (message.flag === Message$2.Flag.RESET) {
|
|
32844
|
+
// Stop reading and writing to the stream immediately
|
|
32845
|
+
this.reset();
|
|
32846
|
+
}
|
|
32847
|
+
if (message.flag === Message$2.Flag.STOP_SENDING) {
|
|
32848
|
+
// The remote has stopped reading
|
|
32849
|
+
this.remoteCloseRead();
|
|
32850
|
+
}
|
|
32851
|
+
if (message.flag === Message$2.Flag.FIN_ACK) {
|
|
32852
|
+
this.log.trace('received FIN_ACK');
|
|
32853
|
+
this.receiveFinAck.resolve();
|
|
32854
|
+
}
|
|
32855
|
+
}
|
|
32856
|
+
// ignore data messages if we've closed the readable end already
|
|
32857
|
+
if (this.readStatus === 'ready') {
|
|
32858
|
+
return message.message;
|
|
32859
|
+
}
|
|
32860
|
+
}
|
|
32861
|
+
async _sendFlag(flag) {
|
|
32862
|
+
if (this.channel.readyState !== 'open') {
|
|
32863
|
+
// flags can be sent while we or the remote are closing the datachannel so
|
|
32864
|
+
// if the channel isn't open, don't try to send it but return false to let
|
|
32865
|
+
// the caller know and act if they need to
|
|
32866
|
+
this.log.trace('not sending flag %s because channel is "%s" and not "open"', this.channel.readyState, flag.toString());
|
|
32867
|
+
return false;
|
|
32868
|
+
}
|
|
32869
|
+
this.log.trace('sending flag %s', flag.toString());
|
|
32870
|
+
const msgbuf = Message$2.encode({ flag });
|
|
32871
|
+
const prefixedBuf = encode.single(msgbuf);
|
|
32872
|
+
try {
|
|
32873
|
+
await this._sendMessage(prefixedBuf, false);
|
|
32874
|
+
return true;
|
|
32875
|
+
}
|
|
32876
|
+
catch (err) {
|
|
32877
|
+
this.log.error('could not send flag %s', flag.toString(), err);
|
|
32878
|
+
}
|
|
32879
|
+
return false;
|
|
32880
|
+
}
|
|
32881
|
+
}
|
|
32882
|
+
function createStream(options) {
|
|
32883
|
+
const { channel, direction } = options;
|
|
32884
|
+
return new WebRTCStream({
|
|
32885
|
+
id: direction === 'inbound' ? (`i${channel.id}`) : `r${channel.id}`,
|
|
32886
|
+
log: options.logger.forComponent(`libp2p:webrtc:stream:${direction}:${channel.id}`),
|
|
32887
|
+
...options
|
|
32888
|
+
});
|
|
32889
|
+
}
|
|
32890
|
+
|
|
32891
|
+
const PROTOCOL$1 = '/webrtc';
|
|
32892
|
+
class DataChannelMuxerFactory {
|
|
32893
|
+
protocol;
|
|
32894
|
+
/**
|
|
32895
|
+
* WebRTC Peer Connection
|
|
32896
|
+
*/
|
|
32897
|
+
peerConnection;
|
|
32898
|
+
bufferedStreams = [];
|
|
32899
|
+
metrics;
|
|
32900
|
+
dataChannelOptions;
|
|
32901
|
+
components;
|
|
32902
|
+
log;
|
|
32903
|
+
constructor(components, init) {
|
|
32904
|
+
this.components = components;
|
|
32905
|
+
this.peerConnection = init.peerConnection;
|
|
32906
|
+
this.metrics = init.metrics;
|
|
32907
|
+
this.protocol = init.protocol ?? PROTOCOL$1;
|
|
32908
|
+
this.dataChannelOptions = init.dataChannelOptions ?? {};
|
|
32909
|
+
this.log = components.logger.forComponent('libp2p:webrtc:datachannelmuxerfactory');
|
|
32910
|
+
// store any datachannels opened before upgrade has been completed
|
|
32911
|
+
this.peerConnection.ondatachannel = ({ channel }) => {
|
|
32912
|
+
this.log.trace('incoming early datachannel with channel id %d and label "%s"', channel.id);
|
|
32913
|
+
// 'init' channel is only used during connection establishment
|
|
32914
|
+
if (channel.label === 'init') {
|
|
32915
|
+
this.log.trace('closing early init channel');
|
|
32916
|
+
channel.close();
|
|
32917
|
+
return;
|
|
32918
|
+
}
|
|
32919
|
+
// @ts-expect-error fields are set below
|
|
32920
|
+
const bufferedStream = {};
|
|
32921
|
+
const stream = createStream({
|
|
32922
|
+
channel,
|
|
32923
|
+
direction: 'inbound',
|
|
32924
|
+
onEnd: (err) => {
|
|
32925
|
+
bufferedStream.onEnd(err);
|
|
32926
|
+
},
|
|
32927
|
+
logger: components.logger,
|
|
32928
|
+
...this.dataChannelOptions
|
|
32929
|
+
});
|
|
32930
|
+
bufferedStream.stream = stream;
|
|
32931
|
+
bufferedStream.channel = channel;
|
|
32932
|
+
bufferedStream.onEnd = () => {
|
|
32933
|
+
this.bufferedStreams = this.bufferedStreams.filter(s => s.stream.id !== stream.id);
|
|
32934
|
+
};
|
|
32935
|
+
this.bufferedStreams.push(bufferedStream);
|
|
32936
|
+
};
|
|
32937
|
+
}
|
|
32938
|
+
createStreamMuxer(init) {
|
|
32939
|
+
return new DataChannelMuxer(this.components, {
|
|
32940
|
+
...init,
|
|
32941
|
+
peerConnection: this.peerConnection,
|
|
32942
|
+
dataChannelOptions: this.dataChannelOptions,
|
|
32943
|
+
metrics: this.metrics,
|
|
32944
|
+
streams: this.bufferedStreams,
|
|
32945
|
+
protocol: this.protocol
|
|
32946
|
+
});
|
|
32947
|
+
}
|
|
32948
|
+
}
|
|
32949
|
+
/**
|
|
32950
|
+
* A libp2p data channel stream muxer
|
|
32951
|
+
*/
|
|
32952
|
+
class DataChannelMuxer {
|
|
32953
|
+
init;
|
|
32954
|
+
/**
|
|
32955
|
+
* Array of streams in the data channel
|
|
32956
|
+
*/
|
|
32957
|
+
streams;
|
|
32958
|
+
protocol;
|
|
32959
|
+
log;
|
|
32960
|
+
peerConnection;
|
|
32961
|
+
dataChannelOptions;
|
|
32962
|
+
metrics;
|
|
32963
|
+
logger;
|
|
32964
|
+
constructor(components, init) {
|
|
32965
|
+
this.init = init;
|
|
32966
|
+
this.log = components.logger.forComponent('libp2p:webrtc:muxer');
|
|
32967
|
+
this.logger = components.logger;
|
|
32968
|
+
this.streams = init.streams.map(s => s.stream);
|
|
32969
|
+
this.peerConnection = init.peerConnection;
|
|
32970
|
+
this.protocol = init.protocol ?? PROTOCOL$1;
|
|
32971
|
+
this.metrics = init.metrics;
|
|
32972
|
+
this.dataChannelOptions = init.dataChannelOptions ?? {};
|
|
32973
|
+
/**
|
|
32974
|
+
* Fired when a data channel has been added to the connection has been
|
|
32975
|
+
* added by the remote peer.
|
|
32976
|
+
*
|
|
32977
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/datachannel_event}
|
|
32978
|
+
*/
|
|
32979
|
+
this.peerConnection.ondatachannel = ({ channel }) => {
|
|
32980
|
+
this.log.trace('incoming datachannel with channel id %d', channel.id);
|
|
32981
|
+
// 'init' channel is only used during connection establishment
|
|
32982
|
+
if (channel.label === 'init') {
|
|
32983
|
+
this.log.trace('closing init channel');
|
|
32984
|
+
channel.close();
|
|
32985
|
+
return;
|
|
32986
|
+
}
|
|
32987
|
+
const stream = createStream({
|
|
32988
|
+
channel,
|
|
32989
|
+
direction: 'inbound',
|
|
32990
|
+
onEnd: () => {
|
|
32991
|
+
this.log('incoming channel %s ended with state %s', channel.id, channel.readyState);
|
|
32992
|
+
this.#onStreamEnd(stream, channel);
|
|
32993
|
+
},
|
|
32994
|
+
logger: this.logger,
|
|
32995
|
+
...this.dataChannelOptions
|
|
32996
|
+
});
|
|
32997
|
+
this.streams.push(stream);
|
|
32998
|
+
this.metrics?.increment({ incoming_stream: true });
|
|
32999
|
+
init?.onIncomingStream?.(stream);
|
|
33000
|
+
};
|
|
33001
|
+
// the DataChannelMuxer constructor is called during set up of the
|
|
33002
|
+
// connection by the upgrader.
|
|
33003
|
+
//
|
|
33004
|
+
// If we invoke `init.onIncomingStream` immediately, the connection object
|
|
33005
|
+
// will not be set up yet so add a tiny delay before letting the
|
|
33006
|
+
// connection know about early streams
|
|
33007
|
+
if (this.init.streams.length > 0) {
|
|
33008
|
+
queueMicrotask(() => {
|
|
33009
|
+
this.init.streams.forEach(bufferedStream => {
|
|
33010
|
+
bufferedStream.onEnd = () => {
|
|
33011
|
+
this.log('incoming early channel %s ended with state %s', bufferedStream.channel.id, bufferedStream.channel.readyState);
|
|
33012
|
+
this.#onStreamEnd(bufferedStream.stream, bufferedStream.channel);
|
|
33013
|
+
};
|
|
33014
|
+
this.metrics?.increment({ incoming_stream: true });
|
|
33015
|
+
this.init?.onIncomingStream?.(bufferedStream.stream);
|
|
33016
|
+
});
|
|
33017
|
+
});
|
|
33018
|
+
}
|
|
33019
|
+
}
|
|
33020
|
+
#onStreamEnd(stream, channel) {
|
|
33021
|
+
this.log.trace('stream %s %s %s onEnd', stream.direction, stream.id, stream.protocol);
|
|
33022
|
+
drainAndClose(channel, `${stream.direction} ${stream.id} ${stream.protocol}`, this.dataChannelOptions.drainTimeout, {
|
|
33023
|
+
log: this.log
|
|
33024
|
+
});
|
|
33025
|
+
this.streams = this.streams.filter(s => s.id !== stream.id);
|
|
33026
|
+
this.metrics?.increment({ stream_end: true });
|
|
33027
|
+
this.init?.onStreamEnd?.(stream);
|
|
33028
|
+
}
|
|
33029
|
+
/**
|
|
33030
|
+
* Gracefully close all tracked streams and stop the muxer
|
|
33031
|
+
*/
|
|
33032
|
+
async close(options) {
|
|
33033
|
+
try {
|
|
33034
|
+
await Promise.all(this.streams.map(async (stream) => stream.close(options)));
|
|
33035
|
+
}
|
|
33036
|
+
catch (err) {
|
|
33037
|
+
this.abort(err);
|
|
33038
|
+
}
|
|
33039
|
+
}
|
|
33040
|
+
/**
|
|
33041
|
+
* Abort all tracked streams and stop the muxer
|
|
33042
|
+
*/
|
|
33043
|
+
abort(err) {
|
|
33044
|
+
for (const stream of this.streams) {
|
|
33045
|
+
stream.abort(err);
|
|
33046
|
+
}
|
|
33047
|
+
}
|
|
33048
|
+
/**
|
|
33049
|
+
* The stream source, a no-op as the transport natively supports multiplexing
|
|
33050
|
+
*/
|
|
33051
|
+
source = nopSource();
|
|
33052
|
+
/**
|
|
33053
|
+
* The stream destination, a no-op as the transport natively supports multiplexing
|
|
33054
|
+
*/
|
|
33055
|
+
sink = nopSink;
|
|
33056
|
+
newStream() {
|
|
33057
|
+
// The spec says the label SHOULD be an empty string: https://github.com/libp2p/specs/blob/master/webrtc/README.md#rtcdatachannel-label
|
|
33058
|
+
const channel = this.peerConnection.createDataChannel('');
|
|
33059
|
+
this.log.trace('opened outgoing datachannel with channel id %s', channel.id);
|
|
33060
|
+
const stream = createStream({
|
|
33061
|
+
channel,
|
|
33062
|
+
direction: 'outbound',
|
|
33063
|
+
onEnd: () => {
|
|
33064
|
+
this.log('outgoing channel %s ended with state %s', channel.id, channel.readyState);
|
|
33065
|
+
this.#onStreamEnd(stream, channel);
|
|
33066
|
+
},
|
|
33067
|
+
logger: this.logger,
|
|
33068
|
+
...this.dataChannelOptions
|
|
33069
|
+
});
|
|
33070
|
+
this.streams.push(stream);
|
|
33071
|
+
this.metrics?.increment({ outgoing_stream: true });
|
|
33072
|
+
return stream;
|
|
33073
|
+
}
|
|
33074
|
+
}
|
|
33075
|
+
|
|
33076
|
+
const RTCPeerConnection = globalThis.RTCPeerConnection;
|
|
33077
|
+
const RTCSessionDescription = globalThis.RTCSessionDescription;
|
|
33078
|
+
const RTCIceCandidate = globalThis.RTCIceCandidate;
|
|
33079
|
+
|
|
33080
|
+
/**
|
|
33081
|
+
* @packageDocumentation
|
|
33082
|
+
*
|
|
33083
|
+
* This module makes it easy to send and receive length-prefixed Protobuf encoded
|
|
33084
|
+
* messages over streams.
|
|
33085
|
+
*
|
|
33086
|
+
* @example
|
|
33087
|
+
*
|
|
33088
|
+
* ```typescript
|
|
33089
|
+
* import { pbStream } from 'it-protobuf-stream'
|
|
33090
|
+
* import { MessageType } from './src/my-message-type.js'
|
|
33091
|
+
*
|
|
33092
|
+
* // RequestType and ResponseType have been generate from `.proto` files and have
|
|
33093
|
+
* // `.encode` and `.decode` methods for serialization/deserialization
|
|
33094
|
+
*
|
|
33095
|
+
* const stream = pbStream(duplex)
|
|
33096
|
+
*
|
|
33097
|
+
* // write a message to the stream
|
|
33098
|
+
* stream.write({
|
|
33099
|
+
* foo: 'bar'
|
|
33100
|
+
* }, MessageType)
|
|
33101
|
+
*
|
|
33102
|
+
* // read a message from the stream
|
|
33103
|
+
* const res = await stream.read(MessageType)
|
|
33104
|
+
* ```
|
|
33105
|
+
*/
|
|
33106
|
+
function pbStream(duplex, opts) {
|
|
33107
|
+
const lp = lpStream(duplex, opts);
|
|
33108
|
+
const W = {
|
|
33109
|
+
read: async (proto, options) => {
|
|
33110
|
+
// readLP, decode
|
|
33111
|
+
const value = await lp.read(options);
|
|
33112
|
+
return proto.decode(value);
|
|
33113
|
+
},
|
|
33114
|
+
write: async (message, proto, options) => {
|
|
33115
|
+
// encode, writeLP
|
|
33116
|
+
await lp.write(proto.encode(message), options);
|
|
33117
|
+
},
|
|
33118
|
+
writeV: async (messages, proto, options) => {
|
|
33119
|
+
// encode, writeLP
|
|
33120
|
+
await lp.writeV(messages.map(message => proto.encode(message)), options);
|
|
33121
|
+
},
|
|
33122
|
+
pb: (proto) => {
|
|
33123
|
+
return {
|
|
33124
|
+
read: async (options) => W.read(proto, options),
|
|
33125
|
+
write: async (d, options) => W.write(d, proto, options),
|
|
33126
|
+
writeV: async (d, options) => W.writeV(d, proto, options),
|
|
33127
|
+
unwrap: () => W
|
|
33128
|
+
};
|
|
33129
|
+
},
|
|
33130
|
+
unwrap: () => {
|
|
33131
|
+
return lp.unwrap();
|
|
33132
|
+
}
|
|
33133
|
+
};
|
|
33134
|
+
return W;
|
|
33135
|
+
}
|
|
33136
|
+
|
|
33137
|
+
/* eslint-disable import/export */
|
|
33138
|
+
/* eslint-disable complexity */
|
|
33139
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
33140
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
|
|
33141
|
+
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
33142
|
+
var Message$1;
|
|
33143
|
+
(function (Message) {
|
|
33144
|
+
(function (Type) {
|
|
33145
|
+
Type["SDP_OFFER"] = "SDP_OFFER";
|
|
33146
|
+
Type["SDP_ANSWER"] = "SDP_ANSWER";
|
|
33147
|
+
Type["ICE_CANDIDATE"] = "ICE_CANDIDATE";
|
|
33148
|
+
})(Message.Type || (Message.Type = {}));
|
|
33149
|
+
let __TypeValues;
|
|
33150
|
+
(function (__TypeValues) {
|
|
33151
|
+
__TypeValues[__TypeValues["SDP_OFFER"] = 0] = "SDP_OFFER";
|
|
33152
|
+
__TypeValues[__TypeValues["SDP_ANSWER"] = 1] = "SDP_ANSWER";
|
|
33153
|
+
__TypeValues[__TypeValues["ICE_CANDIDATE"] = 2] = "ICE_CANDIDATE";
|
|
33154
|
+
})(__TypeValues || (__TypeValues = {}));
|
|
33155
|
+
(function (Type) {
|
|
33156
|
+
Type.codec = () => {
|
|
33157
|
+
return enumeration(__TypeValues);
|
|
33158
|
+
};
|
|
33159
|
+
})(Message.Type || (Message.Type = {}));
|
|
33160
|
+
let _codec;
|
|
33161
|
+
Message.codec = () => {
|
|
33162
|
+
if (_codec == null) {
|
|
33163
|
+
_codec = message((obj, w, opts = {}) => {
|
|
33164
|
+
if (opts.lengthDelimited !== false) {
|
|
33165
|
+
w.fork();
|
|
33166
|
+
}
|
|
33167
|
+
if (obj.type != null) {
|
|
33168
|
+
w.uint32(8);
|
|
33169
|
+
Message.Type.codec().encode(obj.type, w);
|
|
33170
|
+
}
|
|
33171
|
+
if (obj.data != null) {
|
|
33172
|
+
w.uint32(18);
|
|
33173
|
+
w.string(obj.data);
|
|
33174
|
+
}
|
|
33175
|
+
if (opts.lengthDelimited !== false) {
|
|
33176
|
+
w.ldelim();
|
|
33177
|
+
}
|
|
33178
|
+
}, (reader, length) => {
|
|
33179
|
+
const obj = {};
|
|
33180
|
+
const end = length == null ? reader.len : reader.pos + length;
|
|
33181
|
+
while (reader.pos < end) {
|
|
33182
|
+
const tag = reader.uint32();
|
|
33183
|
+
switch (tag >>> 3) {
|
|
33184
|
+
case 1:
|
|
33185
|
+
obj.type = Message.Type.codec().decode(reader);
|
|
33186
|
+
break;
|
|
33187
|
+
case 2:
|
|
33188
|
+
obj.data = reader.string();
|
|
33189
|
+
break;
|
|
33190
|
+
default:
|
|
33191
|
+
reader.skipType(tag & 7);
|
|
33192
|
+
break;
|
|
33193
|
+
}
|
|
33194
|
+
}
|
|
33195
|
+
return obj;
|
|
33196
|
+
});
|
|
33197
|
+
}
|
|
33198
|
+
return _codec;
|
|
33199
|
+
};
|
|
33200
|
+
Message.encode = (obj) => {
|
|
33201
|
+
return encodeMessage(obj, Message.codec());
|
|
33202
|
+
};
|
|
33203
|
+
Message.decode = (buf) => {
|
|
33204
|
+
return decodeMessage(buf, Message.codec());
|
|
33205
|
+
};
|
|
33206
|
+
})(Message$1 || (Message$1 = {}));
|
|
33207
|
+
|
|
33208
|
+
const readCandidatesUntilConnected = async (pc, stream, options) => {
|
|
33209
|
+
try {
|
|
33210
|
+
const connectedPromise = pDefer();
|
|
33211
|
+
resolveOnConnected(pc, connectedPromise);
|
|
33212
|
+
// read candidates until we are connected or we reach the end of the stream
|
|
33213
|
+
while (true) {
|
|
33214
|
+
// if we connect, stop trying to read from the stream
|
|
33215
|
+
const message = await Promise.race([
|
|
33216
|
+
connectedPromise.promise,
|
|
33217
|
+
stream.read({
|
|
33218
|
+
signal: options.signal
|
|
33219
|
+
}).catch(() => { })
|
|
33220
|
+
]);
|
|
33221
|
+
// stream ended or we became connected
|
|
33222
|
+
if (message == null) {
|
|
33223
|
+
// throw if we timed out
|
|
33224
|
+
options.signal?.throwIfAborted();
|
|
33225
|
+
break;
|
|
33226
|
+
}
|
|
33227
|
+
if (message.type !== Message$1.Type.ICE_CANDIDATE) {
|
|
33228
|
+
throw new CodeError$2('ICE candidate message expected', 'ERR_NOT_ICE_CANDIDATE');
|
|
33229
|
+
}
|
|
33230
|
+
const candidateInit = JSON.parse(message.data ?? 'null');
|
|
33231
|
+
// an empty string means this generation of candidates is complete, a null
|
|
33232
|
+
// candidate means candidate gathering has finished
|
|
33233
|
+
// see - https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent
|
|
33234
|
+
if (candidateInit === '' || candidateInit === null) {
|
|
33235
|
+
options.log.trace('end-of-candidates received');
|
|
33236
|
+
continue;
|
|
33237
|
+
}
|
|
33238
|
+
const candidate = new RTCIceCandidate(candidateInit);
|
|
33239
|
+
options.log.trace('%s received new ICE candidate %o', options.direction, candidateInit);
|
|
33240
|
+
try {
|
|
33241
|
+
await pc.addIceCandidate(candidate);
|
|
33242
|
+
}
|
|
33243
|
+
catch (err) {
|
|
33244
|
+
options.log.error('%s bad candidate received', options.direction, candidateInit, err);
|
|
33245
|
+
}
|
|
33246
|
+
}
|
|
33247
|
+
}
|
|
33248
|
+
catch (err) {
|
|
33249
|
+
options.log.error('%s error parsing ICE candidate', options.direction, err);
|
|
33250
|
+
if (options.signal?.aborted === true) {
|
|
33251
|
+
throw err;
|
|
33252
|
+
}
|
|
33253
|
+
}
|
|
33254
|
+
};
|
|
33255
|
+
function getConnectionState(pc) {
|
|
33256
|
+
return isFirefox ? pc.iceConnectionState : pc.connectionState;
|
|
33257
|
+
}
|
|
33258
|
+
function resolveOnConnected(pc, promise) {
|
|
33259
|
+
pc[isFirefox ? 'oniceconnectionstatechange' : 'onconnectionstatechange'] = (_) => {
|
|
33260
|
+
switch (getConnectionState(pc)) {
|
|
33261
|
+
case 'connected':
|
|
33262
|
+
promise.resolve();
|
|
33263
|
+
break;
|
|
33264
|
+
case 'failed':
|
|
33265
|
+
case 'disconnected':
|
|
33266
|
+
case 'closed':
|
|
33267
|
+
promise.reject(new CodeError$2('RTCPeerConnection was closed', 'ERR_CONNECTION_CLOSED_BEFORE_CONNECTED'));
|
|
33268
|
+
break;
|
|
33269
|
+
}
|
|
33270
|
+
};
|
|
33271
|
+
}
|
|
33272
|
+
|
|
33273
|
+
async function initiateConnection({ rtcConfiguration, dataChannel, signal, metrics, multiaddr: ma, connectionManager, transportManager, log, logger }) {
|
|
33274
|
+
const { baseAddr } = splitAddr(ma);
|
|
33275
|
+
metrics?.dialerEvents.increment({ open: true });
|
|
33276
|
+
log.trace('dialing base address: %a', baseAddr);
|
|
33277
|
+
const relayPeer = baseAddr.getPeerId();
|
|
33278
|
+
if (relayPeer == null) {
|
|
33279
|
+
throw new CodeError$2('Relay peer was missing', 'ERR_INVALID_ADDRESS');
|
|
33280
|
+
}
|
|
33281
|
+
const connections = connectionManager.getConnections(peerIdFromString(relayPeer));
|
|
33282
|
+
let connection;
|
|
33283
|
+
let shouldCloseConnection = false;
|
|
33284
|
+
if (connections.length === 0) {
|
|
33285
|
+
// use the transport manager to open a connection. Initiating a WebRTC
|
|
33286
|
+
// connection takes place in the context of a dial - if we use the
|
|
33287
|
+
// connection manager instead we can end up joining our own dial context
|
|
33288
|
+
connection = await transportManager.dial(baseAddr, {
|
|
33289
|
+
signal
|
|
33290
|
+
});
|
|
33291
|
+
// this connection is unmanaged by the connection manager so we should
|
|
33292
|
+
// close it when we are done
|
|
33293
|
+
shouldCloseConnection = true;
|
|
33294
|
+
}
|
|
33295
|
+
else {
|
|
33296
|
+
connection = connections[0];
|
|
33297
|
+
}
|
|
33298
|
+
try {
|
|
33299
|
+
const stream = await connection.newStream(SIGNALING_PROTO_ID, {
|
|
33300
|
+
signal,
|
|
33301
|
+
runOnTransientConnection: true
|
|
33302
|
+
});
|
|
33303
|
+
const messageStream = pbStream(stream).pb(Message$1);
|
|
33304
|
+
const peerConnection = new RTCPeerConnection(rtcConfiguration);
|
|
33305
|
+
const muxerFactory = new DataChannelMuxerFactory({
|
|
33306
|
+
logger
|
|
33307
|
+
}, {
|
|
33308
|
+
peerConnection,
|
|
33309
|
+
dataChannelOptions: dataChannel
|
|
33310
|
+
});
|
|
33311
|
+
try {
|
|
33312
|
+
// we create the channel so that the RTCPeerConnection has a component for
|
|
33313
|
+
// which to collect candidates. The label is not relevant to connection
|
|
33314
|
+
// initiation but can be useful for debugging
|
|
33315
|
+
const channel = peerConnection.createDataChannel('init');
|
|
33316
|
+
// setup callback to write ICE candidates to the remote peer
|
|
33317
|
+
peerConnection.onicecandidate = ({ candidate }) => {
|
|
33318
|
+
// a null candidate means end-of-candidates, an empty string candidate
|
|
33319
|
+
// means end-of-candidates for this generation, otherwise this should
|
|
33320
|
+
// be a valid candidate object
|
|
33321
|
+
// see - https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent
|
|
33322
|
+
const data = JSON.stringify(candidate?.toJSON() ?? null);
|
|
33323
|
+
log.trace('initiator sending ICE candidate %o', candidate);
|
|
33324
|
+
void messageStream.write({
|
|
33325
|
+
type: Message$1.Type.ICE_CANDIDATE,
|
|
33326
|
+
data
|
|
33327
|
+
}, {
|
|
33328
|
+
signal
|
|
33329
|
+
})
|
|
33330
|
+
.catch(err => {
|
|
33331
|
+
log.error('error sending ICE candidate', err);
|
|
33332
|
+
});
|
|
33333
|
+
};
|
|
33334
|
+
peerConnection.onicecandidateerror = (event) => {
|
|
33335
|
+
log.error('initiator ICE candidate error', event);
|
|
33336
|
+
};
|
|
33337
|
+
// create an offer
|
|
33338
|
+
const offerSdp = await peerConnection.createOffer().catch(err => {
|
|
33339
|
+
log.error('could not execute createOffer', err);
|
|
33340
|
+
throw new CodeError$2('Failed to set createOffer', 'ERR_SDP_HANDSHAKE_FAILED');
|
|
33341
|
+
});
|
|
33342
|
+
log.trace('initiator send SDP offer %s', offerSdp.sdp);
|
|
33343
|
+
// write the offer to the stream
|
|
33344
|
+
await messageStream.write({ type: Message$1.Type.SDP_OFFER, data: offerSdp.sdp }, {
|
|
33345
|
+
signal
|
|
33346
|
+
});
|
|
33347
|
+
// set offer as local description
|
|
33348
|
+
await peerConnection.setLocalDescription(offerSdp).catch(err => {
|
|
33349
|
+
log.error('could not execute setLocalDescription', err);
|
|
33350
|
+
throw new CodeError$2('Failed to set localDescription', 'ERR_SDP_HANDSHAKE_FAILED');
|
|
33351
|
+
});
|
|
33352
|
+
// read answer
|
|
33353
|
+
const answerMessage = await messageStream.read({
|
|
33354
|
+
signal
|
|
33355
|
+
});
|
|
33356
|
+
if (answerMessage.type !== Message$1.Type.SDP_ANSWER) {
|
|
33357
|
+
throw new CodeError$2('Remote should send an SDP answer', 'ERR_SDP_HANDSHAKE_FAILED');
|
|
33358
|
+
}
|
|
33359
|
+
log.trace('initiator receive SDP answer %s', answerMessage.data);
|
|
33360
|
+
const answerSdp = new RTCSessionDescription({ type: 'answer', sdp: answerMessage.data });
|
|
33361
|
+
await peerConnection.setRemoteDescription(answerSdp).catch(err => {
|
|
33362
|
+
log.error('could not execute setRemoteDescription', err);
|
|
33363
|
+
throw new CodeError$2('Failed to set remoteDescription', 'ERR_SDP_HANDSHAKE_FAILED');
|
|
33364
|
+
});
|
|
33365
|
+
log.trace('initiator read candidates until connected');
|
|
33366
|
+
await readCandidatesUntilConnected(peerConnection, messageStream, {
|
|
33367
|
+
direction: 'initiator',
|
|
33368
|
+
signal,
|
|
33369
|
+
log
|
|
33370
|
+
});
|
|
33371
|
+
log.trace('initiator connected, closing init channel');
|
|
33372
|
+
channel.close();
|
|
33373
|
+
log.trace('closing signalling channel');
|
|
33374
|
+
await stream.close({
|
|
33375
|
+
signal
|
|
33376
|
+
});
|
|
33377
|
+
log.trace('initiator connected to remote address %s', ma);
|
|
33378
|
+
return {
|
|
33379
|
+
remoteAddress: ma,
|
|
33380
|
+
peerConnection,
|
|
33381
|
+
muxerFactory
|
|
33382
|
+
};
|
|
33383
|
+
}
|
|
33384
|
+
catch (err) {
|
|
33385
|
+
log.error('outgoing signalling error', err);
|
|
33386
|
+
peerConnection.close();
|
|
33387
|
+
stream.abort(err);
|
|
33388
|
+
throw err;
|
|
33389
|
+
}
|
|
33390
|
+
finally {
|
|
33391
|
+
peerConnection.onicecandidate = null;
|
|
33392
|
+
peerConnection.onicecandidateerror = null;
|
|
33393
|
+
}
|
|
33394
|
+
}
|
|
33395
|
+
finally {
|
|
33396
|
+
// if we had to open a connection to perform the SDP handshake
|
|
33397
|
+
// close it because it's not tracked by the connection manager
|
|
33398
|
+
if (shouldCloseConnection) {
|
|
33399
|
+
try {
|
|
33400
|
+
await connection.close({
|
|
33401
|
+
signal
|
|
33402
|
+
});
|
|
33403
|
+
}
|
|
33404
|
+
catch (err) {
|
|
33405
|
+
connection.abort(err);
|
|
33406
|
+
}
|
|
33407
|
+
}
|
|
33408
|
+
}
|
|
33409
|
+
}
|
|
33410
|
+
|
|
33411
|
+
class WebRTCPeerListener extends TypedEventEmitter {
|
|
33412
|
+
peerId;
|
|
33413
|
+
transportManager;
|
|
33414
|
+
shutdownController;
|
|
33415
|
+
constructor(components, init) {
|
|
33416
|
+
super();
|
|
33417
|
+
this.peerId = components.peerId;
|
|
33418
|
+
this.transportManager = components.transportManager;
|
|
33419
|
+
this.shutdownController = init.shutdownController;
|
|
33420
|
+
}
|
|
33421
|
+
async listen() {
|
|
33422
|
+
this.safeDispatchEvent('listening', {});
|
|
33423
|
+
}
|
|
33424
|
+
getAddrs() {
|
|
33425
|
+
return this.transportManager
|
|
33426
|
+
.getListeners()
|
|
33427
|
+
.filter(l => l !== this)
|
|
33428
|
+
.map(l => l.getAddrs()
|
|
33429
|
+
.filter(ma => Circuit.matches(ma))
|
|
33430
|
+
.map(ma => {
|
|
33431
|
+
return ma.encapsulate(`/webrtc/p2p/${this.peerId}`);
|
|
33432
|
+
}))
|
|
33433
|
+
.flat();
|
|
33434
|
+
}
|
|
33435
|
+
async close() {
|
|
33436
|
+
this.shutdownController.abort();
|
|
33437
|
+
this.safeDispatchEvent('close', {});
|
|
33438
|
+
}
|
|
33439
|
+
}
|
|
33440
|
+
|
|
33441
|
+
async function handleIncomingStream({ peerConnection, stream, signal, connection, log }) {
|
|
33442
|
+
log.trace('new inbound signaling stream');
|
|
33443
|
+
const messageStream = pbStream(stream).pb(Message$1);
|
|
33444
|
+
try {
|
|
33445
|
+
// candidate callbacks
|
|
33446
|
+
peerConnection.onicecandidate = ({ candidate }) => {
|
|
33447
|
+
// a null candidate means end-of-candidates, an empty string candidate
|
|
33448
|
+
// means end-of-candidates for this generation, otherwise this should
|
|
33449
|
+
// be a valid candidate object
|
|
33450
|
+
// see - https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent
|
|
33451
|
+
const data = JSON.stringify(candidate?.toJSON() ?? null);
|
|
33452
|
+
log.trace('recipient sending ICE candidate %s', data);
|
|
33453
|
+
messageStream.write({
|
|
33454
|
+
type: Message$1.Type.ICE_CANDIDATE,
|
|
33455
|
+
data
|
|
33456
|
+
}, {
|
|
33457
|
+
signal
|
|
33458
|
+
})
|
|
33459
|
+
.catch(err => {
|
|
33460
|
+
log.error('error sending ICE candidate', err);
|
|
33461
|
+
});
|
|
33462
|
+
};
|
|
33463
|
+
// read an SDP offer
|
|
33464
|
+
const pbOffer = await messageStream.read({
|
|
33465
|
+
signal
|
|
33466
|
+
});
|
|
33467
|
+
if (pbOffer.type !== Message$1.Type.SDP_OFFER) {
|
|
33468
|
+
throw new CodeError$2(`expected message type SDP_OFFER, received: ${pbOffer.type ?? 'undefined'} `, 'ERR_SDP_HANDSHAKE_FAILED');
|
|
33469
|
+
}
|
|
33470
|
+
log.trace('recipient receive SDP offer %s', pbOffer.data);
|
|
33471
|
+
const offer = new RTCSessionDescription({
|
|
33472
|
+
type: 'offer',
|
|
33473
|
+
sdp: pbOffer.data
|
|
33474
|
+
});
|
|
33475
|
+
await peerConnection.setRemoteDescription(offer).catch(err => {
|
|
33476
|
+
log.error('could not execute setRemoteDescription', err);
|
|
33477
|
+
throw new CodeError$2('Failed to set remoteDescription', 'ERR_SDP_HANDSHAKE_FAILED');
|
|
33478
|
+
});
|
|
33479
|
+
// create and write an SDP answer
|
|
33480
|
+
const answer = await peerConnection.createAnswer().catch(err => {
|
|
33481
|
+
log.error('could not execute createAnswer', err);
|
|
33482
|
+
throw new CodeError$2('Failed to create answer', 'ERR_SDP_HANDSHAKE_FAILED');
|
|
33483
|
+
});
|
|
33484
|
+
log.trace('recipient send SDP answer %s', answer.sdp);
|
|
33485
|
+
// write the answer to the remote
|
|
33486
|
+
await messageStream.write({ type: Message$1.Type.SDP_ANSWER, data: answer.sdp }, {
|
|
33487
|
+
signal
|
|
33488
|
+
});
|
|
33489
|
+
await peerConnection.setLocalDescription(answer).catch(err => {
|
|
33490
|
+
log.error('could not execute setLocalDescription', err);
|
|
33491
|
+
throw new CodeError$2('Failed to set localDescription', 'ERR_SDP_HANDSHAKE_FAILED');
|
|
33492
|
+
});
|
|
33493
|
+
log.trace('recipient read candidates until connected');
|
|
33494
|
+
// wait until candidates are connected
|
|
33495
|
+
await readCandidatesUntilConnected(peerConnection, messageStream, {
|
|
33496
|
+
direction: 'recipient',
|
|
33497
|
+
signal,
|
|
33498
|
+
log
|
|
33499
|
+
});
|
|
33500
|
+
}
|
|
33501
|
+
catch (err) {
|
|
33502
|
+
if (peerConnection.connectionState !== 'connected') {
|
|
33503
|
+
log.error('error while handling signaling stream from peer %a', connection.remoteAddr, err);
|
|
33504
|
+
peerConnection.close();
|
|
33505
|
+
throw err;
|
|
33506
|
+
}
|
|
33507
|
+
else {
|
|
33508
|
+
log('error while handling signaling stream from peer %a, ignoring as the RTCPeerConnection is already connected', connection.remoteAddr, err);
|
|
33509
|
+
}
|
|
33510
|
+
}
|
|
33511
|
+
const remoteAddress = multiaddr(`/webrtc/p2p/${connection.remoteAddr.getPeerId()}`);
|
|
33512
|
+
log.trace('recipient connected to remote address %s', remoteAddress);
|
|
33513
|
+
return { remoteAddress };
|
|
33514
|
+
}
|
|
33515
|
+
|
|
33516
|
+
const WEBRTC_TRANSPORT = '/webrtc';
|
|
33517
|
+
const CIRCUIT_RELAY_TRANSPORT = '/p2p-circuit';
|
|
33518
|
+
const SIGNALING_PROTO_ID = '/webrtc-signaling/0.0.1';
|
|
33519
|
+
const INBOUND_CONNECTION_TIMEOUT = 30 * 1000;
|
|
33520
|
+
class WebRTCTransport {
|
|
33521
|
+
components;
|
|
33522
|
+
init;
|
|
33523
|
+
log;
|
|
33524
|
+
_started = false;
|
|
33525
|
+
metrics;
|
|
33526
|
+
shutdownController;
|
|
33527
|
+
constructor(components, init = {}) {
|
|
33528
|
+
this.components = components;
|
|
33529
|
+
this.init = init;
|
|
33530
|
+
this.log = components.logger.forComponent('libp2p:webrtc');
|
|
33531
|
+
this.shutdownController = new AbortController();
|
|
33532
|
+
setMaxListeners(Infinity, this.shutdownController.signal);
|
|
33533
|
+
if (components.metrics != null) {
|
|
33534
|
+
this.metrics = {
|
|
33535
|
+
dialerEvents: components.metrics.registerCounterGroup('libp2p_webrtc_dialer_events_total', {
|
|
33536
|
+
label: 'event',
|
|
33537
|
+
help: 'Total count of WebRTC dialer events by type'
|
|
33538
|
+
}),
|
|
33539
|
+
listenerEvents: components.metrics.registerCounterGroup('libp2p_webrtc_listener_events_total', {
|
|
33540
|
+
label: 'event',
|
|
33541
|
+
help: 'Total count of WebRTC listener events by type'
|
|
33542
|
+
})
|
|
33543
|
+
};
|
|
33544
|
+
}
|
|
33545
|
+
}
|
|
33546
|
+
[transportSymbol] = true;
|
|
33547
|
+
[Symbol.toStringTag] = '@libp2p/webrtc';
|
|
33548
|
+
[serviceCapabilities] = [
|
|
33549
|
+
'@libp2p/transport'
|
|
33550
|
+
];
|
|
33551
|
+
[serviceDependencies] = [
|
|
33552
|
+
'@libp2p/identify',
|
|
33553
|
+
'@libp2p/circuit-relay-v2-transport'
|
|
33554
|
+
];
|
|
33555
|
+
isStarted() {
|
|
33556
|
+
return this._started;
|
|
33557
|
+
}
|
|
33558
|
+
async start() {
|
|
33559
|
+
await this.components.registrar.handle(SIGNALING_PROTO_ID, (data) => {
|
|
33560
|
+
this._onProtocol(data).catch(err => { this.log.error('failed to handle incoming connect from %p', data.connection.remotePeer, err); });
|
|
33561
|
+
}, {
|
|
33562
|
+
runOnTransientConnection: true
|
|
33563
|
+
});
|
|
33564
|
+
this._started = true;
|
|
33565
|
+
}
|
|
33566
|
+
async stop() {
|
|
33567
|
+
await this.components.registrar.unhandle(SIGNALING_PROTO_ID);
|
|
33568
|
+
this._started = false;
|
|
33569
|
+
}
|
|
33570
|
+
createListener(options) {
|
|
33571
|
+
return new WebRTCPeerListener(this.components, {
|
|
33572
|
+
shutdownController: this.shutdownController
|
|
33573
|
+
});
|
|
33574
|
+
}
|
|
33575
|
+
/**
|
|
33576
|
+
* Filter check for all Multiaddrs that this transport can listen on
|
|
33577
|
+
*/
|
|
33578
|
+
listenFilter(multiaddrs) {
|
|
33579
|
+
return multiaddrs.filter(WebRTC.exactMatch);
|
|
33580
|
+
}
|
|
33581
|
+
/**
|
|
33582
|
+
* Filter check for all Multiaddrs that this transport can dial
|
|
33583
|
+
*/
|
|
33584
|
+
dialFilter(multiaddrs) {
|
|
33585
|
+
return this.listenFilter(multiaddrs);
|
|
33586
|
+
}
|
|
33587
|
+
/*
|
|
33588
|
+
* dial connects to a remote via the circuit relay or any other protocol
|
|
33589
|
+
* and proceeds to upgrade to a webrtc connection.
|
|
33590
|
+
* multiaddr of the form: <multiaddr>/webrtc/p2p/<destination-peer>
|
|
33591
|
+
* For a circuit relay, this will be of the form
|
|
33592
|
+
* <relay address>/p2p/<relay-peer>/p2p-circuit/webrtc/p2p/<destination-peer>
|
|
33593
|
+
*/
|
|
33594
|
+
async dial(ma, options) {
|
|
33595
|
+
this.log.trace('dialing address: %a', ma);
|
|
33596
|
+
const { remoteAddress, peerConnection, muxerFactory } = await initiateConnection({
|
|
33597
|
+
rtcConfiguration: typeof this.init.rtcConfiguration === 'function' ? await this.init.rtcConfiguration() : this.init.rtcConfiguration,
|
|
33598
|
+
dataChannel: this.init.dataChannel,
|
|
33599
|
+
multiaddr: ma,
|
|
33600
|
+
dataChannelOptions: this.init.dataChannel,
|
|
33601
|
+
signal: options.signal,
|
|
33602
|
+
connectionManager: this.components.connectionManager,
|
|
33603
|
+
transportManager: this.components.transportManager,
|
|
33604
|
+
log: this.log,
|
|
33605
|
+
logger: this.components.logger
|
|
33606
|
+
});
|
|
33607
|
+
const webRTCConn = new WebRTCMultiaddrConnection(this.components, {
|
|
33608
|
+
peerConnection,
|
|
33609
|
+
timeline: { open: Date.now() },
|
|
33610
|
+
remoteAddr: remoteAddress,
|
|
33611
|
+
metrics: this.metrics?.dialerEvents
|
|
33612
|
+
});
|
|
33613
|
+
const connection = await options.upgrader.upgradeOutbound(webRTCConn, {
|
|
33614
|
+
skipProtection: true,
|
|
33615
|
+
skipEncryption: true,
|
|
33616
|
+
muxerFactory
|
|
33617
|
+
});
|
|
33618
|
+
// close the connection on shut down
|
|
33619
|
+
this._closeOnShutdown(peerConnection, webRTCConn);
|
|
33620
|
+
return connection;
|
|
33621
|
+
}
|
|
33622
|
+
async _onProtocol({ connection, stream }) {
|
|
33623
|
+
const signal = AbortSignal.timeout(this.init.inboundConnectionTimeout ?? INBOUND_CONNECTION_TIMEOUT);
|
|
33624
|
+
const peerConnection = new RTCPeerConnection(typeof this.init.rtcConfiguration === 'function' ? await this.init.rtcConfiguration() : this.init.rtcConfiguration);
|
|
33625
|
+
const muxerFactory = new DataChannelMuxerFactory(this.components, {
|
|
33626
|
+
peerConnection,
|
|
33627
|
+
dataChannelOptions: this.init.dataChannel
|
|
33628
|
+
});
|
|
33629
|
+
try {
|
|
33630
|
+
const { remoteAddress } = await handleIncomingStream({
|
|
33631
|
+
peerConnection,
|
|
33632
|
+
connection,
|
|
33633
|
+
stream,
|
|
33634
|
+
signal,
|
|
33635
|
+
log: this.log
|
|
33636
|
+
});
|
|
33637
|
+
// close the stream if SDP messages have been exchanged successfully
|
|
33638
|
+
await stream.close({
|
|
33639
|
+
signal
|
|
33640
|
+
});
|
|
33641
|
+
const webRTCConn = new WebRTCMultiaddrConnection(this.components, {
|
|
33642
|
+
peerConnection,
|
|
33643
|
+
timeline: { open: (new Date()).getTime() },
|
|
33644
|
+
remoteAddr: remoteAddress,
|
|
33645
|
+
metrics: this.metrics?.listenerEvents
|
|
33646
|
+
});
|
|
33647
|
+
await this.components.upgrader.upgradeInbound(webRTCConn, {
|
|
33648
|
+
skipEncryption: true,
|
|
33649
|
+
skipProtection: true,
|
|
33650
|
+
muxerFactory
|
|
33651
|
+
});
|
|
33652
|
+
// close the connection on shut down
|
|
33653
|
+
this._closeOnShutdown(peerConnection, webRTCConn);
|
|
33654
|
+
}
|
|
33655
|
+
catch (err) {
|
|
33656
|
+
this.log.error('incoming signalling error', err);
|
|
33657
|
+
peerConnection.close();
|
|
33658
|
+
stream.abort(err);
|
|
33659
|
+
throw err;
|
|
33660
|
+
}
|
|
33661
|
+
}
|
|
33662
|
+
_closeOnShutdown(pc, webRTCConn) {
|
|
33663
|
+
// close the connection on shut down
|
|
33664
|
+
const shutDownListener = () => {
|
|
33665
|
+
webRTCConn.close()
|
|
33666
|
+
.catch(err => {
|
|
33667
|
+
this.log.error('could not close WebRTCMultiaddrConnection', err);
|
|
33668
|
+
});
|
|
33669
|
+
};
|
|
33670
|
+
this.shutdownController.signal.addEventListener('abort', shutDownListener);
|
|
33671
|
+
pc.addEventListener('close', () => {
|
|
33672
|
+
this.shutdownController.signal.removeEventListener('abort', shutDownListener);
|
|
33673
|
+
});
|
|
33674
|
+
}
|
|
33675
|
+
}
|
|
33676
|
+
function splitAddr(ma) {
|
|
33677
|
+
const addrs = ma.toString().split(WEBRTC_TRANSPORT + '/');
|
|
33678
|
+
if (addrs.length !== 2) {
|
|
33679
|
+
throw new CodeError$2('webrtc protocol was not present in multiaddr', codes.ERR_INVALID_MULTIADDR);
|
|
33680
|
+
}
|
|
33681
|
+
if (!addrs[0].includes(CIRCUIT_RELAY_TRANSPORT)) {
|
|
33682
|
+
throw new CodeError$2('p2p-circuit protocol was not present in multiaddr', codes.ERR_INVALID_MULTIADDR);
|
|
33683
|
+
}
|
|
33684
|
+
// look for remote peerId
|
|
33685
|
+
let remoteAddr = multiaddr(addrs[0]);
|
|
33686
|
+
const destination = multiaddr('/' + addrs[1]);
|
|
33687
|
+
const destinationIdString = destination.getPeerId();
|
|
33688
|
+
if (destinationIdString == null) {
|
|
33689
|
+
throw new CodeError$2('destination peer id was missing', codes.ERR_INVALID_MULTIADDR);
|
|
33690
|
+
}
|
|
33691
|
+
const lastProtoInRemote = remoteAddr.protos().pop();
|
|
33692
|
+
if (lastProtoInRemote === undefined) {
|
|
33693
|
+
throw new CodeError$2('invalid multiaddr', codes.ERR_INVALID_MULTIADDR);
|
|
33694
|
+
}
|
|
33695
|
+
if (lastProtoInRemote.name !== 'p2p') {
|
|
33696
|
+
remoteAddr = remoteAddr.encapsulate(`/p2p/${destinationIdString}`);
|
|
33697
|
+
}
|
|
33698
|
+
return { baseAddr: remoteAddr, peerId: peerIdFromString(destinationIdString) };
|
|
33699
|
+
}
|
|
33700
|
+
|
|
33701
|
+
/**
|
|
33702
|
+
* @packageDocumentation
|
|
33703
|
+
*
|
|
33704
|
+
* A [libp2p transport](https://docs.libp2p.io/concepts/transports/overview/) based on [WebRTC datachannels](https://webrtc.org/).
|
|
33705
|
+
*
|
|
33706
|
+
* [WebRTC](https://www.w3.org/TR/webrtc/) is a specification that allows real-time communication between nodes - it's commonly used in browser video conferencing applications but it also provides a reliable data transport mechanism called [data channels](https://www.w3.org/TR/webrtc/#peer-to-peer-data-api) which libp2p uses to facilitate [protocol streams](https://docs.libp2p.io/concepts/multiplex/overview/) between peers.
|
|
33707
|
+
*
|
|
33708
|
+
* There are two transports exposed by this module, [webRTC](https://github.com/libp2p/specs/blob/master/webrtc/webrtc.md) and [webRTCDirect](https://github.com/libp2p/specs/blob/master/webrtc/webrtc-direct.md).
|
|
33709
|
+
*
|
|
33710
|
+
* ## WebRTC vs WebRTC Direct
|
|
33711
|
+
*
|
|
33712
|
+
* The connection establishment phase of WebRTC involves a handshake using [SDP](https://en.wikipedia.org/wiki/Session_Description_Protocol) during which two peers will exchange information such as open ports, network addresses and required capabilities.
|
|
33713
|
+
*
|
|
33714
|
+
* A third party is usually necessary to carry out this handshake, forwarding messages between the two peers until they can make a direct connection between themselves.
|
|
33715
|
+
*
|
|
33716
|
+
* The WebRTC transport uses libp2p [Circuit Relay](https://docs.libp2p.io/concepts/nat/circuit-relay/)s to forward SDP messages. Once a direct connection is formed the relay plays no further part in the exchange.
|
|
33717
|
+
*
|
|
33718
|
+
* WebRTC Direct uses a technique known as [SDP munging](https://webrtchacks.com/not-a-guide-to-sdp-munging/) to skip the handshake step, instead encoding enough information in the connection request that the responder can derive what would have been in the handshake messages and so requires no third parties to establish a connection.
|
|
33719
|
+
*
|
|
33720
|
+
* A WebRTC Direct multiaddr also includes a certhash of the target peer - this is used to allow opening a connection to the remote, which would otherwise be denied due to use of a self-signed certificate.
|
|
33721
|
+
*
|
|
33722
|
+
* In both cases, once the connection is established a [Noise handshake](https://noiseprotocol.org/noise.html) is carried out to ensure that the remote peer has the private key that corresponds to the public key that makes up their PeerId, giving you both encryption and authentication.
|
|
33723
|
+
*
|
|
33724
|
+
* ## Support
|
|
33725
|
+
*
|
|
33726
|
+
* WebRTC is supported in both Node.js and browsers.
|
|
33727
|
+
*
|
|
33728
|
+
* At the time of writing, WebRTC Direct is dial-only in browsers and not supported in Node.js at all.
|
|
33729
|
+
*
|
|
33730
|
+
* Support in Node.js is possible but PRs will need to be opened to [libdatachannel](https://github.com/paullouisageneau/libdatachannel) and the appropriate APIs exposed in [node-datachannel](https://github.com/murat-dogan/node-datachannel).
|
|
33731
|
+
*
|
|
33732
|
+
* WebRTC Direct support is available in rust-libp2p and arriving soon in go-libp2p.
|
|
33733
|
+
*
|
|
33734
|
+
* See the WebRTC section of https://connectivity.libp2p.io for more information.
|
|
33735
|
+
*
|
|
33736
|
+
* @example WebRTC
|
|
33737
|
+
*
|
|
33738
|
+
* WebRTC requires use of a relay to connect two nodes. The listener first discovers a relay server and makes a reservation, then the dialer can connect via the relayed address.
|
|
33739
|
+
*
|
|
33740
|
+
* ```TypeScript
|
|
33741
|
+
* import { noise } from '@chainsafe/libp2p-noise'
|
|
33742
|
+
* import { yamux } from '@chainsafe/libp2p-yamux'
|
|
33743
|
+
* import { echo } from '@libp2p/echo'
|
|
33744
|
+
* import { circuitRelayTransport, circuitRelayServer } from '@libp2p/circuit-relay-v2'
|
|
33745
|
+
* import { identify } from '@libp2p/identify'
|
|
33746
|
+
* import { webRTC } from '@libp2p/webrtc'
|
|
33747
|
+
* import { webSockets } from '@libp2p/websockets'
|
|
33748
|
+
* import * as filters from '@libp2p/websockets/filters'
|
|
33749
|
+
* import { WebRTC } from '@multiformats/multiaddr-matcher'
|
|
33750
|
+
* import delay from 'delay'
|
|
33751
|
+
* import { pipe } from 'it-pipe'
|
|
33752
|
+
* import { createLibp2p } from 'libp2p'
|
|
33753
|
+
* import type { Multiaddr } from '@multiformats/multiaddr'
|
|
33754
|
+
*
|
|
33755
|
+
* // the relay server listens on a transport dialable by the listener and the
|
|
33756
|
+
* // dialer, and has a relay service configured
|
|
33757
|
+
* const relay = await createLibp2p({
|
|
33758
|
+
* addresses: {
|
|
33759
|
+
* listen: ['/ip4/127.0.0.1/tcp/0/ws']
|
|
33760
|
+
* },
|
|
33761
|
+
* transports: [
|
|
33762
|
+
* webSockets({filter: filters.all})
|
|
33763
|
+
* ],
|
|
33764
|
+
* connectionEncryption: [noise()],
|
|
33765
|
+
* streamMuxers: [yamux()],
|
|
33766
|
+
* services: {
|
|
33767
|
+
* identify: identify(),
|
|
33768
|
+
* relay: circuitRelayServer()
|
|
33769
|
+
* }
|
|
33770
|
+
* })
|
|
33771
|
+
*
|
|
33772
|
+
* // the listener has a WebSocket transport to dial the relay, a Circuit Relay
|
|
33773
|
+
* // transport to make a reservation, and a WebRTC transport to accept incoming
|
|
33774
|
+
* // WebRTC connections
|
|
33775
|
+
* const listener = await createLibp2p({
|
|
33776
|
+
* addresses: {
|
|
33777
|
+
* listen: ['/webrtc']
|
|
33778
|
+
* },
|
|
33779
|
+
* transports: [
|
|
33780
|
+
* webSockets({filter: filters.all}),
|
|
33781
|
+
* webRTC(),
|
|
33782
|
+
* circuitRelayTransport({
|
|
33783
|
+
* discoverRelays: 1
|
|
33784
|
+
* })
|
|
33785
|
+
* ],
|
|
33786
|
+
* connectionEncryption: [noise()],
|
|
33787
|
+
* streamMuxers: [yamux()],
|
|
33788
|
+
* services: {
|
|
33789
|
+
* identify: identify(),
|
|
33790
|
+
* echo: echo()
|
|
33791
|
+
* }
|
|
33792
|
+
* })
|
|
33793
|
+
*
|
|
33794
|
+
* // the listener dials the relay (or discovers a public relay via some other
|
|
33795
|
+
* // method)
|
|
33796
|
+
* await listener.dial(relay.getMultiaddrs(), {
|
|
33797
|
+
* signal: AbortSignal.timeout(5000)
|
|
33798
|
+
* })
|
|
33799
|
+
*
|
|
33800
|
+
* let webRTCMultiaddr: Multiaddr | undefined
|
|
33801
|
+
*
|
|
33802
|
+
* // wait for the listener to make a reservation on the relay
|
|
33803
|
+
* while (true) {
|
|
33804
|
+
* webRTCMultiaddr = listener.getMultiaddrs().find(ma => WebRTC.matches(ma))
|
|
33805
|
+
*
|
|
33806
|
+
* if (webRTCMultiaddr != null) {
|
|
33807
|
+
* break
|
|
33808
|
+
* }
|
|
33809
|
+
*
|
|
33810
|
+
* // try again later
|
|
33811
|
+
* await delay(1000)
|
|
33812
|
+
* }
|
|
33813
|
+
*
|
|
33814
|
+
* // the dialer has Circuit Relay, WebSocket and WebRTC transports to dial
|
|
33815
|
+
* // the listener via the relay, complete the SDP handshake and establish a
|
|
33816
|
+
* // direct WebRTC connection
|
|
33817
|
+
* const dialer = await createLibp2p({
|
|
33818
|
+
* transports: [
|
|
33819
|
+
* webSockets({filter: filters.all}),
|
|
33820
|
+
* webRTC(),
|
|
33821
|
+
* circuitRelayTransport()
|
|
33822
|
+
* ],
|
|
33823
|
+
* connectionEncryption: [noise()],
|
|
33824
|
+
* streamMuxers: [yamux()],
|
|
33825
|
+
* services: {
|
|
33826
|
+
* identify: identify(),
|
|
33827
|
+
* echo: echo()
|
|
33828
|
+
* }
|
|
33829
|
+
* })
|
|
33830
|
+
*
|
|
33831
|
+
* // dial the listener and open an echo protocol stream
|
|
33832
|
+
* const stream = await dialer.dialProtocol(webRTCMultiaddr, dialer.services.echo.protocol, {
|
|
33833
|
+
* signal: AbortSignal.timeout(5000)
|
|
33834
|
+
* })
|
|
33835
|
+
*
|
|
33836
|
+
* // we can now stop the relay
|
|
33837
|
+
* await relay.stop()
|
|
33838
|
+
*
|
|
33839
|
+
* // send/receive some data from the remote peer via a direct connection
|
|
33840
|
+
* await pipe(
|
|
33841
|
+
* [new TextEncoder().encode('hello world')],
|
|
33842
|
+
* stream,
|
|
33843
|
+
* async source => {
|
|
33844
|
+
* for await (const buf of source) {
|
|
33845
|
+
* console.info(new TextDecoder().decode(buf.subarray()))
|
|
33846
|
+
* }
|
|
33847
|
+
* }
|
|
33848
|
+
* )
|
|
33849
|
+
* ```
|
|
33850
|
+
*
|
|
33851
|
+
* @example WebRTC Direct
|
|
33852
|
+
*
|
|
33853
|
+
* At the time of writing WebRTC Direct is dial-only in browsers and unsupported in Node.js.
|
|
33854
|
+
*
|
|
33855
|
+
* The only implementation that supports a WebRTC Direct listener is go-libp2p and it's not yet enabled by default.
|
|
33856
|
+
*
|
|
33857
|
+
* ```TypeScript
|
|
33858
|
+
* import { createLibp2p } from 'libp2p'
|
|
33859
|
+
* import { noise } from '@chainsafe/libp2p-noise'
|
|
33860
|
+
* import { multiaddr } from '@multiformats/multiaddr'
|
|
33861
|
+
* import { pipe } from 'it-pipe'
|
|
33862
|
+
* import { fromString, toString } from 'uint8arrays'
|
|
33863
|
+
* import { webRTCDirect } from '@libp2p/webrtc'
|
|
33864
|
+
*
|
|
33865
|
+
* const node = await createLibp2p({
|
|
33866
|
+
* transports: [
|
|
33867
|
+
* webRTCDirect()
|
|
33868
|
+
* ],
|
|
33869
|
+
* connectionEncryption: [
|
|
33870
|
+
* noise()
|
|
33871
|
+
* ]
|
|
33872
|
+
* })
|
|
33873
|
+
*
|
|
33874
|
+
* await node.start()
|
|
33875
|
+
*
|
|
33876
|
+
* // this multiaddr corresponds to a remote node running a WebRTC Direct listener
|
|
33877
|
+
* const ma = multiaddr('/ip4/0.0.0.0/udp/56093/webrtc-direct/certhash/uEiByaEfNSLBexWBNFZy_QB1vAKEj7JAXDizRs4_SnTflsQ')
|
|
33878
|
+
* const stream = await node.dialProtocol(ma, '/my-protocol/1.0.0', {
|
|
33879
|
+
* signal: AbortSignal.timeout(10_000)
|
|
33880
|
+
* })
|
|
33881
|
+
*
|
|
33882
|
+
* await pipe(
|
|
33883
|
+
* [fromString(`Hello js-libp2p-webrtc\n`)],
|
|
33884
|
+
* stream,
|
|
33885
|
+
* async function (source) {
|
|
33886
|
+
* for await (const buf of source) {
|
|
33887
|
+
* console.info(toString(buf.subarray()))
|
|
33888
|
+
* }
|
|
33889
|
+
* }
|
|
33890
|
+
* )
|
|
33891
|
+
* ```
|
|
33892
|
+
*/
|
|
33893
|
+
/**
|
|
33894
|
+
* @param {WebRTCTransportInit} init - WebRTC transport configuration
|
|
33895
|
+
* @param {RTCConfiguration} init.rtcConfiguration - RTCConfiguration
|
|
33896
|
+
* @param init.dataChannel - DataChannel configurations
|
|
33897
|
+
* @param {number} init.dataChannel.maxMessageSize - Max message size that can be sent through the DataChannel. Larger messages will be chunked into smaller messages below this size (default 16kb)
|
|
33898
|
+
* @param {number} init.dataChannel.maxBufferedAmount - Max buffered amount a DataChannel can have (default 16mb)
|
|
33899
|
+
* @param {number} init.dataChannel.bufferedAmountLowEventTimeout - If max buffered amount is reached, this is the max time that is waited before the buffer is cleared (default 30 seconds)
|
|
33900
|
+
* @returns
|
|
33901
|
+
*/
|
|
33902
|
+
function webRTC(init) {
|
|
33903
|
+
return (components) => new WebRTCTransport(components, init);
|
|
33904
|
+
}
|
|
33905
|
+
|
|
33906
|
+
/* eslint-disable import/export */
|
|
33907
|
+
/* eslint-disable complexity */
|
|
33908
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
33909
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
|
|
33910
|
+
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
33911
|
+
var HolePunch;
|
|
33912
|
+
(function (HolePunch) {
|
|
33913
|
+
(function (Type) {
|
|
33914
|
+
Type["UNUSED"] = "UNUSED";
|
|
33915
|
+
Type["CONNECT"] = "CONNECT";
|
|
33916
|
+
Type["SYNC"] = "SYNC";
|
|
33917
|
+
})(HolePunch.Type || (HolePunch.Type = {}));
|
|
33918
|
+
let __TypeValues;
|
|
33919
|
+
(function (__TypeValues) {
|
|
33920
|
+
__TypeValues[__TypeValues["UNUSED"] = 0] = "UNUSED";
|
|
33921
|
+
__TypeValues[__TypeValues["CONNECT"] = 100] = "CONNECT";
|
|
33922
|
+
__TypeValues[__TypeValues["SYNC"] = 300] = "SYNC";
|
|
33923
|
+
})(__TypeValues || (__TypeValues = {}));
|
|
33924
|
+
(function (Type) {
|
|
33925
|
+
Type.codec = () => {
|
|
33926
|
+
return enumeration(__TypeValues);
|
|
33927
|
+
};
|
|
33928
|
+
})(HolePunch.Type || (HolePunch.Type = {}));
|
|
33929
|
+
let _codec;
|
|
33930
|
+
HolePunch.codec = () => {
|
|
33931
|
+
if (_codec == null) {
|
|
33932
|
+
_codec = message((obj, w, opts = {}) => {
|
|
33933
|
+
if (opts.lengthDelimited !== false) {
|
|
33934
|
+
w.fork();
|
|
33935
|
+
}
|
|
33936
|
+
if (obj.type != null) {
|
|
33937
|
+
w.uint32(8);
|
|
33938
|
+
HolePunch.Type.codec().encode(obj.type, w);
|
|
33939
|
+
}
|
|
33940
|
+
if (obj.observedAddresses != null) {
|
|
33941
|
+
for (const value of obj.observedAddresses) {
|
|
33942
|
+
w.uint32(18);
|
|
33943
|
+
w.bytes(value);
|
|
33944
|
+
}
|
|
33945
|
+
}
|
|
33946
|
+
if (opts.lengthDelimited !== false) {
|
|
33947
|
+
w.ldelim();
|
|
33948
|
+
}
|
|
33949
|
+
}, (reader, length) => {
|
|
33950
|
+
const obj = {
|
|
33951
|
+
observedAddresses: []
|
|
33952
|
+
};
|
|
33953
|
+
const end = length == null ? reader.len : reader.pos + length;
|
|
33954
|
+
while (reader.pos < end) {
|
|
33955
|
+
const tag = reader.uint32();
|
|
33956
|
+
switch (tag >>> 3) {
|
|
33957
|
+
case 1:
|
|
33958
|
+
obj.type = HolePunch.Type.codec().decode(reader);
|
|
33959
|
+
break;
|
|
33960
|
+
case 2:
|
|
33961
|
+
obj.observedAddresses.push(reader.bytes());
|
|
33962
|
+
break;
|
|
33963
|
+
default:
|
|
33964
|
+
reader.skipType(tag & 7);
|
|
33965
|
+
break;
|
|
33966
|
+
}
|
|
33967
|
+
}
|
|
33968
|
+
return obj;
|
|
33969
|
+
});
|
|
33970
|
+
}
|
|
33971
|
+
return _codec;
|
|
33972
|
+
};
|
|
33973
|
+
HolePunch.encode = (obj) => {
|
|
33974
|
+
return encodeMessage(obj, HolePunch.codec());
|
|
33975
|
+
};
|
|
33976
|
+
HolePunch.decode = (buf) => {
|
|
33977
|
+
return decodeMessage(buf, HolePunch.codec());
|
|
33978
|
+
};
|
|
33979
|
+
})(HolePunch || (HolePunch = {}));
|
|
33980
|
+
|
|
33981
|
+
/**
|
|
33982
|
+
* Returns true if the passed multiaddr is public, not relayed and we have a
|
|
33983
|
+
* transport that can dial it
|
|
33984
|
+
*/
|
|
33985
|
+
function isPublicAndDialable(ma, transportManager) {
|
|
33986
|
+
// ignore circuit relay
|
|
33987
|
+
if (Circuit$1.matches(ma)) {
|
|
33988
|
+
return false;
|
|
33989
|
+
}
|
|
33990
|
+
const transport = transportManager.dialTransportForMultiaddr(ma);
|
|
33991
|
+
if (transport == null) {
|
|
33992
|
+
return false;
|
|
33993
|
+
}
|
|
33994
|
+
// dns addresses are probably public?
|
|
33995
|
+
if (DNS$2.matches(ma)) {
|
|
33996
|
+
return true;
|
|
33997
|
+
}
|
|
33998
|
+
// ensure we have only IPv4/IPv6 addresses
|
|
33999
|
+
if (!IP$1.matches(ma)) {
|
|
34000
|
+
return false;
|
|
34001
|
+
}
|
|
34002
|
+
return isPrivateIp(ma.toOptions().host) === false;
|
|
34003
|
+
}
|
|
34004
|
+
|
|
34005
|
+
// https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#rpc-messages
|
|
34006
|
+
const MAX_DCUTR_MESSAGE_SIZE = 1024 * 4;
|
|
34007
|
+
// ensure the dial has a high priority to jump to the head of the dial queue
|
|
34008
|
+
const DCUTR_DIAL_PRIORITY = 100;
|
|
34009
|
+
const defaultValues$1 = {
|
|
34010
|
+
// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/holepunch/holepuncher.go#L27
|
|
34011
|
+
timeout: 5000,
|
|
34012
|
+
// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/holepunch/holepuncher.go#L28
|
|
34013
|
+
retries: 3,
|
|
34014
|
+
maxInboundStreams: 1,
|
|
34015
|
+
maxOutboundStreams: 1
|
|
34016
|
+
};
|
|
34017
|
+
class DefaultDCUtRService {
|
|
34018
|
+
started;
|
|
34019
|
+
timeout;
|
|
34020
|
+
retries;
|
|
34021
|
+
maxInboundStreams;
|
|
34022
|
+
maxOutboundStreams;
|
|
34023
|
+
peerStore;
|
|
34024
|
+
registrar;
|
|
34025
|
+
connectionManager;
|
|
34026
|
+
addressManager;
|
|
34027
|
+
transportManager;
|
|
34028
|
+
topologyId;
|
|
34029
|
+
log;
|
|
34030
|
+
constructor(components, init) {
|
|
34031
|
+
this.log = components.logger.forComponent('libp2p:dcutr');
|
|
34032
|
+
this.started = false;
|
|
34033
|
+
this.peerStore = components.peerStore;
|
|
34034
|
+
this.registrar = components.registrar;
|
|
34035
|
+
this.addressManager = components.addressManager;
|
|
34036
|
+
this.connectionManager = components.connectionManager;
|
|
34037
|
+
this.transportManager = components.transportManager;
|
|
34038
|
+
this.timeout = init.timeout ?? defaultValues$1.timeout;
|
|
34039
|
+
this.retries = init.retries ?? defaultValues$1.retries;
|
|
34040
|
+
this.maxInboundStreams = init.maxInboundStreams ?? defaultValues$1.maxInboundStreams;
|
|
34041
|
+
this.maxOutboundStreams = init.maxOutboundStreams ?? defaultValues$1.maxOutboundStreams;
|
|
34042
|
+
}
|
|
34043
|
+
[Symbol.toStringTag] = '@libp2p/dcutr';
|
|
34044
|
+
[serviceDependencies] = [
|
|
34045
|
+
'@libp2p/identify'
|
|
34046
|
+
];
|
|
34047
|
+
isStarted() {
|
|
34048
|
+
return this.started;
|
|
34049
|
+
}
|
|
34050
|
+
async start() {
|
|
34051
|
+
if (this.started) {
|
|
34052
|
+
return;
|
|
34053
|
+
}
|
|
34054
|
+
// register for notifications of when peers that support DCUtR connect
|
|
34055
|
+
// nb. requires the identify service to be enabled
|
|
34056
|
+
this.topologyId = await this.registrar.register(multicodec, {
|
|
34057
|
+
notifyOnTransient: true,
|
|
34058
|
+
onConnect: (peerId, connection) => {
|
|
34059
|
+
if (!connection.transient) {
|
|
34060
|
+
// the connection is already direct, no upgrade is required
|
|
34061
|
+
return;
|
|
34062
|
+
}
|
|
34063
|
+
// the inbound peer starts the connection upgrade
|
|
34064
|
+
if (connection.direction !== 'inbound') {
|
|
34065
|
+
return;
|
|
34066
|
+
}
|
|
34067
|
+
this.upgradeInbound(connection)
|
|
34068
|
+
.catch(err => {
|
|
34069
|
+
this.log.error('error during outgoing DCUtR attempt', err);
|
|
34070
|
+
});
|
|
34071
|
+
}
|
|
34072
|
+
});
|
|
34073
|
+
await this.registrar.handle(multicodec, (data) => {
|
|
34074
|
+
void this.handleIncomingUpgrade(data.stream, data.connection).catch(err => {
|
|
34075
|
+
this.log.error('error during incoming DCUtR attempt', err);
|
|
34076
|
+
data.stream.abort(err);
|
|
34077
|
+
});
|
|
34078
|
+
}, {
|
|
34079
|
+
maxInboundStreams: this.maxInboundStreams,
|
|
34080
|
+
maxOutboundStreams: this.maxOutboundStreams,
|
|
34081
|
+
runOnTransientConnection: true
|
|
34082
|
+
});
|
|
34083
|
+
this.started = true;
|
|
34084
|
+
}
|
|
34085
|
+
async stop() {
|
|
34086
|
+
await this.registrar.unhandle(multicodec);
|
|
34087
|
+
if (this.topologyId != null) {
|
|
34088
|
+
this.registrar.unregister(this.topologyId);
|
|
34089
|
+
}
|
|
34090
|
+
this.started = false;
|
|
34091
|
+
}
|
|
34092
|
+
/**
|
|
34093
|
+
* Perform the inbound connection upgrade as B
|
|
34094
|
+
*
|
|
34095
|
+
* @see https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol
|
|
34096
|
+
*/
|
|
34097
|
+
async upgradeInbound(relayedConnection) {
|
|
34098
|
+
// Upon observing the new connection, the inbound peer (here B) checks the
|
|
34099
|
+
// addresses advertised by A via identify.
|
|
34100
|
+
//
|
|
34101
|
+
// If that set includes public addresses, then A may be reachable by a direct
|
|
34102
|
+
// connection, in which case B attempts a unilateral connection upgrade by
|
|
34103
|
+
// initiating a direct connection to A.
|
|
34104
|
+
if (await this.attemptUnilateralConnectionUpgrade(relayedConnection)) {
|
|
34105
|
+
return;
|
|
34106
|
+
}
|
|
34107
|
+
let stream;
|
|
34108
|
+
for (let i = 0; i < this.retries; i++) {
|
|
34109
|
+
const options = {
|
|
34110
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
34111
|
+
};
|
|
34112
|
+
try {
|
|
34113
|
+
// 1. B opens a stream to A using the /libp2p/dcutr protocol.
|
|
34114
|
+
stream = await relayedConnection.newStream([multicodec], {
|
|
34115
|
+
signal: options.signal,
|
|
34116
|
+
runOnTransientConnection: true
|
|
34117
|
+
});
|
|
34118
|
+
const pb = pbStream(stream, {
|
|
34119
|
+
maxDataLength: MAX_DCUTR_MESSAGE_SIZE
|
|
34120
|
+
}).pb(HolePunch);
|
|
34121
|
+
// 2. B sends to A a Connect message containing its observed (and
|
|
34122
|
+
// possibly predicted) addresses from identify and starts a timer
|
|
34123
|
+
// to measure RTT of the relay connection.
|
|
34124
|
+
this.log('B sending connect to %p', relayedConnection.remotePeer);
|
|
34125
|
+
const connectTimer = Date.now();
|
|
34126
|
+
await pb.write({
|
|
34127
|
+
type: HolePunch.Type.CONNECT,
|
|
34128
|
+
observedAddresses: this.addressManager.getAddresses().map(ma => ma.bytes)
|
|
34129
|
+
}, options);
|
|
34130
|
+
this.log('B receiving connect from %p', relayedConnection.remotePeer);
|
|
34131
|
+
// 4. Upon receiving the Connect, B sends a Sync message
|
|
34132
|
+
const connect = await pb.read(options);
|
|
34133
|
+
if (connect.type !== HolePunch.Type.CONNECT) {
|
|
34134
|
+
this.log('A sent wrong message type');
|
|
34135
|
+
throw new CodeError$2('DCUtR message type was incorrect', ERR_INVALID_MESSAGE);
|
|
34136
|
+
}
|
|
34137
|
+
const multiaddrs = this.getDialableMultiaddrs(connect.observedAddresses);
|
|
34138
|
+
if (multiaddrs.length === 0) {
|
|
34139
|
+
this.log('A did not have any dialable multiaddrs');
|
|
34140
|
+
throw new CodeError$2('DCUtR connect message had no multiaddrs', ERR_INVALID_MESSAGE);
|
|
34141
|
+
}
|
|
34142
|
+
const rtt = Date.now() - connectTimer;
|
|
34143
|
+
this.log('A sending sync, rtt %dms', rtt);
|
|
34144
|
+
await pb.write({
|
|
34145
|
+
type: HolePunch.Type.SYNC,
|
|
34146
|
+
observedAddresses: []
|
|
34147
|
+
}, options);
|
|
34148
|
+
this.log('A waiting for half RTT');
|
|
34149
|
+
// ..and starts a timer for half the RTT measured from the time between
|
|
34150
|
+
// sending the initial Connect and receiving the response
|
|
34151
|
+
await delay(rtt / 2);
|
|
34152
|
+
// TODO: when we have a QUIC transport, the dial step is different - for
|
|
34153
|
+
// now we only have tcp support
|
|
34154
|
+
// https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol
|
|
34155
|
+
this.log('B dialing', multiaddrs);
|
|
34156
|
+
// Upon expiry of the timer, B dials the address to A.
|
|
34157
|
+
const conn = await this.connectionManager.openConnection(multiaddrs, {
|
|
34158
|
+
signal: options.signal,
|
|
34159
|
+
priority: DCUTR_DIAL_PRIORITY
|
|
34160
|
+
});
|
|
34161
|
+
this.log('DCUtR to %p succeeded to address %a, closing relayed connection', relayedConnection.remotePeer, conn.remoteAddr);
|
|
34162
|
+
await relayedConnection.close(options);
|
|
34163
|
+
break;
|
|
34164
|
+
}
|
|
34165
|
+
catch (err) {
|
|
34166
|
+
this.log.error('error while attempting DCUtR on attempt %d of %d', i + 1, this.retries, err);
|
|
34167
|
+
stream?.abort(err);
|
|
34168
|
+
if (i === this.retries) {
|
|
34169
|
+
throw err;
|
|
34170
|
+
}
|
|
34171
|
+
}
|
|
34172
|
+
finally {
|
|
34173
|
+
if (stream != null) {
|
|
34174
|
+
await stream.close(options);
|
|
34175
|
+
}
|
|
34176
|
+
}
|
|
34177
|
+
}
|
|
34178
|
+
}
|
|
34179
|
+
/**
|
|
34180
|
+
* This is performed when A has dialed B via a relay but A also has a public
|
|
34181
|
+
* address that B can dial directly
|
|
34182
|
+
*/
|
|
34183
|
+
async attemptUnilateralConnectionUpgrade(relayedConnection) {
|
|
34184
|
+
// Upon observing the new connection, the inbound peer (here B) checks the
|
|
34185
|
+
// addresses advertised by A via identify.
|
|
34186
|
+
const peerInfo = await this.peerStore.get(relayedConnection.remotePeer);
|
|
34187
|
+
// If that set includes public addresses, then A may be reachable by a direct
|
|
34188
|
+
// connection, in which case B attempts a unilateral connection upgrade by
|
|
34189
|
+
// initiating a direct connection to A.
|
|
34190
|
+
const publicAddresses = peerInfo.addresses
|
|
34191
|
+
.map(address => {
|
|
34192
|
+
const ma = address.multiaddr;
|
|
34193
|
+
// ensure all multiaddrs have the peer id
|
|
34194
|
+
if (ma.getPeerId() == null) {
|
|
34195
|
+
return ma.encapsulate(`/p2p/${relayedConnection.remotePeer}`);
|
|
34196
|
+
}
|
|
34197
|
+
return ma;
|
|
34198
|
+
})
|
|
34199
|
+
.filter(ma => {
|
|
34200
|
+
return isPublicAndDialable(ma, this.transportManager);
|
|
34201
|
+
});
|
|
34202
|
+
if (publicAddresses.length > 0) {
|
|
34203
|
+
const signal = AbortSignal.timeout(this.timeout);
|
|
34204
|
+
try {
|
|
34205
|
+
this.log('attempting unilateral connection upgrade to %a', publicAddresses);
|
|
34206
|
+
// force-dial the multiaddr(s), otherwise `connectionManager.openConnection`
|
|
34207
|
+
// will return the existing relayed connection
|
|
34208
|
+
const connection = await this.connectionManager.openConnection(publicAddresses, {
|
|
34209
|
+
signal,
|
|
34210
|
+
force: true
|
|
34211
|
+
});
|
|
34212
|
+
if (connection.transient) {
|
|
34213
|
+
throw new Error('Could not open a new, non-transient, connection');
|
|
34214
|
+
}
|
|
34215
|
+
this.log('unilateral connection upgrade to %p succeeded via %a, closing relayed connection', relayedConnection.remotePeer, connection.remoteAddr);
|
|
34216
|
+
await relayedConnection.close({
|
|
34217
|
+
signal
|
|
34218
|
+
});
|
|
34219
|
+
return true;
|
|
34220
|
+
}
|
|
34221
|
+
catch (err) {
|
|
34222
|
+
this.log.error('unilateral connection upgrade to %p on addresses %a failed', relayedConnection.remotePeer, publicAddresses, err);
|
|
34223
|
+
}
|
|
34224
|
+
}
|
|
34225
|
+
else {
|
|
34226
|
+
this.log('peer %p has no public addresses, not attempting unilateral connection upgrade', relayedConnection.remotePeer);
|
|
34227
|
+
}
|
|
34228
|
+
// no public addresses or failed to dial public addresses
|
|
34229
|
+
return false;
|
|
34230
|
+
}
|
|
34231
|
+
/**
|
|
34232
|
+
* Perform the connection upgrade as A
|
|
34233
|
+
*
|
|
34234
|
+
* @see https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol
|
|
34235
|
+
*/
|
|
34236
|
+
async handleIncomingUpgrade(stream, relayedConnection) {
|
|
34237
|
+
const options = {
|
|
34238
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
34239
|
+
};
|
|
34240
|
+
try {
|
|
34241
|
+
const pb = pbStream(stream, {
|
|
34242
|
+
maxDataLength: MAX_DCUTR_MESSAGE_SIZE
|
|
34243
|
+
}).pb(HolePunch);
|
|
34244
|
+
this.log('A receiving connect');
|
|
34245
|
+
// 3. Upon receiving the Connect, A responds back with a Connect message
|
|
34246
|
+
// containing its observed (and possibly predicted) addresses.
|
|
34247
|
+
const connect = await pb.read(options);
|
|
34248
|
+
if (connect.type !== HolePunch.Type.CONNECT) {
|
|
34249
|
+
this.log('B sent wrong message type');
|
|
34250
|
+
throw new CodeError$2('DCUtR message type was incorrect', ERR_INVALID_MESSAGE);
|
|
34251
|
+
}
|
|
34252
|
+
if (connect.observedAddresses.length === 0) {
|
|
34253
|
+
this.log('B sent no multiaddrs');
|
|
34254
|
+
throw new CodeError$2('DCUtR connect message had no multiaddrs', ERR_INVALID_MESSAGE);
|
|
34255
|
+
}
|
|
34256
|
+
const multiaddrs = this.getDialableMultiaddrs(connect.observedAddresses);
|
|
34257
|
+
if (multiaddrs.length === 0) {
|
|
34258
|
+
this.log('B had no dialable multiaddrs');
|
|
34259
|
+
throw new CodeError$2('DCUtR connect message had no dialable multiaddrs', ERR_INVALID_MESSAGE);
|
|
34260
|
+
}
|
|
34261
|
+
this.log('A sending connect');
|
|
34262
|
+
await pb.write({
|
|
34263
|
+
type: HolePunch.Type.CONNECT,
|
|
34264
|
+
observedAddresses: this.addressManager.getAddresses().map(ma => ma.bytes)
|
|
34265
|
+
});
|
|
34266
|
+
this.log('A receiving sync');
|
|
34267
|
+
const sync = await pb.read(options);
|
|
34268
|
+
if (sync.type !== HolePunch.Type.SYNC) {
|
|
34269
|
+
throw new CodeError$2('DCUtR message type was incorrect', ERR_INVALID_MESSAGE);
|
|
34270
|
+
}
|
|
34271
|
+
// TODO: when we have a QUIC transport, the dial step is different - for
|
|
34272
|
+
// now we only have tcp support
|
|
34273
|
+
// https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol
|
|
34274
|
+
// Upon receiving the Sync, A immediately dials the address to B
|
|
34275
|
+
this.log('A dialing', multiaddrs);
|
|
34276
|
+
const connection = await this.connectionManager.openConnection(multiaddrs, {
|
|
34277
|
+
signal: options.signal,
|
|
34278
|
+
priority: DCUTR_DIAL_PRIORITY,
|
|
34279
|
+
force: true
|
|
34280
|
+
});
|
|
34281
|
+
this.log('DCUtR to %p succeeded via %a, closing relayed connection', relayedConnection.remotePeer, connection.remoteAddr);
|
|
34282
|
+
await relayedConnection.close(options);
|
|
34283
|
+
}
|
|
34284
|
+
catch (err) {
|
|
34285
|
+
this.log.error('incoming DCUtR from %p failed', relayedConnection.remotePeer, err);
|
|
34286
|
+
stream.abort(err);
|
|
34287
|
+
}
|
|
34288
|
+
finally {
|
|
34289
|
+
await stream.close(options);
|
|
34290
|
+
}
|
|
34291
|
+
}
|
|
34292
|
+
/**
|
|
34293
|
+
* Takes the `addr` and converts it to a Multiaddr if possible
|
|
34294
|
+
*/
|
|
34295
|
+
getDialableMultiaddrs(addrs) {
|
|
34296
|
+
const output = [];
|
|
34297
|
+
for (const addr of addrs) {
|
|
34298
|
+
if (addr == null || addr.length === 0) {
|
|
34299
|
+
continue;
|
|
34300
|
+
}
|
|
34301
|
+
try {
|
|
34302
|
+
const ma = multiaddr(addr);
|
|
34303
|
+
if (!isPublicAndDialable(ma, this.transportManager)) {
|
|
34304
|
+
continue;
|
|
34305
|
+
}
|
|
34306
|
+
output.push(ma);
|
|
34307
|
+
}
|
|
34308
|
+
catch { }
|
|
34309
|
+
}
|
|
34310
|
+
return output;
|
|
34311
|
+
}
|
|
34312
|
+
}
|
|
34313
|
+
|
|
34314
|
+
/**
|
|
34315
|
+
* @packageDocumentation
|
|
34316
|
+
*
|
|
34317
|
+
* Direct Connection Upgrade through Relay (DCUtR) is a protocol that allows two
|
|
34318
|
+
* nodes to connect to each other who would otherwise be prevented doing so due
|
|
34319
|
+
* to being behind NATed connections or firewalls.
|
|
34320
|
+
*
|
|
34321
|
+
* The protocol involves making a relayed connection between the two peers and
|
|
34322
|
+
* using the relay to synchronise connection timings so that they dial each other
|
|
34323
|
+
* at precisely the same moment.
|
|
34324
|
+
*
|
|
34325
|
+
* @example
|
|
34326
|
+
*
|
|
34327
|
+
* ```TypeScript
|
|
34328
|
+
* import { createLibp2p } from 'libp2p'
|
|
34329
|
+
* import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
|
34330
|
+
* import { tcp } from '@libp2p/tcp'
|
|
34331
|
+
* import { identify } from '@libp2p/identify'
|
|
34332
|
+
* import { dcutr } from '@libp2p/dcutr'
|
|
34333
|
+
* import { multiaddr } from '@multiformats/multiaddr'
|
|
34334
|
+
*
|
|
34335
|
+
* const node = await createLibp2p({
|
|
34336
|
+
* transports: [
|
|
34337
|
+
* circuitRelayTransport(),
|
|
34338
|
+
* tcp()
|
|
34339
|
+
* ],
|
|
34340
|
+
* services: {
|
|
34341
|
+
* identify: identify(),
|
|
34342
|
+
* dcutr: dcutr()
|
|
34343
|
+
* }
|
|
34344
|
+
* })
|
|
34345
|
+
*
|
|
34346
|
+
* // QmTarget is a peer that is behind a NAT, supports TCP and has a relay
|
|
34347
|
+
* // reservation
|
|
34348
|
+
* const ma = multiaddr('/ip4/.../p2p/QmRelay/p2p-circuit/p2p/QmTarget')
|
|
34349
|
+
* await node.dial(ma)
|
|
34350
|
+
*
|
|
34351
|
+
* // after a while the connection should automatically get upgraded to a
|
|
34352
|
+
* // direct connection (e.g. non-transient)
|
|
34353
|
+
* while (true) {
|
|
34354
|
+
* const connections = node.getConnections()
|
|
34355
|
+
*
|
|
34356
|
+
* if (connections.find(conn => conn.transient === false)) {
|
|
34357
|
+
* console.info('have direct connection')
|
|
34358
|
+
* break
|
|
34359
|
+
* } else {
|
|
34360
|
+
* console.info('have relayed connection')
|
|
34361
|
+
*
|
|
34362
|
+
* // wait a few seconds to see if it's succeeded yet
|
|
34363
|
+
* await new Promise<void>((resolve) => {
|
|
34364
|
+
* setTimeout(() => resolve(), 5000)
|
|
34365
|
+
* })
|
|
34366
|
+
* }
|
|
34367
|
+
* }
|
|
34368
|
+
* ```
|
|
34369
|
+
*/
|
|
34370
|
+
/**
|
|
34371
|
+
* The DCUtR protocol
|
|
34372
|
+
*/
|
|
34373
|
+
const multicodec = '/libp2p/dcutr';
|
|
34374
|
+
function dcutr(init = {}) {
|
|
34375
|
+
return (components) => new DefaultDCUtRService(components, init);
|
|
34376
|
+
}
|
|
34377
|
+
|
|
34378
|
+
// Protocol violation errors
|
|
34379
|
+
const ERR_INVALID_FRAME = 'ERR_INVALID_FRAME';
|
|
34380
|
+
const ERR_UNREQUESTED_PING = 'ERR_UNREQUESTED_PING';
|
|
34381
|
+
const ERR_NOT_MATCHING_PING = 'ERR_NOT_MATCHING_PING';
|
|
34382
|
+
const ERR_STREAM_ALREADY_EXISTS = 'ERR_STREAM_ALREADY_EXISTS';
|
|
34383
|
+
const ERR_DECODE_INVALID_VERSION = 'ERR_DECODE_INVALID_VERSION';
|
|
34384
|
+
const ERR_BOTH_CLIENTS = 'ERR_BOTH_CLIENTS';
|
|
34385
|
+
const ERR_RECV_WINDOW_EXCEEDED = 'ERR_RECV_WINDOW_EXCEEDED';
|
|
34386
|
+
const PROTOCOL_ERRORS = new Set([
|
|
34387
|
+
ERR_INVALID_FRAME,
|
|
34388
|
+
ERR_UNREQUESTED_PING,
|
|
34389
|
+
ERR_NOT_MATCHING_PING,
|
|
34390
|
+
ERR_STREAM_ALREADY_EXISTS,
|
|
34391
|
+
ERR_DECODE_INVALID_VERSION,
|
|
34392
|
+
ERR_BOTH_CLIENTS,
|
|
34393
|
+
ERR_RECV_WINDOW_EXCEEDED
|
|
34394
|
+
]);
|
|
34395
|
+
// local errors
|
|
34396
|
+
const ERR_INVALID_CONFIG = 'ERR_INVALID_CONFIG';
|
|
34397
|
+
const ERR_MUXER_LOCAL_CLOSED = 'ERR_MUXER_LOCAL_CLOSED';
|
|
34398
|
+
const ERR_MUXER_REMOTE_CLOSED = 'ERR_MUXER_REMOTE_CLOSED';
|
|
34399
|
+
const ERR_STREAM_ABORT = 'ERR_STREAM_ABORT';
|
|
34400
|
+
const ERR_MAX_OUTBOUND_STREAMS_EXCEEDED = 'ERROR_MAX_OUTBOUND_STREAMS_EXCEEDED';
|
|
34401
|
+
const ERR_DECODE_IN_PROGRESS = 'ERR_DECODE_IN_PROGRESS';
|
|
34402
|
+
/**
|
|
34403
|
+
* INITIAL_STREAM_WINDOW is the initial stream window size.
|
|
34404
|
+
*
|
|
34405
|
+
* Not an implementation choice, this is defined in the specification
|
|
34406
|
+
*/
|
|
34407
|
+
const INITIAL_STREAM_WINDOW = 256 * 1024;
|
|
34408
|
+
/**
|
|
34409
|
+
* Default max stream window
|
|
34410
|
+
*/
|
|
34411
|
+
const MAX_STREAM_WINDOW = 16 * 1024 * 1024;
|
|
34412
|
+
|
|
34413
|
+
const defaultConfig = {
|
|
34414
|
+
enableKeepAlive: true,
|
|
34415
|
+
keepAliveInterval: 30000,
|
|
34416
|
+
maxInboundStreams: 1000,
|
|
34417
|
+
maxOutboundStreams: 1000,
|
|
34418
|
+
initialStreamWindowSize: INITIAL_STREAM_WINDOW,
|
|
34419
|
+
maxStreamWindowSize: MAX_STREAM_WINDOW,
|
|
34420
|
+
maxMessageSize: 64 * 1024
|
|
34421
|
+
};
|
|
34422
|
+
function verifyConfig(config) {
|
|
34423
|
+
if (config.keepAliveInterval <= 0) {
|
|
34424
|
+
throw new CodeError$2('keep-alive interval must be positive', ERR_INVALID_CONFIG);
|
|
34425
|
+
}
|
|
34426
|
+
if (config.maxInboundStreams < 0) {
|
|
34427
|
+
throw new CodeError$2('max inbound streams must be larger or equal 0', ERR_INVALID_CONFIG);
|
|
34428
|
+
}
|
|
34429
|
+
if (config.maxOutboundStreams < 0) {
|
|
34430
|
+
throw new CodeError$2('max outbound streams must be larger or equal 0', ERR_INVALID_CONFIG);
|
|
34431
|
+
}
|
|
34432
|
+
if (config.initialStreamWindowSize < INITIAL_STREAM_WINDOW) {
|
|
34433
|
+
throw new CodeError$2('InitialStreamWindowSize must be larger or equal 256 kB', ERR_INVALID_CONFIG);
|
|
34434
|
+
}
|
|
34435
|
+
if (config.maxStreamWindowSize < config.initialStreamWindowSize) {
|
|
34436
|
+
throw new CodeError$2('MaxStreamWindowSize must be larger than the InitialStreamWindowSize', ERR_INVALID_CONFIG);
|
|
34437
|
+
}
|
|
34438
|
+
if (config.maxStreamWindowSize > 2 ** 32 - 1) {
|
|
34439
|
+
throw new CodeError$2('MaxStreamWindowSize must be less than equal MAX_UINT32', ERR_INVALID_CONFIG);
|
|
34440
|
+
}
|
|
34441
|
+
if (config.maxMessageSize < 1024) {
|
|
34442
|
+
throw new CodeError$2('MaxMessageSize must be greater than a kilobyte', ERR_INVALID_CONFIG);
|
|
34443
|
+
}
|
|
34444
|
+
}
|
|
34445
|
+
|
|
34446
|
+
var FrameType;
|
|
34447
|
+
(function (FrameType) {
|
|
34448
|
+
/** Used to transmit data. May transmit zero length payloads depending on the flags. */
|
|
34449
|
+
FrameType[FrameType["Data"] = 0] = "Data";
|
|
34450
|
+
/** Used to updated the senders receive window size. This is used to implement per-session flow control. */
|
|
34451
|
+
FrameType[FrameType["WindowUpdate"] = 1] = "WindowUpdate";
|
|
34452
|
+
/** Used to measure RTT. It can also be used to heart-beat and do keep-alives over TCP. */
|
|
34453
|
+
FrameType[FrameType["Ping"] = 2] = "Ping";
|
|
34454
|
+
/** Used to close a session. */
|
|
34455
|
+
FrameType[FrameType["GoAway"] = 3] = "GoAway";
|
|
34456
|
+
})(FrameType || (FrameType = {}));
|
|
34457
|
+
var Flag;
|
|
34458
|
+
(function (Flag) {
|
|
34459
|
+
/** Signals the start of a new stream. May be sent with a data or window update message. Also sent with a ping to indicate outbound. */
|
|
34460
|
+
Flag[Flag["SYN"] = 1] = "SYN";
|
|
34461
|
+
/** Acknowledges the start of a new stream. May be sent with a data or window update message. Also sent with a ping to indicate response. */
|
|
34462
|
+
Flag[Flag["ACK"] = 2] = "ACK";
|
|
34463
|
+
/** Performs a half-close of a stream. May be sent with a data message or window update. */
|
|
34464
|
+
Flag[Flag["FIN"] = 4] = "FIN";
|
|
34465
|
+
/** Reset a stream immediately. May be sent with a data or window update message. */
|
|
34466
|
+
Flag[Flag["RST"] = 8] = "RST";
|
|
34467
|
+
})(Flag || (Flag = {}));
|
|
34468
|
+
Object.values(Flag).filter((x) => typeof x !== 'string');
|
|
34469
|
+
const YAMUX_VERSION = 0;
|
|
34470
|
+
var GoAwayCode;
|
|
34471
|
+
(function (GoAwayCode) {
|
|
34472
|
+
GoAwayCode[GoAwayCode["NormalTermination"] = 0] = "NormalTermination";
|
|
34473
|
+
GoAwayCode[GoAwayCode["ProtocolError"] = 1] = "ProtocolError";
|
|
34474
|
+
GoAwayCode[GoAwayCode["InternalError"] = 2] = "InternalError";
|
|
34475
|
+
})(GoAwayCode || (GoAwayCode = {}));
|
|
34476
|
+
const HEADER_LENGTH = 12;
|
|
34477
|
+
|
|
34478
|
+
// used to bitshift in decoding
|
|
34479
|
+
// native bitshift can overflow into a negative number, so we bitshift by multiplying by a power of 2
|
|
34480
|
+
const twoPow24 = 2 ** 24;
|
|
34481
|
+
/**
|
|
34482
|
+
* Decode a header from the front of a buffer
|
|
34483
|
+
*
|
|
34484
|
+
* @param data - Assumed to have enough bytes for a header
|
|
34485
|
+
*/
|
|
34486
|
+
function decodeHeader(data) {
|
|
34487
|
+
if (data[0] !== YAMUX_VERSION) {
|
|
34488
|
+
throw new CodeError$2('Invalid frame version', ERR_DECODE_INVALID_VERSION);
|
|
34489
|
+
}
|
|
34490
|
+
return {
|
|
34491
|
+
type: data[1],
|
|
34492
|
+
flag: (data[2] << 8) + data[3],
|
|
34493
|
+
streamID: (data[4] * twoPow24) + (data[5] << 16) + (data[6] << 8) + data[7],
|
|
34494
|
+
length: (data[8] * twoPow24) + (data[9] << 16) + (data[10] << 8) + data[11]
|
|
34495
|
+
};
|
|
34496
|
+
}
|
|
34497
|
+
/**
|
|
34498
|
+
* Decodes yamux frames from a source
|
|
34499
|
+
*/
|
|
34500
|
+
class Decoder {
|
|
34501
|
+
source;
|
|
34502
|
+
/** Buffer for in-progress frames */
|
|
34503
|
+
buffer;
|
|
34504
|
+
/** Used to sanity check against decoding while in an inconsistent state */
|
|
34505
|
+
frameInProgress;
|
|
34506
|
+
constructor(source) {
|
|
34507
|
+
// Normally, when entering a for-await loop with an iterable/async iterable, the only ways to exit the loop are:
|
|
34508
|
+
// 1. exhaust the iterable
|
|
34509
|
+
// 2. throw an error - slow, undesirable if there's not actually an error
|
|
34510
|
+
// 3. break or return - calls the iterable's `return` method, finalizing the iterable, no more iteration possible
|
|
34511
|
+
//
|
|
34512
|
+
// In this case, we want to enter (and exit) a for-await loop per chunked data frame and continue processing the iterable.
|
|
34513
|
+
// To do this, we strip the `return` method from the iterator and can now `break` early and continue iterating.
|
|
34514
|
+
// Exiting the main for-await is still possible via 1. and 2.
|
|
34515
|
+
this.source = returnlessSource(source);
|
|
34516
|
+
this.buffer = new Uint8ArrayList();
|
|
34517
|
+
this.frameInProgress = false;
|
|
34518
|
+
}
|
|
34519
|
+
/**
|
|
34520
|
+
* Emits frames from the decoder source.
|
|
34521
|
+
*
|
|
34522
|
+
* Note: If `readData` is emitted, it _must_ be called before the next iteration
|
|
34523
|
+
* Otherwise an error is thrown
|
|
34524
|
+
*/
|
|
34525
|
+
async *emitFrames() {
|
|
34526
|
+
for await (const chunk of this.source) {
|
|
34527
|
+
this.buffer.append(chunk);
|
|
34528
|
+
// Loop to consume as many bytes from the buffer as possible
|
|
34529
|
+
// Eg: when a single chunk contains several frames
|
|
34530
|
+
while (true) {
|
|
34531
|
+
const header = this.readHeader();
|
|
34532
|
+
if (header === undefined) {
|
|
34533
|
+
break;
|
|
34534
|
+
}
|
|
34535
|
+
const { type, length } = header;
|
|
34536
|
+
if (type === FrameType.Data) {
|
|
34537
|
+
// This is a data frame, the frame body must still be read
|
|
34538
|
+
// `readData` must be called before the next iteration here
|
|
34539
|
+
this.frameInProgress = true;
|
|
34540
|
+
yield {
|
|
34541
|
+
header,
|
|
34542
|
+
readData: this.readBytes.bind(this, length)
|
|
34543
|
+
};
|
|
34544
|
+
}
|
|
34545
|
+
else {
|
|
34546
|
+
yield { header };
|
|
34547
|
+
}
|
|
34548
|
+
}
|
|
34549
|
+
}
|
|
34550
|
+
}
|
|
34551
|
+
readHeader() {
|
|
34552
|
+
// Sanity check to ensure a header isn't read when another frame is partially decoded
|
|
34553
|
+
// In practice this shouldn't happen
|
|
34554
|
+
if (this.frameInProgress) {
|
|
34555
|
+
throw new CodeError$2('decoding frame already in progress', ERR_DECODE_IN_PROGRESS);
|
|
34556
|
+
}
|
|
34557
|
+
if (this.buffer.length < HEADER_LENGTH) {
|
|
34558
|
+
// not enough data yet
|
|
34559
|
+
return;
|
|
34560
|
+
}
|
|
34561
|
+
const header = decodeHeader(this.buffer.subarray(0, HEADER_LENGTH));
|
|
34562
|
+
this.buffer.consume(HEADER_LENGTH);
|
|
34563
|
+
return header;
|
|
34564
|
+
}
|
|
34565
|
+
async readBytes(length) {
|
|
34566
|
+
if (this.buffer.length < length) {
|
|
34567
|
+
for await (const chunk of this.source) {
|
|
34568
|
+
this.buffer.append(chunk);
|
|
34569
|
+
if (this.buffer.length >= length) {
|
|
34570
|
+
// see note above, the iterator is not `return`ed here
|
|
34571
|
+
break;
|
|
34572
|
+
}
|
|
34573
|
+
}
|
|
34574
|
+
}
|
|
34575
|
+
const out = this.buffer.sublist(0, length);
|
|
34576
|
+
this.buffer.consume(length);
|
|
34577
|
+
// The next frame can now be decoded
|
|
34578
|
+
this.frameInProgress = false;
|
|
34579
|
+
return out;
|
|
34580
|
+
}
|
|
34581
|
+
}
|
|
34582
|
+
/**
|
|
34583
|
+
* Strip the `return` method from a `Source`
|
|
34584
|
+
*/
|
|
34585
|
+
function returnlessSource(source) {
|
|
34586
|
+
if (source[Symbol.iterator] !== undefined) {
|
|
34587
|
+
const iterator = source[Symbol.iterator]();
|
|
34588
|
+
iterator.return = undefined;
|
|
34589
|
+
return {
|
|
34590
|
+
[Symbol.iterator]() { return iterator; }
|
|
34591
|
+
};
|
|
34592
|
+
}
|
|
34593
|
+
else if (source[Symbol.asyncIterator] !== undefined) {
|
|
34594
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
34595
|
+
iterator.return = undefined;
|
|
34596
|
+
return {
|
|
34597
|
+
[Symbol.asyncIterator]() { return iterator; }
|
|
34598
|
+
};
|
|
34599
|
+
}
|
|
34600
|
+
else {
|
|
34601
|
+
throw new Error('a source must be either an iterable or an async iterable');
|
|
34602
|
+
}
|
|
34603
|
+
}
|
|
34604
|
+
|
|
34605
|
+
function encodeHeader(header) {
|
|
34606
|
+
const frame = new Uint8Array(HEADER_LENGTH);
|
|
34607
|
+
// always assume version 0
|
|
34608
|
+
// frameView.setUint8(0, header.version)
|
|
34609
|
+
frame[1] = header.type;
|
|
34610
|
+
frame[2] = header.flag >>> 8;
|
|
34611
|
+
frame[3] = header.flag;
|
|
34612
|
+
frame[4] = header.streamID >>> 24;
|
|
34613
|
+
frame[5] = header.streamID >>> 16;
|
|
34614
|
+
frame[6] = header.streamID >>> 8;
|
|
34615
|
+
frame[7] = header.streamID;
|
|
34616
|
+
frame[8] = header.length >>> 24;
|
|
34617
|
+
frame[9] = header.length >>> 16;
|
|
34618
|
+
frame[10] = header.length >>> 8;
|
|
34619
|
+
frame[11] = header.length;
|
|
34620
|
+
return frame;
|
|
34621
|
+
}
|
|
34622
|
+
|
|
34623
|
+
/**
|
|
34624
|
+
* @packageDocumentation
|
|
34625
|
+
*
|
|
34626
|
+
* Calls a function for each value in an (async)iterable.
|
|
34627
|
+
*
|
|
34628
|
+
* The function can be sync or async.
|
|
34629
|
+
*
|
|
34630
|
+
* Async functions can be awaited on so may slow down processing of the (async)iterable.
|
|
34631
|
+
*
|
|
34632
|
+
* @example
|
|
34633
|
+
*
|
|
34634
|
+
* ```javascript
|
|
34635
|
+
* import each from 'it-foreach'
|
|
34636
|
+
* import drain from 'it-drain'
|
|
34637
|
+
*
|
|
34638
|
+
* // This can also be an iterator, generator, etc
|
|
34639
|
+
* const values = [0, 1, 2, 3, 4]
|
|
34640
|
+
*
|
|
34641
|
+
* // prints [0, 0], [1, 1], [2, 2], [3, 3], [4, 4]
|
|
34642
|
+
* const arr = drain(
|
|
34643
|
+
* each(values, console.info)
|
|
34644
|
+
* )
|
|
34645
|
+
* ```
|
|
34646
|
+
*
|
|
34647
|
+
* Async sources and callbacks must be awaited:
|
|
34648
|
+
*
|
|
34649
|
+
* ```javascript
|
|
34650
|
+
* import each from 'it-foreach'
|
|
34651
|
+
* import drain from 'it-drain'
|
|
34652
|
+
*
|
|
34653
|
+
* const values = async function * () {
|
|
34654
|
+
* yield * [0, 1, 2, 3, 4]
|
|
34655
|
+
* }
|
|
34656
|
+
*
|
|
34657
|
+
* // prints [0, 0], [1, 1], [2, 2], [3, 3], [4, 4]
|
|
34658
|
+
* const arr = await drain(
|
|
34659
|
+
* each(values(), console.info)
|
|
34660
|
+
* )
|
|
34661
|
+
* ```
|
|
34662
|
+
*/
|
|
34663
|
+
function isAsyncIterable$1(thing) {
|
|
34664
|
+
return thing[Symbol.asyncIterator] != null;
|
|
34665
|
+
}
|
|
34666
|
+
function isPromise$1(thing) {
|
|
34667
|
+
return thing?.then != null;
|
|
34668
|
+
}
|
|
34669
|
+
function forEach(source, fn) {
|
|
34670
|
+
let index = 0;
|
|
34671
|
+
if (isAsyncIterable$1(source)) {
|
|
34672
|
+
return (async function* () {
|
|
34673
|
+
for await (const val of source) {
|
|
34674
|
+
const res = fn(val, index++);
|
|
34675
|
+
if (isPromise$1(res)) {
|
|
34676
|
+
await res;
|
|
34677
|
+
}
|
|
34678
|
+
yield val;
|
|
34679
|
+
}
|
|
34680
|
+
})();
|
|
34681
|
+
}
|
|
34682
|
+
// if fn function returns a promise we have to return an async generator
|
|
34683
|
+
const peekable$1 = peekable(source);
|
|
34684
|
+
const { value, done } = peekable$1.next();
|
|
34685
|
+
if (done === true) {
|
|
34686
|
+
return (function* () { }());
|
|
34687
|
+
}
|
|
34688
|
+
const res = fn(value, index++);
|
|
34689
|
+
if (typeof res?.then === 'function') {
|
|
34690
|
+
return (async function* () {
|
|
34691
|
+
yield value;
|
|
34692
|
+
for await (const val of peekable$1) {
|
|
34693
|
+
const res = fn(val, index++);
|
|
34694
|
+
if (isPromise$1(res)) {
|
|
34695
|
+
await res;
|
|
34696
|
+
}
|
|
34697
|
+
yield val;
|
|
34698
|
+
}
|
|
34699
|
+
})();
|
|
34700
|
+
}
|
|
34701
|
+
const func = fn;
|
|
34702
|
+
return (function* () {
|
|
34703
|
+
yield value;
|
|
34704
|
+
for (const val of peekable$1) {
|
|
34705
|
+
func(val, index++);
|
|
34706
|
+
yield val;
|
|
34707
|
+
}
|
|
34708
|
+
})();
|
|
34709
|
+
}
|
|
34710
|
+
|
|
34711
|
+
var StreamState;
|
|
34712
|
+
(function (StreamState) {
|
|
34713
|
+
StreamState[StreamState["Init"] = 0] = "Init";
|
|
34714
|
+
StreamState[StreamState["SYNSent"] = 1] = "SYNSent";
|
|
34715
|
+
StreamState[StreamState["SYNReceived"] = 2] = "SYNReceived";
|
|
34716
|
+
StreamState[StreamState["Established"] = 3] = "Established";
|
|
34717
|
+
StreamState[StreamState["Finished"] = 4] = "Finished";
|
|
34718
|
+
})(StreamState || (StreamState = {}));
|
|
34719
|
+
/** YamuxStream is used to represent a logical stream within a session */
|
|
34720
|
+
class YamuxStream extends AbstractStream {
|
|
34721
|
+
name;
|
|
34722
|
+
state;
|
|
34723
|
+
config;
|
|
34724
|
+
_id;
|
|
34725
|
+
/** The number of available bytes to send */
|
|
34726
|
+
sendWindowCapacity;
|
|
34727
|
+
/** Callback to notify that the sendWindowCapacity has been updated */
|
|
34728
|
+
sendWindowCapacityUpdate;
|
|
34729
|
+
/** The number of bytes available to receive in a full window */
|
|
34730
|
+
recvWindow;
|
|
34731
|
+
/** The number of available bytes to receive */
|
|
34732
|
+
recvWindowCapacity;
|
|
34733
|
+
/**
|
|
34734
|
+
* An 'epoch' is the time it takes to process and read data
|
|
34735
|
+
*
|
|
34736
|
+
* Used in conjunction with RTT to determine whether to increase the recvWindow
|
|
34737
|
+
*/
|
|
34738
|
+
epochStart;
|
|
34739
|
+
getRTT;
|
|
34740
|
+
sendFrame;
|
|
34741
|
+
constructor(init) {
|
|
34742
|
+
super({
|
|
34743
|
+
...init,
|
|
34744
|
+
onEnd: (err) => {
|
|
34745
|
+
this.state = StreamState.Finished;
|
|
34746
|
+
init.onEnd?.(err);
|
|
34747
|
+
}
|
|
34748
|
+
});
|
|
34749
|
+
this.config = init.config;
|
|
34750
|
+
this._id = parseInt(init.id, 10);
|
|
34751
|
+
this.name = init.name;
|
|
34752
|
+
this.state = init.state;
|
|
34753
|
+
this.sendWindowCapacity = INITIAL_STREAM_WINDOW;
|
|
34754
|
+
this.recvWindow = this.config.initialStreamWindowSize;
|
|
34755
|
+
this.recvWindowCapacity = this.recvWindow;
|
|
34756
|
+
this.epochStart = Date.now();
|
|
34757
|
+
this.getRTT = init.getRTT;
|
|
34758
|
+
this.sendFrame = init.sendFrame;
|
|
34759
|
+
this.source = forEach(this.source, () => {
|
|
34760
|
+
this.sendWindowUpdate();
|
|
34761
|
+
});
|
|
34762
|
+
}
|
|
34763
|
+
/**
|
|
34764
|
+
* Send a message to the remote muxer informing them a new stream is being
|
|
34765
|
+
* opened.
|
|
34766
|
+
*
|
|
34767
|
+
* This is a noop for Yamux because the first window update is sent when
|
|
34768
|
+
* .newStream is called on the muxer which opens the stream on the remote.
|
|
34769
|
+
*/
|
|
34770
|
+
async sendNewStream() {
|
|
34771
|
+
}
|
|
34772
|
+
/**
|
|
34773
|
+
* Send a data message to the remote muxer
|
|
34774
|
+
*/
|
|
34775
|
+
async sendData(buf, options = {}) {
|
|
34776
|
+
buf = buf.sublist();
|
|
34777
|
+
// send in chunks, waiting for window updates
|
|
34778
|
+
while (buf.byteLength !== 0) {
|
|
34779
|
+
// wait for the send window to refill
|
|
34780
|
+
if (this.sendWindowCapacity === 0) {
|
|
34781
|
+
this.log?.trace('wait for send window capacity, status %s', this.status);
|
|
34782
|
+
await this.waitForSendWindowCapacity(options);
|
|
34783
|
+
// check we didn't close while waiting for send window capacity
|
|
34784
|
+
if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
|
|
34785
|
+
this.log?.trace('%s while waiting for send window capacity', this.status);
|
|
34786
|
+
return;
|
|
34787
|
+
}
|
|
34788
|
+
}
|
|
34789
|
+
// send as much as we can
|
|
34790
|
+
const toSend = Math.min(this.sendWindowCapacity, this.config.maxMessageSize - HEADER_LENGTH, buf.length);
|
|
34791
|
+
const flags = this.getSendFlags();
|
|
34792
|
+
this.sendFrame({
|
|
34793
|
+
type: FrameType.Data,
|
|
34794
|
+
flag: flags,
|
|
34795
|
+
streamID: this._id,
|
|
34796
|
+
length: toSend
|
|
34797
|
+
}, buf.sublist(0, toSend));
|
|
34798
|
+
this.sendWindowCapacity -= toSend;
|
|
34799
|
+
buf.consume(toSend);
|
|
34800
|
+
}
|
|
34801
|
+
}
|
|
34802
|
+
/**
|
|
34803
|
+
* Send a reset message to the remote muxer
|
|
34804
|
+
*/
|
|
34805
|
+
async sendReset() {
|
|
34806
|
+
this.sendFrame({
|
|
34807
|
+
type: FrameType.WindowUpdate,
|
|
34808
|
+
flag: Flag.RST,
|
|
34809
|
+
streamID: this._id,
|
|
34810
|
+
length: 0
|
|
34811
|
+
});
|
|
34812
|
+
}
|
|
34813
|
+
/**
|
|
34814
|
+
* Send a message to the remote muxer, informing them no more data messages
|
|
34815
|
+
* will be sent by this end of the stream
|
|
34816
|
+
*/
|
|
34817
|
+
async sendCloseWrite() {
|
|
34818
|
+
const flags = this.getSendFlags() | Flag.FIN;
|
|
34819
|
+
this.sendFrame({
|
|
34820
|
+
type: FrameType.WindowUpdate,
|
|
34821
|
+
flag: flags,
|
|
34822
|
+
streamID: this._id,
|
|
34823
|
+
length: 0
|
|
34824
|
+
});
|
|
34825
|
+
}
|
|
34826
|
+
/**
|
|
34827
|
+
* Send a message to the remote muxer, informing them no more data messages
|
|
34828
|
+
* will be read by this end of the stream
|
|
34829
|
+
*/
|
|
34830
|
+
async sendCloseRead() {
|
|
34831
|
+
}
|
|
34832
|
+
/**
|
|
34833
|
+
* Wait for the send window to be non-zero
|
|
34834
|
+
*
|
|
34835
|
+
* Will throw with ERR_STREAM_ABORT if the stream gets aborted
|
|
34836
|
+
*/
|
|
34837
|
+
async waitForSendWindowCapacity(options = {}) {
|
|
34838
|
+
if (this.sendWindowCapacity > 0) {
|
|
34839
|
+
return;
|
|
34840
|
+
}
|
|
34841
|
+
let resolve;
|
|
34842
|
+
let reject;
|
|
34843
|
+
const abort = () => {
|
|
34844
|
+
if (this.status === 'open' || this.status === 'closing') {
|
|
34845
|
+
reject(new CodeError$2('stream aborted', ERR_STREAM_ABORT));
|
|
34846
|
+
}
|
|
34847
|
+
else {
|
|
34848
|
+
// the stream was closed already, ignore the failure to send
|
|
34849
|
+
resolve();
|
|
34850
|
+
}
|
|
34851
|
+
};
|
|
34852
|
+
options.signal?.addEventListener('abort', abort);
|
|
34853
|
+
try {
|
|
34854
|
+
await new Promise((_resolve, _reject) => {
|
|
34855
|
+
this.sendWindowCapacityUpdate = () => {
|
|
32548
34856
|
_resolve();
|
|
32549
34857
|
};
|
|
32550
34858
|
reject = _reject;
|
|
@@ -33558,63 +35866,6 @@
|
|
|
33558
35866
|
return (components) => new PubSubPeerDiscovery(components, init);
|
|
33559
35867
|
}
|
|
33560
35868
|
|
|
33561
|
-
/**
|
|
33562
|
-
* @packageDocumentation
|
|
33563
|
-
*
|
|
33564
|
-
* This module makes it easy to send and receive length-prefixed Protobuf encoded
|
|
33565
|
-
* messages over streams.
|
|
33566
|
-
*
|
|
33567
|
-
* @example
|
|
33568
|
-
*
|
|
33569
|
-
* ```typescript
|
|
33570
|
-
* import { pbStream } from 'it-protobuf-stream'
|
|
33571
|
-
* import { MessageType } from './src/my-message-type.js'
|
|
33572
|
-
*
|
|
33573
|
-
* // RequestType and ResponseType have been generate from `.proto` files and have
|
|
33574
|
-
* // `.encode` and `.decode` methods for serialization/deserialization
|
|
33575
|
-
*
|
|
33576
|
-
* const stream = pbStream(duplex)
|
|
33577
|
-
*
|
|
33578
|
-
* // write a message to the stream
|
|
33579
|
-
* stream.write({
|
|
33580
|
-
* foo: 'bar'
|
|
33581
|
-
* }, MessageType)
|
|
33582
|
-
*
|
|
33583
|
-
* // read a message from the stream
|
|
33584
|
-
* const res = await stream.read(MessageType)
|
|
33585
|
-
* ```
|
|
33586
|
-
*/
|
|
33587
|
-
function pbStream(duplex, opts) {
|
|
33588
|
-
const lp = lpStream(duplex, opts);
|
|
33589
|
-
const W = {
|
|
33590
|
-
read: async (proto, options) => {
|
|
33591
|
-
// readLP, decode
|
|
33592
|
-
const value = await lp.read(options);
|
|
33593
|
-
return proto.decode(value);
|
|
33594
|
-
},
|
|
33595
|
-
write: async (message, proto, options) => {
|
|
33596
|
-
// encode, writeLP
|
|
33597
|
-
await lp.write(proto.encode(message), options);
|
|
33598
|
-
},
|
|
33599
|
-
writeV: async (messages, proto, options) => {
|
|
33600
|
-
// encode, writeLP
|
|
33601
|
-
await lp.writeV(messages.map(message => proto.encode(message)), options);
|
|
33602
|
-
},
|
|
33603
|
-
pb: (proto) => {
|
|
33604
|
-
return {
|
|
33605
|
-
read: async (options) => W.read(proto, options),
|
|
33606
|
-
write: async (d, options) => W.write(d, proto, options),
|
|
33607
|
-
writeV: async (d, options) => W.writeV(d, proto, options),
|
|
33608
|
-
unwrap: () => W
|
|
33609
|
-
};
|
|
33610
|
-
},
|
|
33611
|
-
unwrap: () => {
|
|
33612
|
-
return lp.unwrap();
|
|
33613
|
-
}
|
|
33614
|
-
};
|
|
33615
|
-
return W;
|
|
33616
|
-
}
|
|
33617
|
-
|
|
33618
35869
|
/**
|
|
33619
35870
|
* Multicodec code
|
|
33620
35871
|
*/
|
|
@@ -43902,121 +46153,6 @@
|
|
|
43902
46153
|
}
|
|
43903
46154
|
}
|
|
43904
46155
|
|
|
43905
|
-
const normalizeEmitter = emitter => {
|
|
43906
|
-
const addListener = emitter.addEventListener || emitter.on || emitter.addListener;
|
|
43907
|
-
const removeListener = emitter.removeEventListener || emitter.off || emitter.removeListener;
|
|
43908
|
-
|
|
43909
|
-
if (!addListener || !removeListener) {
|
|
43910
|
-
throw new TypeError('Emitter is not compatible');
|
|
43911
|
-
}
|
|
43912
|
-
|
|
43913
|
-
return {
|
|
43914
|
-
addListener: addListener.bind(emitter),
|
|
43915
|
-
removeListener: removeListener.bind(emitter),
|
|
43916
|
-
};
|
|
43917
|
-
};
|
|
43918
|
-
|
|
43919
|
-
function pEventMultiple(emitter, event, options) {
|
|
43920
|
-
let cancel;
|
|
43921
|
-
const returnValue = new Promise((resolve, reject) => {
|
|
43922
|
-
options = {
|
|
43923
|
-
rejectionEvents: ['error'],
|
|
43924
|
-
multiArgs: false,
|
|
43925
|
-
resolveImmediately: false,
|
|
43926
|
-
...options,
|
|
43927
|
-
};
|
|
43928
|
-
|
|
43929
|
-
if (!(options.count >= 0 && (options.count === Number.POSITIVE_INFINITY || Number.isInteger(options.count)))) {
|
|
43930
|
-
throw new TypeError('The `count` option should be at least 0 or more');
|
|
43931
|
-
}
|
|
43932
|
-
|
|
43933
|
-
options.signal?.throwIfAborted();
|
|
43934
|
-
|
|
43935
|
-
// Allow multiple events
|
|
43936
|
-
const events = [event].flat();
|
|
43937
|
-
|
|
43938
|
-
const items = [];
|
|
43939
|
-
const {addListener, removeListener} = normalizeEmitter(emitter);
|
|
43940
|
-
|
|
43941
|
-
const onItem = (...arguments_) => {
|
|
43942
|
-
const value = options.multiArgs ? arguments_ : arguments_[0];
|
|
43943
|
-
|
|
43944
|
-
// eslint-disable-next-line unicorn/no-array-callback-reference
|
|
43945
|
-
if (options.filter && !options.filter(value)) {
|
|
43946
|
-
return;
|
|
43947
|
-
}
|
|
43948
|
-
|
|
43949
|
-
items.push(value);
|
|
43950
|
-
|
|
43951
|
-
if (options.count === items.length) {
|
|
43952
|
-
cancel();
|
|
43953
|
-
resolve(items);
|
|
43954
|
-
}
|
|
43955
|
-
};
|
|
43956
|
-
|
|
43957
|
-
const rejectHandler = error => {
|
|
43958
|
-
cancel();
|
|
43959
|
-
reject(error);
|
|
43960
|
-
};
|
|
43961
|
-
|
|
43962
|
-
cancel = () => {
|
|
43963
|
-
for (const event of events) {
|
|
43964
|
-
removeListener(event, onItem);
|
|
43965
|
-
}
|
|
43966
|
-
|
|
43967
|
-
for (const rejectionEvent of options.rejectionEvents) {
|
|
43968
|
-
removeListener(rejectionEvent, rejectHandler);
|
|
43969
|
-
}
|
|
43970
|
-
};
|
|
43971
|
-
|
|
43972
|
-
for (const event of events) {
|
|
43973
|
-
addListener(event, onItem);
|
|
43974
|
-
}
|
|
43975
|
-
|
|
43976
|
-
for (const rejectionEvent of options.rejectionEvents) {
|
|
43977
|
-
addListener(rejectionEvent, rejectHandler);
|
|
43978
|
-
}
|
|
43979
|
-
|
|
43980
|
-
if (options.signal) {
|
|
43981
|
-
options.signal.addEventListener('abort', () => {
|
|
43982
|
-
rejectHandler(options.signal.reason);
|
|
43983
|
-
}, {once: true});
|
|
43984
|
-
}
|
|
43985
|
-
|
|
43986
|
-
if (options.resolveImmediately) {
|
|
43987
|
-
resolve(items);
|
|
43988
|
-
}
|
|
43989
|
-
});
|
|
43990
|
-
|
|
43991
|
-
returnValue.cancel = cancel;
|
|
43992
|
-
|
|
43993
|
-
if (typeof options.timeout === 'number') {
|
|
43994
|
-
const timeout = pTimeout(returnValue, {milliseconds: options.timeout});
|
|
43995
|
-
timeout.cancel = cancel;
|
|
43996
|
-
return timeout;
|
|
43997
|
-
}
|
|
43998
|
-
|
|
43999
|
-
return returnValue;
|
|
44000
|
-
}
|
|
44001
|
-
|
|
44002
|
-
function pEvent(emitter, event, options) {
|
|
44003
|
-
if (typeof options === 'function') {
|
|
44004
|
-
options = {filter: options};
|
|
44005
|
-
}
|
|
44006
|
-
|
|
44007
|
-
options = {
|
|
44008
|
-
...options,
|
|
44009
|
-
count: 1,
|
|
44010
|
-
resolveImmediately: false,
|
|
44011
|
-
};
|
|
44012
|
-
|
|
44013
|
-
const arrayPromise = pEventMultiple(emitter, event, options);
|
|
44014
|
-
const promise = arrayPromise.then(array => array[0]);
|
|
44015
|
-
promise.cancel = arrayPromise.cancel;
|
|
44016
|
-
|
|
44017
|
-
return promise;
|
|
44018
|
-
}
|
|
44019
|
-
|
|
44020
46156
|
/**
|
|
44021
46157
|
* Receives notifications of new peers joining the network that support the DHT protocol
|
|
44022
46158
|
*/
|
|
@@ -47108,6 +49244,10 @@
|
|
|
47108
49244
|
|
|
47109
49245
|
this.id = this.#libp2p.peerId.toString();
|
|
47110
49246
|
|
|
49247
|
+
for(const topic of CONFIG_PUPSUB_PEER_DATA){
|
|
49248
|
+
this.#libp2p.services.pubsub.subscribe(topic);
|
|
49249
|
+
}
|
|
49250
|
+
|
|
47111
49251
|
|
|
47112
49252
|
//listen to peer connect event
|
|
47113
49253
|
this.#libp2p.addEventListener("peer:connect",async (evt) => {
|
|
@@ -47174,9 +49314,9 @@
|
|
|
47174
49314
|
return
|
|
47175
49315
|
}
|
|
47176
49316
|
{
|
|
47177
|
-
|
|
49317
|
+
event.detail.topic;
|
|
47178
49318
|
const senderPeerId = event.detail.from.toString();
|
|
47179
|
-
|
|
49319
|
+
|
|
47180
49320
|
try{
|
|
47181
49321
|
|
|
47182
49322
|
//if it is webpeer
|
|
@@ -47212,15 +49352,17 @@
|
|
|
47212
49352
|
const addrs = this.#discoveredPeers.get(senderPeerId);
|
|
47213
49353
|
let mddrs = [];
|
|
47214
49354
|
for(const addr of addrs){
|
|
49355
|
+
if(!addr.includes('webrtc'))continue
|
|
47215
49356
|
const mddr = multiaddr(addr);
|
|
47216
49357
|
mddrs.push(mddr);
|
|
47217
49358
|
}
|
|
47218
49359
|
this.#dialMultiaddress(mddrs);
|
|
47219
49360
|
}
|
|
47220
|
-
else {
|
|
49361
|
+
else if(this.#connectedPeers.has(senderPeerId)){
|
|
47221
49362
|
const addrs = this.#connectedPeers.get(senderPeerId).addrs;
|
|
47222
49363
|
let mddrs = [];
|
|
47223
49364
|
for(const addr of addrs){
|
|
49365
|
+
if(!addr.includes('webrtc'))continue
|
|
47224
49366
|
const mddr = multiaddr(addr);
|
|
47225
49367
|
mddrs.push(mddr);
|
|
47226
49368
|
}
|
|
@@ -47269,8 +49411,10 @@
|
|
|
47269
49411
|
|
|
47270
49412
|
//update room members
|
|
47271
49413
|
if(!this.#rooms[room].members.includes(id)){
|
|
47272
|
-
this.#
|
|
47273
|
-
|
|
49414
|
+
if(this.#connectedPeers.has(id)){
|
|
49415
|
+
this.#rooms[room].members.push(id);
|
|
49416
|
+
this.#rooms[room].onMembers(this.#rooms[room].members);
|
|
49417
|
+
}
|
|
47274
49418
|
}
|
|
47275
49419
|
|
|
47276
49420
|
//inbound message
|
|
@@ -47289,8 +49433,10 @@
|
|
|
47289
49433
|
for(const room of Object.keys(this.#rooms)){
|
|
47290
49434
|
//update room members
|
|
47291
49435
|
if(!this.#rooms[room].members.includes(id)){
|
|
47292
|
-
this.#
|
|
47293
|
-
|
|
49436
|
+
if(this.#connectedPeers.has(id)){
|
|
49437
|
+
this.#rooms[room].members.push(id);
|
|
49438
|
+
this.#rooms[room].onMembers(this.#rooms[room].members);
|
|
49439
|
+
}
|
|
47294
49440
|
}
|
|
47295
49441
|
}
|
|
47296
49442
|
}
|
|
@@ -47299,7 +49445,7 @@
|
|
|
47299
49445
|
|
|
47300
49446
|
//repply announce with ping
|
|
47301
49447
|
if(signal == 'announce'){
|
|
47302
|
-
setTimeout(()=>{this.#ping('
|
|
49448
|
+
setTimeout(()=>{this.#ping('');},1000);
|
|
47303
49449
|
//console.log('rooms',rooms)
|
|
47304
49450
|
}
|
|
47305
49451
|
|
|
@@ -47312,12 +49458,7 @@
|
|
|
47312
49458
|
|
|
47313
49459
|
}catch(err){
|
|
47314
49460
|
}
|
|
47315
|
-
|
|
47316
|
-
const json = JSON.parse(topic);
|
|
47317
|
-
const room = json.room;
|
|
47318
|
-
const message = new TextDecoder().decode(event.detail.data);
|
|
47319
|
-
this.#rooms[room].onMessage(message);
|
|
47320
|
-
}
|
|
49461
|
+
|
|
47321
49462
|
}
|
|
47322
49463
|
|
|
47323
49464
|
});
|
|
@@ -47358,6 +49499,7 @@
|
|
|
47358
49499
|
if(!this.#connections.has(id)){
|
|
47359
49500
|
let mddrs = [];
|
|
47360
49501
|
for(const addr of addrs){
|
|
49502
|
+
if(!addr.includes('webrtc'))continue
|
|
47361
49503
|
const mddr = multiaddr(addr);
|
|
47362
49504
|
mddrs.push(mddr);
|
|
47363
49505
|
}
|
|
@@ -47445,7 +49587,7 @@
|
|
|
47445
49587
|
const mddrs = [];
|
|
47446
49588
|
peer.addresses.forEach((addr)=>{
|
|
47447
49589
|
const maddr = addr.multiaddr.toString()+'/p2p/'+id;
|
|
47448
|
-
if(maddr.includes('webtransport') && maddr.includes('certhash')){
|
|
49590
|
+
if(maddr.includes('webtransport') && maddr.includes('certhash') && maddr.includes('webrtc')){
|
|
47449
49591
|
mddrs.push(maddr);
|
|
47450
49592
|
}
|
|
47451
49593
|
});
|
|
@@ -47607,7 +49749,7 @@
|
|
|
47607
49749
|
//join room version 1 user pupsub via pupsub peer discovery
|
|
47608
49750
|
{
|
|
47609
49751
|
|
|
47610
|
-
const topics =
|
|
49752
|
+
const topics = CONFIG_PUPSUB_PEER_DATA;
|
|
47611
49753
|
|
|
47612
49754
|
this.#rooms[room] = {
|
|
47613
49755
|
onMessage : () => {},
|
|
@@ -47661,6 +49803,7 @@
|
|
|
47661
49803
|
else {
|
|
47662
49804
|
this.status = 'unconnected';
|
|
47663
49805
|
}
|
|
49806
|
+
this.#ping();
|
|
47664
49807
|
}
|
|
47665
49808
|
|
|
47666
49809
|
async #registerProtocol(){
|
|
@@ -47753,7 +49896,7 @@
|
|
|
47753
49896
|
await this.#libp2p.handle(CONFIG_PROTOCOL, handler, {
|
|
47754
49897
|
maxInboundStreams: 50,
|
|
47755
49898
|
maxOutboundStreams: 50,
|
|
47756
|
-
runOnTransientConnection:
|
|
49899
|
+
runOnTransientConnection:false
|
|
47757
49900
|
});
|
|
47758
49901
|
|
|
47759
49902
|
await this.#libp2p.register(CONFIG_PROTOCOL, {
|
|
@@ -47800,7 +49943,7 @@
|
|
|
47800
49943
|
|
|
47801
49944
|
try{
|
|
47802
49945
|
|
|
47803
|
-
const stream = await this.#libp2p.dialProtocol(mddr, CONFIG_PROTOCOL,{runOnTransientConnection:
|
|
49946
|
+
const stream = await this.#libp2p.dialProtocol(mddr, CONFIG_PROTOCOL,{runOnTransientConnection:false});
|
|
47804
49947
|
|
|
47805
49948
|
const output = await pipe(
|
|
47806
49949
|
message,
|
|
@@ -47817,7 +49960,7 @@
|
|
|
47817
49960
|
return string
|
|
47818
49961
|
}
|
|
47819
49962
|
);
|
|
47820
|
-
|
|
49963
|
+
//console.log(output)
|
|
47821
49964
|
const json = JSON.parse(output);
|
|
47822
49965
|
if(json.protocol == CONFIG_PROTOCOL){
|
|
47823
49966
|
const address = [addr];
|
|
@@ -48021,7 +50164,7 @@
|
|
|
48021
50164
|
|
|
48022
50165
|
//announce and ping via pupsub peer discovery
|
|
48023
50166
|
async #announce(){
|
|
48024
|
-
const topics =
|
|
50167
|
+
const topics = CONFIG_PUPSUB_PEER_DATA;
|
|
48025
50168
|
const data = JSON.stringify({prefix:CONFIG_PREFIX,signal:'announce',id:this.#libp2p.peerId.toString(),address:this.address,rooms:this.#rooms});
|
|
48026
50169
|
const peer = {
|
|
48027
50170
|
publicKey: this.#libp2p.peerId.publicKey,
|
|
@@ -48033,7 +50176,7 @@
|
|
|
48033
50176
|
}
|
|
48034
50177
|
}
|
|
48035
50178
|
async #ping(){
|
|
48036
|
-
const topics =
|
|
50179
|
+
const topics = CONFIG_PUPSUB_PEER_DATA;
|
|
48037
50180
|
const data = JSON.stringify({prefix:CONFIG_PREFIX,signal:'ping',id:this.#libp2p.peerId.toString(),address:this.address,rooms:this.#rooms});
|
|
48038
50181
|
const peer = {
|
|
48039
50182
|
publicKey: this.#libp2p.peerId.publicKey,
|
|
@@ -48573,11 +50716,13 @@
|
|
|
48573
50716
|
const libp2p = await createLibp2p({
|
|
48574
50717
|
addresses: {
|
|
48575
50718
|
listen: [
|
|
50719
|
+
'/webrtc'
|
|
48576
50720
|
],
|
|
48577
50721
|
},
|
|
48578
50722
|
transports:[
|
|
48579
50723
|
webTransport(),
|
|
48580
50724
|
webSockets(),
|
|
50725
|
+
webRTC(),
|
|
48581
50726
|
circuitRelayTransport({
|
|
48582
50727
|
discoverRelays: CONFIG_DISCOVER_RELAYS,
|
|
48583
50728
|
reservationConcurrency: 1,
|
|
@@ -48642,7 +50787,7 @@
|
|
|
48642
50787
|
allowPublishToZeroTopicPeers: true,
|
|
48643
50788
|
msgIdFn: msgIdFnStrictNoSign$1,
|
|
48644
50789
|
ignoreDuplicatePublishError: true,
|
|
48645
|
-
runOnTransientConnection:
|
|
50790
|
+
runOnTransientConnection:false,
|
|
48646
50791
|
}),
|
|
48647
50792
|
identify: identify(),
|
|
48648
50793
|
identifyPush: identifyPush(),
|
|
@@ -48651,7 +50796,7 @@
|
|
|
48651
50796
|
peerInfoMapper: removePrivateAddressesMapper,
|
|
48652
50797
|
clientMode: false
|
|
48653
50798
|
}),
|
|
48654
|
-
|
|
50799
|
+
dcutr: dcutr()
|
|
48655
50800
|
},
|
|
48656
50801
|
peerStore: {
|
|
48657
50802
|
persistence: true,
|