@libp2p/webtransport 0.0.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/LICENSE +4 -0
- package/README.md +77 -0
- package/dist/src/index.d.ts +33 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +395 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +198 -0
- package/src/index.ts +461 -0
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @libp2p/webtransport <!-- omit in toc -->
|
|
2
|
+
|
|
3
|
+
[](http://libp2p.io/)
|
|
4
|
+
[](http://webchat.freenode.net/?channels=%23libp2p)
|
|
5
|
+
[](https://discuss.libp2p.io)
|
|
6
|
+
|
|
7
|
+
> JavaScript implementation of the WebTransport module that libp2p uses and that implements the interface-transport spec
|
|
8
|
+
|
|
9
|
+
## Table of contents <!-- omit in toc -->
|
|
10
|
+
|
|
11
|
+
- [Install](#install)
|
|
12
|
+
- [Description](#description)
|
|
13
|
+
- [Usage](#usage)
|
|
14
|
+
- [Libp2p Usage Example](#libp2p-usage-example)
|
|
15
|
+
- [API](#api)
|
|
16
|
+
- [Transport](#transport)
|
|
17
|
+
- [Connection](#connection)
|
|
18
|
+
- [License](#license)
|
|
19
|
+
- [Contribute](#contribute)
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```console
|
|
24
|
+
$ npm i @libp2p/webtransport
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
[](https://github.com/libp2p/interface-transport)
|
|
28
|
+
[](https://github.com/libp2p/interface-connection)
|
|
29
|
+
|
|
30
|
+
## Description
|
|
31
|
+
|
|
32
|
+
`libp2p-webtransport` is the WebTransport transport implementation compatible with libp2p.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
> npm i @libp2p/webtransport
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Libp2p Usage Example
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import Libp2p from 'libp2p'
|
|
44
|
+
import { WebTransport } from '@libp2p/webtransport'
|
|
45
|
+
import { NOISE } from 'libp2p-noise'
|
|
46
|
+
|
|
47
|
+
const node = await Libp2p.create({
|
|
48
|
+
modules: {
|
|
49
|
+
connEncryption: [NOISE]
|
|
50
|
+
transports: [new WebTransport()],
|
|
51
|
+
connectionEncryption: [new Noise()]
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For more information see [libp2p/js-libp2p/doc/CONFIGURATION.md#customizing-transports](https://github.com/libp2p/js-libp2p/blob/master/doc/CONFIGURATION.md#customizing-transports).
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
### Transport
|
|
61
|
+
|
|
62
|
+
[](https://github.com/libp2p/interface-transport)
|
|
63
|
+
|
|
64
|
+
### Connection
|
|
65
|
+
|
|
66
|
+
[](https://github.com/libp2p/interface-connection)
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
Licensed under either of
|
|
71
|
+
|
|
72
|
+
- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)
|
|
73
|
+
- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)
|
|
74
|
+
|
|
75
|
+
## Contribute
|
|
76
|
+
|
|
77
|
+
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Components, Initializable } from '@libp2p/components';
|
|
2
|
+
import { Transport, symbol, CreateListenerOptions, DialOptions } from '@libp2p/interface-transport';
|
|
3
|
+
import type { Connection } from '@libp2p/interface-connection';
|
|
4
|
+
import { Multiaddr } from '@multiformats/multiaddr';
|
|
5
|
+
import type { MultihashDigest } from 'multiformats/hashes/interface';
|
|
6
|
+
import type { PeerId } from '@libp2p/interface-peer-id';
|
|
7
|
+
import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer';
|
|
8
|
+
declare global {
|
|
9
|
+
interface Window {
|
|
10
|
+
WebTransport: any;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export declare function isSubset(set: Uint8Array[], maybeSubset: Uint8Array[]): boolean;
|
|
14
|
+
export interface WebTransportConfig {
|
|
15
|
+
maxInboundStreams: 1000;
|
|
16
|
+
}
|
|
17
|
+
export declare class WebTransport implements Transport, Initializable {
|
|
18
|
+
private components?;
|
|
19
|
+
private readonly config;
|
|
20
|
+
init(components: Components): void;
|
|
21
|
+
get [Symbol.toStringTag](): string;
|
|
22
|
+
get [symbol](): true;
|
|
23
|
+
constructor(config?: WebTransportConfig);
|
|
24
|
+
dial(ma: Multiaddr, options: DialOptions): Promise<Connection>;
|
|
25
|
+
authenticateWebTransport(wt: typeof window.WebTransport, localPeer: PeerId, remotePeer: PeerId, certhashes: Array<MultihashDigest<number>>): Promise<boolean>;
|
|
26
|
+
webtransportMuxer(wt: typeof window.WebTransport): StreamMuxerFactory;
|
|
27
|
+
createListener(options: CreateListenerOptions): any;
|
|
28
|
+
/**
|
|
29
|
+
* Takes a list of `Multiaddr`s and returns only valid webtransport addresses.
|
|
30
|
+
*/
|
|
31
|
+
filter(multiaddrs: Multiaddr[]): Multiaddr[];
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AACnG,OAAO,KAAK,EAAE,UAAU,EAA0C,MAAM,8BAA8B,CAAA;AACtG,OAAO,EAAE,SAAS,EAAa,MAAM,yBAAyB,CAAA;AAG9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AACpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAEvD,OAAO,KAAK,EAAE,kBAAkB,EAAgC,MAAM,gCAAgC,CAAA;AAKtG,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,YAAY,EAAE,GAAG,CAAA;KAClB;CACF;AA4MD,wBAAgB,QAAQ,CAAE,GAAG,EAAE,UAAU,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,CAgB/E;AAED,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,EAAE,IAAI,CAAA;CACxB;AAED,qBAAa,YAAa,YAAW,SAAS,EAAE,aAAa;IAC3D,OAAO,CAAC,UAAU,CAAC,CAAY;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAE3C,IAAI,CAAE,UAAU,EAAE,UAAU;IAI5B,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,WAEvB;IAED,IAAI,CAAC,MAAM,CAAC,IAAK,IAAI,CAEpB;gBAEY,MAAM,CAAC,EAAE,kBAAkB;IAIlC,IAAI,CAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IA6D/D,wBAAwB,CAAE,EAAE,EAAE,OAAO,MAAM,CAAC,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAyCjJ,iBAAiB,CAAE,EAAE,EAAE,OAAO,MAAM,CAAC,YAAY,GAAG,kBAAkB;IA+EtE,cAAc,CAAE,OAAO,EAAE,qBAAqB;IAO9C;;OAEG;IACH,MAAM,CAAE,UAAU,EAAE,SAAS,EAAE;CAGhC"}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { logger } from '@libp2p/logger';
|
|
2
|
+
import { Noise } from '@chainsafe/libp2p-noise';
|
|
3
|
+
import { symbol } from '@libp2p/interface-transport';
|
|
4
|
+
import { protocols } from '@multiformats/multiaddr';
|
|
5
|
+
import { peerIdFromString } from '@libp2p/peer-id';
|
|
6
|
+
import { bases, digest } from 'multiformats/basics';
|
|
7
|
+
import { Uint8ArrayList } from 'uint8arraylist';
|
|
8
|
+
const log = logger('libp2p:webtransport');
|
|
9
|
+
// @ts-expect-error - Not easy to combine these types.
|
|
10
|
+
const multibaseDecoder = Object.values(bases).map(b => b.decoder).reduce((d, b) => d.or(b));
|
|
11
|
+
function decodeCerthashStr(s) {
|
|
12
|
+
return digest.decode(multibaseDecoder.decode(s));
|
|
13
|
+
}
|
|
14
|
+
// Duplex that does nothing. Needed to fulfill the interface
|
|
15
|
+
function inertDuplex() {
|
|
16
|
+
return {
|
|
17
|
+
source: {
|
|
18
|
+
[Symbol.asyncIterator]() {
|
|
19
|
+
return {
|
|
20
|
+
async next() {
|
|
21
|
+
// This will never resolve
|
|
22
|
+
return await new Promise(() => { });
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
sink: async (source) => {
|
|
28
|
+
// This will never resolve
|
|
29
|
+
return await new Promise(() => { });
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function webtransportBiDiStreamToStream(bidiStream, streamId, direction, activeStreams, onStreamEnd) {
|
|
34
|
+
const writer = bidiStream.writable.getWriter();
|
|
35
|
+
const reader = bidiStream.readable.getReader();
|
|
36
|
+
await writer.ready;
|
|
37
|
+
function cleanupStreamFromActiveStreams() {
|
|
38
|
+
const index = activeStreams.findIndex(s => s === stream);
|
|
39
|
+
if (index !== -1) {
|
|
40
|
+
activeStreams.splice(index, 1);
|
|
41
|
+
stream.stat.timeline.close = Date.now();
|
|
42
|
+
onStreamEnd?.(stream);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
let writerClosed = false;
|
|
46
|
+
let readerClosed = false;
|
|
47
|
+
(async function () {
|
|
48
|
+
await (writer.closed.then((ok) => ({ ok })).catch((err) => ({ err })));
|
|
49
|
+
writerClosed = true;
|
|
50
|
+
if (writerClosed && readerClosed) {
|
|
51
|
+
cleanupStreamFromActiveStreams();
|
|
52
|
+
}
|
|
53
|
+
})().catch(() => {
|
|
54
|
+
log.error('WebTransport failed to cleanup closed stream');
|
|
55
|
+
});
|
|
56
|
+
(async function () {
|
|
57
|
+
await (reader.closed.then((ok) => ({ ok })).catch((err) => ({ err })));
|
|
58
|
+
readerClosed = true;
|
|
59
|
+
if (writerClosed && readerClosed) {
|
|
60
|
+
cleanupStreamFromActiveStreams();
|
|
61
|
+
}
|
|
62
|
+
})().catch(() => {
|
|
63
|
+
log.error('WebTransport failed to cleanup closed stream');
|
|
64
|
+
});
|
|
65
|
+
const stream = {
|
|
66
|
+
id: streamId,
|
|
67
|
+
abort(_err) {
|
|
68
|
+
if (!writerClosed) {
|
|
69
|
+
writer.abort();
|
|
70
|
+
writerClosed = true;
|
|
71
|
+
}
|
|
72
|
+
stream.closeRead();
|
|
73
|
+
readerClosed = true;
|
|
74
|
+
cleanupStreamFromActiveStreams();
|
|
75
|
+
},
|
|
76
|
+
close() {
|
|
77
|
+
stream.closeRead();
|
|
78
|
+
stream.closeWrite();
|
|
79
|
+
cleanupStreamFromActiveStreams();
|
|
80
|
+
},
|
|
81
|
+
closeRead() {
|
|
82
|
+
if (!readerClosed) {
|
|
83
|
+
reader.cancel().catch((err) => {
|
|
84
|
+
if (err.toString().includes('RESET_STREAM') === true) {
|
|
85
|
+
writerClosed = true;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
readerClosed = true;
|
|
89
|
+
}
|
|
90
|
+
if (writerClosed) {
|
|
91
|
+
cleanupStreamFromActiveStreams();
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
closeWrite() {
|
|
95
|
+
if (!writerClosed) {
|
|
96
|
+
writerClosed = true;
|
|
97
|
+
writer.close().catch((err) => {
|
|
98
|
+
if (err.toString().includes('RESET_STREAM') === true) {
|
|
99
|
+
readerClosed = true;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (readerClosed) {
|
|
104
|
+
cleanupStreamFromActiveStreams();
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
reset() {
|
|
108
|
+
stream.close();
|
|
109
|
+
},
|
|
110
|
+
stat: {
|
|
111
|
+
direction: direction,
|
|
112
|
+
timeline: { open: Date.now() }
|
|
113
|
+
},
|
|
114
|
+
metadata: {},
|
|
115
|
+
source: (async function* () {
|
|
116
|
+
while (true) {
|
|
117
|
+
const val = await reader.read();
|
|
118
|
+
if (val.done === true) {
|
|
119
|
+
readerClosed = true;
|
|
120
|
+
if (writerClosed) {
|
|
121
|
+
cleanupStreamFromActiveStreams();
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
yield new Uint8ArrayList(val.value);
|
|
126
|
+
}
|
|
127
|
+
})(),
|
|
128
|
+
sink: async function (source) {
|
|
129
|
+
for await (const chunks of source) {
|
|
130
|
+
if (chunks.constructor === Uint8Array) {
|
|
131
|
+
await writer.write(chunks);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
for (const buf of chunks) {
|
|
135
|
+
await writer.write(buf);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
return stream;
|
|
142
|
+
}
|
|
143
|
+
function parseMultiaddr(ma) {
|
|
144
|
+
const parts = ma.stringTuples();
|
|
145
|
+
// This is simpler to have inline than extract into a separate function
|
|
146
|
+
// eslint-disable-next-line complexity
|
|
147
|
+
const { url, certhashes, remotePeer } = parts.reduce((state, [proto, value]) => {
|
|
148
|
+
switch (proto) {
|
|
149
|
+
case protocols('ip4').code:
|
|
150
|
+
case protocols('ip6').code:
|
|
151
|
+
case protocols('dns4').code:
|
|
152
|
+
case protocols('dns6').code:
|
|
153
|
+
if (state.seenHost || state.seenPort) {
|
|
154
|
+
throw new Error('Invalid multiaddr, saw host and already saw the host or port');
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
...state,
|
|
158
|
+
url: `${state.url}${value ?? ''}`,
|
|
159
|
+
seenHost: true
|
|
160
|
+
};
|
|
161
|
+
case protocols('quic').code:
|
|
162
|
+
case protocols('webtransport').code:
|
|
163
|
+
if (!state.seenHost || !state.seenPort) {
|
|
164
|
+
throw new Error("Invalid multiaddr, Didn't see host and port, but saw quic/webtransport");
|
|
165
|
+
}
|
|
166
|
+
return state;
|
|
167
|
+
case protocols('udp').code:
|
|
168
|
+
if (state.seenPort) {
|
|
169
|
+
throw new Error('Invalid multiaddr, saw port but already saw the port');
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
...state,
|
|
173
|
+
url: `${state.url}:${value ?? ''}`,
|
|
174
|
+
seenPort: true
|
|
175
|
+
};
|
|
176
|
+
case protocols('certhash').code:
|
|
177
|
+
if (!state.seenHost || !state.seenPort) {
|
|
178
|
+
throw new Error('Invalid multiaddr, saw the certhash before seeing the host and port');
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
...state,
|
|
182
|
+
certhashes: state.certhashes.concat([decodeCerthashStr(value ?? '')])
|
|
183
|
+
};
|
|
184
|
+
case protocols('p2p').code:
|
|
185
|
+
return {
|
|
186
|
+
...state,
|
|
187
|
+
remotePeer: peerIdFromString(value ?? '')
|
|
188
|
+
};
|
|
189
|
+
default:
|
|
190
|
+
throw new Error(`unexpected component in multiaddr: ${proto} ${protocols(proto).name} ${value ?? ''} `);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
// All webtransport urls are https
|
|
194
|
+
{ url: 'https://', seenHost: false, seenPort: false, certhashes: [] });
|
|
195
|
+
return { url, certhashes, remotePeer };
|
|
196
|
+
}
|
|
197
|
+
// Determines if `maybeSubset` is a subset of `set`. This means that all byte arrays in `maybeSubset` are present in `set`.
|
|
198
|
+
export function isSubset(set, maybeSubset) {
|
|
199
|
+
const intersection = maybeSubset.filter(byteArray => {
|
|
200
|
+
return Boolean(set.find((otherByteArray) => {
|
|
201
|
+
if (byteArray.length !== otherByteArray.length) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
for (let index = 0; index < byteArray.length; index++) {
|
|
205
|
+
if (otherByteArray[index] !== byteArray[index]) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}));
|
|
211
|
+
});
|
|
212
|
+
return (intersection.length === maybeSubset.length);
|
|
213
|
+
}
|
|
214
|
+
export class WebTransport {
|
|
215
|
+
constructor(config) {
|
|
216
|
+
this.config = config ?? { maxInboundStreams: 1000 };
|
|
217
|
+
}
|
|
218
|
+
init(components) {
|
|
219
|
+
this.components = components;
|
|
220
|
+
}
|
|
221
|
+
get [Symbol.toStringTag]() {
|
|
222
|
+
return '@libp2p/webtransport';
|
|
223
|
+
}
|
|
224
|
+
get [symbol]() {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
async dial(ma, options) {
|
|
228
|
+
log('dialing %s', ma);
|
|
229
|
+
const localPeer = this.components?.getPeerId();
|
|
230
|
+
if (localPeer === undefined) {
|
|
231
|
+
throw new Error('Need a local peerid');
|
|
232
|
+
}
|
|
233
|
+
options = options ?? {};
|
|
234
|
+
const { url, certhashes, remotePeer } = parseMultiaddr(ma);
|
|
235
|
+
const wt = new window.WebTransport(`${url}/.well-known/libp2p-webtransport?type=noise`, {
|
|
236
|
+
serverCertificateHashes: certhashes.map(certhash => ({
|
|
237
|
+
algorithm: 'sha-256',
|
|
238
|
+
value: certhash.digest
|
|
239
|
+
}))
|
|
240
|
+
});
|
|
241
|
+
wt.closed.catch((error) => {
|
|
242
|
+
log.error('WebTransport transport closed due to:', error);
|
|
243
|
+
});
|
|
244
|
+
await wt.ready;
|
|
245
|
+
if (remotePeer == null) {
|
|
246
|
+
throw new Error('Need a target peerid');
|
|
247
|
+
}
|
|
248
|
+
if (!await this.authenticateWebTransport(wt, localPeer, remotePeer, certhashes)) {
|
|
249
|
+
throw new Error('Failed to authenticate webtransport');
|
|
250
|
+
}
|
|
251
|
+
const maConn = {
|
|
252
|
+
close: async (err) => {
|
|
253
|
+
if (err != null) {
|
|
254
|
+
log('Closing webtransport with err:', err);
|
|
255
|
+
}
|
|
256
|
+
wt.close();
|
|
257
|
+
},
|
|
258
|
+
remoteAddr: ma,
|
|
259
|
+
timeline: {
|
|
260
|
+
open: Date.now()
|
|
261
|
+
},
|
|
262
|
+
// This connection is never used directly since webtransport supports native streams.
|
|
263
|
+
...inertDuplex()
|
|
264
|
+
};
|
|
265
|
+
wt.closed.catch((err) => {
|
|
266
|
+
log.error('WebTransport connection closed:', err);
|
|
267
|
+
// This is how we specify the connection is closed and shouldn't be used.
|
|
268
|
+
maConn.timeline.close = Date.now();
|
|
269
|
+
});
|
|
270
|
+
try {
|
|
271
|
+
options?.signal?.throwIfAborted();
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
wt.close();
|
|
275
|
+
throw e;
|
|
276
|
+
}
|
|
277
|
+
return await options.upgrader.upgradeOutbound(maConn, { skipEncryption: true, muxerFactory: this.webtransportMuxer(wt), skipProtection: true });
|
|
278
|
+
}
|
|
279
|
+
async authenticateWebTransport(wt, localPeer, remotePeer, certhashes) {
|
|
280
|
+
const stream = await wt.createBidirectionalStream();
|
|
281
|
+
const writer = stream.writable.getWriter();
|
|
282
|
+
const reader = stream.readable.getReader();
|
|
283
|
+
await writer.ready;
|
|
284
|
+
const duplex = {
|
|
285
|
+
source: (async function* () {
|
|
286
|
+
while (true) {
|
|
287
|
+
const val = await reader.read();
|
|
288
|
+
yield val.value;
|
|
289
|
+
}
|
|
290
|
+
})(),
|
|
291
|
+
sink: async function (source) {
|
|
292
|
+
for await (const chunk of source) {
|
|
293
|
+
await writer.write(chunk);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const noise = new Noise();
|
|
298
|
+
const { remoteExtensions } = await noise.secureOutbound(localPeer, duplex, remotePeer);
|
|
299
|
+
// We're done with this authentication stream
|
|
300
|
+
writer.close().catch((err) => {
|
|
301
|
+
log.error(`Failed to close authentication stream writer: ${err.message}`);
|
|
302
|
+
});
|
|
303
|
+
reader.cancel().catch((err) => {
|
|
304
|
+
log.error(`Failed to close authentication stream reader: ${err.message}`);
|
|
305
|
+
});
|
|
306
|
+
// Verify the certhashes we used when dialing are a subset of the certhashes relayed by the remote peer
|
|
307
|
+
if (!isSubset(remoteExtensions?.webtransportCerthashes ?? [], certhashes.map(ch => ch.bytes))) {
|
|
308
|
+
throw new Error("Our certhashes are not a subset of the remote's reported certhashes");
|
|
309
|
+
}
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
webtransportMuxer(wt) {
|
|
313
|
+
let streamIDCounter = 0;
|
|
314
|
+
const config = this.config;
|
|
315
|
+
return {
|
|
316
|
+
protocol: 'webtransport',
|
|
317
|
+
createStreamMuxer: (init) => {
|
|
318
|
+
// !TODO handle abort signal when WebTransport supports this.
|
|
319
|
+
if (typeof init === 'function') {
|
|
320
|
+
// The api docs say that init may be a function
|
|
321
|
+
init = { onIncomingStream: init };
|
|
322
|
+
}
|
|
323
|
+
const activeStreams = [];
|
|
324
|
+
(async function () {
|
|
325
|
+
//! TODO unclear how to add backpressure here?
|
|
326
|
+
const reader = wt.incomingBidirectionalStreams.getReader();
|
|
327
|
+
while (true) {
|
|
328
|
+
const { done, value: wtStream } = await reader.read();
|
|
329
|
+
if (done === true) {
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
if (activeStreams.length >= config.maxInboundStreams) {
|
|
333
|
+
// We've reached our limit, close this stream.
|
|
334
|
+
wtStream.writable.close().catch((err) => {
|
|
335
|
+
log.error(`Failed to close inbound stream that crossed our maxInboundStream limit: ${err.message}`);
|
|
336
|
+
});
|
|
337
|
+
wtStream.readable.cancel().catch((err) => {
|
|
338
|
+
log.error(`Failed to close inbound stream that crossed our maxInboundStream limit: ${err.message}`);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const stream = await webtransportBiDiStreamToStream(wtStream, String(streamIDCounter++), 'inbound', activeStreams, init?.onStreamEnd);
|
|
343
|
+
activeStreams.push(stream);
|
|
344
|
+
init?.onIncomingStream?.(stream);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
})().catch(() => {
|
|
348
|
+
log.error('WebTransport failed to receive incoming stream');
|
|
349
|
+
});
|
|
350
|
+
const muxer = {
|
|
351
|
+
protocol: 'webtransport',
|
|
352
|
+
streams: activeStreams,
|
|
353
|
+
newStream: async (name) => {
|
|
354
|
+
const wtStream = await wt.createBidirectionalStream();
|
|
355
|
+
const stream = await webtransportBiDiStreamToStream(wtStream, String(streamIDCounter++), init?.direction ?? 'outbound', activeStreams, init?.onStreamEnd);
|
|
356
|
+
activeStreams.push(stream);
|
|
357
|
+
return stream;
|
|
358
|
+
},
|
|
359
|
+
/**
|
|
360
|
+
* Close or abort all tracked streams and stop the muxer
|
|
361
|
+
*/
|
|
362
|
+
close: (err) => {
|
|
363
|
+
if (err != null) {
|
|
364
|
+
log('Closing webtransport muxer with err:', err);
|
|
365
|
+
}
|
|
366
|
+
wt.close();
|
|
367
|
+
},
|
|
368
|
+
// This stream muxer is webtransport native. Therefore it doesn't plug in with any other duplex.
|
|
369
|
+
...inertDuplex()
|
|
370
|
+
};
|
|
371
|
+
try {
|
|
372
|
+
init?.signal?.throwIfAborted();
|
|
373
|
+
}
|
|
374
|
+
catch (e) {
|
|
375
|
+
wt.close();
|
|
376
|
+
throw e;
|
|
377
|
+
}
|
|
378
|
+
return muxer;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
createListener(options) {
|
|
383
|
+
throw new Error('Webtransport servers are not supported in Node or the browser');
|
|
384
|
+
// Unreachable, here to make TS happy
|
|
385
|
+
// eslint-disable-next-line no-unreachable
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Takes a list of `Multiaddr`s and returns only valid webtransport addresses.
|
|
390
|
+
*/
|
|
391
|
+
filter(multiaddrs) {
|
|
392
|
+
return multiaddrs.filter(ma => ma.protoNames().includes('webtransport'));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@libp2p/webtransport",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "JavaScript implementation of the webtransport module that libp2p uses and that implements the interface-transport spec",
|
|
5
|
+
"license": "Apache-2.0 OR MIT",
|
|
6
|
+
"homepage": "https://github.com/libp2p/js-libp2p-webtransport#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/libp2p/js-libp2p-webtransport.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/libp2p/js-libp2p-webtransport/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"IPFS"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=16.0.0",
|
|
19
|
+
"npm": ">=7.0.0"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"types": "./dist/src/index.d.ts",
|
|
23
|
+
"typesVersions": {
|
|
24
|
+
"*": {
|
|
25
|
+
"*": [
|
|
26
|
+
"*",
|
|
27
|
+
"dist/*",
|
|
28
|
+
"dist/src/*",
|
|
29
|
+
"dist/src/*/index"
|
|
30
|
+
],
|
|
31
|
+
"src/*": [
|
|
32
|
+
"*",
|
|
33
|
+
"dist/*",
|
|
34
|
+
"dist/src/*",
|
|
35
|
+
"dist/src/*/index"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"src",
|
|
41
|
+
"dist/src",
|
|
42
|
+
"!dist/test",
|
|
43
|
+
"!**/*.tsbuildinfo"
|
|
44
|
+
],
|
|
45
|
+
"exports": {
|
|
46
|
+
".": {
|
|
47
|
+
"types": "./src/index.d.ts",
|
|
48
|
+
"import": "./dist/src/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./filters": {
|
|
51
|
+
"types": "./dist/src/filters.d.ts",
|
|
52
|
+
"import": "./dist/src/filters.js"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"eslintConfig": {
|
|
56
|
+
"extends": "ipfs",
|
|
57
|
+
"parserOptions": {
|
|
58
|
+
"sourceType": "module"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"release": {
|
|
62
|
+
"branches": [
|
|
63
|
+
"main"
|
|
64
|
+
],
|
|
65
|
+
"plugins": [
|
|
66
|
+
[
|
|
67
|
+
"@semantic-release/commit-analyzer",
|
|
68
|
+
{
|
|
69
|
+
"preset": "conventionalcommits",
|
|
70
|
+
"releaseRules": [
|
|
71
|
+
{
|
|
72
|
+
"breaking": true,
|
|
73
|
+
"release": "major"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"revert": true,
|
|
77
|
+
"release": "patch"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"type": "feat",
|
|
81
|
+
"release": "minor"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"type": "fix",
|
|
85
|
+
"release": "patch"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"type": "docs",
|
|
89
|
+
"release": "patch"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"type": "test",
|
|
93
|
+
"release": "patch"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "deps",
|
|
97
|
+
"release": "patch"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"scope": "no-release",
|
|
101
|
+
"release": false
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
"@semantic-release/release-notes-generator",
|
|
108
|
+
{
|
|
109
|
+
"preset": "conventionalcommits",
|
|
110
|
+
"presetConfig": {
|
|
111
|
+
"types": [
|
|
112
|
+
{
|
|
113
|
+
"type": "feat",
|
|
114
|
+
"section": "Features"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"type": "fix",
|
|
118
|
+
"section": "Bug Fixes"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"type": "chore",
|
|
122
|
+
"section": "Trivial Changes"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"type": "docs",
|
|
126
|
+
"section": "Documentation"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"type": "deps",
|
|
130
|
+
"section": "Dependencies"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"type": "test",
|
|
134
|
+
"section": "Tests"
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
"@semantic-release/changelog",
|
|
141
|
+
"@semantic-release/npm",
|
|
142
|
+
"@semantic-release/github",
|
|
143
|
+
"@semantic-release/git"
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
"scripts": {
|
|
147
|
+
"clean": "aegir clean",
|
|
148
|
+
"lint": "aegir lint",
|
|
149
|
+
"dep-check": "aegir dep-check -i libp2p",
|
|
150
|
+
"build": "aegir build",
|
|
151
|
+
"test": "aegir test",
|
|
152
|
+
"test:chrome": "aegir test -t browser -f ./dist/test/browser.js --cov",
|
|
153
|
+
"release": "aegir release",
|
|
154
|
+
"proto:gen": "protons ./src/proto/webtransport.proto"
|
|
155
|
+
},
|
|
156
|
+
"dependencies": {
|
|
157
|
+
"@chainsafe/libp2p-noise": "^9.0.0",
|
|
158
|
+
"@libp2p/interface-connection": "^3.0.1",
|
|
159
|
+
"@libp2p/interface-stream-muxer": "^3.0.0",
|
|
160
|
+
"@libp2p/interface-transport": "^2.0.0",
|
|
161
|
+
"@libp2p/interfaces": "^3.0.1",
|
|
162
|
+
"@libp2p/logger": "^2.0.0",
|
|
163
|
+
"@libp2p/peer-id": "^1.1.15",
|
|
164
|
+
"@libp2p/utils": "^3.0.0",
|
|
165
|
+
"@multiformats/mafmt": "^11.0.3",
|
|
166
|
+
"@multiformats/multiaddr": "^11.0.3",
|
|
167
|
+
"@multiformats/multiaddr-to-uri": "^9.0.0",
|
|
168
|
+
"abortable-iterator": "^4.0.2",
|
|
169
|
+
"err-code": "^3.0.1",
|
|
170
|
+
"it-ws": "^5.0.0",
|
|
171
|
+
"multiformats": "^10.0.0",
|
|
172
|
+
"p-defer": "^4.0.0",
|
|
173
|
+
"p-timeout": "^6.0.0",
|
|
174
|
+
"protons-runtime": "^4.0.1",
|
|
175
|
+
"uint8arraylist": "^2.3.2",
|
|
176
|
+
"wherearewe": "^2.0.1"
|
|
177
|
+
},
|
|
178
|
+
"devDependencies": {
|
|
179
|
+
"@libp2p/interface-mocks": "^7.0.1",
|
|
180
|
+
"@libp2p/interface-transport-compliance-tests": "^3.0.0",
|
|
181
|
+
"@types/ws": "^8.2.2",
|
|
182
|
+
"aegir": "^37.3.0",
|
|
183
|
+
"is-loopback-addr": "^2.0.1",
|
|
184
|
+
"it-all": "^1.0.6",
|
|
185
|
+
"it-drain": "^1.0.5",
|
|
186
|
+
"it-goodbye": "^4.0.1",
|
|
187
|
+
"it-pipe": "^2.0.3",
|
|
188
|
+
"it-stream-types": "^1.0.4",
|
|
189
|
+
"it-take": "^1.0.2",
|
|
190
|
+
"libp2p": "next",
|
|
191
|
+
"p-wait-for": "^5.0.0",
|
|
192
|
+
"protons": "^6.0.0",
|
|
193
|
+
"uint8arrays": "^4.0.2"
|
|
194
|
+
},
|
|
195
|
+
"browser": {
|
|
196
|
+
"./dist/src/listener.js": "./dist/src/listener.browser.js"
|
|
197
|
+
}
|
|
198
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { logger } from '@libp2p/logger'
|
|
2
|
+
import { Noise } from '@chainsafe/libp2p-noise'
|
|
3
|
+
import type { Components, Initializable } from '@libp2p/components'
|
|
4
|
+
import { Transport, symbol, CreateListenerOptions, DialOptions } from '@libp2p/interface-transport'
|
|
5
|
+
import type { Connection, Direction, MultiaddrConnection, Stream } from '@libp2p/interface-connection'
|
|
6
|
+
import { Multiaddr, protocols } from '@multiformats/multiaddr'
|
|
7
|
+
import { peerIdFromString } from '@libp2p/peer-id'
|
|
8
|
+
import { bases, digest } from 'multiformats/basics'
|
|
9
|
+
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
10
|
+
import type { PeerId } from '@libp2p/interface-peer-id'
|
|
11
|
+
import type { Duplex, Source } from 'it-stream-types'
|
|
12
|
+
import type { StreamMuxerFactory, StreamMuxerInit, StreamMuxer } from '@libp2p/interface-stream-muxer'
|
|
13
|
+
import { Uint8ArrayList } from 'uint8arraylist'
|
|
14
|
+
|
|
15
|
+
const log = logger('libp2p:webtransport')
|
|
16
|
+
|
|
17
|
+
declare global {
|
|
18
|
+
interface Window {
|
|
19
|
+
WebTransport: any
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// @ts-expect-error - Not easy to combine these types.
|
|
24
|
+
const multibaseDecoder = Object.values(bases).map(b => b.decoder).reduce((d, b) => d.or(b))
|
|
25
|
+
|
|
26
|
+
function decodeCerthashStr (s: string): MultihashDigest {
|
|
27
|
+
return digest.decode(multibaseDecoder.decode(s))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Duplex that does nothing. Needed to fulfill the interface
|
|
31
|
+
function inertDuplex (): Duplex<any, any, any> {
|
|
32
|
+
return {
|
|
33
|
+
source: {
|
|
34
|
+
[Symbol.asyncIterator] () {
|
|
35
|
+
return {
|
|
36
|
+
async next () {
|
|
37
|
+
// This will never resolve
|
|
38
|
+
return await new Promise(() => { })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
sink: async (source: Source<any>) => {
|
|
44
|
+
// This will never resolve
|
|
45
|
+
return await new Promise(() => { })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function webtransportBiDiStreamToStream (bidiStream: any, streamId: string, direction: Direction, activeStreams: Stream[], onStreamEnd: undefined | ((s: Stream) => void)): Promise<Stream> {
|
|
51
|
+
const writer = bidiStream.writable.getWriter()
|
|
52
|
+
const reader = bidiStream.readable.getReader()
|
|
53
|
+
await writer.ready
|
|
54
|
+
|
|
55
|
+
function cleanupStreamFromActiveStreams () {
|
|
56
|
+
const index = activeStreams.findIndex(s => s === stream)
|
|
57
|
+
if (index !== -1) {
|
|
58
|
+
activeStreams.splice(index, 1)
|
|
59
|
+
stream.stat.timeline.close = Date.now()
|
|
60
|
+
onStreamEnd?.(stream)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let writerClosed = false
|
|
65
|
+
let readerClosed = false;
|
|
66
|
+
(async function () {
|
|
67
|
+
await (writer.closed.then((ok: any) => ({ ok })).catch((err: any) => ({ err })))
|
|
68
|
+
writerClosed = true
|
|
69
|
+
if (writerClosed && readerClosed) {
|
|
70
|
+
cleanupStreamFromActiveStreams()
|
|
71
|
+
}
|
|
72
|
+
})().catch(() => {
|
|
73
|
+
log.error('WebTransport failed to cleanup closed stream')
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
(async function () {
|
|
77
|
+
await (reader.closed.then((ok: any) => ({ ok })).catch((err: any) => ({ err })))
|
|
78
|
+
readerClosed = true
|
|
79
|
+
if (writerClosed && readerClosed) {
|
|
80
|
+
cleanupStreamFromActiveStreams()
|
|
81
|
+
}
|
|
82
|
+
})().catch(() => {
|
|
83
|
+
log.error('WebTransport failed to cleanup closed stream')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const stream: Stream = {
|
|
87
|
+
id: streamId,
|
|
88
|
+
abort (_err: Error) {
|
|
89
|
+
if (!writerClosed) {
|
|
90
|
+
writer.abort()
|
|
91
|
+
writerClosed = true
|
|
92
|
+
}
|
|
93
|
+
stream.closeRead()
|
|
94
|
+
readerClosed = true
|
|
95
|
+
cleanupStreamFromActiveStreams()
|
|
96
|
+
},
|
|
97
|
+
close () {
|
|
98
|
+
stream.closeRead()
|
|
99
|
+
stream.closeWrite()
|
|
100
|
+
cleanupStreamFromActiveStreams()
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
closeRead () {
|
|
104
|
+
if (!readerClosed) {
|
|
105
|
+
reader.cancel().catch((err: any) => {
|
|
106
|
+
if (err.toString().includes('RESET_STREAM') === true) {
|
|
107
|
+
writerClosed = true
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
readerClosed = true
|
|
111
|
+
}
|
|
112
|
+
if (writerClosed) {
|
|
113
|
+
cleanupStreamFromActiveStreams()
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
closeWrite () {
|
|
117
|
+
if (!writerClosed) {
|
|
118
|
+
writerClosed = true
|
|
119
|
+
writer.close().catch((err: any) => {
|
|
120
|
+
if (err.toString().includes('RESET_STREAM') === true) {
|
|
121
|
+
readerClosed = true
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
if (readerClosed) {
|
|
126
|
+
cleanupStreamFromActiveStreams()
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
reset () {
|
|
130
|
+
stream.close()
|
|
131
|
+
},
|
|
132
|
+
stat: {
|
|
133
|
+
direction: direction,
|
|
134
|
+
timeline: { open: Date.now() }
|
|
135
|
+
},
|
|
136
|
+
metadata: {},
|
|
137
|
+
source: (async function * () {
|
|
138
|
+
while (true) {
|
|
139
|
+
const val = await reader.read()
|
|
140
|
+
if (val.done === true) {
|
|
141
|
+
readerClosed = true
|
|
142
|
+
if (writerClosed) {
|
|
143
|
+
cleanupStreamFromActiveStreams()
|
|
144
|
+
}
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
yield new Uint8ArrayList(val.value)
|
|
149
|
+
}
|
|
150
|
+
})(),
|
|
151
|
+
sink: async function (source: Source<Uint8Array | Uint8ArrayList>) {
|
|
152
|
+
for await (const chunks of source) {
|
|
153
|
+
if (chunks.constructor === Uint8Array) {
|
|
154
|
+
await writer.write(chunks)
|
|
155
|
+
} else {
|
|
156
|
+
for (const buf of chunks) {
|
|
157
|
+
await writer.write(buf)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return stream
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function parseMultiaddr (ma: Multiaddr): { url: string, certhashes: MultihashDigest[], remotePeer?: PeerId } {
|
|
168
|
+
const parts = ma.stringTuples()
|
|
169
|
+
|
|
170
|
+
// This is simpler to have inline than extract into a separate function
|
|
171
|
+
// eslint-disable-next-line complexity
|
|
172
|
+
const { url, certhashes, remotePeer } = parts.reduce((state: { url: string, certhashes: MultihashDigest[], seenHost: boolean, seenPort: boolean, remotePeer?: PeerId }, [proto, value]) => {
|
|
173
|
+
switch (proto) {
|
|
174
|
+
case protocols('ip4').code:
|
|
175
|
+
case protocols('ip6').code:
|
|
176
|
+
case protocols('dns4').code:
|
|
177
|
+
case protocols('dns6').code:
|
|
178
|
+
if (state.seenHost || state.seenPort) {
|
|
179
|
+
throw new Error('Invalid multiaddr, saw host and already saw the host or port')
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
...state,
|
|
183
|
+
url: `${state.url}${value ?? ''}`,
|
|
184
|
+
seenHost: true
|
|
185
|
+
}
|
|
186
|
+
case protocols('quic').code:
|
|
187
|
+
case protocols('webtransport').code:
|
|
188
|
+
if (!state.seenHost || !state.seenPort) {
|
|
189
|
+
throw new Error("Invalid multiaddr, Didn't see host and port, but saw quic/webtransport")
|
|
190
|
+
}
|
|
191
|
+
return state
|
|
192
|
+
case protocols('udp').code:
|
|
193
|
+
if (state.seenPort) {
|
|
194
|
+
throw new Error('Invalid multiaddr, saw port but already saw the port')
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
...state,
|
|
198
|
+
url: `${state.url}:${value ?? ''}`,
|
|
199
|
+
seenPort: true
|
|
200
|
+
}
|
|
201
|
+
case protocols('certhash').code:
|
|
202
|
+
if (!state.seenHost || !state.seenPort) {
|
|
203
|
+
throw new Error('Invalid multiaddr, saw the certhash before seeing the host and port')
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
...state,
|
|
207
|
+
certhashes: state.certhashes.concat([decodeCerthashStr(value ?? '')])
|
|
208
|
+
}
|
|
209
|
+
case protocols('p2p').code:
|
|
210
|
+
return {
|
|
211
|
+
...state,
|
|
212
|
+
remotePeer: peerIdFromString(value ?? '')
|
|
213
|
+
}
|
|
214
|
+
default:
|
|
215
|
+
throw new Error(`unexpected component in multiaddr: ${proto} ${protocols(proto).name} ${value ?? ''} `)
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
// All webtransport urls are https
|
|
219
|
+
{ url: 'https://', seenHost: false, seenPort: false, certhashes: [] })
|
|
220
|
+
|
|
221
|
+
return { url, certhashes, remotePeer }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Determines if `maybeSubset` is a subset of `set`. This means that all byte arrays in `maybeSubset` are present in `set`.
|
|
225
|
+
export function isSubset (set: Uint8Array[], maybeSubset: Uint8Array[]): boolean {
|
|
226
|
+
const intersection = maybeSubset.filter(byteArray => {
|
|
227
|
+
return Boolean(set.find((otherByteArray: Uint8Array) => {
|
|
228
|
+
if (byteArray.length !== otherByteArray.length) {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
for (let index = 0; index < byteArray.length; index++) {
|
|
233
|
+
if (otherByteArray[index] !== byteArray[index]) {
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return true
|
|
238
|
+
}))
|
|
239
|
+
})
|
|
240
|
+
return (intersection.length === maybeSubset.length)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface WebTransportConfig {
|
|
244
|
+
maxInboundStreams: 1000
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export class WebTransport implements Transport, Initializable {
|
|
248
|
+
private components?: Components
|
|
249
|
+
private readonly config: WebTransportConfig
|
|
250
|
+
|
|
251
|
+
init (components: Components) {
|
|
252
|
+
this.components = components
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
get [Symbol.toStringTag] () {
|
|
256
|
+
return '@libp2p/webtransport'
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
get [symbol] (): true {
|
|
260
|
+
return true
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
constructor (config?: WebTransportConfig) {
|
|
264
|
+
this.config = config ?? { maxInboundStreams: 1000 }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async dial (ma: Multiaddr, options: DialOptions): Promise<Connection> {
|
|
268
|
+
log('dialing %s', ma)
|
|
269
|
+
const localPeer = this.components?.getPeerId()
|
|
270
|
+
if (localPeer === undefined) {
|
|
271
|
+
throw new Error('Need a local peerid')
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
options = options ?? {}
|
|
275
|
+
|
|
276
|
+
const { url, certhashes, remotePeer } = parseMultiaddr(ma)
|
|
277
|
+
|
|
278
|
+
const wt = new window.WebTransport(`${url}/.well-known/libp2p-webtransport?type=noise`, {
|
|
279
|
+
serverCertificateHashes: certhashes.map(certhash => ({
|
|
280
|
+
algorithm: 'sha-256',
|
|
281
|
+
value: certhash.digest
|
|
282
|
+
}))
|
|
283
|
+
})
|
|
284
|
+
wt.closed.catch((error: Error) => {
|
|
285
|
+
log.error('WebTransport transport closed due to:', error)
|
|
286
|
+
})
|
|
287
|
+
await wt.ready
|
|
288
|
+
|
|
289
|
+
if (remotePeer == null) {
|
|
290
|
+
throw new Error('Need a target peerid')
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!await this.authenticateWebTransport(wt, localPeer, remotePeer, certhashes)) {
|
|
294
|
+
throw new Error('Failed to authenticate webtransport')
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const maConn: MultiaddrConnection = {
|
|
298
|
+
close: async (err?: Error) => {
|
|
299
|
+
if (err != null) {
|
|
300
|
+
log('Closing webtransport with err:', err)
|
|
301
|
+
}
|
|
302
|
+
wt.close()
|
|
303
|
+
},
|
|
304
|
+
remoteAddr: ma,
|
|
305
|
+
timeline: {
|
|
306
|
+
open: Date.now()
|
|
307
|
+
},
|
|
308
|
+
// This connection is never used directly since webtransport supports native streams.
|
|
309
|
+
...inertDuplex()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
wt.closed.catch((err: Error) => {
|
|
313
|
+
log.error('WebTransport connection closed:', err)
|
|
314
|
+
// This is how we specify the connection is closed and shouldn't be used.
|
|
315
|
+
maConn.timeline.close = Date.now()
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
options?.signal?.throwIfAborted()
|
|
320
|
+
} catch (e) {
|
|
321
|
+
wt.close()
|
|
322
|
+
throw e
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return await options.upgrader.upgradeOutbound(maConn, { skipEncryption: true, muxerFactory: this.webtransportMuxer(wt), skipProtection: true })
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async authenticateWebTransport (wt: typeof window.WebTransport, localPeer: PeerId, remotePeer: PeerId, certhashes: Array<MultihashDigest<number>>) {
|
|
329
|
+
const stream = await wt.createBidirectionalStream()
|
|
330
|
+
const writer = stream.writable.getWriter()
|
|
331
|
+
const reader = stream.readable.getReader()
|
|
332
|
+
await writer.ready
|
|
333
|
+
|
|
334
|
+
const duplex = {
|
|
335
|
+
source: (async function * () {
|
|
336
|
+
while (true) {
|
|
337
|
+
const val = await reader.read()
|
|
338
|
+
yield val.value
|
|
339
|
+
}
|
|
340
|
+
})(),
|
|
341
|
+
sink: async function (source: Source<Uint8Array>) {
|
|
342
|
+
for await (const chunk of source) {
|
|
343
|
+
await writer.write(chunk)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const noise = new Noise()
|
|
349
|
+
|
|
350
|
+
const { remoteExtensions } = await noise.secureOutbound(localPeer, duplex, remotePeer)
|
|
351
|
+
|
|
352
|
+
// We're done with this authentication stream
|
|
353
|
+
writer.close().catch((err: Error) => {
|
|
354
|
+
log.error(`Failed to close authentication stream writer: ${err.message}`)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
reader.cancel().catch((err: Error) => {
|
|
358
|
+
log.error(`Failed to close authentication stream reader: ${err.message}`)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
// Verify the certhashes we used when dialing are a subset of the certhashes relayed by the remote peer
|
|
362
|
+
if (!isSubset(remoteExtensions?.webtransportCerthashes ?? [], certhashes.map(ch => ch.bytes))) {
|
|
363
|
+
throw new Error("Our certhashes are not a subset of the remote's reported certhashes")
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return true
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
webtransportMuxer (wt: typeof window.WebTransport): StreamMuxerFactory {
|
|
370
|
+
let streamIDCounter = 0
|
|
371
|
+
const config = this.config
|
|
372
|
+
return {
|
|
373
|
+
protocol: 'webtransport',
|
|
374
|
+
createStreamMuxer: (init?: StreamMuxerInit): StreamMuxer => {
|
|
375
|
+
// !TODO handle abort signal when WebTransport supports this.
|
|
376
|
+
|
|
377
|
+
if (typeof init === 'function') {
|
|
378
|
+
// The api docs say that init may be a function
|
|
379
|
+
init = { onIncomingStream: init }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const activeStreams: Stream[] = [];
|
|
383
|
+
|
|
384
|
+
(async function () {
|
|
385
|
+
//! TODO unclear how to add backpressure here?
|
|
386
|
+
|
|
387
|
+
const reader = wt.incomingBidirectionalStreams.getReader()
|
|
388
|
+
while (true) {
|
|
389
|
+
const { done, value: wtStream } = await reader.read()
|
|
390
|
+
if (done === true) {
|
|
391
|
+
break
|
|
392
|
+
}
|
|
393
|
+
if (activeStreams.length >= config.maxInboundStreams) {
|
|
394
|
+
// We've reached our limit, close this stream.
|
|
395
|
+
wtStream.writable.close().catch((err: Error) => {
|
|
396
|
+
log.error(`Failed to close inbound stream that crossed our maxInboundStream limit: ${err.message}`)
|
|
397
|
+
})
|
|
398
|
+
wtStream.readable.cancel().catch((err: Error) => {
|
|
399
|
+
log.error(`Failed to close inbound stream that crossed our maxInboundStream limit: ${err.message}`)
|
|
400
|
+
})
|
|
401
|
+
} else {
|
|
402
|
+
const stream = await webtransportBiDiStreamToStream(wtStream, String(streamIDCounter++), 'inbound', activeStreams, init?.onStreamEnd)
|
|
403
|
+
activeStreams.push(stream)
|
|
404
|
+
init?.onIncomingStream?.(stream)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
})().catch(() => {
|
|
408
|
+
log.error('WebTransport failed to receive incoming stream')
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
const muxer: StreamMuxer = {
|
|
412
|
+
protocol: 'webtransport',
|
|
413
|
+
streams: activeStreams,
|
|
414
|
+
newStream: async (name?: string): Promise<Stream> => {
|
|
415
|
+
const wtStream = await wt.createBidirectionalStream()
|
|
416
|
+
|
|
417
|
+
const stream = await webtransportBiDiStreamToStream(wtStream, String(streamIDCounter++), init?.direction ?? 'outbound', activeStreams, init?.onStreamEnd)
|
|
418
|
+
activeStreams.push(stream)
|
|
419
|
+
|
|
420
|
+
return stream
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Close or abort all tracked streams and stop the muxer
|
|
425
|
+
*/
|
|
426
|
+
close: (err?: Error) => {
|
|
427
|
+
if (err != null) {
|
|
428
|
+
log('Closing webtransport muxer with err:', err)
|
|
429
|
+
}
|
|
430
|
+
wt.close()
|
|
431
|
+
},
|
|
432
|
+
// This stream muxer is webtransport native. Therefore it doesn't plug in with any other duplex.
|
|
433
|
+
...inertDuplex()
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
try {
|
|
437
|
+
init?.signal?.throwIfAborted()
|
|
438
|
+
} catch (e) {
|
|
439
|
+
wt.close()
|
|
440
|
+
throw e
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return muxer
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
createListener (options: CreateListenerOptions) {
|
|
449
|
+
throw new Error('Webtransport servers are not supported in Node or the browser')
|
|
450
|
+
// Unreachable, here to make TS happy
|
|
451
|
+
// eslint-disable-next-line no-unreachable
|
|
452
|
+
return null as any
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Takes a list of `Multiaddr`s and returns only valid webtransport addresses.
|
|
457
|
+
*/
|
|
458
|
+
filter (multiaddrs: Multiaddr[]) {
|
|
459
|
+
return multiaddrs.filter(ma => ma.protoNames().includes('webtransport'))
|
|
460
|
+
}
|
|
461
|
+
}
|