@libp2p/utils 4.0.7-9c67c5b3d → 4.0.7-adea7bbbf

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.
@@ -1 +1 @@
1
- {"version":3,"file":"stream-to-ma-conn.js","sourceRoot":"","sources":["../../src/stream-to-ma-conn.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAE,KAAuB;IAC3D,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAC5C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;IAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,yBAAyB,CAAC,CAAA;IAE1D,IAAI,UAAU,GAAG,KAAK,CAAA;IACtB,IAAI,WAAW,GAAG,KAAK,CAAA;IAEvB,MAAM,SAAS,GAAG,CAAC,KAAK,SAAU,CAAC;QACjC,IAAI;YACF,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,EAAE;gBAC/B,IAAI,IAAI,YAAY,UAAU,EAAE;oBAC9B,MAAM,IAAI,CAAA;iBACX;qBAAM;oBACL,KAAM,CAAC,CAAC,IAAI,CAAA;iBACb;aACF;SACF;gBAAS;YACR,UAAU,GAAG,IAAI,CAAA;YACjB,KAAK,EAAE,CAAA;SACR;IACH,CAAC,EAAE,CAAC,CAAA;IAEJ,MAAM,MAAM,GAAwB;QAClC,KAAK,CAAC,IAAI,CAAE,MAAM;YAChB,IAAI;gBACF,MAAM,IAAI,CAAC,MAAM,CAAC,CAAA;aACnB;YAAC,OAAO,GAAQ,EAAE;gBACjB,kCAAkC;gBAClC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE;oBAC1B,uEAAuE;oBACvE,gEAAgE;oBAChE,uEAAuE;oBACvE,GAAG,CAAC,GAAG,CAAC,CAAA;iBACT;aACF;oBAAS;gBACR,WAAW,GAAG,IAAI,CAAA;gBAClB,KAAK,EAAE,CAAA;aACR;QACH,CAAC;QACD,MAAM,EAAE,SAAS;QACjB,UAAU;QACV,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;QAChD,KAAK,CAAC,KAAK,CAAE,OAAsB;YACjC,KAAK,CAAC,IAAI,CAAC,CAAA;YACX,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC7B,CAAC;QACD,KAAK,CAAE,GAAU;YACf,KAAK,CAAC,IAAI,CAAC,CAAA;YACX,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACnB,CAAC;KACF,CAAA;IAED,SAAS,KAAK,CAAE,KAAe;QAC7B,IAAI,KAAK,KAAK,IAAI,EAAE;YAClB,UAAU,GAAG,IAAI,CAAA;YACjB,WAAW,GAAG,IAAI,CAAA;SACnB;QAED,IAAI,UAAU,IAAI,WAAW,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE;YAC9D,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;SACnC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"stream-to-ma-conn.js","sourceRoot":"","sources":["../../src/stream-to-ma-conn.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAE,KAAuB;IAC3D,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,yBAAyB,CAAC,CAAA;IAE1D,IAAI,UAAU,GAAG,KAAK,CAAA;IACtB,IAAI,WAAW,GAAG,KAAK,CAAA;IAEvB,0DAA0D;IAC1D,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7C,MAAM,CAAC,KAAK,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE;QAC/B,MAAM,WAAW,CAAC,OAAO,CAAC,CAAA;QAC1B,KAAK,CAAC,IAAI,CAAC,CAAA;IACb,CAAC,CAAA;IAED,0DAA0D;IAC1D,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7C,MAAM,CAAC,KAAK,GAAG,CAAC,GAAG,EAAE,EAAE;QACrB,WAAW,CAAC,GAAG,CAAC,CAAA;QAChB,KAAK,CAAC,IAAI,CAAC,CAAA;IACb,CAAC,CAAA;IAED,yDAAyD;IACzD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC3C,MAAM,CAAC,IAAI,GAAG,KAAK,EAAE,MAAM,EAAE,EAAE;QAC7B,IAAI;YACF,MAAM,UAAU,CAAC,MAAM,CAAC,CAAA;SACzB;QAAC,OAAO,GAAQ,EAAE;YACjB,kCAAkC;YAClC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE;gBAC1B,uEAAuE;gBACvE,gEAAgE;gBAChE,uEAAuE;gBACvE,GAAG,CAAC,GAAG,CAAC,CAAA;aACT;SACF;gBAAS;YACR,WAAW,GAAG,IAAI,CAAA;YAClB,KAAK,EAAE,CAAA;SACR;IACH,CAAC,CAAA;IAED,MAAM,MAAM,GAAwB;QAClC,GAAG;QACH,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,CAAC,KAAK,SAAU,CAAC;YACvB,IAAI;gBACF,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE;oBACtC,IAAI,IAAI,YAAY,UAAU,EAAE;wBAC9B,MAAM,IAAI,CAAA;qBACX;yBAAM;wBACL,KAAM,CAAC,CAAC,IAAI,CAAA;qBACb;iBACF;aACF;oBAAS;gBACR,UAAU,GAAG,IAAI,CAAA;gBACjB,KAAK,EAAE,CAAA;aACR;QACH,CAAC,EAAE,CAAC;QACJ,UAAU;QACV,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;QAChD,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAA;IAED,SAAS,KAAK,CAAE,KAAe;QAC7B,IAAI,KAAK,KAAK,IAAI,EAAE;YAClB,UAAU,GAAG,IAAI,CAAA;YACjB,WAAW,GAAG,IAAI,CAAA;SACnB;QAED,IAAI,UAAU,IAAI,WAAW,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE;YAC9D,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;SACnC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/utils",
3
- "version": "4.0.7-9c67c5b3d",
3
+ "version": "4.0.7-adea7bbbf",
4
4
  "description": "Package to aggregate shared logic and dependencies for the libp2p ecosystem",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/libp2p/js-libp2p/tree/master/packages/utils#readme",
@@ -40,6 +40,10 @@
40
40
  "types": "./src/index.d.ts",
41
41
  "import": "./dist/src/index.js"
42
42
  },
43
+ "./abstract-stream": {
44
+ "types": "./dist/src/abstract-stream.d.ts",
45
+ "import": "./dist/src/abstract-stream.js"
46
+ },
43
47
  "./address-sort": {
44
48
  "types": "./dist/src/address-sort.d.ts",
45
49
  "import": "./dist/src/address-sort.js"
@@ -48,10 +52,18 @@
48
52
  "types": "./dist/src/array-equals.d.ts",
49
53
  "import": "./dist/src/array-equals.js"
50
54
  },
55
+ "./close-source": {
56
+ "types": "./dist/src/close-source.d.ts",
57
+ "import": "./dist/src/close-source.js"
58
+ },
51
59
  "./ip-port-to-multiaddr": {
52
60
  "types": "./dist/src/ip-port-to-multiaddr.d.ts",
53
61
  "import": "./dist/src/ip-port-to-multiaddr.js"
54
62
  },
63
+ "./is-promise": {
64
+ "types": "./dist/src/is-promise.d.ts",
65
+ "import": "./dist/src/is-promise.js"
66
+ },
55
67
  "./multiaddr/is-loopback": {
56
68
  "types": "./dist/src/multiaddr/is-loopback.d.ts",
57
69
  "import": "./dist/src/multiaddr/is-loopback.js"
@@ -91,23 +103,30 @@
91
103
  },
92
104
  "dependencies": {
93
105
  "@chainsafe/is-ip": "^2.0.2",
94
- "@libp2p/interface": "0.1.6-9c67c5b3d",
106
+ "@libp2p/interface": "0.1.6-adea7bbbf",
95
107
  "@multiformats/multiaddr": "^12.1.10",
96
108
  "@multiformats/multiaddr-matcher": "^1.1.0",
109
+ "get-iterator": "^2.0.1",
97
110
  "is-loopback-addr": "^2.0.1",
111
+ "it-pushable": "^3.2.2",
98
112
  "it-stream-types": "^2.0.1",
99
113
  "p-queue": "^7.4.1",
100
114
  "private-ip": "^3.0.1",
115
+ "race-signal": "^1.0.1",
101
116
  "uint8arraylist": "^2.4.3"
102
117
  },
103
118
  "devDependencies": {
104
- "@libp2p/logger": "3.1.0-9c67c5b3d",
105
- "@libp2p/peer-id-factory": "3.0.8-9c67c5b3d",
119
+ "@libp2p/logger": "3.1.0-adea7bbbf",
120
+ "@libp2p/peer-id-factory": "3.0.8-adea7bbbf",
106
121
  "aegir": "^41.0.2",
122
+ "delay": "^6.0.0",
107
123
  "it-all": "^3.0.3",
124
+ "it-drain": "^3.0.5",
108
125
  "it-pair": "^2.0.6",
109
126
  "it-pipe": "^3.0.1",
110
127
  "p-defer": "^4.0.0",
128
+ "sinon": "^17.0.1",
129
+ "sinon-ts": "^2.0.0",
111
130
  "uint8arrays": "^4.0.6"
112
131
  }
113
132
  }
@@ -0,0 +1,500 @@
1
+ import { CodeError } from '@libp2p/interface/errors'
2
+ import { type Pushable, pushable } from 'it-pushable'
3
+ import defer, { type DeferredPromise } from 'p-defer'
4
+ import { raceSignal } from 'race-signal'
5
+ import { Uint8ArrayList } from 'uint8arraylist'
6
+ import { closeSource } from './close-source.js'
7
+ import type { AbortOptions } from '@libp2p/interface'
8
+ import type { Direction, ReadStatus, Stream, StreamStatus, StreamTimeline, WriteStatus } from '@libp2p/interface/connection'
9
+ import type { Logger } from '@libp2p/logger'
10
+ import type { Source } from 'it-stream-types'
11
+
12
+ const ERR_STREAM_RESET = 'ERR_STREAM_RESET'
13
+ const ERR_SINK_INVALID_STATE = 'ERR_SINK_INVALID_STATE'
14
+ const DEFAULT_SEND_CLOSE_WRITE_TIMEOUT = 5000
15
+
16
+ export interface AbstractStreamInit {
17
+ /**
18
+ * A unique identifier for this stream
19
+ */
20
+ id: string
21
+
22
+ /**
23
+ * The stream direction
24
+ */
25
+ direction: Direction
26
+
27
+ /**
28
+ * A Logger implementation used to log stream-specific information
29
+ */
30
+ log: Logger
31
+
32
+ /**
33
+ * User specific stream metadata
34
+ */
35
+ metadata?: Record<string, unknown>
36
+
37
+ /**
38
+ * Invoked when the stream ends
39
+ */
40
+ onEnd?(err?: Error | undefined): void
41
+
42
+ /**
43
+ * Invoked when the readable end of the stream is closed
44
+ */
45
+ onCloseRead?(): void
46
+
47
+ /**
48
+ * Invoked when the writable end of the stream is closed
49
+ */
50
+ onCloseWrite?(): void
51
+
52
+ /**
53
+ * Invoked when the the stream has been reset by the remote
54
+ */
55
+ onReset?(): void
56
+
57
+ /**
58
+ * Invoked when the the stream has errored
59
+ */
60
+ onAbort?(err: Error): void
61
+
62
+ /**
63
+ * How long to wait in ms for stream data to be written to the underlying
64
+ * connection when closing the writable end of the stream. (default: 500)
65
+ */
66
+ closeTimeout?: number
67
+
68
+ /**
69
+ * After the stream sink has closed, a limit on how long it takes to send
70
+ * a close-write message to the remote peer.
71
+ */
72
+ sendCloseWriteTimeout?: number
73
+ }
74
+
75
+ function isPromise <T = unknown> (thing: any): thing is Promise<T> {
76
+ if (thing == null) {
77
+ return false
78
+ }
79
+
80
+ return typeof thing.then === 'function' &&
81
+ typeof thing.catch === 'function' &&
82
+ typeof thing.finally === 'function'
83
+ }
84
+
85
+ export abstract class AbstractStream implements Stream {
86
+ public id: string
87
+ public direction: Direction
88
+ public timeline: StreamTimeline
89
+ public protocol?: string
90
+ public metadata: Record<string, unknown>
91
+ public source: AsyncGenerator<Uint8ArrayList, void, unknown>
92
+ public status: StreamStatus
93
+ public readStatus: ReadStatus
94
+ public writeStatus: WriteStatus
95
+ public readonly log: Logger
96
+
97
+ private readonly sinkController: AbortController
98
+ private readonly sinkEnd: DeferredPromise<void>
99
+ private endErr: Error | undefined
100
+ private readonly streamSource: Pushable<Uint8ArrayList>
101
+ private readonly onEnd?: (err?: Error | undefined) => void
102
+ private readonly onCloseRead?: () => void
103
+ private readonly onCloseWrite?: () => void
104
+ private readonly onReset?: () => void
105
+ private readonly onAbort?: (err: Error) => void
106
+ private readonly sendCloseWriteTimeout: number
107
+
108
+ constructor (init: AbstractStreamInit) {
109
+ this.sinkController = new AbortController()
110
+ this.sinkEnd = defer()
111
+ this.log = init.log
112
+
113
+ // stream status
114
+ this.status = 'open'
115
+ this.readStatus = 'ready'
116
+ this.writeStatus = 'ready'
117
+
118
+ this.id = init.id
119
+ this.metadata = init.metadata ?? {}
120
+ this.direction = init.direction
121
+ this.timeline = {
122
+ open: Date.now()
123
+ }
124
+ this.sendCloseWriteTimeout = init.sendCloseWriteTimeout ?? DEFAULT_SEND_CLOSE_WRITE_TIMEOUT
125
+
126
+ this.onEnd = init.onEnd
127
+ this.onCloseRead = init?.onCloseRead
128
+ this.onCloseWrite = init?.onCloseWrite
129
+ this.onReset = init?.onReset
130
+ this.onAbort = init?.onAbort
131
+
132
+ this.source = this.streamSource = pushable<Uint8ArrayList>({
133
+ onEnd: (err) => {
134
+ if (err != null) {
135
+ this.log.trace('source ended with error', err)
136
+ } else {
137
+ this.log.trace('source ended')
138
+ }
139
+
140
+ this.onSourceEnd(err)
141
+ }
142
+ })
143
+
144
+ // necessary because the libp2p upgrader wraps the sink function
145
+ this.sink = this.sink.bind(this)
146
+ }
147
+
148
+ async sink (source: Source<Uint8ArrayList | Uint8Array>): Promise<void> {
149
+ if (this.writeStatus !== 'ready') {
150
+ throw new CodeError(`writable end state is "${this.writeStatus}" not "ready"`, ERR_SINK_INVALID_STATE)
151
+ }
152
+
153
+ try {
154
+ this.writeStatus = 'writing'
155
+
156
+ const options: AbortOptions = {
157
+ signal: this.sinkController.signal
158
+ }
159
+
160
+ if (this.direction === 'outbound') { // If initiator, open a new stream
161
+ const res = this.sendNewStream(options)
162
+
163
+ if (isPromise(res)) {
164
+ await res
165
+ }
166
+ }
167
+
168
+ const abortListener = (): void => {
169
+ closeSource(source, this.log)
170
+ }
171
+
172
+ try {
173
+ this.sinkController.signal.addEventListener('abort', abortListener)
174
+
175
+ this.log.trace('sink reading from source')
176
+
177
+ for await (let data of source) {
178
+ data = data instanceof Uint8Array ? new Uint8ArrayList(data) : data
179
+
180
+ const res = this.sendData(data, options)
181
+
182
+ if (isPromise(res)) { // eslint-disable-line max-depth
183
+ await res
184
+ }
185
+ }
186
+ } finally {
187
+ this.sinkController.signal.removeEventListener('abort', abortListener)
188
+ }
189
+
190
+ this.log.trace('sink finished reading from source, write status is "%s"', this.writeStatus)
191
+
192
+ if (this.writeStatus === 'writing') {
193
+ this.writeStatus = 'closing'
194
+
195
+ this.log.trace('send close write to remote')
196
+ await this.sendCloseWrite({
197
+ signal: AbortSignal.timeout(this.sendCloseWriteTimeout)
198
+ })
199
+
200
+ this.writeStatus = 'closed'
201
+ }
202
+
203
+ this.onSinkEnd()
204
+ } catch (err: any) {
205
+ this.log.trace('sink ended with error, calling abort with error', err)
206
+ this.abort(err)
207
+
208
+ throw err
209
+ } finally {
210
+ this.log.trace('resolve sink end')
211
+ this.sinkEnd.resolve()
212
+ }
213
+ }
214
+
215
+ protected onSourceEnd (err?: Error): void {
216
+ if (this.timeline.closeRead != null) {
217
+ return
218
+ }
219
+
220
+ this.timeline.closeRead = Date.now()
221
+ this.readStatus = 'closed'
222
+
223
+ if (err != null && this.endErr == null) {
224
+ this.endErr = err
225
+ }
226
+
227
+ this.onCloseRead?.()
228
+
229
+ if (this.timeline.closeWrite != null) {
230
+ this.log.trace('source and sink ended')
231
+ this.timeline.close = Date.now()
232
+
233
+ if (this.status !== 'aborted' && this.status !== 'reset') {
234
+ this.status = 'closed'
235
+ }
236
+
237
+ if (this.onEnd != null) {
238
+ this.onEnd(this.endErr)
239
+ }
240
+ } else {
241
+ this.log.trace('source ended, waiting for sink to end')
242
+ }
243
+ }
244
+
245
+ protected onSinkEnd (err?: Error): void {
246
+ if (this.timeline.closeWrite != null) {
247
+ return
248
+ }
249
+
250
+ this.timeline.closeWrite = Date.now()
251
+ this.writeStatus = 'closed'
252
+
253
+ if (err != null && this.endErr == null) {
254
+ this.endErr = err
255
+ }
256
+
257
+ this.onCloseWrite?.()
258
+
259
+ if (this.timeline.closeRead != null) {
260
+ this.log.trace('sink and source ended')
261
+ this.timeline.close = Date.now()
262
+
263
+ if (this.status !== 'aborted' && this.status !== 'reset') {
264
+ this.status = 'closed'
265
+ }
266
+
267
+ if (this.onEnd != null) {
268
+ this.onEnd(this.endErr)
269
+ }
270
+ } else {
271
+ this.log.trace('sink ended, waiting for source to end')
272
+ }
273
+ }
274
+
275
+ // Close for both Reading and Writing
276
+ async close (options?: AbortOptions): Promise<void> {
277
+ this.log.trace('closing gracefully')
278
+
279
+ this.status = 'closing'
280
+
281
+ await Promise.all([
282
+ this.closeRead(options),
283
+ this.closeWrite(options)
284
+ ])
285
+
286
+ this.status = 'closed'
287
+
288
+ this.log.trace('closed gracefully')
289
+ }
290
+
291
+ async closeRead (options: AbortOptions = {}): Promise<void> {
292
+ if (this.readStatus === 'closing' || this.readStatus === 'closed') {
293
+ return
294
+ }
295
+
296
+ this.log.trace('closing readable end of stream with starting read status "%s"', this.readStatus)
297
+
298
+ const readStatus = this.readStatus
299
+ this.readStatus = 'closing'
300
+
301
+ if (this.status !== 'reset' && this.status !== 'aborted' && this.timeline.closeRead == null) {
302
+ this.log.trace('send close read to remote')
303
+ await this.sendCloseRead(options)
304
+ }
305
+
306
+ if (readStatus === 'ready') {
307
+ this.log.trace('ending internal source queue with %d queued bytes', this.streamSource.readableLength)
308
+ this.streamSource.end()
309
+ }
310
+
311
+ this.log.trace('closed readable end of stream')
312
+ }
313
+
314
+ async closeWrite (options: AbortOptions = {}): Promise<void> {
315
+ if (this.writeStatus === 'closing' || this.writeStatus === 'closed') {
316
+ return
317
+ }
318
+
319
+ this.log.trace('closing writable end of stream with starting write status "%s"', this.writeStatus)
320
+
321
+ if (this.writeStatus === 'ready') {
322
+ this.log.trace('sink was never sunk, sink an empty array')
323
+
324
+ await raceSignal(this.sink([]), options.signal)
325
+ }
326
+
327
+ if (this.writeStatus === 'writing') {
328
+ // stop reading from the source passed to `.sink` in the microtask queue
329
+ // - this lets any data queued by the user in the current tick get read
330
+ // before we exit
331
+ await new Promise((resolve, reject) => {
332
+ queueMicrotask(() => {
333
+ this.log.trace('aborting source passed to .sink')
334
+ this.sinkController.abort()
335
+ raceSignal(this.sinkEnd.promise, options.signal)
336
+ .then(resolve, reject)
337
+ })
338
+ })
339
+ }
340
+
341
+ this.writeStatus = 'closed'
342
+
343
+ this.log.trace('closed writable end of stream')
344
+ }
345
+
346
+ /**
347
+ * Close immediately for reading and writing and send a reset message (local
348
+ * error)
349
+ */
350
+ abort (err: Error): void {
351
+ if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
352
+ return
353
+ }
354
+
355
+ this.log('abort with error', err)
356
+
357
+ // try to send a reset message
358
+ this.log('try to send reset to remote')
359
+ const res = this.sendReset()
360
+
361
+ if (isPromise(res)) {
362
+ res.catch((err) => {
363
+ this.log.error('error sending reset message', err)
364
+ })
365
+ }
366
+
367
+ this.status = 'aborted'
368
+ this.timeline.abort = Date.now()
369
+ this._closeSinkAndSource(err)
370
+ this.onAbort?.(err)
371
+ }
372
+
373
+ /**
374
+ * Receive a reset message - close immediately for reading and writing (remote
375
+ * error)
376
+ */
377
+ reset (): void {
378
+ if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
379
+ return
380
+ }
381
+
382
+ const err = new CodeError('stream reset', ERR_STREAM_RESET)
383
+
384
+ this.status = 'reset'
385
+ this.timeline.reset = Date.now()
386
+ this._closeSinkAndSource(err)
387
+ this.onReset?.()
388
+ }
389
+
390
+ _closeSinkAndSource (err?: Error): void {
391
+ this._closeSink(err)
392
+ this._closeSource(err)
393
+ }
394
+
395
+ _closeSink (err?: Error): void {
396
+ // if the sink function is running, cause it to end
397
+ if (this.writeStatus === 'writing') {
398
+ this.log.trace('end sink source')
399
+ this.sinkController.abort()
400
+ }
401
+
402
+ this.onSinkEnd(err)
403
+ }
404
+
405
+ _closeSource (err?: Error): void {
406
+ // if the source is not ending, end it
407
+ if (this.readStatus !== 'closing' && this.readStatus !== 'closed') {
408
+ this.log.trace('ending source with %d bytes to be read by consumer', this.streamSource.readableLength)
409
+ this.readStatus = 'closing'
410
+ this.streamSource.end(err)
411
+ }
412
+ }
413
+
414
+ /**
415
+ * The remote closed for writing so we should expect to receive no more
416
+ * messages
417
+ */
418
+ remoteCloseWrite (): void {
419
+ if (this.readStatus === 'closing' || this.readStatus === 'closed') {
420
+ this.log('received remote close write but local source is already closed')
421
+ return
422
+ }
423
+
424
+ this.log.trace('remote close write')
425
+ this._closeSource()
426
+ }
427
+
428
+ /**
429
+ * The remote closed for reading so we should not send any more
430
+ * messages
431
+ */
432
+ remoteCloseRead (): void {
433
+ if (this.writeStatus === 'closing' || this.writeStatus === 'closed') {
434
+ this.log('received remote close read but local sink is already closed')
435
+ return
436
+ }
437
+
438
+ this.log.trace('remote close read')
439
+ this._closeSink()
440
+ }
441
+
442
+ /**
443
+ * The underlying muxer has closed, no more messages can be sent or will
444
+ * be received, close immediately to free up resources
445
+ */
446
+ destroy (): void {
447
+ if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
448
+ this.log('received destroy but we are already closed')
449
+ return
450
+ }
451
+
452
+ this.log.trace('stream destroyed')
453
+
454
+ this._closeSinkAndSource()
455
+ }
456
+
457
+ /**
458
+ * When an extending class reads data from it's implementation-specific source,
459
+ * call this method to allow the stream consumer to read the data.
460
+ */
461
+ sourcePush (data: Uint8ArrayList): void {
462
+ this.streamSource.push(data)
463
+ }
464
+
465
+ /**
466
+ * Returns the amount of unread data - can be used to prevent large amounts of
467
+ * data building up when the stream consumer is too slow.
468
+ */
469
+ sourceReadableLength (): number {
470
+ return this.streamSource.readableLength
471
+ }
472
+
473
+ /**
474
+ * Send a message to the remote muxer informing them a new stream is being
475
+ * opened
476
+ */
477
+ abstract sendNewStream (options?: AbortOptions): void | Promise<void>
478
+
479
+ /**
480
+ * Send a data message to the remote muxer
481
+ */
482
+ abstract sendData (buf: Uint8ArrayList, options?: AbortOptions): void | Promise<void>
483
+
484
+ /**
485
+ * Send a reset message to the remote muxer
486
+ */
487
+ abstract sendReset (options?: AbortOptions): void | Promise<void>
488
+
489
+ /**
490
+ * Send a message to the remote muxer, informing them no more data messages
491
+ * will be sent by this end of the stream
492
+ */
493
+ abstract sendCloseWrite (options?: AbortOptions): void | Promise<void>
494
+
495
+ /**
496
+ * Send a message to the remote muxer, informing them no more data messages
497
+ * will be read by this end of the stream
498
+ */
499
+ abstract sendCloseRead (options?: AbortOptions): void | Promise<void>
500
+ }
@@ -0,0 +1,14 @@
1
+ import { getIterator } from 'get-iterator'
2
+ import { isPromise } from './is-promise.js'
3
+ import type { Logger } from '@libp2p/logger'
4
+ import type { Source } from 'it-stream-types'
5
+
6
+ export function closeSource (source: Source<unknown>, log: Logger): void {
7
+ const res = getIterator(source).return?.()
8
+
9
+ if (isPromise(res)) {
10
+ res.catch(err => {
11
+ log.error('could not cause iterator to return', err)
12
+ })
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ export function isPromise <T = unknown> (thing: any): thing is Promise<T> {
2
+ if (thing == null) {
3
+ return false
4
+ }
5
+
6
+ return typeof thing.then === 'function' &&
7
+ typeof thing.catch === 'function' &&
8
+ typeof thing.finally === 'function'
9
+ }