Skip to content

Instantly share code, notes, and snippets.

@Fuzzyma
Last active January 22, 2025 22:21
Show Gist options
  • Save Fuzzyma/bcd48102e28dcc9f54413f3b9c5c34bf to your computer and use it in GitHub Desktop.
Save Fuzzyma/bcd48102e28dcc9f54413f3b9c5c34bf to your computer and use it in GitHub Desktop.
export function createBase64Encoder() {
let leftover = null; // Buffer for leftover bytes from the previous chunk
return new TransformStream({
transform(chunk, controller) {
// Concatenate leftover from the previous chunk
let input = leftover ? new Uint8Array([...leftover, ...chunk]) : chunk;
// Process in chunks of 3 bytes
const length = input.length - (input.length % 3);
for (let i = 0; i < length; i += 3) {
const threeBytes = input.subarray(i, i + 3);
const binary = String.fromCharCode(...threeBytes);
controller.enqueue(btoa(binary));
}
// Save remaining bytes for the next chunk
leftover = input.slice(length);
},
flush(controller) {
// Process leftover bytes at the end (if any)
if (leftover && leftover.length > 0) {
const binary = String.fromCharCode(...leftover);
controller.enqueue(btoa(binary).replace(/=+$/, "")); // Strip padding
}
},
});
}
export function createBase64Decoder() {
let buffer = ""; // Buffer to hold incomplete Base64 chunks
return new TransformStream({
transform(chunk, controller) {
// Concatenate chunks into the buffer
buffer += chunk;
// Process complete Base64 segments (multiple of 4)
const validLength = buffer.length - (buffer.length % 4);
const validBase64 = buffer.slice(0, validLength);
buffer = buffer.slice(validLength); // Keep the remaining part in the buffer
if (validBase64) {
const binaryString = atob(validBase64);
const uint8Array = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
uint8Array[i] = binaryString.charCodeAt(i);
}
controller.enqueue(uint8Array);
}
},
flush(controller) {
// Process remaining buffered data on stream close
if (buffer) {
const binaryString = atob(buffer);
const uint8Array = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
uint8Array[i] = binaryString.charCodeAt(i);
}
controller.enqueue(uint8Array);
}
},
});
}
export async function textStreamToString(stream) {
const reader = stream.getReader();
let result = "";
let done = false;
while (!done) {
const { value, done: isDone } = await reader.read();
if (value) {
result += value; // Directly append the text chunk
}
done = isDone;
}
return result;
}
export async function arrayStreamToString(stream, encoding = "utf-8") {
const reader = stream.getReader();
const decoder = new TextDecoder(encoding);
let result = "";
let done = false;
while (!done) {
const { value, done: isDone } = await reader.read();
if (value) {
result += decoder.decode(value, { stream: true });
}
done = isDone;
}
// Ensure the final decoding is handled
result += decoder.decode();
return result;
}
export async function compressAndEncode(input) {
const inputStream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(input));
controller.close();
},
});
const compressedStream = inputStream.pipeThrough(
new CompressionStream("gzip")
);
const encodedStream = compressedStream.pipeThrough(createBase64Encoder());
const base64String = await textStreamToString(encodedStream);
const encodedURI = encodeURIComponent(base64String);
return encodedURI;
}
export async function decodeAndDecompress(encodedURI) {
const base64String = decodeURIComponent(encodedURI);
const base64Stream = new ReadableStream({
start(controller) {
controller.enqueue(base64String);
controller.close();
},
});
const decodedStream = base64Stream.pipeThrough(createBase64Decoder());
const decompressedStream = decodedStream.pipeThrough(
new DecompressionStream("gzip")
);
const output = await arrayStreamToString(decompressedStream, "utf-8");
return output;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment