@libp2p/websockets 9.2.19-6059227cb → 9.2.19-87bc8d4fb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +4 -4
- package/dist/src/filters.d.ts +18 -0
- package/dist/src/filters.d.ts.map +1 -0
- package/dist/src/filters.js +34 -0
- package/dist/src/filters.js.map +1 -0
- package/dist/src/index.d.ts +13 -15
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +41 -26
- package/dist/src/index.js.map +1 -1
- package/dist/src/listener.d.ts +1 -5
- package/dist/src/listener.d.ts.map +1 -1
- package/dist/src/listener.js +32 -32
- package/dist/src/listener.js.map +1 -1
- package/dist/src/socket-to-conn.d.ts +11 -0
- package/dist/src/socket-to-conn.d.ts.map +1 -0
- package/dist/src/socket-to-conn.js +88 -0
- package/dist/src/socket-to-conn.js.map +1 -0
- package/package.json +13 -14
- package/src/filters.ts +38 -0
- package/src/index.ts +61 -41
- package/src/listener.ts +34 -37
- package/src/socket-to-conn.ts +114 -0
- package/dist/src/utils.d.ts +0 -7
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/utils.js +0 -30
- package/dist/src/utils.js.map +0 -1
- package/dist/src/websocket-to-conn.d.ts +0 -9
- package/dist/src/websocket-to-conn.d.ts.map +0 -1
- package/dist/src/websocket-to-conn.js +0 -83
- package/dist/src/websocket-to-conn.js.map +0 -1
- package/src/utils.ts +0 -36
- package/src/websocket-to-conn.ts +0 -108
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libp2p/websockets",
|
|
3
|
-
"version": "9.2.19-
|
|
3
|
+
"version": "9.2.19-87bc8d4fb",
|
|
4
4
|
"description": "JavaScript implementation of the WebSockets module that libp2p uses and that implements the interface-transport spec",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/transport-websockets#readme",
|
|
@@ -67,28 +67,27 @@
|
|
|
67
67
|
"test:electron-main": "aegir test -t electron-main -f ./dist/test/node.js --cov"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@libp2p/interface": "2.11.0-
|
|
71
|
-
"@libp2p/utils": "6.7.2-
|
|
72
|
-
"@multiformats/multiaddr": "^
|
|
73
|
-
"@multiformats/multiaddr-matcher": "^
|
|
74
|
-
"@multiformats/multiaddr-to-uri": "^
|
|
70
|
+
"@libp2p/interface": "2.11.0-87bc8d4fb",
|
|
71
|
+
"@libp2p/utils": "6.7.2-87bc8d4fb",
|
|
72
|
+
"@multiformats/multiaddr": "^12.4.4",
|
|
73
|
+
"@multiformats/multiaddr-matcher": "^2.0.0",
|
|
74
|
+
"@multiformats/multiaddr-to-uri": "^11.0.0",
|
|
75
75
|
"@types/ws": "^8.18.1",
|
|
76
76
|
"it-ws": "^6.1.5",
|
|
77
77
|
"main-event": "^1.0.1",
|
|
78
|
+
"p-defer": "^4.0.1",
|
|
78
79
|
"p-event": "^6.0.1",
|
|
79
80
|
"progress-events": "^1.0.1",
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"ws": "^8.18.3"
|
|
81
|
+
"race-signal": "^1.1.3",
|
|
82
|
+
"ws": "^8.18.2"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
|
-
"@libp2p/logger": "5.2.0-
|
|
86
|
-
"aegir": "^47.0.
|
|
85
|
+
"@libp2p/logger": "5.2.0-87bc8d4fb",
|
|
86
|
+
"aegir": "^47.0.14",
|
|
87
87
|
"is-loopback-addr": "^2.0.2",
|
|
88
88
|
"p-wait-for": "^5.0.2",
|
|
89
|
-
"sinon": "^
|
|
90
|
-
"sinon-ts": "^2.0.0"
|
|
91
|
-
"undici": "^7.15.0"
|
|
89
|
+
"sinon": "^20.0.0",
|
|
90
|
+
"sinon-ts": "^2.0.0"
|
|
92
91
|
},
|
|
93
92
|
"browser": {
|
|
94
93
|
"./dist/src/listener.js": "./dist/src/listener.browser.js"
|
package/src/filters.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { WebSocketsSecure, WebSockets, DNS } from '@multiformats/multiaddr-matcher'
|
|
2
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Configure this globally by passing a `connectionGater` to `createLibp2p` with a `denyDialMultiaddr` method that returns `false`
|
|
6
|
+
*/
|
|
7
|
+
export function all (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
8
|
+
return multiaddrs.filter((ma) => {
|
|
9
|
+
return WebSocketsSecure.exactMatch(ma) || WebSockets.exactMatch(ma)
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Configure this globally by passing a `connectionGater` to `createLibp2p`
|
|
15
|
+
*/
|
|
16
|
+
export function wss (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
17
|
+
return multiaddrs.filter((ma) => {
|
|
18
|
+
return WebSocketsSecure.exactMatch(ma)
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Configure this globally by passing a `connectionGater` to `createLibp2p`
|
|
24
|
+
*/
|
|
25
|
+
export function dnsWss (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
26
|
+
return multiaddrs.filter((ma) => {
|
|
27
|
+
return DNS.matches(ma) && WebSocketsSecure.exactMatch(ma)
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated Configure this globally by passing a `connectionGater` to `createLibp2p`
|
|
33
|
+
*/
|
|
34
|
+
export function dnsWsOrWss (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
35
|
+
return multiaddrs.filter((ma) => {
|
|
36
|
+
return DNS.matches(ma) && (WebSocketsSecure.exactMatch(ma) || WebSockets.exactMatch(ma))
|
|
37
|
+
})
|
|
38
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -24,21 +24,35 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { transportSymbol, serviceCapabilities, ConnectionFailedError } from '@libp2p/interface'
|
|
27
|
-
import { WebSockets as WebSocketsMatcher, WebSocketsSecure } from '@multiformats/multiaddr-matcher'
|
|
28
27
|
import { multiaddrToUri as toUri } from '@multiformats/multiaddr-to-uri'
|
|
29
|
-
import {
|
|
28
|
+
import { connect } from 'it-ws/client'
|
|
29
|
+
import pDefer from 'p-defer'
|
|
30
30
|
import { CustomProgressEvent } from 'progress-events'
|
|
31
|
+
import { raceSignal } from 'race-signal'
|
|
32
|
+
import * as filters from './filters.js'
|
|
31
33
|
import { createListener } from './listener.js'
|
|
32
|
-
import {
|
|
33
|
-
import type { Transport, CreateListenerOptions, DialTransportOptions, Listener, AbortOptions, ComponentLogger, Logger, Connection, OutboundConnectionUpgradeEvents, Metrics, CounterGroup, Libp2pEvents } from '@libp2p/interface'
|
|
34
|
+
import { socketToMaConn } from './socket-to-conn.js'
|
|
35
|
+
import type { Transport, MultiaddrFilter, CreateListenerOptions, DialTransportOptions, Listener, AbortOptions, ComponentLogger, Logger, Connection, OutboundConnectionUpgradeEvents, Metrics, CounterGroup, Libp2pEvents } from '@libp2p/interface'
|
|
34
36
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
35
37
|
import type { WebSocketOptions } from 'it-ws/client'
|
|
38
|
+
import type { DuplexWebSocket } from 'it-ws/duplex'
|
|
36
39
|
import type { TypedEventTarget } from 'main-event'
|
|
37
40
|
import type http from 'node:http'
|
|
38
41
|
import type https from 'node:https'
|
|
39
42
|
import type { ProgressEvent } from 'progress-events'
|
|
43
|
+
import type { ClientOptions } from 'ws'
|
|
40
44
|
|
|
41
45
|
export interface WebSocketsInit extends AbortOptions, WebSocketOptions {
|
|
46
|
+
/**
|
|
47
|
+
* @deprecated Use a ConnectionGater instead
|
|
48
|
+
*/
|
|
49
|
+
filter?: MultiaddrFilter
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Options used to create WebSockets
|
|
53
|
+
*/
|
|
54
|
+
websocket?: ClientOptions
|
|
55
|
+
|
|
42
56
|
/**
|
|
43
57
|
* Options used to create the HTTP server
|
|
44
58
|
*/
|
|
@@ -51,23 +65,11 @@ export interface WebSocketsInit extends AbortOptions, WebSocketOptions {
|
|
|
51
65
|
https?: https.ServerOptions
|
|
52
66
|
|
|
53
67
|
/**
|
|
54
|
-
*
|
|
55
|
-
* property of incoming and outgoing websockets is allowed to get in bytes.
|
|
56
|
-
*
|
|
57
|
-
* If this limit is exceeded, backpressure will be applied to the writer.
|
|
58
|
-
*
|
|
59
|
-
* @default 4_194_304
|
|
60
|
-
*/
|
|
61
|
-
maxBufferedAmount?: number
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* If the [bufferedAmount](https://websockets.spec.whatwg.org/#dom-websocket-bufferedamount)
|
|
65
|
-
* property of a WebSocket exceeds `maxBufferedAmount`, poll the field every
|
|
66
|
-
* this number of ms to see if the socket can accept new data.
|
|
68
|
+
* Inbound connections must complete their upgrade within this many ms
|
|
67
69
|
*
|
|
68
|
-
* @
|
|
70
|
+
* @deprecated Use the `connectionManager.inboundUpgradeTimeout` libp2p config key instead
|
|
69
71
|
*/
|
|
70
|
-
|
|
72
|
+
inboundConnectionUpgradeTimeout?: number
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
export interface WebSocketsComponents {
|
|
@@ -119,14 +121,10 @@ class WebSockets implements Transport<WebSocketsDialEvents> {
|
|
|
119
121
|
this.log('dialing %s', ma)
|
|
120
122
|
options = options ?? {}
|
|
121
123
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
metrics: this.metrics?.dialerEvents
|
|
126
|
-
direction: 'outbound',
|
|
127
|
-
log: this.components.logger.forComponent('libp2p:websockets:connection'),
|
|
128
|
-
maxBufferedAmount: this.init.maxBufferedAmount,
|
|
129
|
-
bufferedAmountPollInterval: this.init.bufferedAmountPollInterval
|
|
124
|
+
const socket = await this._connect(ma, options)
|
|
125
|
+
const maConn = socketToMaConn(socket, ma, {
|
|
126
|
+
logger: this.logger,
|
|
127
|
+
metrics: this.metrics?.dialerEvents
|
|
130
128
|
})
|
|
131
129
|
this.log('new outbound connection %s', maConn.remoteAddr)
|
|
132
130
|
|
|
@@ -135,35 +133,43 @@ class WebSockets implements Transport<WebSocketsDialEvents> {
|
|
|
135
133
|
return conn
|
|
136
134
|
}
|
|
137
135
|
|
|
138
|
-
async _connect (ma: Multiaddr, options: DialTransportOptions<WebSocketsDialEvents>): Promise<
|
|
136
|
+
async _connect (ma: Multiaddr, options: DialTransportOptions<WebSocketsDialEvents>): Promise<DuplexWebSocket> {
|
|
139
137
|
options?.signal?.throwIfAborted()
|
|
140
138
|
|
|
141
|
-
const
|
|
142
|
-
this.log('
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
const cOpts = ma.toOptions()
|
|
140
|
+
this.log('dialing %s:%s', cOpts.host, cOpts.port)
|
|
141
|
+
|
|
142
|
+
const errorPromise = pDefer()
|
|
143
|
+
const rawSocket = connect(toUri(ma), this.init)
|
|
144
|
+
rawSocket.socket.addEventListener('error', () => {
|
|
145
|
+
// the WebSocket.ErrorEvent type doesn't actually give us any useful
|
|
146
|
+
// information about what happened
|
|
147
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/error_event
|
|
148
|
+
const err = new ConnectionFailedError(`Could not connect to ${ma.toString()}`)
|
|
149
|
+
this.log.error('connection error:', err)
|
|
150
|
+
this.metrics?.dialerEvents.increment({ error: true })
|
|
151
|
+
errorPromise.reject(err)
|
|
152
|
+
})
|
|
145
153
|
|
|
146
154
|
try {
|
|
147
155
|
options.onProgress?.(new CustomProgressEvent('websockets:open-connection'))
|
|
148
|
-
await
|
|
156
|
+
await raceSignal(Promise.race([rawSocket.connected(), errorPromise.promise]), options.signal)
|
|
149
157
|
} catch (err: any) {
|
|
150
158
|
if (options.signal?.aborted) {
|
|
151
159
|
this.metrics?.dialerEvents.increment({ abort: true })
|
|
152
|
-
throw new ConnectionFailedError(`Could not connect to ${uri}`)
|
|
153
|
-
} else {
|
|
154
|
-
this.metrics?.dialerEvents.increment({ error: true })
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
162
|
+
rawSocket.close()
|
|
163
|
+
.catch(err => {
|
|
164
|
+
this.log.error('error closing raw socket', err)
|
|
165
|
+
})
|
|
160
166
|
|
|
161
167
|
throw err
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
this.log('connected %s', ma)
|
|
165
171
|
this.metrics?.dialerEvents.increment({ connect: true })
|
|
166
|
-
return
|
|
172
|
+
return rawSocket
|
|
167
173
|
}
|
|
168
174
|
|
|
169
175
|
/**
|
|
@@ -182,10 +188,24 @@ class WebSockets implements Transport<WebSocketsDialEvents> {
|
|
|
182
188
|
})
|
|
183
189
|
}
|
|
184
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Takes a list of `Multiaddr`s and returns only valid WebSockets addresses.
|
|
193
|
+
* By default, in a browser environment only DNS+WSS multiaddr is accepted,
|
|
194
|
+
* while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted.
|
|
195
|
+
*/
|
|
185
196
|
listenFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
186
|
-
|
|
197
|
+
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
|
|
198
|
+
|
|
199
|
+
if (this.init?.filter != null) {
|
|
200
|
+
return this.init?.filter(multiaddrs)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return filters.all(multiaddrs)
|
|
187
204
|
}
|
|
188
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Filter check for all Multiaddrs that this transport can dial
|
|
208
|
+
*/
|
|
189
209
|
dialFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
190
210
|
return this.listenFilter(multiaddrs)
|
|
191
211
|
}
|
package/src/listener.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import http from 'node:http'
|
|
2
2
|
import https from 'node:https'
|
|
3
3
|
import net from 'node:net'
|
|
4
|
-
import {
|
|
4
|
+
import { getThinWaistAddresses } from '@libp2p/utils/get-thin-waist-addresses'
|
|
5
|
+
import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr'
|
|
5
6
|
import { multiaddr } from '@multiformats/multiaddr'
|
|
6
7
|
import { WebSockets, WebSocketsSecure } from '@multiformats/multiaddr-matcher'
|
|
8
|
+
import duplex from 'it-ws/duplex'
|
|
7
9
|
import { TypedEventEmitter, setMaxListeners } from 'main-event'
|
|
8
10
|
import { pEvent } from 'p-event'
|
|
9
11
|
import * as ws from 'ws'
|
|
10
|
-
import {
|
|
11
|
-
import { webSocketToMaConn } from './websocket-to-conn.js'
|
|
12
|
+
import { socketToMaConn } from './socket-to-conn.js'
|
|
12
13
|
import type { ComponentLogger, Logger, Listener, ListenerEvents, CreateListenerOptions, CounterGroup, MetricGroup, Metrics, TLSCertificate, Libp2pEvents, Upgrader, MultiaddrConnection } from '@libp2p/interface'
|
|
13
14
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
15
|
+
import type { DuplexWebSocket } from 'it-ws/duplex'
|
|
14
16
|
import type { TypedEventTarget } from 'main-event'
|
|
15
17
|
import type { EventEmitter } from 'node:events'
|
|
16
18
|
import type { Server } from 'node:http'
|
|
@@ -29,8 +31,6 @@ export interface WebSocketListenerInit extends CreateListenerOptions {
|
|
|
29
31
|
key?: string
|
|
30
32
|
http?: http.ServerOptions
|
|
31
33
|
https?: http.ServerOptions
|
|
32
|
-
maxBufferedAmount?: number
|
|
33
|
-
bufferedAmountPollInterval?: number
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export interface WebSocketListenerMetrics {
|
|
@@ -40,8 +40,8 @@ export interface WebSocketListenerMetrics {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export class WebSocketListener extends TypedEventEmitter<ListenerEvents> implements Listener {
|
|
43
|
-
private components: WebSocketListenerComponents
|
|
44
43
|
private readonly log: Logger
|
|
44
|
+
private readonly logger: ComponentLogger
|
|
45
45
|
private readonly server: net.Server
|
|
46
46
|
private readonly wsServer: ws.WebSocketServer
|
|
47
47
|
private readonly metrics: WebSocketListenerMetrics
|
|
@@ -54,19 +54,15 @@ export class WebSocketListener extends TypedEventEmitter<ListenerEvents> impleme
|
|
|
54
54
|
private https?: https.Server
|
|
55
55
|
private addr?: string
|
|
56
56
|
private listeningMultiaddr?: Multiaddr
|
|
57
|
-
private maxBufferedAmount?: number
|
|
58
|
-
private bufferedAmountPollInterval?: number
|
|
59
57
|
|
|
60
58
|
constructor (components: WebSocketListenerComponents, init: WebSocketListenerInit) {
|
|
61
59
|
super()
|
|
62
60
|
|
|
63
|
-
this.components = components
|
|
64
61
|
this.log = components.logger.forComponent('libp2p:websockets:listener')
|
|
62
|
+
this.logger = components.logger
|
|
65
63
|
this.upgrader = init.upgrader
|
|
66
64
|
this.httpOptions = init.http
|
|
67
65
|
this.httpsOptions = init.https ?? init.http
|
|
68
|
-
this.maxBufferedAmount = init.maxBufferedAmount
|
|
69
|
-
this.bufferedAmountPollInterval = init.bufferedAmountPollInterval
|
|
70
66
|
this.sockets = new Set()
|
|
71
67
|
this.shutdownController = new AbortController()
|
|
72
68
|
setMaxListeners(Infinity, this.shutdownController.signal)
|
|
@@ -175,7 +171,6 @@ export class WebSocketListener extends TypedEventEmitter<ListenerEvents> impleme
|
|
|
175
171
|
|
|
176
172
|
onWsServerConnection (socket: ws.WebSocket, req: http.IncomingMessage): void {
|
|
177
173
|
let addr: string | ws.AddressInfo | null
|
|
178
|
-
socket.binaryType = 'arraybuffer'
|
|
179
174
|
|
|
180
175
|
try {
|
|
181
176
|
addr = this.server.address()
|
|
@@ -194,18 +189,22 @@ export class WebSocketListener extends TypedEventEmitter<ListenerEvents> impleme
|
|
|
194
189
|
return
|
|
195
190
|
}
|
|
196
191
|
|
|
192
|
+
const stream: DuplexWebSocket = {
|
|
193
|
+
...duplex(socket, {
|
|
194
|
+
remoteAddress: req.socket.remoteAddress ?? '0.0.0.0',
|
|
195
|
+
remotePort: req.socket.remotePort ?? 0
|
|
196
|
+
}),
|
|
197
|
+
localAddress: addr.address,
|
|
198
|
+
localPort: addr.port
|
|
199
|
+
}
|
|
200
|
+
|
|
197
201
|
let maConn: MultiaddrConnection
|
|
198
202
|
|
|
199
203
|
try {
|
|
200
|
-
maConn =
|
|
201
|
-
|
|
202
|
-
remoteAddr: toMultiaddr(req.socket.remoteAddress ?? '0.0.0.0', req.socket.remotePort ?? 0).encapsulate('/ws'),
|
|
204
|
+
maConn = socketToMaConn(stream, toMultiaddr(stream.remoteAddress ?? '', stream.remotePort ?? 0), {
|
|
205
|
+
logger: this.logger,
|
|
203
206
|
metrics: this.metrics?.events,
|
|
204
|
-
metricPrefix: `${this.addr}
|
|
205
|
-
direction: 'inbound',
|
|
206
|
-
log: this.components.logger.forComponent('libp2p:websockets:connection'),
|
|
207
|
-
maxBufferedAmount: this.maxBufferedAmount,
|
|
208
|
-
bufferedAmountPollInterval: this.bufferedAmountPollInterval
|
|
207
|
+
metricPrefix: `${this.addr} `
|
|
209
208
|
})
|
|
210
209
|
} catch (err: any) {
|
|
211
210
|
this.log.error('inbound connection failed', err)
|
|
@@ -223,7 +222,11 @@ export class WebSocketListener extends TypedEventEmitter<ListenerEvents> impleme
|
|
|
223
222
|
this.log.error('inbound connection failed to upgrade - %e', err)
|
|
224
223
|
this.metrics.errors?.increment({ [`${this.addr} inbound_upgrade`]: true })
|
|
225
224
|
|
|
226
|
-
maConn.close()
|
|
225
|
+
await maConn.close()
|
|
226
|
+
.catch(err => {
|
|
227
|
+
this.log.error('inbound connection failed to close after upgrade failed', err)
|
|
228
|
+
this.metrics.errors?.increment({ [`${this.addr} inbound_closing_failed`]: true })
|
|
229
|
+
})
|
|
227
230
|
})
|
|
228
231
|
}
|
|
229
232
|
|
|
@@ -246,12 +249,12 @@ export class WebSocketListener extends TypedEventEmitter<ListenerEvents> impleme
|
|
|
246
249
|
this.https.addListener('tlsClientError', this.onTLSClientError.bind(this))
|
|
247
250
|
}
|
|
248
251
|
|
|
249
|
-
const
|
|
250
|
-
this.addr = `${
|
|
252
|
+
const options = ma.toOptions()
|
|
253
|
+
this.addr = `${options.host}:${options.port}`
|
|
251
254
|
|
|
252
255
|
this.server.listen({
|
|
253
|
-
...
|
|
254
|
-
ipv6Only:
|
|
256
|
+
...options,
|
|
257
|
+
ipv6Only: options.family === 6
|
|
255
258
|
})
|
|
256
259
|
|
|
257
260
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -331,20 +334,14 @@ export class WebSocketListener extends TypedEventEmitter<ListenerEvents> impleme
|
|
|
331
334
|
// abort and in-flight connection upgrades
|
|
332
335
|
this.shutdownController.abort()
|
|
333
336
|
|
|
334
|
-
|
|
337
|
+
await Promise.all([
|
|
335
338
|
pEvent(this.server, 'close'),
|
|
339
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
340
|
+
this.http == null ? null : pEvent(this.http, 'close'),
|
|
341
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
342
|
+
this.https == null ? null : pEvent(this.https, 'close'),
|
|
336
343
|
pEvent(this.wsServer, 'close')
|
|
337
|
-
]
|
|
338
|
-
|
|
339
|
-
if (this.http != null) {
|
|
340
|
-
events.push(pEvent(this.http, 'close'))
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (this.https != null) {
|
|
344
|
-
events.push(pEvent(this.https, 'close'))
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
await Promise.all(events)
|
|
344
|
+
])
|
|
348
345
|
|
|
349
346
|
this.safeDispatchEvent('close')
|
|
350
347
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { AbortError, ConnectionFailedError } from '@libp2p/interface'
|
|
2
|
+
import { CLOSE_TIMEOUT } from './constants.js'
|
|
3
|
+
import type { AbortOptions, ComponentLogger, CounterGroup, MultiaddrConnection } from '@libp2p/interface'
|
|
4
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
5
|
+
import type { DuplexWebSocket } from 'it-ws/duplex'
|
|
6
|
+
|
|
7
|
+
export interface SocketToConnOptions {
|
|
8
|
+
localAddr?: Multiaddr
|
|
9
|
+
logger: ComponentLogger
|
|
10
|
+
metrics?: CounterGroup
|
|
11
|
+
metricPrefix?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Convert a stream into a MultiaddrConnection
|
|
15
|
+
// https://github.com/libp2p/interface-transport#multiaddrconnection
|
|
16
|
+
export function socketToMaConn (stream: DuplexWebSocket, remoteAddr: Multiaddr, options: SocketToConnOptions): MultiaddrConnection {
|
|
17
|
+
const metrics = options.metrics
|
|
18
|
+
const metricPrefix = options.metricPrefix ?? ''
|
|
19
|
+
|
|
20
|
+
const maConn: MultiaddrConnection = {
|
|
21
|
+
log: options.logger.forComponent('libp2p:websockets:connection'),
|
|
22
|
+
|
|
23
|
+
async sink (source) {
|
|
24
|
+
try {
|
|
25
|
+
await stream.sink((async function * () {
|
|
26
|
+
for await (const buf of source) {
|
|
27
|
+
if (buf instanceof Uint8Array) {
|
|
28
|
+
yield buf
|
|
29
|
+
} else {
|
|
30
|
+
yield buf.subarray()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})())
|
|
34
|
+
} catch (err: any) {
|
|
35
|
+
if (err.type !== 'aborted') {
|
|
36
|
+
maConn.log.error(err)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
source: stream.source,
|
|
42
|
+
|
|
43
|
+
remoteAddr,
|
|
44
|
+
|
|
45
|
+
timeline: { open: Date.now() },
|
|
46
|
+
|
|
47
|
+
async close (options: AbortOptions = {}) {
|
|
48
|
+
const start = Date.now()
|
|
49
|
+
|
|
50
|
+
if (options.signal == null) {
|
|
51
|
+
const signal = AbortSignal.timeout(CLOSE_TIMEOUT)
|
|
52
|
+
|
|
53
|
+
options = {
|
|
54
|
+
...options,
|
|
55
|
+
signal
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const listener = (): void => {
|
|
60
|
+
const { host, port } = maConn.remoteAddr.toOptions()
|
|
61
|
+
maConn.log('timeout closing stream to %s:%s after %dms, destroying it manually',
|
|
62
|
+
host, port, Date.now() - start)
|
|
63
|
+
|
|
64
|
+
this.abort(new AbortError('Socket close timeout'))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
options.signal?.addEventListener('abort', listener)
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await stream.close()
|
|
71
|
+
} catch (err: any) {
|
|
72
|
+
maConn.log.error('error closing WebSocket gracefully - %e', err)
|
|
73
|
+
this.abort(err)
|
|
74
|
+
} finally {
|
|
75
|
+
options.signal?.removeEventListener('abort', listener)
|
|
76
|
+
maConn.timeline.close = Date.now()
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
abort (err: Error): void {
|
|
81
|
+
maConn.log.error('destroying WebSocket after error - %e', err)
|
|
82
|
+
stream.destroy()
|
|
83
|
+
maConn.timeline.close = Date.now()
|
|
84
|
+
|
|
85
|
+
// ws WebSocket.terminate does not accept an Error arg to emit an 'error'
|
|
86
|
+
// event on destroy like other node streams so we can't update a metric
|
|
87
|
+
// with an event listener
|
|
88
|
+
// https://github.com/websockets/ws/issues/1752#issuecomment-622380981
|
|
89
|
+
metrics?.increment({ [`${metricPrefix}error`]: true })
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// track local vs remote closing
|
|
94
|
+
let closedLocally = false
|
|
95
|
+
const close = stream.socket.close.bind(stream.socket)
|
|
96
|
+
stream.socket.close = (...args) => {
|
|
97
|
+
closedLocally = true
|
|
98
|
+
return close(...args)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
stream.socket.addEventListener('close', (evt) => {
|
|
102
|
+
maConn.log('closed %s, code %d, reason "%s", wasClean %s', closedLocally ? 'locally' : 'by remote', evt.code, evt.reason, evt.wasClean)
|
|
103
|
+
|
|
104
|
+
if (!evt.wasClean) {
|
|
105
|
+
maConn.abort(new ConnectionFailedError(`${closedLocally ? 'Local' : 'Remote'} did not close WebSocket cleanly`))
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
metrics?.increment({ [`${metricPrefix}close`]: true })
|
|
110
|
+
maConn.timeline.close = Date.now()
|
|
111
|
+
}, { once: true })
|
|
112
|
+
|
|
113
|
+
return maConn
|
|
114
|
+
}
|
package/dist/src/utils.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { WebSocket as WSSWebSocket } from 'ws';
|
|
2
|
-
/**
|
|
3
|
-
* Adds properties/methods to a `WebSocket` instance from the `ws` module to be
|
|
4
|
-
* compatible with the `globalThis.WebSocket` API
|
|
5
|
-
*/
|
|
6
|
-
export declare function toWebSocket(ws: WSSWebSocket): WebSocket;
|
|
7
|
-
//# sourceMappingURL=utils.d.ts.map
|
package/dist/src/utils.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,IAAI,CAAA;AAEnD;;;GAGG;AACH,wBAAgB,WAAW,CAAE,EAAE,EAAE,YAAY,GAAG,SAAS,CA6BxD"}
|
package/dist/src/utils.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adds properties/methods to a `WebSocket` instance from the `ws` module to be
|
|
3
|
-
* compatible with the `globalThis.WebSocket` API
|
|
4
|
-
*/
|
|
5
|
-
export function toWebSocket(ws) {
|
|
6
|
-
Object.defineProperty(ws, 'url', {
|
|
7
|
-
value: '',
|
|
8
|
-
writable: false
|
|
9
|
-
});
|
|
10
|
-
// @ts-expect-error not a WS/WebSocket method
|
|
11
|
-
ws.dispatchEvent = (evt) => {
|
|
12
|
-
if (evt.type === 'close') {
|
|
13
|
-
ws.emit('close');
|
|
14
|
-
}
|
|
15
|
-
if (evt.type === 'open') {
|
|
16
|
-
ws.emit('open');
|
|
17
|
-
}
|
|
18
|
-
if (evt.type === 'message') {
|
|
19
|
-
const m = evt;
|
|
20
|
-
ws.emit('data', m.data);
|
|
21
|
-
}
|
|
22
|
-
if (evt.type === 'error') {
|
|
23
|
-
ws.emit('error', new Error('An error occurred'));
|
|
24
|
-
}
|
|
25
|
-
ws.emit(evt.type, evt);
|
|
26
|
-
};
|
|
27
|
-
// @ts-expect-error ws is now WebSocket
|
|
28
|
-
return ws;
|
|
29
|
-
}
|
|
30
|
-
//# sourceMappingURL=utils.js.map
|
package/dist/src/utils.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAE,EAAgB;IAC3C,MAAM,CAAC,cAAc,CAAC,EAAE,EAAE,KAAK,EAAE;QAC/B,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAA;IAEF,6CAA6C;IAC7C,EAAE,CAAC,aAAa,GAAG,CAAC,GAAU,EAAE,EAAE;QAChC,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACzB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAClB,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACxB,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACjB,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,GAAmB,CAAA;YAC7B,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;QACzB,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACzB,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAA;QAClD,CAAC;QACD,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACxB,CAAC,CAAA;IAED,uCAAuC;IACvC,OAAO,EAAE,CAAA;AACX,CAAC"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { MultiaddrConnection } from '@libp2p/interface';
|
|
2
|
-
import type { AbstractMultiaddrConnectionInit } from '@libp2p/utils';
|
|
3
|
-
export interface WebSocketMultiaddrConnectionInit extends Omit<AbstractMultiaddrConnectionInit, 'name'> {
|
|
4
|
-
websocket: WebSocket;
|
|
5
|
-
maxBufferedAmount?: number;
|
|
6
|
-
bufferedAmountPollInterval?: number;
|
|
7
|
-
}
|
|
8
|
-
export declare function webSocketToMaConn(init: WebSocketMultiaddrConnectionInit): MultiaddrConnection;
|
|
9
|
-
//# sourceMappingURL=websocket-to-conn.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-to-conn.d.ts","sourceRoot":"","sources":["../../src/websocket-to-conn.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC1E,OAAO,KAAK,EAAE,+BAA+B,EAA6B,MAAM,eAAe,CAAA;AAK/F,MAAM,WAAW,gCAAiC,SAAQ,IAAI,CAAC,+BAA+B,EAAE,MAAM,CAAC;IACrG,SAAS,EAAE,SAAS,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAA;CACpC;AA4FD,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,gCAAgC,GAAG,mBAAmB,CAE9F"}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { AbstractMultiaddrConnection, repeatingTask } from '@libp2p/utils';
|
|
2
|
-
import { Uint8ArrayList } from 'uint8arraylist';
|
|
3
|
-
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string';
|
|
4
|
-
const DEFAULT_MAX_BUFFERED_AMOUNT = 1024 * 1024 * 4;
|
|
5
|
-
const DEFAULT_BUFFERED_AMOUNT_POLL_INTERVAL = 10;
|
|
6
|
-
class WebSocketMultiaddrConnection extends AbstractMultiaddrConnection {
|
|
7
|
-
websocket;
|
|
8
|
-
maxBufferedAmount;
|
|
9
|
-
checkBufferedAmountTask;
|
|
10
|
-
constructor(init) {
|
|
11
|
-
super(init);
|
|
12
|
-
this.websocket = init.websocket;
|
|
13
|
-
this.maxBufferedAmount = init.maxBufferedAmount ?? DEFAULT_MAX_BUFFERED_AMOUNT;
|
|
14
|
-
this.checkBufferedAmountTask = repeatingTask(this.checkBufferedAmount.bind(this), init.bufferedAmountPollInterval ?? DEFAULT_BUFFERED_AMOUNT_POLL_INTERVAL);
|
|
15
|
-
this.websocket.addEventListener('close', (evt) => {
|
|
16
|
-
this.log('closed - code %d, reason "%s", wasClean %s', evt.code, evt.reason, evt.wasClean);
|
|
17
|
-
this.checkBufferedAmountTask.stop();
|
|
18
|
-
if (!evt.wasClean) {
|
|
19
|
-
this.onRemoteReset();
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
this.onTransportClosed();
|
|
23
|
-
}, { once: true });
|
|
24
|
-
this.websocket.addEventListener('message', (evt) => {
|
|
25
|
-
try {
|
|
26
|
-
let buf;
|
|
27
|
-
if (typeof evt.data === 'string') {
|
|
28
|
-
buf = uint8ArrayFromString(evt.data);
|
|
29
|
-
}
|
|
30
|
-
else if (evt.data instanceof ArrayBuffer) {
|
|
31
|
-
buf = new Uint8Array(evt.data, 0, evt.data.byteLength);
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
this.abort(new Error('Incorrect binary type'));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
this.onData(buf);
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
this.log.error('error receiving data - %e', err);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
sendData(data) {
|
|
45
|
-
for (const buf of data) {
|
|
46
|
-
this.websocket.send(buf);
|
|
47
|
-
}
|
|
48
|
-
const canSendMore = this.websocket.bufferedAmount < this.maxBufferedAmount;
|
|
49
|
-
if (!canSendMore) {
|
|
50
|
-
this.checkBufferedAmountTask.start();
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
sentBytes: data.byteLength,
|
|
54
|
-
canSendMore
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
sendReset() {
|
|
58
|
-
this.websocket.close(1006); // abnormal closure
|
|
59
|
-
}
|
|
60
|
-
async sendClose(options) {
|
|
61
|
-
this.websocket.close();
|
|
62
|
-
options?.signal?.throwIfAborted();
|
|
63
|
-
}
|
|
64
|
-
sendPause() {
|
|
65
|
-
// read backpressure is not supported
|
|
66
|
-
}
|
|
67
|
-
sendResume() {
|
|
68
|
-
// read backpressure is not supported
|
|
69
|
-
}
|
|
70
|
-
checkBufferedAmount() {
|
|
71
|
-
this.log('buffered amount now %d', this.websocket.bufferedAmount);
|
|
72
|
-
if (this.websocket.bufferedAmount === 0) {
|
|
73
|
-
this.checkBufferedAmountTask.stop();
|
|
74
|
-
this.safeDispatchEvent('drain');
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// Convert a stream into a MultiaddrConnection
|
|
79
|
-
// https://github.com/libp2p/interface-transport#multiaddrconnection
|
|
80
|
-
export function webSocketToMaConn(init) {
|
|
81
|
-
return new WebSocketMultiaddrConnection(init);
|
|
82
|
-
}
|
|
83
|
-
//# sourceMappingURL=websocket-to-conn.js.map
|