1const Stream = require('stream') 2 3class MuteStream extends Stream { 4 #isTTY = null 5 6 constructor (opts = {}) { 7 super(opts) 8 this.writable = this.readable = true 9 this.muted = false 10 this.on('pipe', this._onpipe) 11 this.replace = opts.replace 12 13 // For readline-type situations 14 // This much at the start of a line being redrawn after a ctrl char 15 // is seen (such as backspace) won't be redrawn as the replacement 16 this._prompt = opts.prompt || null 17 this._hadControl = false 18 } 19 20 #destSrc (key, def) { 21 if (this._dest) { 22 return this._dest[key] 23 } 24 if (this._src) { 25 return this._src[key] 26 } 27 return def 28 } 29 30 #proxy (method, ...args) { 31 if (typeof this._dest?.[method] === 'function') { 32 this._dest[method](...args) 33 } 34 if (typeof this._src?.[method] === 'function') { 35 this._src[method](...args) 36 } 37 } 38 39 get isTTY () { 40 if (this.#isTTY !== null) { 41 return this.#isTTY 42 } 43 return this.#destSrc('isTTY', false) 44 } 45 46 // basically just get replace the getter/setter with a regular value 47 set isTTY (val) { 48 this.#isTTY = val 49 } 50 51 get rows () { 52 return this.#destSrc('rows') 53 } 54 55 get columns () { 56 return this.#destSrc('columns') 57 } 58 59 mute () { 60 this.muted = true 61 } 62 63 unmute () { 64 this.muted = false 65 } 66 67 _onpipe (src) { 68 this._src = src 69 } 70 71 pipe (dest, options) { 72 this._dest = dest 73 return super.pipe(dest, options) 74 } 75 76 pause () { 77 if (this._src) { 78 return this._src.pause() 79 } 80 } 81 82 resume () { 83 if (this._src) { 84 return this._src.resume() 85 } 86 } 87 88 write (c) { 89 if (this.muted) { 90 if (!this.replace) { 91 return true 92 } 93 // eslint-disable-next-line no-control-regex 94 if (c.match(/^\u001b/)) { 95 if (c.indexOf(this._prompt) === 0) { 96 c = c.slice(this._prompt.length) 97 c = c.replace(/./g, this.replace) 98 c = this._prompt + c 99 } 100 this._hadControl = true 101 return this.emit('data', c) 102 } else { 103 if (this._prompt && this._hadControl && 104 c.indexOf(this._prompt) === 0) { 105 this._hadControl = false 106 this.emit('data', this._prompt) 107 c = c.slice(this._prompt.length) 108 } 109 c = c.toString().replace(/./g, this.replace) 110 } 111 } 112 this.emit('data', c) 113 } 114 115 end (c) { 116 if (this.muted) { 117 if (c && this.replace) { 118 c = c.toString().replace(/./g, this.replace) 119 } else { 120 c = null 121 } 122 } 123 if (c) { 124 this.emit('data', c) 125 } 126 this.emit('end') 127 } 128 129 destroy (...args) { 130 return this.#proxy('destroy', ...args) 131 } 132 133 destroySoon (...args) { 134 return this.#proxy('destroySoon', ...args) 135 } 136 137 close (...args) { 138 return this.#proxy('close', ...args) 139 } 140} 141 142module.exports = MuteStream 143