Skip to content

Instantly share code, notes, and snippets.

@asidko
Created April 20, 2022 14:00
Show Gist options
  • Save asidko/9c7064027039411a11323eaf7d8ea2a4 to your computer and use it in GitHub Desktop.
Save asidko/9c7064027039411a11323eaf7d8ea2a4 to your computer and use it in GitHub Desktop.
Encode and decode base64 GZIP with pure JavaScript. Works in all modern browsers.
// Paste the following example to browser console
// Comppreses string to GZIP. Retruns a Promise with Base64 string
const compress = string => {
const blobToBase64 = blob => new Promise((resolve, _) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result.split(',')[1]);
reader.readAsDataURL(blob);
});
const byteArray = new TextEncoder().encode(string);
const cs = new CompressionStream('gzip');
const writer = cs.writable.getWriter();
writer.write(byteArray);
writer.close();
return new Response(cs.readable).blob().then(blobToBase64);
};
// Decompresses base64 encoded GZIP string. Retruns a string with original text.
const decompress = base64string => {
const bytes = Uint8Array.from(atob(base64string), c => c.charCodeAt(0));
const cs = new DecompressionStream('gzip');
const writer = cs.writable.getWriter();
writer.write(bytes);
writer.close();
return new Response(cs.readable).arrayBuffer().then(function (arrayBuffer) {
return new TextDecoder().decode(arrayBuffer);
});
}
/////////////////////
// Checking
/////////////////////
Promise.resolve("Hello GZIP!")
.then(v => {
console.log("Original value: %s", v);
return v;
})
.then(v => compress(v))
.then(v => {
console.log("Compressed value: %s", v);
return v;
})
.then(v => decompress(v))
.then(v => console.log("Decomporessed value: %s", v));
@A99US
Copy link

A99US commented Aug 9, 2024

How do you catch the error?

I tried to decompress random Buffer bytes and got an error thrown Uncaught (in promise) TypeError : The compress data was not valid. Invalid block data.

I tried to catch it with .catch(err=>{console.log(err)}) but failed. Only got TypeError : Failed to fetch.

I've been reading about async function and Promise, but still a dead end. Sorry if the question is too stupid or has an obvious answer.

@asidko
Copy link
Author

asidko commented Aug 17, 2024

Yeah ๐Ÿ˜“. There's a mixture of promise and classical code.
So we should wrap everything inside compress/decompress functions in Promise to make error follow it's .catch() flow, otherwise for current code you need both synchronous handling and promise handling:

try {
decompress("invalid$base64%#@").catch(e => console.log("Got a promise error when decompressing: " + e))
} catch (e) {
console.log("Got an synchronous error: " + e);
}

Result:

Got an error: InvalidCharacterError: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

So, it's better to wrap a whole function in Promise and move try-catch block there

const decompress = (base64string) => {
    return new Promise((resolve, reject) => {
        try {
            const bytes = Uint8Array.from(atob(base64string), c => c.charCodeAt(0));
            const cs = new DecompressionStream('gzip');
            const writer = cs.writable.getWriter();
            
            writer.write(bytes)
                .then(() => writer.close())
                .then(() => new Response(cs.readable).arrayBuffer())
                .then(arrayBuffer => resolve(new TextDecoder().decode(arrayBuffer)))
                .catch(reject); // Catch any errors in the promise chain
        } catch (error) {
            reject(error); // Catch any synchronous errors
        }
    });
};

// Trying it
decompress("invalid$base64%#@").catch(e => console.log("Got a promise error when decompressing: " + e))
// Result: just .catch() is enough now to handle all errors
Got a promise error when decompressing: InvalidCharacterError: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

@A99US
Copy link

A99US commented Oct 15, 2024

Apologies for late re-reply. I just saw your answer after going thru my bookmarks.

I tried your codes and I don't know how to save the result into a var. Granted I'm still grasping on this whole Promise technique.

Anyway, this is the code that I've been using since I asked that question. It catches the error, but somehow the error is still on the console claiming that it's still uncatched. I don't know why this code works, but it works. Maybe you want to check it out. The error message is in writer.closed as far as I understand.

async function decomp(str,mode="gzip"){
  // Using Node Buffer for encoder
  str = Buffer.from(str, "base64")
  const cs = new DecompressionStream(mode)
  const writer = cs.writable.getWriter()
  writer.write(str)
  writer.close()
  return await new Response(cs.readable).arrayBuffer()
    .then(
      // Resolve Function
      arr=>(Buffer.from(arr)).toString("utf8"),
      // Reject Function
      async _=>{throw new Error(await Promise.reject(await writer.closed))}
    )
}

let string = [
  // "These strings were compressed!" compressed with gzip and base64 encoder
  `H4sIAAAAAAAACgvJSC1OVSguKcrMSy9WKE8tSlVIzs8tKEotLk5NUQQAfREzrR4AAAA`,
  // Change capital R near the end
  `H4sIAAAAAAAACgvJSC1OVSguKcrMSy9WKE8tSlVIzs8tKEotLk5NUQQAfREzrr4AAAA`,
  // Change 1 char at the end
  `H4sIAAAAAAAACgvJSC1OVSguKcrMSy9WKE8tSlVIzs8tKEotLk5NUQQAfREzrR4AAAx`,
  // Add 1 char at the end
  `H4sIAAAAAAAACgvJSC1OVSguKcrMSy9WKE8tSlVIzs8tKEotLk5NUQQAfREzrR4AAAAx`,
  ],
  result = []

console.clear()

for(let item of string){
  try{
    result.push(
      "Succesfully decompressing!\n\n" + "String Result : "+ (await decomp(item))
    )
  }
  catch(err){
    result.push("Failed to decompress!\n\n"+ err.name +" : "+ err.message)
  }
}

console.log(result.join("\n\n=====================================================================\n\n"))

Also an important question, is there a notification page somewhere in github? I just saw your reply. But I already subscribe to this post / gist. There must be notification somewhere, right?

Cheers

@davidnormo
Copy link

Thanks @asidko I was trying to roll my own version of this but ran into an error from the Response: "TypeError: Failed to fetch". I believe it was something to do with my approach to base64 encoding. Your implementation worked a treat ๐Ÿ‘

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment