@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 ADDED
@@ -0,0 +1,4 @@
1
+ This project is dual licensed under MIT and Apache-2.0.
2
+
3
+ MIT: https://www.opensource.org/licenses/mit
4
+ Apache-2.0: https://www.apache.org/licenses/license-2.0
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # @libp2p/webtransport <!-- omit in toc -->
2
+
3
+ [![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
4
+ [![IRC](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
5
+ [![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](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://raw.githubusercontent.com/libp2p/interface-transport/master/img/badge.png)](https://github.com/libp2p/interface-transport)
28
+ [![](https://raw.githubusercontent.com/libp2p/interface-connection/master/img/badge.png)](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://raw.githubusercontent.com/libp2p/interface-transport/master/img/badge.png)](https://github.com/libp2p/interface-transport)
63
+
64
+ ### Connection
65
+
66
+ [![](https://raw.githubusercontent.com/libp2p/interface-connection/master/img/badge.png)](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
+ }