| const { Transform } = require('stream'); | |
| /** | |
| * Transforms a Buffer stream of binary data to a stream of Base64 text. Note that this will | |
| * also work on a stream of pure strings, as the Writeable base class will automatically decode | |
| * text string chunks into Buffers. | |
| * You can pass optionally a line length or a prefix | |
| * @extends Transform | |
| */ | |
| module.exports = class Base64Encode extends Transform { | |
| /** | |
| * Creates a Base64Encode | |
| * @param {Object=} options - Options for stream creation. Passed to Transform constructor as-is. | |
| * @param {string=} options.inputEncoding - The input chunk format. Default is 'utf8'. No effect on Buffer input chunks. | |
| * @param {string=} options.outputEncoding - The output chunk format. Default is 'utf8'. Pass `null` for Buffer chunks. | |
| * @param {number=} options.lineLength - The max line-length of the output stream. | |
| * @param {string=} options.prefix - Prefix for output string. | |
| */ | |
| constructor(options) { | |
| super(options); | |
| // Any extra chars from the last chunk | |
| this.extra = null; | |
| this.lineLength = options && options.lineLength; | |
| this.currLineLength = 0; | |
| if (options && options.prefix) { | |
| this.push(options.prefix); | |
| } | |
| // Default string input to be treated as 'utf8' | |
| const encIn = options && options.inputEncoding; | |
| this.setDefaultEncoding(encIn || 'utf8'); | |
| // Default output to be strings | |
| const encOut = options && options.outputEncoding; | |
| if (encOut !== null) { | |
| this.setEncoding(encOut || 'utf8'); | |
| } | |
| } | |
| /** | |
| * Adds \r\n as needed to the data chunk to ensure that the output Base64 string meets | |
| * the maximum line length requirement. | |
| * @param {string} chunk | |
| * @returns {string} | |
| * @private | |
| */ | |
| _fixLineLength(chunk) { | |
| // If we care about line length, add line breaks | |
| if (!this.lineLength) { | |
| return chunk; | |
| } | |
| const size = chunk.length; | |
| const needed = this.lineLength - this.currLineLength; | |
| let start, end; | |
| let _chunk = ''; | |
| for (start = 0, end = needed; end < size; start = end, end += this.lineLength) { | |
| _chunk += chunk.slice(start, end); | |
| _chunk += '\r\n'; | |
| } | |
| const left = chunk.slice(start); | |
| this.currLineLength = left.length; | |
| _chunk += left; | |
| return _chunk; | |
| } | |
| /** | |
| * Transforms a Buffer chunk of data to a Base64 string chunk. | |
| * @param {Buffer} chunk | |
| * @param {string} encoding - unused since chunk is always a Buffer | |
| * @param cb | |
| * @private | |
| */ | |
| _transform(chunk, encoding, cb) { | |
| // Add any previous extra bytes to the chunk | |
| if (this.extra) { | |
| chunk = Buffer.concat([this.extra, chunk]); | |
| this.extra = null; | |
| } | |
| // 3 bytes are represented by 4 characters, so we can only encode in groups of 3 bytes | |
| const remaining = chunk.length % 3; | |
| if (remaining !== 0) { | |
| // Store the extra bytes for later | |
| this.extra = chunk.slice(chunk.length - remaining); | |
| chunk = chunk.slice(0, chunk.length - remaining); | |
| } | |
| // Convert chunk to a base 64 string | |
| chunk = chunk.toString('base64'); | |
| // Push the chunk | |
| this.push(Buffer.from(this._fixLineLength(chunk))); | |
| cb(); | |
| } | |
| /** | |
| * Emits 0 or 4 extra characters of Base64 data. | |
| * @param cb | |
| * @private | |
| */ | |
| _flush(cb) { | |
| if (this.extra) { | |
| this.push(Buffer.from(this._fixLineLength(this.extra.toString('base64')))); | |
| } | |
| cb(); | |
| } | |
| }; | |