@libp2p/websockets 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,122 @@
1
+ # js-libp2p-websockets <!-- omit in toc -->
2
+
3
+ [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
4
+ [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
5
+ [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
6
+ [![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io)
7
+ [![Coverage Status](https://coveralls.io/repos/github/libp2p/js-libp2p-websockets/badge.svg?branch=master)](https://coveralls.io/github/libp2p/js-libp2p-websockets?branch=master)
8
+ [![Build Status](https://github.com/libp2p/js-libp2p-websockets/actions/workflows/js-test-and-release.yml/badge.svg?branch=main)](https://github.com/libp2p/js-libp2p-websockets/actions/workflows/js-test-and-release.yml)
9
+ [![Dependency Status](https://david-dm.org/libp2p/js-libp2p-websockets.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websockets)
10
+ [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
11
+ ![](https://img.shields.io/badge/Node.js-%3E%3D14.0.0-orange.svg?style=flat-square)
12
+
13
+ [![](https://raw.githubusercontent.com/libp2p/interface-transport/master/img/badge.png)](https://github.com/libp2p/interface-transport)
14
+ [![](https://raw.githubusercontent.com/libp2p/interface-connection/master/img/badge.png)](https://github.com/libp2p/interface-connection)
15
+
16
+ > JavaScript implementation of the WebSockets module that libp2p uses and that implements the interface-transport interface
17
+
18
+ ## Table of Contents <!-- omit in toc -->
19
+
20
+ - [Description](#description)
21
+ - [Usage](#usage)
22
+ - [Install](#install)
23
+ - [npm](#npm)
24
+ - [Constructor properties](#constructor-properties)
25
+ - [Libp2p Usage Example](#libp2p-usage-example)
26
+ - [API](#api)
27
+ - [Transport](#transport)
28
+ - [Connection](#connection)
29
+ - [License](#license)
30
+ - [Contribution](#contribution)
31
+
32
+ ## Description
33
+
34
+ `libp2p-websockets` is the WebSockets implementation compatible with libp2p.
35
+
36
+ ## Usage
37
+
38
+ ## Install
39
+
40
+ ### npm
41
+
42
+ ```sh
43
+ > npm i @libp2p/websockets
44
+ ```
45
+
46
+ ### Constructor properties
47
+
48
+ ```js
49
+ import WS from '@libp2p/websockets'
50
+
51
+ const properties = {
52
+ upgrader,
53
+ filter
54
+ }
55
+
56
+ const ws = new WS(properties)
57
+ ```
58
+
59
+ | Name | Type | Description | Default |
60
+ |------|------|-------------|---------|
61
+ | upgrader | [`Upgrader`](https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport#upgrader) | connection upgrader object with `upgradeOutbound` and `upgradeInbound` | **REQUIRED** |
62
+ | filter | `(multiaddrs: Array<Multiaddr>) => Array<Multiaddr>` | override transport addresses filter | **Browser:** DNS+WSS multiaddrs / **Node.js:** DNS+{WS, WSS} multiaddrs |
63
+
64
+ You can create your own address filters for this transports, or rely in the filters [provided](./src/filters.js).
65
+
66
+ The available filters are:
67
+
68
+ - `filters.all`
69
+ - Returns all TCP and DNS based addresses, both with `ws` or `wss`.
70
+ - `filters.dnsWss`
71
+ - Returns all DNS based addresses with `wss`.
72
+ - `filters.dnsWsOrWss`
73
+ - Returns all DNS based addresses, both with `ws` or `wss`.
74
+
75
+ ## Libp2p Usage Example
76
+
77
+ ```js
78
+ import Libp2p from 'libp2p'
79
+ import { Websockets } from '@libp2p/websockets'
80
+ import filters from 'libp2p-websockets/filters'
81
+ import { MPLEX } from 'libp2p-mplex'
82
+ import { NOISE } from 'libp2p-noise'
83
+
84
+ const transportKey = Websockets.prototype[Symbol.toStringTag]
85
+ const node = await Libp2p.create({
86
+ modules: {
87
+ transport: [Websockets],
88
+ streamMuxer: [MPLEX],
89
+ connEncryption: [NOISE]
90
+ },
91
+ config: {
92
+ transport: {
93
+ [transportKey]: { // Transport properties -- Libp2p upgrader is automatically added
94
+ filter: filters.dnsWsOrWss
95
+ }
96
+ }
97
+ }
98
+ })
99
+ ```
100
+
101
+ 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).
102
+
103
+ ## API
104
+
105
+ ### Transport
106
+
107
+ [![](https://raw.githubusercontent.com/libp2p/interface-transport/master/img/badge.png)](https://github.com/libp2p/interface-transport)
108
+
109
+ ### Connection
110
+
111
+ [![](https://raw.githubusercontent.com/libp2p/interface-connection/master/img/badge.png)](https://github.com/libp2p/interface-connection)
112
+
113
+ ## License
114
+
115
+ Licensed under either of
116
+
117
+ * Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / http://www.apache.org/licenses/LICENSE-2.0)
118
+ * MIT ([LICENSE-MIT](LICENSE-MIT) / http://opensource.org/licenses/MIT)
119
+
120
+ ### Contribution
121
+
122
+ 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,7 @@
1
+ export declare const CODE_P2P = 421;
2
+ export declare const CODE_CIRCUIT = 290;
3
+ export declare const CODE_TCP = 6;
4
+ export declare const CODE_WS = 477;
5
+ export declare const CODE_WSS = 478;
6
+ export declare const CLOSE_TIMEOUT = 2000;
7
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,QAAQ,MAAM,CAAA;AAC3B,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B,eAAO,MAAM,QAAQ,IAAI,CAAA;AACzB,eAAO,MAAM,OAAO,MAAM,CAAA;AAC1B,eAAO,MAAM,QAAQ,MAAM,CAAA;AAG3B,eAAO,MAAM,aAAa,OAAO,CAAA"}
@@ -0,0 +1,9 @@
1
+ // p2p multi-address code
2
+ export const CODE_P2P = 421;
3
+ export const CODE_CIRCUIT = 290;
4
+ export const CODE_TCP = 6;
5
+ export const CODE_WS = 477;
6
+ export const CODE_WSS = 478;
7
+ // Time to wait for a connection to close gracefully before destroying it manually
8
+ export const CLOSE_TIMEOUT = 2000;
9
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAC3B,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;AAE/B,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAA;AACzB,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAA;AAC1B,MAAM,CAAC,MAAM,QAAQ,GAAG,GAAG,CAAA;AAE3B,kFAAkF;AAClF,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAA"}
@@ -0,0 +1,5 @@
1
+ import type { Multiaddr } from '@multiformats/multiaddr';
2
+ export declare function all(multiaddrs: Multiaddr[]): Multiaddr[];
3
+ export declare function dnsWss(multiaddrs: Multiaddr[]): Multiaddr[];
4
+ export declare function dnsWsOrWss(multiaddrs: Multiaddr[]): Multiaddr[];
5
+ //# sourceMappingURL=filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../src/filters.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AASxD,wBAAgB,GAAG,CAAE,UAAU,EAAE,SAAS,EAAE,eAW3C;AAED,wBAAgB,MAAM,CAAE,UAAU,EAAE,SAAS,EAAE,eAW9C;AAED,wBAAgB,UAAU,CAAE,UAAU,EAAE,SAAS,EAAE,eAiBlD"}
@@ -0,0 +1,38 @@
1
+ import * as mafmt from '@multiformats/mafmt';
2
+ import { CODE_CIRCUIT, CODE_P2P, CODE_TCP, CODE_WS, CODE_WSS } from './constants.js';
3
+ export function all(multiaddrs) {
4
+ return multiaddrs.filter((ma) => {
5
+ if (ma.protoCodes().includes(CODE_CIRCUIT)) {
6
+ return false;
7
+ }
8
+ const testMa = ma.decapsulateCode(CODE_P2P);
9
+ return mafmt.WebSockets.matches(testMa) ||
10
+ mafmt.WebSocketsSecure.matches(testMa);
11
+ });
12
+ }
13
+ export function dnsWss(multiaddrs) {
14
+ return multiaddrs.filter((ma) => {
15
+ if (ma.protoCodes().includes(CODE_CIRCUIT)) {
16
+ return false;
17
+ }
18
+ const testMa = ma.decapsulateCode(CODE_P2P);
19
+ return mafmt.WebSocketsSecure.matches(testMa) &&
20
+ mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WSS));
21
+ });
22
+ }
23
+ export function dnsWsOrWss(multiaddrs) {
24
+ return multiaddrs.filter((ma) => {
25
+ if (ma.protoCodes().includes(CODE_CIRCUIT)) {
26
+ return false;
27
+ }
28
+ const testMa = ma.decapsulateCode(CODE_P2P);
29
+ // WS
30
+ if (mafmt.WebSockets.matches(testMa)) {
31
+ return mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WS));
32
+ }
33
+ // WSS
34
+ return mafmt.WebSocketsSecure.matches(testMa) &&
35
+ mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WSS));
36
+ });
37
+ }
38
+ //# sourceMappingURL=filters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.js","sourceRoot":"","sources":["../../src/filters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,qBAAqB,CAAA;AAE5C,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,QAAQ,EACT,MAAM,gBAAgB,CAAA;AAEvB,MAAM,UAAU,GAAG,CAAE,UAAuB;IAC1C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE;QAC9B,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE;YAC1C,OAAO,KAAK,CAAA;SACb;QAED,MAAM,MAAM,GAAG,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAA;QAE3C,OAAO,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;YACrC,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,MAAM,CAAE,UAAuB;IAC7C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE;QAC9B,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE;YAC1C,OAAO,KAAK,CAAA;SACb;QAED,MAAM,MAAM,GAAG,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAA;QAE3C,OAAO,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC;YAC3C,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAA;IACjF,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAE,UAAuB;IACjD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE;QAC9B,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE;YAC1C,OAAO,KAAK,CAAA;SACb;QAED,MAAM,MAAM,GAAG,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAA;QAE3C,KAAK;QACL,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACpC,OAAO,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAA;SACpF;QAED,MAAM;QACN,OAAO,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC;YAC3C,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAA;IACjF,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { WebSocketOptions } from 'it-ws/client';
2
+ import type { Transport, Upgrader, MultiaddrFilter } from '@libp2p/interfaces/transport';
3
+ import type { AbortOptions } from '@libp2p/interfaces';
4
+ import type { WebSocketListenerOptions } from './listener.js';
5
+ import type { Multiaddr } from '@multiformats/multiaddr';
6
+ import type { DuplexWebSocket } from 'it-ws/dist/src/duplex';
7
+ /**
8
+ * @class WebSockets
9
+ */
10
+ export declare class WebSockets implements Transport<AbortOptions & WebSocketOptions, WebSocketListenerOptions> {
11
+ private readonly upgrader;
12
+ private readonly _filter?;
13
+ constructor(opts: {
14
+ upgrader: Upgrader;
15
+ filter?: MultiaddrFilter;
16
+ });
17
+ dial(ma: Multiaddr, options?: AbortOptions & WebSocketOptions): Promise<import("@libp2p/interfaces/dist/src/connection").Connection>;
18
+ _connect(ma: Multiaddr, options: AbortOptions & WebSocketOptions): Promise<DuplexWebSocket>;
19
+ /**
20
+ * Creates a Websockets listener. The provided `handler` function will be called
21
+ * anytime a new incoming Connection has been successfully upgraded via
22
+ * `upgrader.upgradeInbound`
23
+ */
24
+ createListener(options?: WebSocketListenerOptions): import("@libp2p/interfaces/transport").Listener;
25
+ /**
26
+ * Takes a list of `Multiaddr`s and returns only valid Websockets addresses.
27
+ * By default, in a browser environment only DNS+WSS multiaddr is accepted,
28
+ * while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted.
29
+ */
30
+ filter(multiaddrs: Multiaddr[]): Multiaddr[];
31
+ }
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,gBAAgB,EAAE,MAAM,cAAc,CAAA;AASxD,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAI5D;;GAEG;AACH,qBAAa,UAAW,YAAW,SAAS,CAAC,YAAY,GAAG,gBAAgB,EAAE,wBAAwB,CAAC;IACrG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAiB;gBAE7B,IAAI,EAAE;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,MAAM,CAAC,EAAE,eAAe,CAAA;KAAE;IAW7D,IAAI,CAAE,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,gBAAgB;IAa9D,QAAQ,CAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,GAAG,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IA8DlG;;;;OAIG;IACH,cAAc,CAAE,OAAO,CAAC,EAAE,wBAAwB;IAIlD;;;;OAIG;IACH,MAAM,CAAE,UAAU,EAAE,SAAS,EAAE;CAchC"}
@@ -0,0 +1,110 @@
1
+ import { connect } from 'it-ws/client';
2
+ import { multiaddrToUri as toUri } from '@multiformats/multiaddr-to-uri';
3
+ import { AbortError } from '@libp2p/interfaces/errors';
4
+ import pDefer from 'p-defer';
5
+ import { logger } from '@libp2p/logger';
6
+ import env from 'wherearewe';
7
+ import { createListener } from './listener.js';
8
+ import { socketToMaConn } from './socket-to-conn.js';
9
+ import * as filters from './filters.js';
10
+ const log = logger('libp2p:websockets');
11
+ /**
12
+ * @class WebSockets
13
+ */
14
+ export class WebSockets {
15
+ constructor(opts) {
16
+ const { upgrader, filter } = opts;
17
+ if (upgrader == null) {
18
+ throw new Error('An upgrader must be provided. See https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport#upgrader');
19
+ }
20
+ this.upgrader = upgrader;
21
+ this._filter = filter;
22
+ }
23
+ async dial(ma, options) {
24
+ log('dialing %s', ma);
25
+ options = options ?? {};
26
+ const socket = await this._connect(ma, options);
27
+ const maConn = socketToMaConn(socket, ma);
28
+ log('new outbound connection %s', maConn.remoteAddr);
29
+ const conn = await this.upgrader.upgradeOutbound(maConn);
30
+ log('outbound connection %s upgraded', maConn.remoteAddr);
31
+ return conn;
32
+ }
33
+ async _connect(ma, options) {
34
+ if (options?.signal?.aborted === true) {
35
+ throw new AbortError();
36
+ }
37
+ const cOpts = ma.toOptions();
38
+ log('dialing %s:%s', cOpts.host, cOpts.port);
39
+ const errorPromise = pDefer();
40
+ const errfn = (err) => {
41
+ log.error('connection error:', err);
42
+ errorPromise.reject(err);
43
+ };
44
+ const rawSocket = connect(toUri(ma), options);
45
+ if (rawSocket.socket.on != null) {
46
+ rawSocket.socket.on('error', errfn);
47
+ }
48
+ else {
49
+ rawSocket.socket.onerror = errfn;
50
+ }
51
+ if (options.signal == null) {
52
+ await Promise.race([rawSocket.connected(), errorPromise.promise]);
53
+ log('connected %s', ma);
54
+ return rawSocket;
55
+ }
56
+ // Allow abort via signal during connect
57
+ let onAbort;
58
+ const abort = new Promise((resolve, reject) => {
59
+ onAbort = () => {
60
+ reject(new AbortError());
61
+ // FIXME: https://github.com/libp2p/js-libp2p-websockets/issues/121
62
+ setTimeout(() => {
63
+ rawSocket.close().catch(err => {
64
+ log.error('error closing raw socket', err);
65
+ });
66
+ });
67
+ };
68
+ // Already aborted?
69
+ if (options?.signal?.aborted === true) {
70
+ return onAbort();
71
+ }
72
+ options?.signal?.addEventListener('abort', onAbort);
73
+ });
74
+ try {
75
+ await Promise.race([abort, errorPromise.promise, rawSocket.connected()]);
76
+ }
77
+ finally {
78
+ if (onAbort != null) {
79
+ options?.signal?.removeEventListener('abort', onAbort);
80
+ }
81
+ }
82
+ log('connected %s', ma);
83
+ return rawSocket;
84
+ }
85
+ /**
86
+ * Creates a Websockets listener. The provided `handler` function will be called
87
+ * anytime a new incoming Connection has been successfully upgraded via
88
+ * `upgrader.upgradeInbound`
89
+ */
90
+ createListener(options) {
91
+ return createListener(this.upgrader, options);
92
+ }
93
+ /**
94
+ * Takes a list of `Multiaddr`s and returns only valid Websockets addresses.
95
+ * By default, in a browser environment only DNS+WSS multiaddr is accepted,
96
+ * while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted.
97
+ */
98
+ filter(multiaddrs) {
99
+ multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs];
100
+ if (this._filter != null) {
101
+ return this._filter(multiaddrs);
102
+ }
103
+ // Browser
104
+ if (env.isBrowser || env.isWebWorker) {
105
+ return filters.dnsWss(multiaddrs);
106
+ }
107
+ return filters.all(multiaddrs);
108
+ }
109
+ }
110
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function createListener(): void;
2
+ //# sourceMappingURL=listener.browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.browser.d.ts","sourceRoot":"","sources":["../../src/listener.browser.ts"],"names":[],"mappings":"AACA,wBAAgB,cAAc,SAE7B"}
@@ -0,0 +1,4 @@
1
+ export function createListener() {
2
+ throw new Error('WebSocket Servers can not be created in the browser!');
3
+ }
4
+ //# sourceMappingURL=listener.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.browser.js","sourceRoot":"","sources":["../../src/listener.browser.ts"],"names":[],"mappings":"AACA,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAA;AACzE,CAAC"}
@@ -0,0 +1,8 @@
1
+ /// <reference types="node" />
2
+ import type { ListenerOptions, Upgrader, Listener } from '@libp2p/interfaces/transport';
3
+ import type { Server } from 'http';
4
+ export interface WebSocketListenerOptions extends ListenerOptions {
5
+ server?: Server;
6
+ }
7
+ export declare function createListener(upgrader: Upgrader, options?: WebSocketListenerOptions): Listener;
8
+ //# sourceMappingURL=listener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../src/listener.ts"],"names":[],"mappings":";AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAkB,MAAM,8BAA8B,CAAA;AACvG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAgJlC,MAAM,WAAW,wBAAyB,SAAQ,eAAe;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,cAAc,CAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,wBAAwB,GAAG,QAAQ,CAIhG"}
@@ -0,0 +1,121 @@
1
+ import os from 'os';
2
+ import { Multiaddr, protocols } from '@multiformats/multiaddr';
3
+ import { createServer } from 'it-ws/server';
4
+ import { logger } from '@libp2p/logger';
5
+ import { socketToMaConn } from './socket-to-conn.js';
6
+ import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr';
7
+ import { EventEmitter, CustomEvent } from '@libp2p/interfaces';
8
+ const log = logger('libp2p:websockets:listener');
9
+ class WebSocketListener extends EventEmitter {
10
+ constructor(upgrader, options) {
11
+ super();
12
+ // Keep track of open connections to destroy when the listener is closed
13
+ this.connections = new Set();
14
+ const self = this; // eslint-disable-line @typescript-eslint/no-this-alias
15
+ this.server = createServer({
16
+ ...options,
17
+ onConnection: (stream) => {
18
+ const maConn = socketToMaConn(stream, toMultiaddr(stream.remoteAddress ?? '', stream.remotePort ?? 0));
19
+ log('new inbound connection %s', maConn.remoteAddr);
20
+ this.connections.add(stream);
21
+ stream.socket.on('close', function () {
22
+ self.connections.delete(stream);
23
+ });
24
+ try {
25
+ void upgrader.upgradeInbound(maConn)
26
+ .then((conn) => {
27
+ log('inbound connection %s upgraded', maConn.remoteAddr);
28
+ if (options?.handler != null) {
29
+ options?.handler(conn);
30
+ }
31
+ self.dispatchEvent(new CustomEvent('connection', {
32
+ detail: conn
33
+ }));
34
+ })
35
+ .catch(async (err) => {
36
+ log.error('inbound connection failed to upgrade', err);
37
+ await maConn.close().catch(err => {
38
+ log.error('inbound connection failed to close after upgrade failed', err);
39
+ });
40
+ });
41
+ }
42
+ catch (err) {
43
+ log.error('inbound connection failed to upgrade', err);
44
+ maConn.close().catch(err => {
45
+ log.error('inbound connection failed to close after upgrade failed', err);
46
+ });
47
+ }
48
+ }
49
+ });
50
+ this.server.on('listening', () => {
51
+ this.dispatchEvent(new CustomEvent('listening'));
52
+ });
53
+ this.server.on('error', (err) => {
54
+ this.dispatchEvent(new CustomEvent('error', {
55
+ detail: err
56
+ }));
57
+ });
58
+ this.server.on('close', () => {
59
+ this.dispatchEvent(new CustomEvent('close'));
60
+ });
61
+ }
62
+ async close() {
63
+ await Promise.all(Array.from(this.connections).map(async (maConn) => await maConn.close()));
64
+ if (this.server.address() == null) {
65
+ // not listening, close will throw an error
66
+ return;
67
+ }
68
+ return await this.server.close();
69
+ }
70
+ async listen(ma) {
71
+ this.listeningMultiaddr = ma;
72
+ await this.server.listen(ma.toOptions());
73
+ }
74
+ getAddrs() {
75
+ const multiaddrs = [];
76
+ const address = this.server.address();
77
+ if (address == null) {
78
+ throw new Error('Listener is not ready yet');
79
+ }
80
+ if (typeof address === 'string') {
81
+ throw new Error('Wrong address type received - expected AddressInfo, got string - are you trying to listen on a unix socket?');
82
+ }
83
+ if (this.listeningMultiaddr == null) {
84
+ throw new Error('Listener is not ready yet');
85
+ }
86
+ const ipfsId = this.listeningMultiaddr.getPeerId();
87
+ const protos = this.listeningMultiaddr.protos();
88
+ // Because TCP will only return the IPv6 version
89
+ // we need to capture from the passed multiaddr
90
+ if (protos.some(proto => proto.code === protocols('ip4').code)) {
91
+ const wsProto = protos.some(proto => proto.code === protocols('ws').code) ? '/ws' : '/wss';
92
+ let m = this.listeningMultiaddr.decapsulate('tcp');
93
+ m = m.encapsulate(`/tcp/${address.port}${wsProto}`);
94
+ if (ipfsId != null) {
95
+ m = m.encapsulate(`/p2p/${ipfsId}`);
96
+ }
97
+ if (m.toString().includes('0.0.0.0')) {
98
+ const netInterfaces = os.networkInterfaces();
99
+ Object.values(netInterfaces).forEach(niInfos => {
100
+ if (niInfos == null) {
101
+ return;
102
+ }
103
+ niInfos.forEach(ni => {
104
+ if (ni.family === 'IPv4') {
105
+ multiaddrs.push(new Multiaddr(m.toString().replace('0.0.0.0', ni.address)));
106
+ }
107
+ });
108
+ });
109
+ }
110
+ else {
111
+ multiaddrs.push(m);
112
+ }
113
+ }
114
+ return multiaddrs;
115
+ }
116
+ }
117
+ export function createListener(upgrader, options) {
118
+ options = options ?? {};
119
+ return new WebSocketListener(upgrader, options);
120
+ }
121
+ //# sourceMappingURL=listener.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.js","sourceRoot":"","sources":["../../src/listener.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,iBAAiB,IAAI,WAAW,EAAE,MAAM,oCAAoC,CAAA;AAKrF,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAE9D,MAAM,GAAG,GAAG,MAAM,CAAC,4BAA4B,CAAC,CAAA;AAEhD,MAAM,iBAAkB,SAAQ,YAA4B;IAK1D,YAAa,QAAkB,EAAE,OAAiC;QAChE,KAAK,EAAE,CAAA;QAEP,wEAAwE;QACxE,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,EAAmB,CAAA;QAE7C,MAAM,IAAI,GAAG,IAAI,CAAA,CAAC,uDAAuD;QAEzE,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC;YACzB,GAAG,OAAO;YACV,YAAY,EAAE,CAAC,MAAuB,EAAE,EAAE;gBACxC,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,EAAE,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAA;gBACtG,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;gBAEnD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;gBAE5B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE;oBACxB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;gBACjC,CAAC,CAAC,CAAA;gBAEF,IAAI;oBACF,KAAK,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC;yBACjC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;wBACb,GAAG,CAAC,gCAAgC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;wBAExD,IAAI,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE;4BAC5B,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;yBACvB;wBAED,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,YAAY,EAAE;4BAC/C,MAAM,EAAE,IAAI;yBACb,CAAC,CAAC,CAAA;oBACL,CAAC,CAAC;yBACD,KAAK,CAAC,KAAK,EAAC,GAAG,EAAC,EAAE;wBACjB,GAAG,CAAC,KAAK,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAA;wBAEtD,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;4BAC/B,GAAG,CAAC,KAAK,CAAC,yDAAyD,EAAE,GAAG,CAAC,CAAA;wBAC3E,CAAC,CAAC,CAAA;oBACJ,CAAC,CAAC,CAAA;iBACL;gBAAC,OAAO,GAAG,EAAE;oBACZ,GAAG,CAAC,KAAK,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAA;oBACtD,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;wBACzB,GAAG,CAAC,KAAK,CAAC,yDAAyD,EAAE,GAAG,CAAC,CAAA;oBAC3E,CAAC,CAAC,CAAA;iBACH;YACH,CAAC;SACF,CAAC,CAAA;QAEF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;YAC/B,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC,CAAA;QAClD,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YACrC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE;gBAC1C,MAAM,EAAE,GAAG;aACZ,CAAC,CAAC,CAAA;QACL,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAC3B,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;QAC9C,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,KAAK,EAAC,MAAM,EAAC,EAAE,CAAC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,CACvE,CAAA;QAED,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE;YACjC,2CAA2C;YAC3C,OAAM;SACP;QAED,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;IAClC,CAAC;IAED,KAAK,CAAC,MAAM,CAAE,EAAa;QACzB,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAA;QAE5B,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,CAAA;IAC1C,CAAC;IAED,QAAQ;QACN,MAAM,UAAU,GAAG,EAAE,CAAA;QACrB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAA;QAErC,IAAI,OAAO,IAAI,IAAI,EAAE;YACnB,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;SAC7C;QAED,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE;YAC/B,MAAM,IAAI,KAAK,CAAC,6GAA6G,CAAC,CAAA;SAC/H;QAED,IAAI,IAAI,CAAC,kBAAkB,IAAI,IAAI,EAAE;YACnC,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;SAC7C;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,CAAA;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,CAAA;QAE/C,gDAAgD;QAChD,+CAA+C;QAC/C,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE;YAC9D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAA;YAC1F,IAAI,CAAC,GAAG,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;YAClD,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,QAAQ,OAAO,CAAC,IAAI,GAAG,OAAO,EAAE,CAAC,CAAA;YACnD,IAAI,MAAM,IAAI,IAAI,EAAE;gBAClB,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAA;aACpC;YAED,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;gBACpC,MAAM,aAAa,GAAG,EAAE,CAAC,iBAAiB,EAAE,CAAA;gBAC5C,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;oBAC7C,IAAI,OAAO,IAAI,IAAI,EAAE;wBACnB,OAAM;qBACP;oBAED,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;wBACnB,IAAI,EAAE,CAAC,MAAM,KAAK,MAAM,EAAE;4BACxB,UAAU,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;yBAC5E;oBACH,CAAC,CAAC,CAAA;gBACJ,CAAC,CAAC,CAAA;aACH;iBAAM;gBACL,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;aACnB;SACF;QAED,OAAO,UAAU,CAAA;IACnB,CAAC;CACF;AAMD,MAAM,UAAU,cAAc,CAAE,QAAkB,EAAE,OAAkC;IACpF,OAAO,GAAG,OAAO,IAAI,EAAE,CAAA;IAEvB,OAAO,IAAI,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;AACjD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { AbortOptions } from '@libp2p/interfaces';
2
+ import type { MultiaddrConnection } from '@libp2p/interfaces/transport';
3
+ import type { Multiaddr } from '@multiformats/multiaddr';
4
+ import type { DuplexWebSocket } from 'it-ws/duplex';
5
+ export interface SocketToConnOptions extends AbortOptions {
6
+ localAddr?: Multiaddr;
7
+ }
8
+ export declare function socketToMaConn(stream: DuplexWebSocket, remoteAddr: Multiaddr, options?: SocketToConnOptions): MultiaddrConnection;
9
+ //# sourceMappingURL=socket-to-conn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-to-conn.d.ts","sourceRoot":"","sources":["../../src/socket-to-conn.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAInD,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB;AAID,wBAAgB,cAAc,CAAE,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,CAmDlI"}
@@ -0,0 +1,52 @@
1
+ import { abortableSource } from 'abortable-iterator';
2
+ import { CLOSE_TIMEOUT } from './constants.js';
3
+ import pTimeout from 'p-timeout';
4
+ import { logger } from '@libp2p/logger';
5
+ const log = logger('libp2p:websockets:socket');
6
+ // Convert a stream into a MultiaddrConnection
7
+ // https://github.com/libp2p/interface-transport#multiaddrconnection
8
+ export function socketToMaConn(stream, remoteAddr, options) {
9
+ options = options ?? {};
10
+ const maConn = {
11
+ async sink(source) {
12
+ if ((options?.signal) != null) {
13
+ source = abortableSource(source, options.signal);
14
+ }
15
+ try {
16
+ await stream.sink(source);
17
+ }
18
+ catch (err) {
19
+ if (err.type !== 'aborted') {
20
+ log.error(err);
21
+ }
22
+ }
23
+ },
24
+ source: (options.signal != null) ? abortableSource(stream.source, options.signal) : stream.source,
25
+ remoteAddr,
26
+ timeline: { open: Date.now() },
27
+ async close() {
28
+ const start = Date.now();
29
+ try {
30
+ await pTimeout(stream.close(), CLOSE_TIMEOUT);
31
+ }
32
+ catch (err) {
33
+ const { host, port } = maConn.remoteAddr.toOptions();
34
+ log('timeout closing stream to %s:%s after %dms, destroying it manually', host, port, Date.now() - start);
35
+ stream.destroy();
36
+ }
37
+ finally {
38
+ maConn.timeline.close = Date.now();
39
+ }
40
+ }
41
+ };
42
+ stream.socket.once != null && stream.socket.once('close', () => {
43
+ // In instances where `close` was not explicitly called,
44
+ // such as an iterable stream ending, ensure we have set the close
45
+ // timeline
46
+ if (maConn.timeline.close == null) {
47
+ maConn.timeline.close = Date.now();
48
+ }
49
+ });
50
+ return maConn;
51
+ }
52
+ //# sourceMappingURL=socket-to-conn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-to-conn.js","sourceRoot":"","sources":["../../src/socket-to-conn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,QAAQ,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAMvC,MAAM,GAAG,GAAG,MAAM,CAAC,0BAA0B,CAAC,CAAA;AAM9C,8CAA8C;AAC9C,oEAAoE;AACpE,MAAM,UAAU,cAAc,CAAE,MAAuB,EAAE,UAAqB,EAAE,OAA6B;IAC3G,OAAO,GAAG,OAAO,IAAI,EAAE,CAAA;IAEvB,MAAM,MAAM,GAAwB;QAClC,KAAK,CAAC,IAAI,CAAE,MAAM;YAChB,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE;gBAC7B,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;aACjD;YAED,IAAI;gBACF,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;aAC1B;YAAC,OAAO,GAAQ,EAAE;gBACjB,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE;oBAC1B,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;iBACf;aACF;QACH,CAAC;QAED,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM;QAEjG,UAAU;QAEV,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;QAE9B,KAAK,CAAC,KAAK;YACT,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YAExB,IAAI;gBACF,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,aAAa,CAAC,CAAA;aAC9C;YAAC,OAAO,GAAG,EAAE;gBACZ,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAA;gBACpD,GAAG,CAAC,oEAAoE,EACtE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAA;gBAEjC,MAAM,CAAC,OAAO,EAAE,CAAA;aACjB;oBAAS;gBACR,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;aACnC;QACH,CAAC;KACF,CAAA;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;QAC7D,wDAAwD;QACxD,kEAAkE;QAClE,WAAW;QACX,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE;YACjC,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;SACnC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC"}
package/package.json ADDED
@@ -0,0 +1,166 @@
1
+ {
2
+ "name": "@libp2p/websockets",
3
+ "version": "0.16.2",
4
+ "description": "JavaScript implementation of the WebSockets 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-websockets#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/libp2p/js-libp2p-websockets.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/libp2p/js-libp2p-websockets/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
+ "files": [
24
+ "src",
25
+ "dist/src",
26
+ "!dist/test",
27
+ "!**/*.tsbuildinfo"
28
+ ],
29
+ "exports": {
30
+ ".": {
31
+ "import": "./dist/src/index.js"
32
+ }
33
+ },
34
+ "eslintConfig": {
35
+ "extends": "ipfs",
36
+ "parserOptions": {
37
+ "sourceType": "module"
38
+ }
39
+ },
40
+ "release": {
41
+ "branches": [
42
+ "master"
43
+ ],
44
+ "plugins": [
45
+ [
46
+ "@semantic-release/commit-analyzer",
47
+ {
48
+ "preset": "conventionalcommits",
49
+ "releaseRules": [
50
+ {
51
+ "breaking": true,
52
+ "release": "major"
53
+ },
54
+ {
55
+ "revert": true,
56
+ "release": "patch"
57
+ },
58
+ {
59
+ "type": "feat",
60
+ "release": "minor"
61
+ },
62
+ {
63
+ "type": "fix",
64
+ "release": "patch"
65
+ },
66
+ {
67
+ "type": "chore",
68
+ "release": "patch"
69
+ },
70
+ {
71
+ "type": "docs",
72
+ "release": "patch"
73
+ },
74
+ {
75
+ "type": "test",
76
+ "release": "patch"
77
+ },
78
+ {
79
+ "scope": "no-release",
80
+ "release": false
81
+ }
82
+ ]
83
+ }
84
+ ],
85
+ [
86
+ "@semantic-release/release-notes-generator",
87
+ {
88
+ "preset": "conventionalcommits",
89
+ "presetConfig": {
90
+ "types": [
91
+ {
92
+ "type": "feat",
93
+ "section": "Features"
94
+ },
95
+ {
96
+ "type": "fix",
97
+ "section": "Bug Fixes"
98
+ },
99
+ {
100
+ "type": "chore",
101
+ "section": "Trivial Changes"
102
+ },
103
+ {
104
+ "type": "docs",
105
+ "section": "Trivial Changes"
106
+ },
107
+ {
108
+ "type": "test",
109
+ "section": "Tests"
110
+ }
111
+ ]
112
+ }
113
+ }
114
+ ],
115
+ "@semantic-release/changelog",
116
+ "@semantic-release/npm",
117
+ "@semantic-release/github",
118
+ "@semantic-release/git"
119
+ ]
120
+ },
121
+ "scripts": {
122
+ "lint": "aegir lint",
123
+ "dep-check": "aegir dep-check dist/src/**/*.js dist/test/**/*.js",
124
+ "build": "tsc",
125
+ "pretest": "npm run build",
126
+ "test": "aegir test",
127
+ "test:chrome": "npm run test -- -t browser -f ./dist/test/browser.js --cov",
128
+ "test:chrome-webworker": "npm run test -- -t webworker -f ./dist/test/browser.js",
129
+ "test:firefox": "npm run test -- -t browser -f ./dist/test/browser.js -- --browser firefox",
130
+ "test:firefox-webworker": "npm run test -- -t webworker -f ./dist/test/browser.js -- --browser firefox",
131
+ "test:node": "npm run test -- -t node -f ./dist/test/node.js --cov",
132
+ "test:electron-main": "npm run test -- -t electron-main -f ./dist/test/node.js --cov",
133
+ "release": "semantic-release"
134
+ },
135
+ "dependencies": {
136
+ "@libp2p/logger": "^1.0.2",
137
+ "@libp2p/utils": "^1.0.0",
138
+ "@multiformats/mafmt": "^11.0.1",
139
+ "@multiformats/multiaddr": "^10.0.0",
140
+ "@multiformats/multiaddr-to-uri": "^9.0.0",
141
+ "abortable-iterator": "^4.0.2",
142
+ "err-code": "^3.0.1",
143
+ "it-ws": "^5.0.0",
144
+ "p-defer": "^4.0.0",
145
+ "p-timeout": "^5.0.2",
146
+ "wherearewe": "^1.0.0"
147
+ },
148
+ "devDependencies": {
149
+ "@libp2p/interface-compliance-tests": "^1.1.2",
150
+ "@libp2p/interfaces": "^1.3.2",
151
+ "@types/ws": "^8.2.2",
152
+ "aegir": "^36.1.3",
153
+ "is-loopback-addr": "^2.0.1",
154
+ "it-all": "^1.0.6",
155
+ "it-drain": "^1.0.5",
156
+ "it-goodbye": "^4.0.1",
157
+ "it-pipe": "^2.0.2",
158
+ "it-take": "^1.0.2",
159
+ "p-wait-for": "^4.1.0",
160
+ "uint8arrays": "^3.0.0",
161
+ "util": "^0.12.3"
162
+ },
163
+ "browser": {
164
+ "./dist/src/listener.js": "./dist/src/listener.browser.js"
165
+ }
166
+ }
@@ -0,0 +1,10 @@
1
+ // p2p multi-address code
2
+ export const CODE_P2P = 421
3
+ export const CODE_CIRCUIT = 290
4
+
5
+ export const CODE_TCP = 6
6
+ export const CODE_WS = 477
7
+ export const CODE_WSS = 478
8
+
9
+ // Time to wait for a connection to close gracefully before destroying it manually
10
+ export const CLOSE_TIMEOUT = 2000
package/src/filters.ts ADDED
@@ -0,0 +1,54 @@
1
+ import * as mafmt from '@multiformats/mafmt'
2
+ import type { Multiaddr } from '@multiformats/multiaddr'
3
+ import {
4
+ CODE_CIRCUIT,
5
+ CODE_P2P,
6
+ CODE_TCP,
7
+ CODE_WS,
8
+ CODE_WSS
9
+ } from './constants.js'
10
+
11
+ export function all (multiaddrs: Multiaddr[]) {
12
+ return multiaddrs.filter((ma) => {
13
+ if (ma.protoCodes().includes(CODE_CIRCUIT)) {
14
+ return false
15
+ }
16
+
17
+ const testMa = ma.decapsulateCode(CODE_P2P)
18
+
19
+ return mafmt.WebSockets.matches(testMa) ||
20
+ mafmt.WebSocketsSecure.matches(testMa)
21
+ })
22
+ }
23
+
24
+ export function dnsWss (multiaddrs: Multiaddr[]) {
25
+ return multiaddrs.filter((ma) => {
26
+ if (ma.protoCodes().includes(CODE_CIRCUIT)) {
27
+ return false
28
+ }
29
+
30
+ const testMa = ma.decapsulateCode(CODE_P2P)
31
+
32
+ return mafmt.WebSocketsSecure.matches(testMa) &&
33
+ mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WSS))
34
+ })
35
+ }
36
+
37
+ export function dnsWsOrWss (multiaddrs: Multiaddr[]) {
38
+ return multiaddrs.filter((ma) => {
39
+ if (ma.protoCodes().includes(CODE_CIRCUIT)) {
40
+ return false
41
+ }
42
+
43
+ const testMa = ma.decapsulateCode(CODE_P2P)
44
+
45
+ // WS
46
+ if (mafmt.WebSockets.matches(testMa)) {
47
+ return mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WS))
48
+ }
49
+
50
+ // WSS
51
+ return mafmt.WebSocketsSecure.matches(testMa) &&
52
+ mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WSS))
53
+ })
54
+ }
package/src/index.ts ADDED
@@ -0,0 +1,139 @@
1
+ import { connect, WebSocketOptions } from 'it-ws/client'
2
+ import { multiaddrToUri as toUri } from '@multiformats/multiaddr-to-uri'
3
+ import { AbortError } from '@libp2p/interfaces/errors'
4
+ import pDefer from 'p-defer'
5
+ import { logger } from '@libp2p/logger'
6
+ import env from 'wherearewe'
7
+ import { createListener } from './listener.js'
8
+ import { socketToMaConn } from './socket-to-conn.js'
9
+ import * as filters from './filters.js'
10
+ import type { Transport, Upgrader, MultiaddrFilter } from '@libp2p/interfaces/transport'
11
+ import type { AbortOptions } from '@libp2p/interfaces'
12
+ import type { WebSocketListenerOptions } from './listener.js'
13
+ import type { Multiaddr } from '@multiformats/multiaddr'
14
+ import type { DuplexWebSocket } from 'it-ws/dist/src/duplex'
15
+
16
+ const log = logger('libp2p:websockets')
17
+
18
+ /**
19
+ * @class WebSockets
20
+ */
21
+ export class WebSockets implements Transport<AbortOptions & WebSocketOptions, WebSocketListenerOptions> {
22
+ private readonly upgrader: Upgrader
23
+ private readonly _filter?: MultiaddrFilter
24
+
25
+ constructor (opts: { upgrader: Upgrader, filter?: MultiaddrFilter }) {
26
+ const { upgrader, filter } = opts
27
+
28
+ if (upgrader == null) {
29
+ throw new Error('An upgrader must be provided. See https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport#upgrader')
30
+ }
31
+
32
+ this.upgrader = upgrader
33
+ this._filter = filter
34
+ }
35
+
36
+ async dial (ma: Multiaddr, options?: AbortOptions & WebSocketOptions) {
37
+ log('dialing %s', ma)
38
+ options = options ?? {}
39
+
40
+ const socket = await this._connect(ma, options)
41
+ const maConn = socketToMaConn(socket, ma)
42
+ log('new outbound connection %s', maConn.remoteAddr)
43
+
44
+ const conn = await this.upgrader.upgradeOutbound(maConn)
45
+ log('outbound connection %s upgraded', maConn.remoteAddr)
46
+ return conn
47
+ }
48
+
49
+ async _connect (ma: Multiaddr, options: AbortOptions & WebSocketOptions): Promise<DuplexWebSocket> {
50
+ if (options?.signal?.aborted === true) {
51
+ throw new AbortError()
52
+ }
53
+ const cOpts = ma.toOptions()
54
+ log('dialing %s:%s', cOpts.host, cOpts.port)
55
+
56
+ const errorPromise = pDefer()
57
+ const errfn = (err: any) => {
58
+ log.error('connection error:', err)
59
+
60
+ errorPromise.reject(err)
61
+ }
62
+
63
+ const rawSocket = connect(toUri(ma), options)
64
+
65
+ if (rawSocket.socket.on != null) {
66
+ rawSocket.socket.on('error', errfn)
67
+ } else {
68
+ rawSocket.socket.onerror = errfn
69
+ }
70
+
71
+ if (options.signal == null) {
72
+ await Promise.race([rawSocket.connected(), errorPromise.promise])
73
+
74
+ log('connected %s', ma)
75
+ return rawSocket
76
+ }
77
+
78
+ // Allow abort via signal during connect
79
+ let onAbort
80
+ const abort = new Promise((resolve, reject) => {
81
+ onAbort = () => {
82
+ reject(new AbortError())
83
+ // FIXME: https://github.com/libp2p/js-libp2p-websockets/issues/121
84
+ setTimeout(() => {
85
+ rawSocket.close().catch(err => {
86
+ log.error('error closing raw socket', err)
87
+ })
88
+ })
89
+ }
90
+
91
+ // Already aborted?
92
+ if (options?.signal?.aborted === true) {
93
+ return onAbort()
94
+ }
95
+
96
+ options?.signal?.addEventListener('abort', onAbort)
97
+ })
98
+
99
+ try {
100
+ await Promise.race([abort, errorPromise.promise, rawSocket.connected()])
101
+ } finally {
102
+ if (onAbort != null) {
103
+ options?.signal?.removeEventListener('abort', onAbort)
104
+ }
105
+ }
106
+
107
+ log('connected %s', ma)
108
+ return rawSocket
109
+ }
110
+
111
+ /**
112
+ * Creates a Websockets listener. The provided `handler` function will be called
113
+ * anytime a new incoming Connection has been successfully upgraded via
114
+ * `upgrader.upgradeInbound`
115
+ */
116
+ createListener (options?: WebSocketListenerOptions) {
117
+ return createListener(this.upgrader, options)
118
+ }
119
+
120
+ /**
121
+ * Takes a list of `Multiaddr`s and returns only valid Websockets addresses.
122
+ * By default, in a browser environment only DNS+WSS multiaddr is accepted,
123
+ * while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted.
124
+ */
125
+ filter (multiaddrs: Multiaddr[]) {
126
+ multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
127
+
128
+ if (this._filter != null) {
129
+ return this._filter(multiaddrs)
130
+ }
131
+
132
+ // Browser
133
+ if (env.isBrowser || env.isWebWorker) {
134
+ return filters.dnsWss(multiaddrs)
135
+ }
136
+
137
+ return filters.all(multiaddrs)
138
+ }
139
+ }
@@ -0,0 +1,4 @@
1
+
2
+ export function createListener () {
3
+ throw new Error('WebSocket Servers can not be created in the browser!')
4
+ }
@@ -0,0 +1,160 @@
1
+ import os from 'os'
2
+ import { Multiaddr, protocols } from '@multiformats/multiaddr'
3
+ import { createServer } from 'it-ws/server'
4
+ import { logger } from '@libp2p/logger'
5
+ import { socketToMaConn } from './socket-to-conn.js'
6
+ import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr'
7
+ import type { ListenerOptions, Upgrader, Listener, ListenerEvents } from '@libp2p/interfaces/transport'
8
+ import type { Server } from 'http'
9
+ import type { WebSocketServer } from 'it-ws/server'
10
+ import type { DuplexWebSocket } from 'it-ws/duplex'
11
+ import { EventEmitter, CustomEvent } from '@libp2p/interfaces'
12
+
13
+ const log = logger('libp2p:websockets:listener')
14
+
15
+ class WebSocketListener extends EventEmitter<ListenerEvents> implements Listener {
16
+ private readonly connections: Set<DuplexWebSocket>
17
+ private listeningMultiaddr?: Multiaddr
18
+ private readonly server: WebSocketServer
19
+
20
+ constructor (upgrader: Upgrader, options: WebSocketListenerOptions) {
21
+ super()
22
+
23
+ // Keep track of open connections to destroy when the listener is closed
24
+ this.connections = new Set<DuplexWebSocket>()
25
+
26
+ const self = this // eslint-disable-line @typescript-eslint/no-this-alias
27
+
28
+ this.server = createServer({
29
+ ...options,
30
+ onConnection: (stream: DuplexWebSocket) => {
31
+ const maConn = socketToMaConn(stream, toMultiaddr(stream.remoteAddress ?? '', stream.remotePort ?? 0))
32
+ log('new inbound connection %s', maConn.remoteAddr)
33
+
34
+ this.connections.add(stream)
35
+
36
+ stream.socket.on('close', function () {
37
+ self.connections.delete(stream)
38
+ })
39
+
40
+ try {
41
+ void upgrader.upgradeInbound(maConn)
42
+ .then((conn) => {
43
+ log('inbound connection %s upgraded', maConn.remoteAddr)
44
+
45
+ if (options?.handler != null) {
46
+ options?.handler(conn)
47
+ }
48
+
49
+ self.dispatchEvent(new CustomEvent('connection', {
50
+ detail: conn
51
+ }))
52
+ })
53
+ .catch(async err => {
54
+ log.error('inbound connection failed to upgrade', err)
55
+
56
+ await maConn.close().catch(err => {
57
+ log.error('inbound connection failed to close after upgrade failed', err)
58
+ })
59
+ })
60
+ } catch (err) {
61
+ log.error('inbound connection failed to upgrade', err)
62
+ maConn.close().catch(err => {
63
+ log.error('inbound connection failed to close after upgrade failed', err)
64
+ })
65
+ }
66
+ }
67
+ })
68
+
69
+ this.server.on('listening', () => {
70
+ this.dispatchEvent(new CustomEvent('listening'))
71
+ })
72
+ this.server.on('error', (err: Error) => {
73
+ this.dispatchEvent(new CustomEvent('error', {
74
+ detail: err
75
+ }))
76
+ })
77
+ this.server.on('close', () => {
78
+ this.dispatchEvent(new CustomEvent('close'))
79
+ })
80
+ }
81
+
82
+ async close () {
83
+ await Promise.all(
84
+ Array.from(this.connections).map(async maConn => await maConn.close())
85
+ )
86
+
87
+ if (this.server.address() == null) {
88
+ // not listening, close will throw an error
89
+ return
90
+ }
91
+
92
+ return await this.server.close()
93
+ }
94
+
95
+ async listen (ma: Multiaddr) {
96
+ this.listeningMultiaddr = ma
97
+
98
+ await this.server.listen(ma.toOptions())
99
+ }
100
+
101
+ getAddrs () {
102
+ const multiaddrs = []
103
+ const address = this.server.address()
104
+
105
+ if (address == null) {
106
+ throw new Error('Listener is not ready yet')
107
+ }
108
+
109
+ if (typeof address === 'string') {
110
+ throw new Error('Wrong address type received - expected AddressInfo, got string - are you trying to listen on a unix socket?')
111
+ }
112
+
113
+ if (this.listeningMultiaddr == null) {
114
+ throw new Error('Listener is not ready yet')
115
+ }
116
+
117
+ const ipfsId = this.listeningMultiaddr.getPeerId()
118
+ const protos = this.listeningMultiaddr.protos()
119
+
120
+ // Because TCP will only return the IPv6 version
121
+ // we need to capture from the passed multiaddr
122
+ if (protos.some(proto => proto.code === protocols('ip4').code)) {
123
+ const wsProto = protos.some(proto => proto.code === protocols('ws').code) ? '/ws' : '/wss'
124
+ let m = this.listeningMultiaddr.decapsulate('tcp')
125
+ m = m.encapsulate(`/tcp/${address.port}${wsProto}`)
126
+ if (ipfsId != null) {
127
+ m = m.encapsulate(`/p2p/${ipfsId}`)
128
+ }
129
+
130
+ if (m.toString().includes('0.0.0.0')) {
131
+ const netInterfaces = os.networkInterfaces()
132
+ Object.values(netInterfaces).forEach(niInfos => {
133
+ if (niInfos == null) {
134
+ return
135
+ }
136
+
137
+ niInfos.forEach(ni => {
138
+ if (ni.family === 'IPv4') {
139
+ multiaddrs.push(new Multiaddr(m.toString().replace('0.0.0.0', ni.address)))
140
+ }
141
+ })
142
+ })
143
+ } else {
144
+ multiaddrs.push(m)
145
+ }
146
+ }
147
+
148
+ return multiaddrs
149
+ }
150
+ }
151
+
152
+ export interface WebSocketListenerOptions extends ListenerOptions {
153
+ server?: Server
154
+ }
155
+
156
+ export function createListener (upgrader: Upgrader, options?: WebSocketListenerOptions): Listener {
157
+ options = options ?? {}
158
+
159
+ return new WebSocketListener(upgrader, options)
160
+ }
@@ -0,0 +1,69 @@
1
+ import { abortableSource } from 'abortable-iterator'
2
+ import { CLOSE_TIMEOUT } from './constants.js'
3
+ import pTimeout from 'p-timeout'
4
+ import { logger } from '@libp2p/logger'
5
+ import type { AbortOptions } from '@libp2p/interfaces'
6
+ import type { MultiaddrConnection } from '@libp2p/interfaces/transport'
7
+ import type { Multiaddr } from '@multiformats/multiaddr'
8
+ import type { DuplexWebSocket } from 'it-ws/duplex'
9
+
10
+ const log = logger('libp2p:websockets:socket')
11
+
12
+ export interface SocketToConnOptions extends AbortOptions {
13
+ localAddr?: Multiaddr
14
+ }
15
+
16
+ // Convert a stream into a MultiaddrConnection
17
+ // https://github.com/libp2p/interface-transport#multiaddrconnection
18
+ export function socketToMaConn (stream: DuplexWebSocket, remoteAddr: Multiaddr, options?: SocketToConnOptions): MultiaddrConnection {
19
+ options = options ?? {}
20
+
21
+ const maConn: MultiaddrConnection = {
22
+ async sink (source) {
23
+ if ((options?.signal) != null) {
24
+ source = abortableSource(source, options.signal)
25
+ }
26
+
27
+ try {
28
+ await stream.sink(source)
29
+ } catch (err: any) {
30
+ if (err.type !== 'aborted') {
31
+ log.error(err)
32
+ }
33
+ }
34
+ },
35
+
36
+ source: (options.signal != null) ? abortableSource(stream.source, options.signal) : stream.source,
37
+
38
+ remoteAddr,
39
+
40
+ timeline: { open: Date.now() },
41
+
42
+ async close () {
43
+ const start = Date.now()
44
+
45
+ try {
46
+ await pTimeout(stream.close(), CLOSE_TIMEOUT)
47
+ } catch (err) {
48
+ const { host, port } = maConn.remoteAddr.toOptions()
49
+ log('timeout closing stream to %s:%s after %dms, destroying it manually',
50
+ host, port, Date.now() - start)
51
+
52
+ stream.destroy()
53
+ } finally {
54
+ maConn.timeline.close = Date.now()
55
+ }
56
+ }
57
+ }
58
+
59
+ stream.socket.once != null && stream.socket.once('close', () => { // eslint-disable-line @typescript-eslint/prefer-optional-chain
60
+ // In instances where `close` was not explicitly called,
61
+ // such as an iterable stream ending, ensure we have set the close
62
+ // timeline
63
+ if (maConn.timeline.close == null) {
64
+ maConn.timeline.close = Date.now()
65
+ }
66
+ })
67
+
68
+ return maConn
69
+ }