@libp2p/websockets 9.0.13 → 9.1.0-5c4a79e5a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -36
- package/dist/index.min.js +1 -1
- package/dist/src/index.d.ts +27 -38
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -43
- package/dist/src/index.js.map +1 -1
- package/dist/src/listener.d.ts +50 -5
- package/dist/src/listener.d.ts.map +1 -1
- package/dist/src/listener.js +294 -97
- package/dist/src/listener.js.map +1 -1
- package/package.json +14 -14
- package/src/index.ts +35 -48
- package/src/listener.ts +348 -110
- package/LICENSE +0 -4
- package/dist/typedoc-urls.json +0 -20
package/src/index.ts
CHANGED
|
@@ -18,43 +18,9 @@
|
|
|
18
18
|
* })
|
|
19
19
|
* await node.start()
|
|
20
20
|
*
|
|
21
|
-
* const ma = multiaddr('/
|
|
21
|
+
* const ma = multiaddr('/dns4/example.com/tcp/9090/tls/ws')
|
|
22
22
|
* await node.dial(ma)
|
|
23
23
|
* ```
|
|
24
|
-
*
|
|
25
|
-
* ## Filters
|
|
26
|
-
*
|
|
27
|
-
* When run in a browser by default this module will only connect to secure web socket addresses.
|
|
28
|
-
*
|
|
29
|
-
* To change this you should pass a filter to the factory function.
|
|
30
|
-
*
|
|
31
|
-
* You can create your own address filters for this transports, or rely in the filters [provided](./src/filters.js).
|
|
32
|
-
*
|
|
33
|
-
* The available filters are:
|
|
34
|
-
*
|
|
35
|
-
* - `filters.all`
|
|
36
|
-
* - Returns all TCP and DNS based addresses, both with `ws` or `wss`.
|
|
37
|
-
* - `filters.dnsWss`
|
|
38
|
-
* - Returns all DNS based addresses with `wss`.
|
|
39
|
-
* - `filters.dnsWsOrWss`
|
|
40
|
-
* - Returns all DNS based addresses, both with `ws` or `wss`.
|
|
41
|
-
*
|
|
42
|
-
* @example Allow dialing insecure WebSockets
|
|
43
|
-
*
|
|
44
|
-
* ```TypeScript
|
|
45
|
-
* import { createLibp2p } from 'libp2p'
|
|
46
|
-
* import { webSockets } from '@libp2p/websockets'
|
|
47
|
-
* import * as filters from '@libp2p/websockets/filters'
|
|
48
|
-
*
|
|
49
|
-
* const node = await createLibp2p({
|
|
50
|
-
* transports: [
|
|
51
|
-
* webSockets({
|
|
52
|
-
* // connect to all sockets, even insecure ones
|
|
53
|
-
* filter: filters.all
|
|
54
|
-
* })
|
|
55
|
-
* ]
|
|
56
|
-
* })
|
|
57
|
-
* ```
|
|
58
24
|
*/
|
|
59
25
|
|
|
60
26
|
import { transportSymbol, serviceCapabilities, ConnectionFailedError } from '@libp2p/interface'
|
|
@@ -63,25 +29,50 @@ import { connect, type WebSocketOptions } from 'it-ws/client'
|
|
|
63
29
|
import pDefer from 'p-defer'
|
|
64
30
|
import { CustomProgressEvent } from 'progress-events'
|
|
65
31
|
import { raceSignal } from 'race-signal'
|
|
66
|
-
import { isBrowser, isWebWorker } from 'wherearewe'
|
|
67
32
|
import * as filters from './filters.js'
|
|
68
33
|
import { createListener } from './listener.js'
|
|
69
34
|
import { socketToMaConn } from './socket-to-conn.js'
|
|
70
|
-
import type { Transport, MultiaddrFilter, CreateListenerOptions, DialTransportOptions, Listener, AbortOptions, ComponentLogger, Logger, Connection, OutboundConnectionUpgradeEvents, Metrics, CounterGroup } from '@libp2p/interface'
|
|
35
|
+
import type { Transport, MultiaddrFilter, CreateListenerOptions, DialTransportOptions, Listener, AbortOptions, ComponentLogger, Logger, Connection, OutboundConnectionUpgradeEvents, Metrics, CounterGroup, TypedEventTarget, Libp2pEvents } from '@libp2p/interface'
|
|
71
36
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
72
|
-
import type { Server } from 'http'
|
|
73
37
|
import type { DuplexWebSocket } from 'it-ws/duplex'
|
|
38
|
+
import type http from 'node:http'
|
|
39
|
+
import type https from 'node:https'
|
|
74
40
|
import type { ProgressEvent } from 'progress-events'
|
|
75
41
|
import type { ClientOptions } from 'ws'
|
|
76
42
|
|
|
77
43
|
export interface WebSocketsInit extends AbortOptions, WebSocketOptions {
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated Use a ConnectionGater instead
|
|
46
|
+
*/
|
|
78
47
|
filter?: MultiaddrFilter
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Options used to create WebSockets
|
|
51
|
+
*/
|
|
79
52
|
websocket?: ClientOptions
|
|
80
|
-
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Options used to create the HTTP server
|
|
56
|
+
*/
|
|
57
|
+
http?: http.ServerOptions
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Options used to create the HTTPs server. `options.http` will be used if
|
|
61
|
+
* unspecified.
|
|
62
|
+
*/
|
|
63
|
+
https?: https.ServerOptions
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Inbound connections must complete their upgrade within this many ms
|
|
67
|
+
*
|
|
68
|
+
* @default 5000
|
|
69
|
+
*/
|
|
70
|
+
inboundConnectionUpgradeTimeout?: number
|
|
81
71
|
}
|
|
82
72
|
|
|
83
73
|
export interface WebSocketsComponents {
|
|
84
74
|
logger: ComponentLogger
|
|
75
|
+
events: TypedEventTarget<Libp2pEvents>
|
|
85
76
|
metrics?: Metrics
|
|
86
77
|
}
|
|
87
78
|
|
|
@@ -95,12 +86,12 @@ export type WebSocketsDialEvents =
|
|
|
95
86
|
|
|
96
87
|
class WebSockets implements Transport<WebSocketsDialEvents> {
|
|
97
88
|
private readonly log: Logger
|
|
98
|
-
private readonly init
|
|
89
|
+
private readonly init: WebSocketsInit
|
|
99
90
|
private readonly logger: ComponentLogger
|
|
100
91
|
private readonly metrics?: WebSocketsMetrics
|
|
101
92
|
private readonly components: WebSocketsComponents
|
|
102
93
|
|
|
103
|
-
constructor (components: WebSocketsComponents, init
|
|
94
|
+
constructor (components: WebSocketsComponents, init: WebSocketsInit = {}) {
|
|
104
95
|
this.log = components.logger.forComponent('libp2p:websockets')
|
|
105
96
|
this.logger = components.logger
|
|
106
97
|
this.components = components
|
|
@@ -180,13 +171,14 @@ class WebSockets implements Transport<WebSocketsDialEvents> {
|
|
|
180
171
|
}
|
|
181
172
|
|
|
182
173
|
/**
|
|
183
|
-
* Creates a
|
|
174
|
+
* Creates a WebSockets listener. The provided `handler` function will be called
|
|
184
175
|
* anytime a new incoming Connection has been successfully upgraded via
|
|
185
176
|
* `upgrader.upgradeInbound`
|
|
186
177
|
*/
|
|
187
178
|
createListener (options: CreateListenerOptions): Listener {
|
|
188
179
|
return createListener({
|
|
189
180
|
logger: this.logger,
|
|
181
|
+
events: this.components.events,
|
|
190
182
|
metrics: this.components.metrics
|
|
191
183
|
}, {
|
|
192
184
|
...this.init,
|
|
@@ -195,7 +187,7 @@ class WebSockets implements Transport<WebSocketsDialEvents> {
|
|
|
195
187
|
}
|
|
196
188
|
|
|
197
189
|
/**
|
|
198
|
-
* Takes a list of `Multiaddr`s and returns only valid
|
|
190
|
+
* Takes a list of `Multiaddr`s and returns only valid WebSockets addresses.
|
|
199
191
|
* By default, in a browser environment only DNS+WSS multiaddr is accepted,
|
|
200
192
|
* while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted.
|
|
201
193
|
*/
|
|
@@ -206,11 +198,6 @@ class WebSockets implements Transport<WebSocketsDialEvents> {
|
|
|
206
198
|
return this.init?.filter(multiaddrs)
|
|
207
199
|
}
|
|
208
200
|
|
|
209
|
-
// Browser
|
|
210
|
-
if (isBrowser || isWebWorker) {
|
|
211
|
-
return filters.wss(multiaddrs)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
201
|
return filters.all(multiaddrs)
|
|
215
202
|
}
|
|
216
203
|
|
package/src/listener.ts
CHANGED
|
@@ -1,145 +1,346 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import http from 'node:http'
|
|
2
|
+
import https from 'node:https'
|
|
3
|
+
import net from 'node:net'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
|
|
3
6
|
import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
7
|
+
import { isLinkLocalIp } from '@libp2p/utils/link-local-ip'
|
|
8
|
+
import { multiaddr } from '@multiformats/multiaddr'
|
|
9
|
+
import { WebSockets, WebSocketsSecure } from '@multiformats/multiaddr-matcher'
|
|
10
|
+
import duplex from 'it-ws/duplex'
|
|
11
|
+
import { pEvent } from 'p-event'
|
|
12
|
+
import * as ws from 'ws'
|
|
6
13
|
import { socketToMaConn } from './socket-to-conn.js'
|
|
7
|
-
import type { ComponentLogger, Logger, Listener, ListenerEvents, CreateListenerOptions, CounterGroup, MetricGroup, Metrics } from '@libp2p/interface'
|
|
14
|
+
import type { ComponentLogger, Logger, Listener, ListenerEvents, CreateListenerOptions, CounterGroup, MetricGroup, Metrics, TLSCertificate, TypedEventTarget, Libp2pEvents, Upgrader, MultiaddrConnection } from '@libp2p/interface'
|
|
8
15
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
9
|
-
import type { Server } from 'http'
|
|
10
16
|
import type { DuplexWebSocket } from 'it-ws/duplex'
|
|
11
|
-
import type {
|
|
17
|
+
import type { EventEmitter } from 'node:events'
|
|
18
|
+
import type { Server } from 'node:http'
|
|
19
|
+
import type { Duplex } from 'node:stream'
|
|
20
|
+
import type tls from 'node:tls'
|
|
12
21
|
|
|
13
22
|
export interface WebSocketListenerComponents {
|
|
14
23
|
logger: ComponentLogger
|
|
24
|
+
events: TypedEventTarget<Libp2pEvents>
|
|
15
25
|
metrics?: Metrics
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
export interface WebSocketListenerInit extends CreateListenerOptions {
|
|
19
29
|
server?: Server
|
|
30
|
+
inboundConnectionUpgradeTimeout?: number
|
|
31
|
+
cert?: string
|
|
32
|
+
key?: string
|
|
33
|
+
http?: http.ServerOptions
|
|
34
|
+
https?: http.ServerOptions
|
|
20
35
|
}
|
|
21
36
|
|
|
22
37
|
export interface WebSocketListenerMetrics {
|
|
23
|
-
status
|
|
24
|
-
errors
|
|
25
|
-
events
|
|
38
|
+
status?: MetricGroup
|
|
39
|
+
errors?: CounterGroup
|
|
40
|
+
events?: CounterGroup
|
|
26
41
|
}
|
|
27
42
|
|
|
28
|
-
class WebSocketListener extends TypedEventEmitter<ListenerEvents> implements Listener {
|
|
29
|
-
private readonly connections: Set<DuplexWebSocket>
|
|
30
|
-
private listeningMultiaddr?: Multiaddr
|
|
31
|
-
private readonly server: WebSocketServer
|
|
43
|
+
export class WebSocketListener extends TypedEventEmitter<ListenerEvents> implements Listener {
|
|
32
44
|
private readonly log: Logger
|
|
33
|
-
private
|
|
34
|
-
private
|
|
45
|
+
private readonly logger: ComponentLogger
|
|
46
|
+
private readonly server: net.Server
|
|
47
|
+
private readonly wsServer: ws.WebSocketServer
|
|
48
|
+
private readonly metrics: WebSocketListenerMetrics
|
|
49
|
+
private readonly sockets: Set<net.Socket>
|
|
50
|
+
private readonly upgrader: Upgrader
|
|
51
|
+
private readonly inboundConnectionUpgradeTimeout: number
|
|
52
|
+
private readonly httpOptions?: http.ServerOptions
|
|
53
|
+
private readonly httpsOptions?: https.ServerOptions
|
|
54
|
+
private http?: http.Server
|
|
55
|
+
private https?: https.Server
|
|
56
|
+
private addr?: string
|
|
57
|
+
private listeningMultiaddr?: Multiaddr
|
|
35
58
|
|
|
36
59
|
constructor (components: WebSocketListenerComponents, init: WebSocketListenerInit) {
|
|
37
60
|
super()
|
|
38
61
|
|
|
39
62
|
this.log = components.logger.forComponent('libp2p:websockets:listener')
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.
|
|
63
|
+
this.logger = components.logger
|
|
64
|
+
this.upgrader = init.upgrader
|
|
65
|
+
this.httpOptions = init.http
|
|
66
|
+
this.httpsOptions = init.https ?? init.http
|
|
67
|
+
this.inboundConnectionUpgradeTimeout = init.inboundConnectionUpgradeTimeout ?? 5000
|
|
68
|
+
this.sockets = new Set()
|
|
43
69
|
|
|
44
|
-
|
|
70
|
+
this.wsServer = new ws.WebSocketServer({
|
|
71
|
+
noServer: true
|
|
72
|
+
})
|
|
73
|
+
this.wsServer.addListener('connection', this.onWsServerConnection.bind(this))
|
|
45
74
|
|
|
46
|
-
|
|
75
|
+
components.metrics?.registerMetricGroup('libp2p_websockets_inbound_connections_total', {
|
|
76
|
+
label: 'address',
|
|
77
|
+
help: 'Current active connections in WebSocket listener',
|
|
78
|
+
calculate: () => {
|
|
79
|
+
if (this.addr == null) {
|
|
80
|
+
return {}
|
|
81
|
+
}
|
|
47
82
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
metrics: this.metrics?.events,
|
|
54
|
-
metricPrefix: `${this.addr} `
|
|
55
|
-
})
|
|
56
|
-
this.log('new inbound connection %s', maConn.remoteAddr)
|
|
83
|
+
return {
|
|
84
|
+
[this.addr]: this.sockets.size
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
})
|
|
57
88
|
|
|
58
|
-
|
|
89
|
+
this.metrics = {
|
|
90
|
+
status: components.metrics?.registerMetricGroup('libp2p_websockets_listener_status_info', {
|
|
91
|
+
label: 'address',
|
|
92
|
+
help: 'Current status of the WebSocket listener socket'
|
|
93
|
+
}),
|
|
94
|
+
errors: components.metrics?.registerMetricGroup('libp2p_websockets_listener_errors_total', {
|
|
95
|
+
label: 'address',
|
|
96
|
+
help: 'Total count of WebSocket listener errors by type'
|
|
97
|
+
}),
|
|
98
|
+
events: components.metrics?.registerMetricGroup('libp2p_websockets_listener_events_total', {
|
|
99
|
+
label: 'address',
|
|
100
|
+
help: 'Total count of WebSocket listener events by type'
|
|
101
|
+
})
|
|
102
|
+
}
|
|
59
103
|
|
|
60
|
-
|
|
61
|
-
|
|
104
|
+
this.server = net.createServer({
|
|
105
|
+
pauseOnConnect: true
|
|
106
|
+
}, (socket) => {
|
|
107
|
+
this.onSocketConnection(socket)
|
|
108
|
+
.catch(err => {
|
|
109
|
+
this.log.error('error handling socket - %e', err)
|
|
110
|
+
socket.destroy()
|
|
62
111
|
})
|
|
112
|
+
})
|
|
63
113
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.metrics?.errors.increment({ [`${this.addr} inbound_upgrade`]: true })
|
|
114
|
+
components.events.addEventListener('certificate:provision', this.onCertificateProvision.bind(this))
|
|
115
|
+
components.events.addEventListener('certificate:renew', this.onCertificateRenew.bind(this))
|
|
116
|
+
}
|
|
68
117
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} catch (err) {
|
|
72
|
-
this.log.error('inbound connection failed to close after upgrade failed - %e', err)
|
|
73
|
-
this.metrics?.errors.increment({ [`${this.addr} inbound_closing_failed`]: true })
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
})
|
|
118
|
+
async onSocketConnection (socket: net.Socket): Promise<void> {
|
|
119
|
+
this.metrics.events?.increment({ [`${this.addr} connection`]: true })
|
|
78
120
|
|
|
79
|
-
|
|
80
|
-
if (metrics != null) {
|
|
81
|
-
const { host, port } = this.listeningMultiaddr?.toOptions() ?? {}
|
|
82
|
-
this.addr = `${host}:${port}`
|
|
83
|
-
|
|
84
|
-
metrics.registerMetricGroup('libp2p_websockets_inbound_connections_total', {
|
|
85
|
-
label: 'address',
|
|
86
|
-
help: 'Current active connections in WebSocket listener',
|
|
87
|
-
calculate: () => {
|
|
88
|
-
return {
|
|
89
|
-
[this.addr]: this.connections.size
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
})
|
|
121
|
+
let buffer = socket.read(1)
|
|
93
122
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.
|
|
123
|
+
if (buffer == null) {
|
|
124
|
+
await pEvent(socket, 'readable')
|
|
125
|
+
buffer = socket.read(1)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// determine if this is an HTTP(s) request
|
|
129
|
+
const byte = buffer[0]
|
|
130
|
+
let server: EventEmitter | undefined = this.http
|
|
131
|
+
|
|
132
|
+
// https://github.com/mscdex/httpolyglot/blob/1c6c4af65f4cf95a32c918d0fdcc532e0c095740/lib/index.js#L92
|
|
133
|
+
if (byte < 32 || byte >= 127) {
|
|
134
|
+
server = this.https
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (server == null) {
|
|
138
|
+
this.log.error('no appropriate listener configured for byte %d', byte)
|
|
139
|
+
socket.destroy()
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// store the socket so we can close it when the listener closes
|
|
144
|
+
this.sockets.add(socket)
|
|
145
|
+
|
|
146
|
+
socket.on('close', () => {
|
|
147
|
+
this.metrics.events?.increment({ [`${this.addr} close`]: true })
|
|
148
|
+
this.sockets.delete(socket)
|
|
110
149
|
})
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
|
|
150
|
+
|
|
151
|
+
socket.on('error', (err) => {
|
|
152
|
+
this.log.error('socket error - %e', err)
|
|
153
|
+
this.metrics.events?.increment({ [`${this.addr} error`]: true })
|
|
154
|
+
socket.destroy()
|
|
116
155
|
})
|
|
117
|
-
|
|
118
|
-
|
|
156
|
+
|
|
157
|
+
socket.once('timeout', () => {
|
|
158
|
+
this.metrics.events?.increment({ [`${this.addr} timeout`]: true })
|
|
119
159
|
})
|
|
160
|
+
|
|
161
|
+
socket.once('end', () => {
|
|
162
|
+
this.metrics.events?.increment({ [`${this.addr} end`]: true })
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// re-queue first data chunk
|
|
166
|
+
socket.unshift(buffer)
|
|
167
|
+
|
|
168
|
+
// hand the socket off to the appropriate server
|
|
169
|
+
server.emit('connection', socket)
|
|
120
170
|
}
|
|
121
171
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
172
|
+
onWsServerConnection (socket: ws.WebSocket, req: http.IncomingMessage): void {
|
|
173
|
+
let addr: string | ws.AddressInfo | null
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
addr = this.server.address()
|
|
177
|
+
|
|
178
|
+
if (typeof addr === 'string') {
|
|
179
|
+
throw new Error('Cannot listen on unix sockets')
|
|
180
|
+
}
|
|
126
181
|
|
|
127
|
-
|
|
128
|
-
|
|
182
|
+
if (addr == null) {
|
|
183
|
+
throw new Error('Server was closing or not running')
|
|
184
|
+
}
|
|
185
|
+
} catch (err: any) {
|
|
186
|
+
this.log.error('error obtaining remote socket address - %e', err)
|
|
187
|
+
req.destroy(err)
|
|
188
|
+
socket.close()
|
|
129
189
|
return
|
|
130
190
|
}
|
|
131
191
|
|
|
132
|
-
|
|
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
|
+
|
|
201
|
+
let maConn: MultiaddrConnection
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
maConn = socketToMaConn(stream, toMultiaddr(stream.remoteAddress ?? '', stream.remotePort ?? 0), {
|
|
205
|
+
logger: this.logger,
|
|
206
|
+
metrics: this.metrics?.events,
|
|
207
|
+
metricPrefix: `${this.addr} `
|
|
208
|
+
})
|
|
209
|
+
} catch (err: any) {
|
|
210
|
+
this.log.error('inbound connection failed', err)
|
|
211
|
+
this.metrics.errors?.increment({ [`${this.addr} inbound_to_connection`]: true })
|
|
212
|
+
socket.close()
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.log('new inbound connection %s', maConn.remoteAddr)
|
|
217
|
+
const signal = AbortSignal.timeout(this.inboundConnectionUpgradeTimeout)
|
|
218
|
+
setMaxListeners(Infinity, signal)
|
|
219
|
+
|
|
220
|
+
this.upgrader.upgradeInbound(maConn, {
|
|
221
|
+
signal
|
|
222
|
+
})
|
|
223
|
+
.catch(async err => {
|
|
224
|
+
this.log.error('inbound connection failed to upgrade - %e', err)
|
|
225
|
+
this.metrics.errors?.increment({ [`${this.addr} inbound_upgrade`]: true })
|
|
226
|
+
|
|
227
|
+
await maConn.close()
|
|
228
|
+
.catch(err => {
|
|
229
|
+
this.log.error('inbound connection failed to close after upgrade failed', err)
|
|
230
|
+
this.metrics.errors?.increment({ [`${this.addr} inbound_closing_failed`]: true })
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
onUpgrade (req: http.IncomingMessage, socket: Duplex, head: Buffer): void {
|
|
236
|
+
this.wsServer.handleUpgrade(req, socket, head, this.onWsServerConnection.bind(this))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
onTLSClientError (err: Error, socket: tls.TLSSocket): void {
|
|
240
|
+
this.log.error('TLS client error - %e', err)
|
|
241
|
+
socket.destroy()
|
|
133
242
|
}
|
|
134
243
|
|
|
135
244
|
async listen (ma: Multiaddr): Promise<void> {
|
|
245
|
+
if (WebSockets.exactMatch(ma)) {
|
|
246
|
+
this.http = http.createServer(this.httpOptions ?? {}, this.httpRequestHandler.bind(this))
|
|
247
|
+
this.http.addListener('upgrade', this.onUpgrade.bind(this))
|
|
248
|
+
} else if (WebSocketsSecure.exactMatch(ma)) {
|
|
249
|
+
this.https = https.createServer(this.httpsOptions ?? {}, this.httpRequestHandler.bind(this))
|
|
250
|
+
this.https.addListener('upgrade', this.onUpgrade.bind(this))
|
|
251
|
+
this.https.addListener('tlsClientError', this.onTLSClientError.bind(this))
|
|
252
|
+
}
|
|
253
|
+
|
|
136
254
|
this.listeningMultiaddr = ma
|
|
255
|
+
const { host, port } = ma.toOptions()
|
|
256
|
+
this.addr = `${host}:${port}`
|
|
257
|
+
|
|
258
|
+
this.server.listen(port, host)
|
|
259
|
+
|
|
260
|
+
await new Promise<void>((resolve, reject) => {
|
|
261
|
+
const onListening = (): void => {
|
|
262
|
+
removeListeners()
|
|
263
|
+
resolve()
|
|
264
|
+
}
|
|
265
|
+
const onError = (err: Error): void => {
|
|
266
|
+
this.metrics.errors?.increment({ [`${this.addr} listen_error`]: true })
|
|
267
|
+
removeListeners()
|
|
268
|
+
reject(err)
|
|
269
|
+
}
|
|
270
|
+
const onDrop = (): void => {
|
|
271
|
+
this.metrics.events?.increment({ [`${this.addr} drop`]: true })
|
|
272
|
+
}
|
|
273
|
+
const removeListeners = (): void => {
|
|
274
|
+
this.server.removeListener('listening', onListening)
|
|
275
|
+
this.server.removeListener('error', onError)
|
|
276
|
+
this.server.removeListener('drop', onDrop)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
this.server.addListener('listening', onListening)
|
|
280
|
+
this.server.addListener('error', onError)
|
|
281
|
+
this.server.addListener('drop', onDrop)
|
|
282
|
+
})
|
|
137
283
|
|
|
138
|
-
|
|
284
|
+
this.safeDispatchEvent('listening')
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
onCertificateProvision (event: CustomEvent<TLSCertificate>): void {
|
|
288
|
+
if (this.https != null) {
|
|
289
|
+
this.log('auto-tls certificate found but already listening on https')
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.log('auto-tls certificate found, starting https server')
|
|
294
|
+
this.https = https.createServer({
|
|
295
|
+
...this.httpsOptions,
|
|
296
|
+
...event.detail
|
|
297
|
+
}, this.httpRequestHandler.bind(this))
|
|
298
|
+
this.https.addListener('upgrade', this.onUpgrade.bind(this))
|
|
299
|
+
this.https.addListener('tlsClientError', this.onTLSClientError.bind(this))
|
|
300
|
+
|
|
301
|
+
this.safeDispatchEvent('listening')
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
onCertificateRenew (event: CustomEvent<TLSCertificate>): void {
|
|
305
|
+
// stop accepting new connections
|
|
306
|
+
this.https?.close()
|
|
307
|
+
|
|
308
|
+
this.log('auto-tls certificate renewed, restarting https server')
|
|
309
|
+
this.https = https.createServer({
|
|
310
|
+
...this.httpsOptions,
|
|
311
|
+
...event.detail
|
|
312
|
+
}, this.httpRequestHandler.bind(this))
|
|
313
|
+
this.https.addListener('upgrade', this.onUpgrade.bind(this))
|
|
314
|
+
this.https.addListener('tlsClientError', this.onTLSClientError.bind(this))
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async close (): Promise<void> {
|
|
318
|
+
this.server.close()
|
|
319
|
+
this.http?.close()
|
|
320
|
+
this.https?.close()
|
|
321
|
+
this.wsServer.close()
|
|
322
|
+
|
|
323
|
+
// close all connections, must be done after closing the server to prevent
|
|
324
|
+
// race conditions where a new connection is accepted while we are closing
|
|
325
|
+
// the existing ones
|
|
326
|
+
this.http?.closeAllConnections()
|
|
327
|
+
this.https?.closeAllConnections()
|
|
328
|
+
|
|
329
|
+
;[...this.sockets].forEach(socket => {
|
|
330
|
+
socket.destroy()
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
await Promise.all([
|
|
334
|
+
pEvent(this.server, 'close'),
|
|
335
|
+
this.http == null ? null : pEvent(this.http, 'close'),
|
|
336
|
+
this.https == null ? null : pEvent(this.https, 'close'),
|
|
337
|
+
pEvent(this.wsServer, 'close')
|
|
338
|
+
])
|
|
339
|
+
|
|
340
|
+
this.safeDispatchEvent('close')
|
|
139
341
|
}
|
|
140
342
|
|
|
141
343
|
getAddrs (): Multiaddr[] {
|
|
142
|
-
const multiaddrs = []
|
|
143
344
|
const address = this.server.address()
|
|
144
345
|
|
|
145
346
|
if (address == null) {
|
|
@@ -154,38 +355,75 @@ class WebSocketListener extends TypedEventEmitter<ListenerEvents> implements Lis
|
|
|
154
355
|
throw new Error('Listener is not ready yet')
|
|
155
356
|
}
|
|
156
357
|
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
// Because TCP will only return the IPv6 version
|
|
161
|
-
// we need to capture from the passed multiaddr
|
|
162
|
-
if (protos.some(proto => proto.code === protocols('ip4').code)) {
|
|
163
|
-
const wsProto = protos.some(proto => proto.code === protocols('ws').code) ? '/ws' : '/wss'
|
|
164
|
-
let m = this.listeningMultiaddr.decapsulate('tcp')
|
|
165
|
-
m = m.encapsulate(`/tcp/${address.port}${wsProto}`)
|
|
166
|
-
if (ipfsId != null) {
|
|
167
|
-
m = m.encapsulate(`/p2p/${ipfsId}`)
|
|
168
|
-
}
|
|
358
|
+
const options = this.listeningMultiaddr.toOptions()
|
|
359
|
+
const multiaddrs: Multiaddr[] = []
|
|
169
360
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Object.values(
|
|
361
|
+
if (options.family === 4) {
|
|
362
|
+
if (options.host === '0.0.0.0') {
|
|
363
|
+
Object.values(os.networkInterfaces()).forEach(niInfos => {
|
|
173
364
|
if (niInfos == null) {
|
|
174
365
|
return
|
|
175
366
|
}
|
|
176
367
|
|
|
177
368
|
niInfos.forEach(ni => {
|
|
178
369
|
if (ni.family === 'IPv4') {
|
|
179
|
-
multiaddrs.push(multiaddr(
|
|
370
|
+
multiaddrs.push(multiaddr(`/ip${options.family}/${ni.address}/${options.transport}/${address.port}`))
|
|
180
371
|
}
|
|
181
372
|
})
|
|
182
373
|
})
|
|
183
374
|
} else {
|
|
184
|
-
multiaddrs.push(
|
|
375
|
+
multiaddrs.push(multiaddr(`/ip${options.family}/${options.host}/${options.transport}/${address.port}`))
|
|
185
376
|
}
|
|
377
|
+
} else if (options.family === 6) {
|
|
378
|
+
if (options.host === '::') {
|
|
379
|
+
Object.values(os.networkInterfaces()).forEach(niInfos => {
|
|
380
|
+
if (niInfos == null) {
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
for (const ni of niInfos) {
|
|
385
|
+
if (ni.family !== 'IPv6') {
|
|
386
|
+
continue
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (isLinkLocalIp(ni.address)) {
|
|
390
|
+
continue
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
multiaddrs.push(multiaddr(`/ip${options.family}/${ni.address}/${options.transport}/${address.port}`))
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
} else {
|
|
397
|
+
multiaddrs.push(multiaddr(`/ip${options.family}/${options.host}/${options.transport}/${address.port}`))
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const insecureMultiaddrs: Multiaddr[] = []
|
|
402
|
+
|
|
403
|
+
if (this.http != null) {
|
|
404
|
+
multiaddrs.forEach(ma => {
|
|
405
|
+
insecureMultiaddrs.push(ma.encapsulate('/ws'))
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const secureMultiaddrs: Multiaddr[] = []
|
|
410
|
+
|
|
411
|
+
if (this.https != null) {
|
|
412
|
+
multiaddrs.forEach(ma => {
|
|
413
|
+
secureMultiaddrs.push(ma.encapsulate('/tls/ws'))
|
|
414
|
+
})
|
|
186
415
|
}
|
|
187
416
|
|
|
188
|
-
return
|
|
417
|
+
return [
|
|
418
|
+
...insecureMultiaddrs,
|
|
419
|
+
...secureMultiaddrs
|
|
420
|
+
]
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private httpRequestHandler (req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
424
|
+
res.writeHead(400)
|
|
425
|
+
res.write('Only WebSocket connections are supported')
|
|
426
|
+
res.end()
|
|
189
427
|
}
|
|
190
428
|
}
|
|
191
429
|
|
package/LICENSE
DELETED