'use strict'; const { ObjectDefineProperties, String, StringPrototypeCharCodeAt, Uint8Array, } = primordials; const { TextDecoder, TextEncoder, } = require('internal/encoding'); const { TransformStream, } = require('internal/webstreams/transformstream'); const { customInspect } = require('internal/webstreams/util'); const { codes: { ERR_INVALID_THIS, }, } = require('internal/errors'); const { customInspectSymbol: kInspect, kEmptyObject, kEnumerableProperty, } = require('internal/util'); /** * @typedef {import('./readablestream').ReadableStream} ReadableStream * @typedef {import('./writablestream').WritableStream} WritableStream */ class TextEncoderStream { #pendingHighSurrogate = null; #handle; #transform; constructor() { this.#handle = new TextEncoder(); this.#transform = new TransformStream({ transform: (chunk, controller) => { // https://encoding.spec.whatwg.org/#encode-and-enqueue-a-chunk chunk = String(chunk); let finalChunk = ''; for (let i = 0; i < chunk.length; i++) { const item = chunk[i]; const codeUnit = StringPrototypeCharCodeAt(item, 0); if (this.#pendingHighSurrogate !== null) { const highSurrogate = this.#pendingHighSurrogate; this.#pendingHighSurrogate = null; if (0xDC00 <= codeUnit && codeUnit <= 0xDFFF) { finalChunk += highSurrogate + item; continue; } finalChunk += '\uFFFD'; } if (0xD800 <= codeUnit && codeUnit <= 0xDBFF) { this.#pendingHighSurrogate = item; continue; } if (0xDC00 <= codeUnit && codeUnit <= 0xDFFF) { finalChunk += '\uFFFD'; continue; } finalChunk += item; } if (finalChunk) { const value = this.#handle.encode(finalChunk); controller.enqueue(value); } }, flush: (controller) => { // https://encoding.spec.whatwg.org/#encode-and-flush if (this.#pendingHighSurrogate !== null) { controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD])); } }, }); } /** * @readonly * @type {string} */ get encoding() { return this.#handle.encoding; } /** * @readonly * @type {ReadableStream} */ get readable() { return this.#transform.readable; } /** * @readonly * @type {WritableStream} */ get writable() { return this.#transform.writable; } [kInspect](depth, options) { if (this == null) throw new ERR_INVALID_THIS('TextEncoderStream'); return customInspect(depth, options, 'TextEncoderStream', { encoding: this.#handle.encoding, readable: this.#transform.readable, writable: this.#transform.writable, }); } } class TextDecoderStream { #handle; #transform; /** * @param {string} [encoding] * @param {{ * fatal? : boolean, * ignoreBOM? : boolean, * }} [options] */ constructor(encoding = 'utf-8', options = kEmptyObject) { this.#handle = new TextDecoder(encoding, options); this.#transform = new TransformStream({ transform: (chunk, controller) => { const value = this.#handle.decode(chunk, { stream: true }); if (value) controller.enqueue(value); }, flush: (controller) => { const value = this.#handle.decode(); if (value) controller.enqueue(value); controller.terminate(); }, }); } /** * @readonly * @type {string} */ get encoding() { return this.#handle.encoding; } /** * @readonly * @type {boolean} */ get fatal() { return this.#handle.fatal; } /** * @readonly * @type {boolean} */ get ignoreBOM() { return this.#handle.ignoreBOM; } /** * @readonly * @type {ReadableStream} */ get readable() { return this.#transform.readable; } /** * @readonly * @type {WritableStream} */ get writable() { return this.#transform.writable; } [kInspect](depth, options) { if (this == null) throw new ERR_INVALID_THIS('TextDecoderStream'); return customInspect(depth, options, 'TextDecoderStream', { encoding: this.#handle.encoding, fatal: this.#handle.fatal, ignoreBOM: this.#handle.ignoreBOM, readable: this.#transform.readable, writable: this.#transform.writable, }); } } ObjectDefineProperties(TextEncoderStream.prototype, { encoding: kEnumerableProperty, readable: kEnumerableProperty, writable: kEnumerableProperty, }); ObjectDefineProperties(TextDecoderStream.prototype, { encoding: kEnumerableProperty, fatal: kEnumerableProperty, ignoreBOM: kEnumerableProperty, readable: kEnumerableProperty, writable: kEnumerableProperty, }); module.exports = { TextEncoderStream, TextDecoderStream, };