libp2p 2.8.14 → 2.9.0-ce6b542a8
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 +15 -15
- package/dist/index.min.js.map +4 -4
- package/dist/src/address-manager/dns-mappings.js +1 -1
- package/dist/src/address-manager/dns-mappings.js.map +1 -1
- package/dist/src/connection-manager/index.js +2 -2
- package/dist/src/connection-manager/index.js.map +1 -1
- package/dist/src/connection.d.ts +58 -0
- package/dist/src/connection.d.ts.map +1 -0
- package/dist/src/connection.js +295 -0
- package/dist/src/connection.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +16 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/upgrader.d.ts +2 -10
- package/dist/src/upgrader.d.ts.map +1 -1
- package/dist/src/upgrader.js +15 -220
- package/dist/src/upgrader.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/package.json +11 -12
- package/src/address-manager/dns-mappings.ts +1 -1
- package/src/connection-manager/index.ts +2 -2
- package/src/connection.ts +395 -0
- package/src/index.ts +20 -0
- package/src/upgrader.ts +20 -281
- package/src/version.ts +1 -1
- package/dist/src/connection/index.d.ts +0 -84
- package/dist/src/connection/index.d.ts.map +0 -1
- package/dist/src/connection/index.js +0 -144
- package/dist/src/connection/index.js.map +0 -1
- package/dist/typedoc-urls.json +0 -22
- package/src/connection/index.ts +0 -199
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { connectionSymbol, LimitedConnectionError, ConnectionClosedError, ConnectionClosingError, TooManyOutboundProtocolStreamsError, TooManyInboundProtocolStreamsError } from '@libp2p/interface'
|
|
2
|
+
import * as mss from '@libp2p/multistream-select'
|
|
3
|
+
import { setMaxListeners } from 'main-event'
|
|
4
|
+
import { PROTOCOL_NEGOTIATION_TIMEOUT } from './connection-manager/constants.defaults.ts'
|
|
5
|
+
import { MuxerUnavailableError } from './errors.ts'
|
|
6
|
+
import { DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS } from './registrar.ts'
|
|
7
|
+
import type { AbortOptions, Logger, Direction, Connection as ConnectionInterface, Stream, ConnectionTimeline, ConnectionStatus, NewStreamOptions, PeerId, ConnectionLimits, StreamMuxerFactory, StreamMuxer, Metrics, PeerStore, MultiaddrConnection } from '@libp2p/interface'
|
|
8
|
+
import type { Registrar } from '@libp2p/interface-internal'
|
|
9
|
+
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
10
|
+
|
|
11
|
+
const CLOSE_TIMEOUT = 500
|
|
12
|
+
|
|
13
|
+
export interface ConnectionComponents {
|
|
14
|
+
peerStore: PeerStore
|
|
15
|
+
registrar: Registrar
|
|
16
|
+
metrics?: Metrics
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ConnectionInit {
|
|
20
|
+
id: string
|
|
21
|
+
maConn: MultiaddrConnection
|
|
22
|
+
remotePeer: PeerId
|
|
23
|
+
direction?: Direction
|
|
24
|
+
muxerFactory?: StreamMuxerFactory
|
|
25
|
+
encryption?: string
|
|
26
|
+
limits?: ConnectionLimits
|
|
27
|
+
outboundStreamProtocolNegotiationTimeout?: number
|
|
28
|
+
inboundStreamProtocolNegotiationTimeout?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* An implementation of the js-libp2p connection.
|
|
33
|
+
* Any libp2p transport should use an upgrader to return this connection.
|
|
34
|
+
*/
|
|
35
|
+
export class Connection implements ConnectionInterface {
|
|
36
|
+
public readonly id: string
|
|
37
|
+
public readonly remoteAddr: Multiaddr
|
|
38
|
+
public readonly remotePeer: PeerId
|
|
39
|
+
public direction: Direction
|
|
40
|
+
public timeline: ConnectionTimeline
|
|
41
|
+
public multiplexer?: string
|
|
42
|
+
public encryption?: string
|
|
43
|
+
public status: ConnectionStatus
|
|
44
|
+
public limits?: ConnectionLimits
|
|
45
|
+
public readonly log: Logger
|
|
46
|
+
public tags: string[]
|
|
47
|
+
|
|
48
|
+
private readonly maConn: MultiaddrConnection
|
|
49
|
+
private readonly muxer?: StreamMuxer
|
|
50
|
+
private readonly components: ConnectionComponents
|
|
51
|
+
private readonly outboundStreamProtocolNegotiationTimeout: number
|
|
52
|
+
private readonly inboundStreamProtocolNegotiationTimeout: number
|
|
53
|
+
|
|
54
|
+
constructor (components: ConnectionComponents, init: ConnectionInit) {
|
|
55
|
+
this.components = components
|
|
56
|
+
|
|
57
|
+
this.id = init.id
|
|
58
|
+
this.remoteAddr = init.maConn.remoteAddr
|
|
59
|
+
this.remotePeer = init.remotePeer
|
|
60
|
+
this.direction = init.direction ?? 'outbound'
|
|
61
|
+
this.status = 'open'
|
|
62
|
+
this.timeline = init.maConn.timeline
|
|
63
|
+
this.encryption = init.encryption
|
|
64
|
+
this.limits = init.limits
|
|
65
|
+
this.maConn = init.maConn
|
|
66
|
+
this.log = init.maConn.log
|
|
67
|
+
this.outboundStreamProtocolNegotiationTimeout = init.outboundStreamProtocolNegotiationTimeout ?? PROTOCOL_NEGOTIATION_TIMEOUT
|
|
68
|
+
this.inboundStreamProtocolNegotiationTimeout = init.inboundStreamProtocolNegotiationTimeout ?? PROTOCOL_NEGOTIATION_TIMEOUT
|
|
69
|
+
|
|
70
|
+
if (this.remoteAddr.getPeerId() == null) {
|
|
71
|
+
this.remoteAddr = this.remoteAddr.encapsulate(`/p2p/${this.remotePeer}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.tags = []
|
|
75
|
+
|
|
76
|
+
if (init.muxerFactory != null) {
|
|
77
|
+
this.multiplexer = init.muxerFactory.protocol
|
|
78
|
+
|
|
79
|
+
this.muxer = init.muxerFactory.createStreamMuxer({
|
|
80
|
+
direction: this.direction,
|
|
81
|
+
log: this.log,
|
|
82
|
+
// Run anytime a remote stream is created
|
|
83
|
+
onIncomingStream: (stream) => {
|
|
84
|
+
this.onIncomingStream(stream)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Pipe all data through the muxer
|
|
89
|
+
void Promise.all([
|
|
90
|
+
this.muxer.sink(this.maConn.source),
|
|
91
|
+
this.maConn.sink(this.muxer.source)
|
|
92
|
+
]).catch(err => {
|
|
93
|
+
this.log.error('error piping data through muxer - %e', err)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
readonly [Symbol.toStringTag] = 'Connection'
|
|
99
|
+
|
|
100
|
+
readonly [connectionSymbol] = true
|
|
101
|
+
|
|
102
|
+
get streams (): Stream[] {
|
|
103
|
+
return this.muxer?.streams ?? []
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create a new stream over this connection
|
|
108
|
+
*/
|
|
109
|
+
newStream = async (protocols: string[], options: NewStreamOptions = {}): Promise<Stream> => {
|
|
110
|
+
if (this.status === 'closing') {
|
|
111
|
+
throw new ConnectionClosingError('the connection is being closed')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (this.status === 'closed') {
|
|
115
|
+
throw new ConnectionClosedError('the connection is closed')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!Array.isArray(protocols)) {
|
|
119
|
+
protocols = [protocols]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (this.limits != null && options?.runOnLimitedConnection !== true) {
|
|
123
|
+
throw new LimitedConnectionError('Cannot open protocol stream on limited connection')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (this.muxer == null) {
|
|
127
|
+
throw new MuxerUnavailableError('Connection is not multiplexed')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.log.trace('starting new stream for protocols %s', protocols)
|
|
131
|
+
const muxedStream = await this.muxer.newStream()
|
|
132
|
+
this.log.trace('started new stream %s for protocols %s', muxedStream.id, protocols)
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
if (options.signal == null) {
|
|
136
|
+
muxedStream.log('no abort signal was passed while trying to negotiate protocols %s falling back to default timeout', protocols)
|
|
137
|
+
|
|
138
|
+
const signal = AbortSignal.timeout(this.outboundStreamProtocolNegotiationTimeout)
|
|
139
|
+
setMaxListeners(Infinity, signal)
|
|
140
|
+
|
|
141
|
+
options = {
|
|
142
|
+
...options,
|
|
143
|
+
signal
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
muxedStream.log.trace('selecting protocol from protocols %s', protocols)
|
|
148
|
+
|
|
149
|
+
const {
|
|
150
|
+
stream,
|
|
151
|
+
protocol
|
|
152
|
+
} = await mss.select(muxedStream, protocols, {
|
|
153
|
+
...options,
|
|
154
|
+
log: muxedStream.log,
|
|
155
|
+
yieldBytes: true
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
muxedStream.log('selected protocol %s', protocol)
|
|
159
|
+
|
|
160
|
+
const outgoingLimit = findOutgoingStreamLimit(protocol, this.components.registrar, options)
|
|
161
|
+
const streamCount = countStreams(protocol, 'outbound', this)
|
|
162
|
+
|
|
163
|
+
if (streamCount >= outgoingLimit) {
|
|
164
|
+
const err = new TooManyOutboundProtocolStreamsError(`Too many outbound protocol streams for protocol "${protocol}" - ${streamCount}/${outgoingLimit}`)
|
|
165
|
+
muxedStream.abort(err)
|
|
166
|
+
|
|
167
|
+
throw err
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If a protocol stream has been successfully negotiated and is to be passed to the application,
|
|
171
|
+
// the peer store should ensure that the peer is registered with that protocol
|
|
172
|
+
await this.components.peerStore.merge(this.remotePeer, {
|
|
173
|
+
protocols: [protocol]
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// after the handshake the returned stream can have early data so override
|
|
177
|
+
// the source/sink
|
|
178
|
+
muxedStream.source = stream.source
|
|
179
|
+
muxedStream.sink = stream.sink
|
|
180
|
+
muxedStream.protocol = protocol
|
|
181
|
+
|
|
182
|
+
// allow closing the write end of a not-yet-negotiated stream
|
|
183
|
+
if (stream.closeWrite != null) {
|
|
184
|
+
muxedStream.closeWrite = stream.closeWrite
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// allow closing the read end of a not-yet-negotiated stream
|
|
188
|
+
if (stream.closeRead != null) {
|
|
189
|
+
muxedStream.closeRead = stream.closeRead
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// make sure we don't try to negotiate a stream we are closing
|
|
193
|
+
if (stream.close != null) {
|
|
194
|
+
muxedStream.close = stream.close
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.components.metrics?.trackProtocolStream(muxedStream, this)
|
|
198
|
+
|
|
199
|
+
muxedStream.direction = 'outbound'
|
|
200
|
+
|
|
201
|
+
return muxedStream
|
|
202
|
+
} catch (err: any) {
|
|
203
|
+
this.log.error('could not create new outbound stream on connection %s %a for protocols %s - %e', this.direction === 'inbound' ? 'from' : 'to', this.remoteAddr, protocols, err)
|
|
204
|
+
|
|
205
|
+
if (muxedStream.timeline.close == null) {
|
|
206
|
+
muxedStream.abort(err)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
throw err
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private onIncomingStream (muxedStream: Stream): void {
|
|
214
|
+
const signal = AbortSignal.timeout(this.inboundStreamProtocolNegotiationTimeout)
|
|
215
|
+
setMaxListeners(Infinity, signal)
|
|
216
|
+
|
|
217
|
+
void Promise.resolve()
|
|
218
|
+
.then(async () => {
|
|
219
|
+
const protocols = this.components.registrar.getProtocols()
|
|
220
|
+
|
|
221
|
+
const { stream, protocol } = await mss.handle(muxedStream, protocols, {
|
|
222
|
+
signal,
|
|
223
|
+
log: muxedStream.log,
|
|
224
|
+
yieldBytes: false
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
this.log('incoming %s stream opened', protocol)
|
|
228
|
+
|
|
229
|
+
const incomingLimit = findIncomingStreamLimit(protocol, this.components.registrar)
|
|
230
|
+
const streamCount = countStreams(protocol, 'inbound', this)
|
|
231
|
+
|
|
232
|
+
if (streamCount === incomingLimit) {
|
|
233
|
+
const err = new TooManyInboundProtocolStreamsError(`Too many inbound protocol streams for protocol "${protocol}" - limit ${incomingLimit}`)
|
|
234
|
+
muxedStream.abort(err)
|
|
235
|
+
|
|
236
|
+
throw err
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// after the handshake the returned stream can have early data so override
|
|
240
|
+
// the source/sink
|
|
241
|
+
muxedStream.source = stream.source
|
|
242
|
+
muxedStream.sink = stream.sink
|
|
243
|
+
muxedStream.protocol = protocol
|
|
244
|
+
|
|
245
|
+
// allow closing the write end of a not-yet-negotiated stream
|
|
246
|
+
if (stream.closeWrite != null) {
|
|
247
|
+
muxedStream.closeWrite = stream.closeWrite
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// allow closing the read end of a not-yet-negotiated stream
|
|
251
|
+
if (stream.closeRead != null) {
|
|
252
|
+
muxedStream.closeRead = stream.closeRead
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// make sure we don't try to negotiate a stream we are closing
|
|
256
|
+
if (stream.close != null) {
|
|
257
|
+
muxedStream.close = stream.close
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// If a protocol stream has been successfully negotiated and is to be passed to the application,
|
|
261
|
+
// the peer store should ensure that the peer is registered with that protocol
|
|
262
|
+
await this.components.peerStore.merge(this.remotePeer, {
|
|
263
|
+
protocols: [protocol]
|
|
264
|
+
}, {
|
|
265
|
+
signal
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
this.components.metrics?.trackProtocolStream(muxedStream, this)
|
|
269
|
+
|
|
270
|
+
const { handler, options } = this.components.registrar.getHandler(protocol)
|
|
271
|
+
|
|
272
|
+
if (this.limits != null && options.runOnLimitedConnection !== true) {
|
|
273
|
+
throw new LimitedConnectionError('Cannot open protocol stream on limited connection')
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
handler({ connection: this, stream: muxedStream })
|
|
277
|
+
})
|
|
278
|
+
.catch(async err => {
|
|
279
|
+
this.log.error('error handling incoming stream id %s - %e', muxedStream.id, err)
|
|
280
|
+
|
|
281
|
+
if (muxedStream.timeline.close == null) {
|
|
282
|
+
await muxedStream.close({
|
|
283
|
+
signal
|
|
284
|
+
})
|
|
285
|
+
.catch(err => muxedStream.abort(err))
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Close the connection
|
|
292
|
+
*/
|
|
293
|
+
async close (options: AbortOptions = {}): Promise<void> {
|
|
294
|
+
if (this.status === 'closed' || this.status === 'closing') {
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.log('closing connection to %a', this.remoteAddr)
|
|
299
|
+
|
|
300
|
+
this.status = 'closing'
|
|
301
|
+
|
|
302
|
+
if (options.signal == null) {
|
|
303
|
+
const signal = AbortSignal.timeout(CLOSE_TIMEOUT)
|
|
304
|
+
setMaxListeners(Infinity, signal)
|
|
305
|
+
|
|
306
|
+
options = {
|
|
307
|
+
...options,
|
|
308
|
+
signal
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
this.log.trace('closing underlying transport')
|
|
314
|
+
|
|
315
|
+
// ensure remaining streams are closed gracefully
|
|
316
|
+
await this.muxer?.close(options)
|
|
317
|
+
|
|
318
|
+
// close the underlying transport
|
|
319
|
+
await this.maConn.close(options)
|
|
320
|
+
|
|
321
|
+
this.log.trace('updating timeline with close time')
|
|
322
|
+
|
|
323
|
+
this.status = 'closed'
|
|
324
|
+
this.timeline.close = Date.now()
|
|
325
|
+
} catch (err: any) {
|
|
326
|
+
this.log.error('error encountered during graceful close of connection to %a', this.remoteAddr, err)
|
|
327
|
+
this.abort(err)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
abort (err: Error): void {
|
|
332
|
+
if (this.status === 'closed') {
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.log.error('aborting connection to %a due to error', this.remoteAddr, err)
|
|
337
|
+
|
|
338
|
+
this.status = 'closing'
|
|
339
|
+
|
|
340
|
+
// ensure remaining streams are aborted
|
|
341
|
+
this.muxer?.abort(err)
|
|
342
|
+
|
|
343
|
+
// abort the underlying transport
|
|
344
|
+
this.maConn.abort(err)
|
|
345
|
+
|
|
346
|
+
this.status = 'closed'
|
|
347
|
+
this.timeline.close = Date.now()
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function createConnection (components: ConnectionComponents, init: ConnectionInit): ConnectionInterface {
|
|
352
|
+
return new Connection(components, init)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function findIncomingStreamLimit (protocol: string, registrar: Registrar): number | undefined {
|
|
356
|
+
try {
|
|
357
|
+
const { options } = registrar.getHandler(protocol)
|
|
358
|
+
|
|
359
|
+
return options.maxInboundStreams
|
|
360
|
+
} catch (err: any) {
|
|
361
|
+
if (err.name !== 'UnhandledProtocolError') {
|
|
362
|
+
throw err
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return DEFAULT_MAX_INBOUND_STREAMS
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function findOutgoingStreamLimit (protocol: string, registrar: Registrar, options: NewStreamOptions = {}): number {
|
|
370
|
+
try {
|
|
371
|
+
const { options } = registrar.getHandler(protocol)
|
|
372
|
+
|
|
373
|
+
if (options.maxOutboundStreams != null) {
|
|
374
|
+
return options.maxOutboundStreams
|
|
375
|
+
}
|
|
376
|
+
} catch (err: any) {
|
|
377
|
+
if (err.name !== 'UnhandledProtocolError') {
|
|
378
|
+
throw err
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return options.maxOutboundStreams ?? DEFAULT_MAX_OUTBOUND_STREAMS
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function countStreams (protocol: string, direction: 'inbound' | 'outbound', connection: Connection): number {
|
|
386
|
+
let streamCount = 0
|
|
387
|
+
|
|
388
|
+
connection.streams.forEach(stream => {
|
|
389
|
+
if (stream.direction === direction && stream.protocol === protocol) {
|
|
390
|
+
streamCount++
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
return streamCount
|
|
395
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -212,3 +212,23 @@ export async function createLibp2p <T extends ServiceMap = ServiceMap> (options:
|
|
|
212
212
|
|
|
213
213
|
return node
|
|
214
214
|
}
|
|
215
|
+
|
|
216
|
+
// a non-exhaustive list of methods found on the libp2p object
|
|
217
|
+
const LIBP2P_METHODS = ['dial', 'dialProtocol', 'hangUp', 'handle', 'unhandle', 'getMultiaddrs', 'getProtocols']
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Returns true if the passed object is a libp2p node - this can be used for
|
|
221
|
+
* type guarding in TypeScript.
|
|
222
|
+
*/
|
|
223
|
+
export function isLibp2p <T extends ServiceMap = ServiceMap> (obj?: any): obj is Libp2p<T> {
|
|
224
|
+
if (obj == null) {
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (obj instanceof Libp2pClass) {
|
|
229
|
+
return true
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// if these are all functions it's probably a libp2p object
|
|
233
|
+
return LIBP2P_METHODS.every(m => typeof obj[m] === 'function')
|
|
234
|
+
}
|