// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // This module is browser compatible. /** * Utilities for encoding and decoding to and from base64url in a streaming manner. * * @experimental **UNSTABLE**: New API, yet to be vetted. * * @module */ import { decodeBase64Url, encodeBase64Url } from "./base64url.ts"; /** * Converts a Uint8Array stream into a base64url-encoded stream. * * @experimental **UNSTABLE**: New API, yet to be vetted. * * @see {@link https://www.rfc-editor.org/rfc/rfc4648.html#section-5} * * @example Usage * ```ts * import { assertEquals } from "@std/assert"; * import { encodeBase64Url } from "@std/encoding/base64url"; * import { Base64UrlEncoderStream } from "@std/encoding/base64url-stream"; * import { toText } from "@std/streams/to-text"; * * const stream = ReadableStream.from(["Hello,", " world!"]) * .pipeThrough(new TextEncoderStream()) * .pipeThrough(new Base64UrlEncoderStream()); * * assertEquals(await toText(stream), encodeBase64Url(new TextEncoder().encode("Hello, world!"))); * ``` */ export class Base64UrlEncoderStream extends TransformStream { constructor() { let push = new Uint8Array(0); super({ transform(chunk, controller) { const concat = new Uint8Array(push.length + chunk.length); concat.set(push); concat.set(chunk, push.length); const remainder = -concat.length % 3; controller.enqueue( encodeBase64Url(concat.slice(0, remainder || undefined)), ); push = remainder ? concat.slice(remainder) : new Uint8Array(0); }, flush(controller) { if (push.length) { controller.enqueue(encodeBase64Url(push)); } }, }); } } /** * Decodes a base64url-encoded stream into a Uint8Array stream. * * @experimental **UNSTABLE**: New API, yet to be vetted. * * @see {@link https://www.rfc-editor.org/rfc/rfc4648.html#section-5} * * @example Usage * ```ts * import { assertEquals } from "@std/assert"; * import { encodeBase64Url } from "@std/encoding/base64url"; * import { Base64UrlDecoderStream } from "@std/encoding/base64url-stream"; * import { toText } from "@std/streams/to-text"; * * const stream = ReadableStream.from(["SGVsbG8s", "IHdvcmxkIQ"]) * .pipeThrough(new Base64UrlDecoderStream()) * .pipeThrough(new TextDecoderStream()); * * assertEquals(await toText(stream), "Hello, world!"); * ``` */ export class Base64UrlDecoderStream extends TransformStream { constructor() { let push = ""; super({ transform(chunk, controller) { push += chunk; if (push.length < 4) { return; } const remainder = -push.length % 4; controller.enqueue( decodeBase64Url(push.slice(0, remainder || undefined)), ); push = remainder ? push.slice(remainder) : ""; }, flush(controller) { if (push.length) { controller.enqueue(decodeBase64Url(push)); } }, }); } }