accrete-kit 0.1.0 → 0.2.0

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +9 -2
  3. package/dist/scaffold/assets/generated/release.json +1 -1
  4. package/dist/scaffold/manifest.json +2 -2
  5. package/dist/ui/LICENSE +21 -0
  6. package/dist/ui/THIRD-PARTY-NOTICES +212 -0
  7. package/dist/ui/bin/accrete-ui.mjs +46 -0
  8. package/dist/ui/page/app.js +49 -0
  9. package/dist/ui/page/index.html +17 -0
  10. package/dist/ui/page/panels/activity.css +80 -0
  11. package/dist/ui/page/panels/activity.js +85 -0
  12. package/dist/ui/page/panels/agents.css +61 -0
  13. package/dist/ui/page/panels/agents.js +173 -0
  14. package/dist/ui/page/panels/objects.css +155 -0
  15. package/dist/ui/page/panels/objects.js +309 -0
  16. package/dist/ui/page/panels/running.css +35 -0
  17. package/dist/ui/page/panels/running.js +159 -0
  18. package/dist/ui/page/style.css +86 -0
  19. package/dist/ui/page/terminal.js +88 -0
  20. package/dist/ui/server/agent-sessions.mjs +600 -0
  21. package/dist/ui/server/auth.mjs +29 -0
  22. package/dist/ui/server/deps.mjs +12 -0
  23. package/dist/ui/server/documents.mjs +285 -0
  24. package/dist/ui/server/events.mjs +18 -0
  25. package/dist/ui/server/processes.mjs +110 -0
  26. package/dist/ui/server/readers/activity.mjs +134 -0
  27. package/dist/ui/server/readers/agents.mjs +45 -0
  28. package/dist/ui/server/readers/objects.mjs +261 -0
  29. package/dist/ui/server/readers/running.mjs +245 -0
  30. package/dist/ui/server/scope.mjs +36 -0
  31. package/dist/ui/server/server.mjs +156 -0
  32. package/dist/ui/server/terminal.mjs +134 -0
  33. package/dist/ui/server/workspace.mjs +26 -0
  34. package/dist/ui/ui.json +3 -0
  35. package/dist/ui/vendor/@xterm/addon-fit/lib/addon-fit.mjs +18 -0
  36. package/dist/ui/vendor/@xterm/addon-unicode11/lib/addon-unicode11.mjs +44 -0
  37. package/dist/ui/vendor/@xterm/addon-webgl/lib/addon-webgl.mjs +101 -0
  38. package/dist/ui/vendor/@xterm/xterm/css/xterm.css +285 -0
  39. package/dist/ui/vendor/@xterm/xterm/lib/xterm.mjs +53 -0
  40. package/dist/ui/vendor/node-pty/lib/eventEmitter2.js +47 -0
  41. package/dist/ui/vendor/node-pty/lib/index.js +52 -0
  42. package/dist/ui/vendor/node-pty/lib/interfaces.js +7 -0
  43. package/dist/ui/vendor/node-pty/lib/terminal.js +190 -0
  44. package/dist/ui/vendor/node-pty/lib/types.js +7 -0
  45. package/dist/ui/vendor/node-pty/lib/unixTerminal.js +346 -0
  46. package/dist/ui/vendor/node-pty/lib/utils.js +39 -0
  47. package/dist/ui/vendor/node-pty/package.json +64 -0
  48. package/dist/ui/vendor/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  49. package/dist/ui/vendor/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  50. package/dist/ui/vendor/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  51. package/dist/ui/vendor/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  52. package/dist/ui/vendor/ws/index.js +22 -0
  53. package/dist/ui/vendor/ws/lib/buffer-util.js +142 -0
  54. package/dist/ui/vendor/ws/lib/constants.js +19 -0
  55. package/dist/ui/vendor/ws/lib/event-target.js +292 -0
  56. package/dist/ui/vendor/ws/lib/extension.js +203 -0
  57. package/dist/ui/vendor/ws/lib/limiter.js +55 -0
  58. package/dist/ui/vendor/ws/lib/permessage-deflate.js +530 -0
  59. package/dist/ui/vendor/ws/lib/receiver.js +743 -0
  60. package/dist/ui/vendor/ws/lib/sender.js +610 -0
  61. package/dist/ui/vendor/ws/lib/stream.js +161 -0
  62. package/dist/ui/vendor/ws/lib/subprotocol.js +62 -0
  63. package/dist/ui/vendor/ws/lib/validation.js +152 -0
  64. package/dist/ui/vendor/ws/lib/websocket-server.js +562 -0
  65. package/dist/ui/vendor/ws/lib/websocket.js +1426 -0
  66. package/dist/ui/vendor/ws/package.json +74 -0
  67. package/lib/cli.mjs +22 -4
  68. package/lib/ui.mjs +24 -0
  69. package/package.json +5 -5
@@ -0,0 +1,610 @@
1
+ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex" }] */
2
+
3
+ 'use strict';
4
+
5
+ const { Duplex } = require('stream');
6
+ const { randomFillSync } = require('crypto');
7
+ const {
8
+ types: { isUint8Array }
9
+ } = require('util');
10
+
11
+ const PerMessageDeflate = require('./permessage-deflate');
12
+ const { EMPTY_BUFFER, kWebSocket, NOOP } = require('./constants');
13
+ const { isBlob, isValidStatusCode } = require('./validation');
14
+ const { mask: applyMask, toBuffer } = require('./buffer-util');
15
+
16
+ const kByteLength = Symbol('kByteLength');
17
+ const maskBuffer = Buffer.alloc(4);
18
+ const RANDOM_POOL_SIZE = 8 * 1024;
19
+ let randomPool;
20
+ let randomPoolPointer = RANDOM_POOL_SIZE;
21
+
22
+ const DEFAULT = 0;
23
+ const DEFLATING = 1;
24
+ const GET_BLOB_DATA = 2;
25
+
26
+ /**
27
+ * HyBi Sender implementation.
28
+ */
29
+ class Sender {
30
+ /**
31
+ * Creates a Sender instance.
32
+ *
33
+ * @param {Duplex} socket The connection socket
34
+ * @param {Object} [extensions] An object containing the negotiated extensions
35
+ * @param {Function} [generateMask] The function used to generate the masking
36
+ * key
37
+ */
38
+ constructor(socket, extensions, generateMask) {
39
+ this._extensions = extensions || {};
40
+
41
+ if (generateMask) {
42
+ this._generateMask = generateMask;
43
+ this._maskBuffer = Buffer.alloc(4);
44
+ }
45
+
46
+ this._socket = socket;
47
+
48
+ this._firstFragment = true;
49
+ this._compress = false;
50
+
51
+ this._bufferedBytes = 0;
52
+ this._queue = [];
53
+ this._state = DEFAULT;
54
+ this.onerror = NOOP;
55
+ this[kWebSocket] = undefined;
56
+ }
57
+
58
+ /**
59
+ * Frames a piece of data according to the HyBi WebSocket protocol.
60
+ *
61
+ * @param {(Buffer|String)} data The data to frame
62
+ * @param {Object} options Options object
63
+ * @param {Boolean} [options.fin=false] Specifies whether or not to set the
64
+ * FIN bit
65
+ * @param {Function} [options.generateMask] The function used to generate the
66
+ * masking key
67
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
68
+ * `data`
69
+ * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
70
+ * key
71
+ * @param {Number} options.opcode The opcode
72
+ * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
73
+ * modified
74
+ * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
75
+ * RSV1 bit
76
+ * @return {(Buffer|String)[]} The framed data
77
+ * @public
78
+ */
79
+ static frame(data, options) {
80
+ let mask;
81
+ let merge = false;
82
+ let offset = 2;
83
+ let skipMasking = false;
84
+
85
+ if (options.mask) {
86
+ mask = options.maskBuffer || maskBuffer;
87
+
88
+ if (options.generateMask) {
89
+ options.generateMask(mask);
90
+ } else {
91
+ if (randomPoolPointer === RANDOM_POOL_SIZE) {
92
+ /* istanbul ignore else */
93
+ if (randomPool === undefined) {
94
+ //
95
+ // This is lazily initialized because server-sent frames must not
96
+ // be masked so it may never be used.
97
+ //
98
+ randomPool = Buffer.alloc(RANDOM_POOL_SIZE);
99
+ }
100
+
101
+ randomFillSync(randomPool, 0, RANDOM_POOL_SIZE);
102
+ randomPoolPointer = 0;
103
+ }
104
+
105
+ mask[0] = randomPool[randomPoolPointer++];
106
+ mask[1] = randomPool[randomPoolPointer++];
107
+ mask[2] = randomPool[randomPoolPointer++];
108
+ mask[3] = randomPool[randomPoolPointer++];
109
+ }
110
+
111
+ skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;
112
+ offset = 6;
113
+ }
114
+
115
+ let dataLength;
116
+
117
+ if (typeof data === 'string') {
118
+ if (
119
+ (!options.mask || skipMasking) &&
120
+ options[kByteLength] !== undefined
121
+ ) {
122
+ dataLength = options[kByteLength];
123
+ } else {
124
+ data = Buffer.from(data);
125
+ dataLength = data.length;
126
+ }
127
+ } else {
128
+ dataLength = data.length;
129
+ merge = options.mask && options.readOnly && !skipMasking;
130
+ }
131
+
132
+ let payloadLength = dataLength;
133
+
134
+ if (dataLength >= 65536) {
135
+ offset += 8;
136
+ payloadLength = 127;
137
+ } else if (dataLength > 125) {
138
+ offset += 2;
139
+ payloadLength = 126;
140
+ }
141
+
142
+ const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
143
+
144
+ target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
145
+ if (options.rsv1) target[0] |= 0x40;
146
+
147
+ target[1] = payloadLength;
148
+
149
+ if (payloadLength === 126) {
150
+ target.writeUInt16BE(dataLength, 2);
151
+ } else if (payloadLength === 127) {
152
+ target[2] = target[3] = 0;
153
+ target.writeUIntBE(dataLength, 4, 6);
154
+ }
155
+
156
+ if (!options.mask) {
157
+ return dataLength ? [target, data] : [target];
158
+ }
159
+
160
+ target[1] |= 0x80;
161
+ target[offset - 4] = mask[0];
162
+ target[offset - 3] = mask[1];
163
+ target[offset - 2] = mask[2];
164
+ target[offset - 1] = mask[3];
165
+
166
+ if (!dataLength) return [target];
167
+ if (skipMasking) return [target, data];
168
+
169
+ if (merge) {
170
+ applyMask(data, mask, target, offset, dataLength);
171
+ return [target];
172
+ }
173
+
174
+ applyMask(data, mask, data, 0, dataLength);
175
+ return [target, data];
176
+ }
177
+
178
+ /**
179
+ * Sends a close message to the other peer.
180
+ *
181
+ * @param {Number} [code] The status code component of the body
182
+ * @param {(String|Buffer)} [data] The message component of the body
183
+ * @param {Boolean} [mask=false] Specifies whether or not to mask the message
184
+ * @param {Function} [cb] Callback
185
+ * @public
186
+ */
187
+ close(code, data, mask, cb) {
188
+ let buf;
189
+
190
+ if (code === undefined) {
191
+ buf = EMPTY_BUFFER;
192
+ } else if (typeof code !== 'number' || !isValidStatusCode(code)) {
193
+ throw new TypeError('First argument must be a valid error code number');
194
+ } else if (data === undefined || !data.length) {
195
+ buf = Buffer.allocUnsafe(2);
196
+ buf.writeUInt16BE(code, 0);
197
+ } else {
198
+ const length = Buffer.byteLength(data);
199
+
200
+ if (length > 123) {
201
+ throw new RangeError('The message must not be greater than 123 bytes');
202
+ }
203
+
204
+ buf = Buffer.allocUnsafe(2 + length);
205
+ buf.writeUInt16BE(code, 0);
206
+
207
+ if (typeof data === 'string') {
208
+ buf.write(data, 2);
209
+ } else if (isUint8Array(data)) {
210
+ buf.set(data, 2);
211
+ } else {
212
+ throw new TypeError('Second argument must be a string or a Uint8Array');
213
+ }
214
+ }
215
+
216
+ const options = {
217
+ [kByteLength]: buf.length,
218
+ fin: true,
219
+ generateMask: this._generateMask,
220
+ mask,
221
+ maskBuffer: this._maskBuffer,
222
+ opcode: 0x08,
223
+ readOnly: false,
224
+ rsv1: false
225
+ };
226
+
227
+ if (this._state !== DEFAULT) {
228
+ this.enqueue([this.dispatch, buf, false, options, cb]);
229
+ } else {
230
+ this.sendFrame(Sender.frame(buf, options), cb);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Sends a ping message to the other peer.
236
+ *
237
+ * @param {*} data The message to send
238
+ * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
239
+ * @param {Function} [cb] Callback
240
+ * @public
241
+ */
242
+ ping(data, mask, cb) {
243
+ let byteLength;
244
+ let readOnly;
245
+
246
+ if (typeof data === 'string') {
247
+ byteLength = Buffer.byteLength(data);
248
+ readOnly = false;
249
+ } else if (isBlob(data)) {
250
+ byteLength = data.size;
251
+ readOnly = false;
252
+ } else {
253
+ data = toBuffer(data);
254
+ byteLength = data.length;
255
+ readOnly = toBuffer.readOnly;
256
+ }
257
+
258
+ if (byteLength > 125) {
259
+ throw new RangeError('The data size must not be greater than 125 bytes');
260
+ }
261
+
262
+ const options = {
263
+ [kByteLength]: byteLength,
264
+ fin: true,
265
+ generateMask: this._generateMask,
266
+ mask,
267
+ maskBuffer: this._maskBuffer,
268
+ opcode: 0x09,
269
+ readOnly,
270
+ rsv1: false
271
+ };
272
+
273
+ if (isBlob(data)) {
274
+ if (this._state !== DEFAULT) {
275
+ this.enqueue([this.getBlobData, data, false, options, cb]);
276
+ } else {
277
+ this.getBlobData(data, false, options, cb);
278
+ }
279
+ } else if (this._state !== DEFAULT) {
280
+ this.enqueue([this.dispatch, data, false, options, cb]);
281
+ } else {
282
+ this.sendFrame(Sender.frame(data, options), cb);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Sends a pong message to the other peer.
288
+ *
289
+ * @param {*} data The message to send
290
+ * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
291
+ * @param {Function} [cb] Callback
292
+ * @public
293
+ */
294
+ pong(data, mask, cb) {
295
+ let byteLength;
296
+ let readOnly;
297
+
298
+ if (typeof data === 'string') {
299
+ byteLength = Buffer.byteLength(data);
300
+ readOnly = false;
301
+ } else if (isBlob(data)) {
302
+ byteLength = data.size;
303
+ readOnly = false;
304
+ } else {
305
+ data = toBuffer(data);
306
+ byteLength = data.length;
307
+ readOnly = toBuffer.readOnly;
308
+ }
309
+
310
+ if (byteLength > 125) {
311
+ throw new RangeError('The data size must not be greater than 125 bytes');
312
+ }
313
+
314
+ const options = {
315
+ [kByteLength]: byteLength,
316
+ fin: true,
317
+ generateMask: this._generateMask,
318
+ mask,
319
+ maskBuffer: this._maskBuffer,
320
+ opcode: 0x0a,
321
+ readOnly,
322
+ rsv1: false
323
+ };
324
+
325
+ if (isBlob(data)) {
326
+ if (this._state !== DEFAULT) {
327
+ this.enqueue([this.getBlobData, data, false, options, cb]);
328
+ } else {
329
+ this.getBlobData(data, false, options, cb);
330
+ }
331
+ } else if (this._state !== DEFAULT) {
332
+ this.enqueue([this.dispatch, data, false, options, cb]);
333
+ } else {
334
+ this.sendFrame(Sender.frame(data, options), cb);
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Sends a data message to the other peer.
340
+ *
341
+ * @param {*} data The message to send
342
+ * @param {Object} options Options object
343
+ * @param {Boolean} [options.binary=false] Specifies whether `data` is binary
344
+ * or text
345
+ * @param {Boolean} [options.compress=false] Specifies whether or not to
346
+ * compress `data`
347
+ * @param {Boolean} [options.fin=false] Specifies whether the fragment is the
348
+ * last one
349
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
350
+ * `data`
351
+ * @param {Function} [cb] Callback
352
+ * @public
353
+ */
354
+ send(data, options, cb) {
355
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
356
+ let opcode = options.binary ? 2 : 1;
357
+ let rsv1 = options.compress;
358
+
359
+ let byteLength;
360
+ let readOnly;
361
+
362
+ if (typeof data === 'string') {
363
+ byteLength = Buffer.byteLength(data);
364
+ readOnly = false;
365
+ } else if (isBlob(data)) {
366
+ byteLength = data.size;
367
+ readOnly = false;
368
+ } else {
369
+ data = toBuffer(data);
370
+ byteLength = data.length;
371
+ readOnly = toBuffer.readOnly;
372
+ }
373
+
374
+ if (this._firstFragment) {
375
+ this._firstFragment = false;
376
+ if (
377
+ rsv1 &&
378
+ perMessageDeflate &&
379
+ perMessageDeflate.params[
380
+ perMessageDeflate._isServer
381
+ ? 'server_no_context_takeover'
382
+ : 'client_no_context_takeover'
383
+ ]
384
+ ) {
385
+ rsv1 = byteLength >= perMessageDeflate._threshold;
386
+ }
387
+ this._compress = rsv1;
388
+ } else {
389
+ rsv1 = false;
390
+ opcode = 0;
391
+ }
392
+
393
+ if (options.fin) this._firstFragment = true;
394
+
395
+ const opts = {
396
+ [kByteLength]: byteLength,
397
+ fin: options.fin,
398
+ generateMask: this._generateMask,
399
+ mask: options.mask,
400
+ maskBuffer: this._maskBuffer,
401
+ opcode,
402
+ readOnly,
403
+ rsv1
404
+ };
405
+
406
+ if (isBlob(data)) {
407
+ if (this._state !== DEFAULT) {
408
+ this.enqueue([this.getBlobData, data, this._compress, opts, cb]);
409
+ } else {
410
+ this.getBlobData(data, this._compress, opts, cb);
411
+ }
412
+ } else if (this._state !== DEFAULT) {
413
+ this.enqueue([this.dispatch, data, this._compress, opts, cb]);
414
+ } else {
415
+ this.dispatch(data, this._compress, opts, cb);
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Gets the contents of a blob as binary data.
421
+ *
422
+ * @param {Blob} blob The blob
423
+ * @param {Boolean} [compress=false] Specifies whether or not to compress
424
+ * the data
425
+ * @param {Object} options Options object
426
+ * @param {Boolean} [options.fin=false] Specifies whether or not to set the
427
+ * FIN bit
428
+ * @param {Function} [options.generateMask] The function used to generate the
429
+ * masking key
430
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
431
+ * `data`
432
+ * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
433
+ * key
434
+ * @param {Number} options.opcode The opcode
435
+ * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
436
+ * modified
437
+ * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
438
+ * RSV1 bit
439
+ * @param {Function} [cb] Callback
440
+ * @private
441
+ */
442
+ getBlobData(blob, compress, options, cb) {
443
+ this._bufferedBytes += options[kByteLength];
444
+ this._state = GET_BLOB_DATA;
445
+
446
+ blob
447
+ .arrayBuffer()
448
+ .then((arrayBuffer) => {
449
+ if (this._socket.destroyed) {
450
+ const err = new Error(
451
+ 'The socket was closed while the blob was being read'
452
+ );
453
+
454
+ //
455
+ // `callCallbacks` is called in the next tick to ensure that errors
456
+ // that might be thrown in the callbacks behave like errors thrown
457
+ // outside the promise chain.
458
+ //
459
+ process.nextTick(callCallbacks, this, err, cb);
460
+ return;
461
+ }
462
+
463
+ this._bufferedBytes -= options[kByteLength];
464
+ const data = toBuffer(arrayBuffer);
465
+
466
+ if (!compress) {
467
+ this._state = DEFAULT;
468
+ this.sendFrame(Sender.frame(data, options), cb);
469
+ this.dequeue();
470
+ } else {
471
+ this.dispatch(data, compress, options, cb);
472
+ }
473
+ })
474
+ .catch((err) => {
475
+ //
476
+ // `onError` is called in the next tick for the same reason that
477
+ // `callCallbacks` above is.
478
+ //
479
+ process.nextTick(onError, this, err, cb);
480
+ });
481
+ }
482
+
483
+ /**
484
+ * Dispatches a message.
485
+ *
486
+ * @param {(Buffer|String)} data The message to send
487
+ * @param {Boolean} [compress=false] Specifies whether or not to compress
488
+ * `data`
489
+ * @param {Object} options Options object
490
+ * @param {Boolean} [options.fin=false] Specifies whether or not to set the
491
+ * FIN bit
492
+ * @param {Function} [options.generateMask] The function used to generate the
493
+ * masking key
494
+ * @param {Boolean} [options.mask=false] Specifies whether or not to mask
495
+ * `data`
496
+ * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
497
+ * key
498
+ * @param {Number} options.opcode The opcode
499
+ * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
500
+ * modified
501
+ * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
502
+ * RSV1 bit
503
+ * @param {Function} [cb] Callback
504
+ * @private
505
+ */
506
+ dispatch(data, compress, options, cb) {
507
+ if (!compress) {
508
+ this.sendFrame(Sender.frame(data, options), cb);
509
+ return;
510
+ }
511
+
512
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
513
+
514
+ this._bufferedBytes += options[kByteLength];
515
+ this._state = DEFLATING;
516
+ perMessageDeflate.compress(data, options.fin, (_, buf) => {
517
+ if (this._socket.destroyed) {
518
+ const err = new Error(
519
+ 'The socket was closed while data was being compressed'
520
+ );
521
+
522
+ callCallbacks(this, err, cb);
523
+ return;
524
+ }
525
+
526
+ this._bufferedBytes -= options[kByteLength];
527
+ this._state = DEFAULT;
528
+ options.readOnly = false;
529
+ this.sendFrame(Sender.frame(buf, options), cb);
530
+ this.dequeue();
531
+ });
532
+ }
533
+
534
+ /**
535
+ * Executes queued send operations.
536
+ *
537
+ * @private
538
+ */
539
+ dequeue() {
540
+ while (this._state === DEFAULT && this._queue.length) {
541
+ const params = this._queue.shift();
542
+
543
+ this._bufferedBytes -= params[3][kByteLength];
544
+ Reflect.apply(params[0], this, params.slice(1));
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Enqueues a send operation.
550
+ *
551
+ * @param {Array} params Send operation parameters.
552
+ * @private
553
+ */
554
+ enqueue(params) {
555
+ this._bufferedBytes += params[3][kByteLength];
556
+ this._queue.push(params);
557
+ }
558
+
559
+ /**
560
+ * Sends a frame.
561
+ *
562
+ * @param {(Buffer | String)[]} list The frame to send
563
+ * @param {Function} [cb] Callback
564
+ * @private
565
+ */
566
+ sendFrame(list, cb) {
567
+ if (list.length === 2) {
568
+ this._socket.cork();
569
+ this._socket.write(list[0]);
570
+ this._socket.write(list[1], cb);
571
+ this._socket.uncork();
572
+ } else {
573
+ this._socket.write(list[0], cb);
574
+ }
575
+ }
576
+ }
577
+
578
+ module.exports = Sender;
579
+
580
+ /**
581
+ * Calls queued callbacks with an error.
582
+ *
583
+ * @param {Sender} sender The `Sender` instance
584
+ * @param {Error} err The error to call the callbacks with
585
+ * @param {Function} [cb] The first callback
586
+ * @private
587
+ */
588
+ function callCallbacks(sender, err, cb) {
589
+ if (typeof cb === 'function') cb(err);
590
+
591
+ for (let i = 0; i < sender._queue.length; i++) {
592
+ const params = sender._queue[i];
593
+ const callback = params[params.length - 1];
594
+
595
+ if (typeof callback === 'function') callback(err);
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Handles a `Sender` error.
601
+ *
602
+ * @param {Sender} sender The `Sender` instance
603
+ * @param {Error} err The error
604
+ * @param {Function} [cb] The first pending callback
605
+ * @private
606
+ */
607
+ function onError(sender, err, cb) {
608
+ callCallbacks(sender, err, cb);
609
+ sender.onerror(err);
610
+ }