@libp2p/interface 0.0.1-8b0e6bef → 0.0.1-b36ec7f2

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":"index.d.ts","sourceRoot":"","sources":["../../../src/stream-muxer/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,iBAAiB,EAAE,CAAC,IAAI,CAAC,EAAE,eAAe,KAAK,WAAW,CAAA;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG,UAAU,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACzH;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAA;IAC1B;;;OAGG;IACH,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAEtD;;OAEG;IACH,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD;;OAEG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAE3C;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAEtC;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/stream-muxer/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,iBAAiB,EAAE,CAAC,IAAI,CAAC,EAAE,eAAe,KAAK,WAAW,CAAA;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG,UAAU,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACzH;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAA;IAC1B;;;OAGG;IACH,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAEtD;;OAEG;IACH,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAEhD;;OAEG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAE3C;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAEtC;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB"}
@@ -1,6 +1,13 @@
1
1
  import { Uint8ArrayList } from 'uint8arraylist';
2
- import type { Direction, Stream, StreamStat } from '../connection/index.js';
2
+ import type { Direction, ReadStatus, Stream, StreamStatus, StreamTimeline, WriteStatus } from '../connection/index.js';
3
+ import type { AbortOptions } from '../index.js';
3
4
  import type { Source } from 'it-stream-types';
5
+ interface Logger {
6
+ (formatter: any, ...args: any[]): void;
7
+ error: (formatter: any, ...args: any[]) => void;
8
+ trace: (formatter: any, ...args: any[]) => void;
9
+ enabled: boolean;
10
+ }
4
11
  export interface AbstractStreamInit {
5
12
  /**
6
13
  * A unique identifier for this stream
@@ -11,10 +18,9 @@ export interface AbstractStreamInit {
11
18
  */
12
19
  direction: Direction;
13
20
  /**
14
- * The maximum allowable data size, any data larger than this will be
15
- * chunked and sent in multiple data messages
21
+ * A Logger implementation used to log stream-specific information
16
22
  */
17
- maxDataSize: number;
23
+ log: Logger;
18
24
  /**
19
25
  * User specific stream metadata
20
26
  */
@@ -23,31 +29,83 @@ export interface AbstractStreamInit {
23
29
  * Invoked when the stream ends
24
30
  */
25
31
  onEnd?: (err?: Error | undefined) => void;
32
+ /**
33
+ * Invoked when the readable end of the stream is closed
34
+ */
35
+ onCloseRead?: () => void;
36
+ /**
37
+ * Invoked when the writable end of the stream is closed
38
+ */
39
+ onCloseWrite?: () => void;
40
+ /**
41
+ * Invoked when the the stream has been reset by the remote
42
+ */
43
+ onReset?: () => void;
44
+ /**
45
+ * Invoked when the the stream has errored
46
+ */
47
+ onAbort?: (err: Error) => void;
48
+ /**
49
+ * How long to wait in ms for stream data to be written to the underlying
50
+ * connection when closing the writable end of the stream. (default: 500)
51
+ */
52
+ closeTimeout?: number;
26
53
  }
27
54
  export declare abstract class AbstractStream implements Stream {
28
55
  id: string;
29
- stat: StreamStat;
56
+ direction: Direction;
57
+ timeline: StreamTimeline;
58
+ protocol?: string;
30
59
  metadata: Record<string, unknown>;
31
60
  source: AsyncGenerator<Uint8ArrayList, void, unknown>;
32
- private readonly abortController;
33
- private readonly resetController;
34
- private readonly closeController;
35
- private sourceEnded;
36
- private sinkEnded;
37
- private sinkSunk;
61
+ status: StreamStatus;
62
+ readStatus: ReadStatus;
63
+ writeStatus: WriteStatus;
64
+ private readonly sinkController;
65
+ private readonly sinkEnd;
38
66
  private endErr;
39
67
  private readonly streamSource;
40
68
  private readonly onEnd?;
41
- private readonly maxDataSize;
69
+ private readonly onCloseRead?;
70
+ private readonly onCloseWrite?;
71
+ private readonly onReset?;
72
+ private readonly onAbort?;
73
+ protected readonly log: Logger;
42
74
  constructor(init: AbstractStreamInit);
75
+ sink(source: Source<Uint8ArrayList | Uint8Array>): Promise<void>;
43
76
  protected onSourceEnd(err?: Error): void;
44
77
  protected onSinkEnd(err?: Error): void;
45
- close(): void;
46
- closeRead(): void;
47
- closeWrite(): void;
78
+ close(options?: AbortOptions): Promise<void>;
79
+ closeRead(options?: AbortOptions): Promise<void>;
80
+ closeWrite(options?: AbortOptions): Promise<void>;
81
+ /**
82
+ * Close immediately for reading and writing and send a reset message (local
83
+ * error)
84
+ */
48
85
  abort(err: Error): void;
86
+ /**
87
+ * Receive a reset message - close immediately for reading and writing (remote
88
+ * error)
89
+ */
49
90
  reset(): void;
50
- sink(source: Source<Uint8ArrayList | Uint8Array>): Promise<void>;
91
+ _closeSinkAndSource(err?: Error): void;
92
+ _closeSink(err?: Error): void;
93
+ _closeSource(err?: Error): void;
94
+ /**
95
+ * The remote closed for writing so we should expect to receive no more
96
+ * messages
97
+ */
98
+ remoteCloseWrite(): void;
99
+ /**
100
+ * The remote closed for reading so we should not send any more
101
+ * messages
102
+ */
103
+ remoteCloseRead(): void;
104
+ /**
105
+ * The underlying muxer has closed, no more messages can be sent or will
106
+ * be received, close immediately to free up resources
107
+ */
108
+ destroy(): void;
51
109
  /**
52
110
  * When an extending class reads data from it's implementation-specific source,
53
111
  * call this method to allow the stream consumer to read the data.
@@ -62,24 +120,25 @@ export declare abstract class AbstractStream implements Stream {
62
120
  * Send a message to the remote muxer informing them a new stream is being
63
121
  * opened
64
122
  */
65
- abstract sendNewStream(): void | Promise<void>;
123
+ abstract sendNewStream(options?: AbortOptions): void | Promise<void>;
66
124
  /**
67
125
  * Send a data message to the remote muxer
68
126
  */
69
- abstract sendData(buf: Uint8ArrayList): void | Promise<void>;
127
+ abstract sendData(buf: Uint8ArrayList, options?: AbortOptions): void | Promise<void>;
70
128
  /**
71
129
  * Send a reset message to the remote muxer
72
130
  */
73
- abstract sendReset(): void | Promise<void>;
131
+ abstract sendReset(options?: AbortOptions): void | Promise<void>;
74
132
  /**
75
133
  * Send a message to the remote muxer, informing them no more data messages
76
134
  * will be sent by this end of the stream
77
135
  */
78
- abstract sendCloseWrite(): void | Promise<void>;
136
+ abstract sendCloseWrite(options?: AbortOptions): void | Promise<void>;
79
137
  /**
80
138
  * Send a message to the remote muxer, informing them no more data messages
81
139
  * will be read by this end of the stream
82
140
  */
83
- abstract sendCloseRead(): void | Promise<void>;
141
+ abstract sendCloseRead(options?: AbortOptions): void | Promise<void>;
84
142
  }
143
+ export {};
85
144
  //# sourceMappingURL=stream.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../../src/stream-muxer/stream.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAC3E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAa7C,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,EAAE,EAAE,MAAM,CAAA;IAEV;;OAEG;IACH,SAAS,EAAE,SAAS,CAAA;IAEpB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAElC;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,KAAK,IAAI,CAAA;CAC1C;AAMD,8BAAsB,cAAe,YAAW,MAAM;IAC7C,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,MAAM,EAAE,cAAc,CAAC,cAAc,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IAE5D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0B;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;gBAEvB,IAAI,EAAE,kBAAkB;IAwCrC,SAAS,CAAC,WAAW,CAAE,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI;IAsBzC,SAAS,CAAC,SAAS,CAAE,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI;IAuBvC,KAAK,IAAK,IAAI;IAQd,SAAS,IAAK,IAAI;IAWlB,UAAU,IAAK,IAAI;IA2BnB,KAAK,CAAE,GAAG,EAAE,KAAK,GAAG,IAAI;IASxB,KAAK,IAAK,IAAI;IAOR,IAAI,CAAE,MAAM,EAAE,MAAM,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAyGvE;;;OAGG;IACH,UAAU,CAAE,IAAI,EAAE,cAAc,GAAG,IAAI;IAIvC;;;OAGG;IACH,oBAAoB,IAAK,MAAM;IAI/B;;;OAGG;IACH,QAAQ,CAAC,aAAa,IAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/C;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAE,GAAG,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE7D;;OAEG;IACH,QAAQ,CAAC,SAAS,IAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE3C;;;OAGG;IACH,QAAQ,CAAC,cAAc,IAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEhD;;;OAGG;IACH,QAAQ,CAAC,aAAa,IAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAChD"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../../src/stream-muxer/stream.ts"],"names":[],"mappings":"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"}
@@ -1,241 +1,297 @@
1
- // import { logger } from '@libp2p/logger'
2
1
  import { abortableSource } from 'abortable-iterator';
3
- import { anySignal } from 'any-signal';
4
2
  import { pushable } from 'it-pushable';
3
+ import defer from 'p-defer';
5
4
  import { Uint8ArrayList } from 'uint8arraylist';
6
5
  import { CodeError } from '../errors.js';
7
- // const log = logger('libp2p:stream')
8
- const log = () => { };
9
- log.trace = () => { };
10
- log.error = () => { };
11
6
  const ERR_STREAM_RESET = 'ERR_STREAM_RESET';
12
- const ERR_STREAM_ABORT = 'ERR_STREAM_ABORT';
13
- const ERR_SINK_ENDED = 'ERR_SINK_ENDED';
14
- const ERR_DOUBLE_SINK = 'ERR_DOUBLE_SINK';
7
+ const ERR_SINK_INVALID_STATE = 'ERR_SINK_INVALID_STATE';
15
8
  function isPromise(res) {
16
9
  return res != null && typeof res.then === 'function';
17
10
  }
18
11
  export class AbstractStream {
19
12
  id;
20
- stat;
13
+ direction;
14
+ timeline;
15
+ protocol;
21
16
  metadata;
22
17
  source;
23
- abortController;
24
- resetController;
25
- closeController;
26
- sourceEnded;
27
- sinkEnded;
28
- sinkSunk;
18
+ status;
19
+ readStatus;
20
+ writeStatus;
21
+ sinkController;
22
+ sinkEnd;
29
23
  endErr;
30
24
  streamSource;
31
25
  onEnd;
32
- maxDataSize;
26
+ onCloseRead;
27
+ onCloseWrite;
28
+ onReset;
29
+ onAbort;
30
+ log;
33
31
  constructor(init) {
34
- this.abortController = new AbortController();
35
- this.resetController = new AbortController();
36
- this.closeController = new AbortController();
37
- this.sourceEnded = false;
38
- this.sinkEnded = false;
39
- this.sinkSunk = false;
32
+ this.sinkController = new AbortController();
33
+ this.sinkEnd = defer();
34
+ this.log = init.log;
35
+ // stream status
36
+ this.status = 'open';
37
+ this.readStatus = 'ready';
38
+ this.writeStatus = 'ready';
40
39
  this.id = init.id;
41
40
  this.metadata = init.metadata ?? {};
42
- this.stat = {
43
- direction: init.direction,
44
- timeline: {
45
- open: Date.now()
46
- }
41
+ this.direction = init.direction;
42
+ this.timeline = {
43
+ open: Date.now()
47
44
  };
48
- this.maxDataSize = init.maxDataSize;
49
45
  this.onEnd = init.onEnd;
46
+ this.onCloseRead = init?.onCloseRead;
47
+ this.onCloseWrite = init?.onCloseWrite;
48
+ this.onReset = init?.onReset;
49
+ this.onAbort = init?.onAbort;
50
50
  this.source = this.streamSource = pushable({
51
- onEnd: () => {
52
- // already sent a reset message
53
- if (this.stat.timeline.reset !== null) {
54
- const res = this.sendCloseRead();
55
- if (isPromise(res)) {
56
- res.catch(err => {
57
- log.error('error while sending close read', err);
58
- });
59
- }
51
+ onEnd: (err) => {
52
+ if (err != null) {
53
+ this.log.trace('source ended with error', err);
54
+ }
55
+ else {
56
+ this.log.trace('source ended');
60
57
  }
61
- this.onSourceEnd();
58
+ this.readStatus = 'closed';
59
+ this.onSourceEnd(err);
62
60
  }
63
61
  });
64
62
  // necessary because the libp2p upgrader wraps the sink function
65
63
  this.sink = this.sink.bind(this);
66
64
  }
65
+ async sink(source) {
66
+ if (this.writeStatus !== 'ready') {
67
+ throw new CodeError(`writable end state is "${this.writeStatus}" not "ready"`, ERR_SINK_INVALID_STATE);
68
+ }
69
+ try {
70
+ this.writeStatus = 'writing';
71
+ const options = {
72
+ signal: this.sinkController.signal
73
+ };
74
+ if (this.direction === 'outbound') { // If initiator, open a new stream
75
+ const res = this.sendNewStream(options);
76
+ if (isPromise(res)) {
77
+ await res;
78
+ }
79
+ }
80
+ source = abortableSource(source, this.sinkController.signal, {
81
+ returnOnAbort: true
82
+ });
83
+ this.log.trace('sink reading from source');
84
+ for await (let data of source) {
85
+ data = data instanceof Uint8Array ? new Uint8ArrayList(data) : data;
86
+ const res = this.sendData(data, options);
87
+ if (isPromise(res)) { // eslint-disable-line max-depth
88
+ await res;
89
+ }
90
+ }
91
+ this.log.trace('sink finished reading from source');
92
+ this.writeStatus = 'done';
93
+ this.log.trace('sink calling closeWrite');
94
+ await this.closeWrite(options);
95
+ this.onSinkEnd();
96
+ }
97
+ catch (err) {
98
+ this.log.trace('sink ended with error, calling abort with error', err);
99
+ this.abort(err);
100
+ throw err;
101
+ }
102
+ finally {
103
+ this.log.trace('resolve sink end');
104
+ this.sinkEnd.resolve();
105
+ }
106
+ }
67
107
  onSourceEnd(err) {
68
- if (this.sourceEnded) {
108
+ if (this.timeline.closeRead != null) {
69
109
  return;
70
110
  }
71
- this.stat.timeline.closeRead = Date.now();
72
- this.sourceEnded = true;
73
- log.trace('%s stream %s source end - err: %o', this.stat.direction, this.id, err);
111
+ this.timeline.closeRead = Date.now();
74
112
  if (err != null && this.endErr == null) {
75
113
  this.endErr = err;
76
114
  }
77
- if (this.sinkEnded) {
78
- this.stat.timeline.close = Date.now();
115
+ this.onCloseRead?.();
116
+ if (this.timeline.closeWrite != null) {
117
+ this.log.trace('source and sink ended');
118
+ this.timeline.close = Date.now();
79
119
  if (this.onEnd != null) {
80
120
  this.onEnd(this.endErr);
81
121
  }
82
122
  }
123
+ else {
124
+ this.log.trace('source ended, waiting for sink to end');
125
+ }
83
126
  }
84
127
  onSinkEnd(err) {
85
- if (this.sinkEnded) {
128
+ if (this.timeline.closeWrite != null) {
86
129
  return;
87
130
  }
88
- this.stat.timeline.closeWrite = Date.now();
89
- this.sinkEnded = true;
90
- log.trace('%s stream %s sink end - err: %o', this.stat.direction, this.id, err);
131
+ this.timeline.closeWrite = Date.now();
91
132
  if (err != null && this.endErr == null) {
92
133
  this.endErr = err;
93
134
  }
94
- if (this.sourceEnded) {
95
- this.stat.timeline.close = Date.now();
135
+ this.onCloseWrite?.();
136
+ if (this.timeline.closeRead != null) {
137
+ this.log.trace('sink and source ended');
138
+ this.timeline.close = Date.now();
96
139
  if (this.onEnd != null) {
97
140
  this.onEnd(this.endErr);
98
141
  }
99
142
  }
143
+ else {
144
+ this.log.trace('sink ended, waiting for source to end');
145
+ }
100
146
  }
101
147
  // Close for both Reading and Writing
102
- close() {
103
- log.trace('%s stream %s close', this.stat.direction, this.id);
104
- this.closeRead();
105
- this.closeWrite();
106
- }
107
- // Close for reading
108
- closeRead() {
109
- log.trace('%s stream %s closeRead', this.stat.direction, this.id);
110
- if (this.sourceEnded) {
148
+ async close(options) {
149
+ this.log.trace('closing gracefully');
150
+ this.status = 'closing';
151
+ await Promise.all([
152
+ this.closeRead(options),
153
+ this.closeWrite(options)
154
+ ]);
155
+ this.status = 'closed';
156
+ this.log.trace('closed gracefully');
157
+ }
158
+ async closeRead(options = {}) {
159
+ if (this.readStatus === 'closing' || this.readStatus === 'closed') {
111
160
  return;
112
161
  }
113
- this.streamSource.end();
162
+ this.log.trace('closing readable end of stream with starting read status "%s"', this.readStatus);
163
+ const readStatus = this.readStatus;
164
+ this.readStatus = 'closing';
165
+ if (readStatus === 'ready') {
166
+ this.log.trace('ending internal source queue');
167
+ this.streamSource.end();
168
+ }
169
+ if (this.status !== 'reset' && this.status !== 'aborted') {
170
+ this.log.trace('send close read to remote');
171
+ await this.sendCloseRead(options);
172
+ }
173
+ this.log.trace('closed readable end of stream');
114
174
  }
115
- // Close for writing
116
- closeWrite() {
117
- log.trace('%s stream %s closeWrite', this.stat.direction, this.id);
118
- if (this.sinkEnded) {
175
+ async closeWrite(options = {}) {
176
+ if (this.writeStatus === 'closing' || this.writeStatus === 'closed') {
119
177
  return;
120
178
  }
121
- this.closeController.abort();
122
- try {
123
- // need to call this here as the sink method returns in the catch block
124
- // when the close controller is aborted
125
- const res = this.sendCloseWrite();
126
- if (isPromise(res)) {
127
- res.catch(err => {
128
- log.error('error while sending close write', err);
179
+ this.log.trace('closing writable end of stream with starting write status "%s"', this.writeStatus);
180
+ const writeStatus = this.writeStatus;
181
+ if (this.writeStatus === 'ready') {
182
+ this.log.trace('sink was never sunk, sink an empty array');
183
+ await this.sink([]);
184
+ }
185
+ this.writeStatus = 'closing';
186
+ if (writeStatus === 'writing') {
187
+ // stop reading from the source passed to `.sink` in the microtask queue
188
+ // - this lets any data queued by the user in the current tick get read
189
+ // before we exit
190
+ await new Promise((resolve, reject) => {
191
+ queueMicrotask(() => {
192
+ this.log.trace('aborting source passed to .sink');
193
+ this.sinkController.abort();
194
+ this.sinkEnd.promise.then(resolve, reject);
129
195
  });
130
- }
196
+ });
131
197
  }
132
- catch (err) {
133
- log.trace('%s stream %s error sending close', this.stat.direction, this.id, err);
198
+ if (this.status !== 'reset' && this.status !== 'aborted') {
199
+ this.log.trace('send close write to remote');
200
+ await this.sendCloseWrite(options);
134
201
  }
135
- this.onSinkEnd();
202
+ this.writeStatus = 'closed';
203
+ this.log.trace('closed writable end of stream');
136
204
  }
137
- // Close for reading and writing (local error)
205
+ /**
206
+ * Close immediately for reading and writing and send a reset message (local
207
+ * error)
208
+ */
138
209
  abort(err) {
139
- log.trace('%s stream %s abort', this.stat.direction, this.id, err);
140
- // End the source with the passed error
141
- this.streamSource.end(err);
142
- this.abortController.abort();
143
- this.onSinkEnd(err);
210
+ if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
211
+ return;
212
+ }
213
+ this.log('abort with error', err);
214
+ // try to send a reset message
215
+ this.log('try to send reset to remote');
216
+ const res = this.sendReset();
217
+ if (isPromise(res)) {
218
+ res.catch((err) => {
219
+ this.log.error('error sending reset message', err);
220
+ });
221
+ }
222
+ this.status = 'aborted';
223
+ this.timeline.abort = Date.now();
224
+ this._closeSinkAndSource(err);
225
+ this.onAbort?.(err);
144
226
  }
145
- // Close immediately for reading and writing (remote error)
227
+ /**
228
+ * Receive a reset message - close immediately for reading and writing (remote
229
+ * error)
230
+ */
146
231
  reset() {
232
+ if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
233
+ return;
234
+ }
147
235
  const err = new CodeError('stream reset', ERR_STREAM_RESET);
148
- this.resetController.abort();
149
- this.streamSource.end(err);
150
- this.onSinkEnd(err);
236
+ this.status = 'reset';
237
+ this._closeSinkAndSource(err);
238
+ this.onReset?.();
151
239
  }
152
- async sink(source) {
153
- if (this.sinkSunk) {
154
- throw new CodeError('sink already called on stream', ERR_DOUBLE_SINK);
155
- }
156
- this.sinkSunk = true;
157
- if (this.sinkEnded) {
158
- throw new CodeError('stream closed for writing', ERR_SINK_ENDED);
159
- }
160
- const signal = anySignal([
161
- this.abortController.signal,
162
- this.resetController.signal,
163
- this.closeController.signal
164
- ]);
165
- try {
166
- source = abortableSource(source, signal);
167
- if (this.stat.direction === 'outbound') { // If initiator, open a new stream
168
- const res = this.sendNewStream();
169
- if (isPromise(res)) {
170
- await res;
171
- }
172
- }
173
- for await (let data of source) {
174
- while (data.length > 0) {
175
- if (data.length <= this.maxDataSize) {
176
- const res = this.sendData(data instanceof Uint8Array ? new Uint8ArrayList(data) : data);
177
- if (isPromise(res)) { // eslint-disable-line max-depth
178
- await res;
179
- }
180
- break;
181
- }
182
- data = data instanceof Uint8Array ? new Uint8ArrayList(data) : data;
183
- const res = this.sendData(data.sublist(0, this.maxDataSize));
184
- if (isPromise(res)) {
185
- await res;
186
- }
187
- data.consume(this.maxDataSize);
188
- }
189
- }
240
+ _closeSinkAndSource(err) {
241
+ this._closeSink(err);
242
+ this._closeSource(err);
243
+ }
244
+ _closeSink(err) {
245
+ // if the sink function is running, cause it to end
246
+ if (this.writeStatus === 'writing') {
247
+ this.log.trace('end sink source');
248
+ this.sinkController.abort();
190
249
  }
191
- catch (err) {
192
- if (err.type === 'aborted' && err.message === 'The operation was aborted') {
193
- if (this.closeController.signal.aborted) {
194
- return;
195
- }
196
- if (this.resetController.signal.aborted) {
197
- err.message = 'stream reset';
198
- err.code = ERR_STREAM_RESET;
199
- }
200
- if (this.abortController.signal.aborted) {
201
- err.message = 'stream aborted';
202
- err.code = ERR_STREAM_ABORT;
203
- }
204
- }
205
- // Send no more data if this stream was remotely reset
206
- if (err.code === ERR_STREAM_RESET) {
207
- log.trace('%s stream %s reset', this.stat.direction, this.id);
208
- }
209
- else {
210
- log.trace('%s stream %s error', this.stat.direction, this.id, err);
211
- try {
212
- const res = this.sendReset();
213
- if (isPromise(res)) {
214
- await res;
215
- }
216
- this.stat.timeline.reset = Date.now();
217
- }
218
- catch (err) {
219
- log.trace('%s stream %s error sending reset', this.stat.direction, this.id, err);
220
- }
221
- }
250
+ this.onSinkEnd(err);
251
+ }
252
+ _closeSource(err) {
253
+ // if the source is not ending, end it
254
+ if (this.readStatus !== 'closing' && this.readStatus !== 'closed') {
255
+ this.log.trace('ending source with %d bytes to be read by consumer', this.streamSource.readableLength);
256
+ this.readStatus = 'closing';
222
257
  this.streamSource.end(err);
223
- this.onSinkEnd(err);
224
- throw err;
225
258
  }
226
- finally {
227
- signal.clear();
259
+ }
260
+ /**
261
+ * The remote closed for writing so we should expect to receive no more
262
+ * messages
263
+ */
264
+ remoteCloseWrite() {
265
+ if (this.readStatus === 'closing' || this.readStatus === 'closed') {
266
+ this.log('received remote close write but local source is already closed');
267
+ return;
228
268
  }
229
- try {
230
- const res = this.sendCloseWrite();
231
- if (isPromise(res)) {
232
- await res;
233
- }
269
+ this.log.trace('remote close write');
270
+ this._closeSource();
271
+ }
272
+ /**
273
+ * The remote closed for reading so we should not send any more
274
+ * messages
275
+ */
276
+ remoteCloseRead() {
277
+ if (this.writeStatus === 'closing' || this.writeStatus === 'closed') {
278
+ this.log('received remote close read but local sink is already closed');
279
+ return;
234
280
  }
235
- catch (err) {
236
- log.trace('%s stream %s error sending close', this.stat.direction, this.id, err);
281
+ this.log.trace('remote close read');
282
+ this._closeSink();
283
+ }
284
+ /**
285
+ * The underlying muxer has closed, no more messages can be sent or will
286
+ * be received, close immediately to free up resources
287
+ */
288
+ destroy() {
289
+ if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
290
+ this.log('received destroy but we are already closed');
291
+ return;
237
292
  }
238
- this.onSinkEnd();
293
+ this.log.trace('muxer destroyed');
294
+ this._closeSinkAndSource();
239
295
  }
240
296
  /**
241
297
  * When an extending class reads data from it's implementation-specific source,