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