import { Duplex } from 'stream'; export const DEFAULT_INITIAL_SIZE = 8 * 1024; export const DEFAULT_INCREMENT_AMOUNT = 8 * 1024; export const DEFAULT_FREQUENCY = 1; export const DEFAULT_CHUNK_SIZE = 1024; export class DuplexBufferStream extends Duplex { private buffer: Buffer = Buffer.alloc(this.initialSize); public size = 0; private allowPush = false; private stopped = false; private timeout: NodeJS.Timeout | null = null; constructor( private initialSize: number = DEFAULT_INITIAL_SIZE, private incrementAmount: number = DEFAULT_INCREMENT_AMOUNT, private frequency: number = DEFAULT_FREQUENCY, private chunkSize: number = DEFAULT_CHUNK_SIZE ) { super(); } maxSize(): number { return this.buffer.length; } getContents(length: number) { if (!this.size) return ''; const data = Buffer.alloc(Math.min(length || this.size, this.size)); this.buffer.copy(data, 0, 0, data.length); if (data.length < this.size) this.buffer.copy(this.buffer, 0, data.length); this.size -= data.length; return data; } getContentsAsString(encoding: string, length: number): string { if (!this.size) return ''; const data = this.buffer.toString( encoding, 0, Math.min(length || this.size, this.size) ); const dataLength = Buffer.byteLength(data); if (dataLength < this.size) this.buffer.copy(this.buffer, 0, dataLength); this.size -= dataLength; return data; } _write(chunk: Buffer, encoding: string, done: Function) { this.emit('data', chunk); this.increaseBufferIfNecessary(chunk.length); chunk.copy(this.buffer, this.size, 0); this.size += chunk.length; done(); } increaseBufferIfNecessary(incomingDataSize: number) { if (this.buffer.length - this.size < incomingDataSize) { const factor = Math.ceil( (incomingDataSize - (this.buffer.length - this.size)) / this.incrementAmount ); const newBuffer = Buffer.alloc( this.buffer.length + this.incrementAmount * factor ); this.buffer.copy(newBuffer, 0, 0, this.size); this.buffer = newBuffer; } } sendData = () => { const amount = Math.min(this.chunkSize, this.size); let sendMore = false; if (amount > 0) { const chunk = Buffer.alloc(amount); this.buffer.copy(chunk, 0, 0, amount); sendMore = this.push(chunk) !== false; this.allowPush = sendMore; this.buffer.copy(this.buffer, 0, amount, this.size); this.size -= amount; } if (this.size === 0 && this.stopped) { this.push(null); } if (sendMore) { this.timeout = setTimeout(this.sendData, this.frequency); } else { this.timeout = null; } }; initiateSendData() { if (!this.timeout && this.allowPush) { this.timeout = setTimeout(this.sendData, this.frequency); } } stop() { this.stopped = true; } _read() { this.allowPush = true; this.initiateSendData(); } }