• Stars
    star
    262
  • Rank 156,136 (Top 4 %)
  • Language
    TypeScript
  • License
    Apache License 2.0
  • Created over 3 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A reliable compressor and decompressor for Brotli, supporting node & browsers via wasm

brotli-wasm Build Status Available on NPM

Part of HTTP Toolkit: powerful tools for building, testing & debugging HTTP(S)

A reliable compressor and decompressor for Brotli, supporting node & browsers via wasm

Brotli is available in modern Node (12+) but not older Node or browsers. With this package, you can immediately use it everywhere.

This package contains a tiny wrapper around the compress & decompress API of the Rust Brotli crate, compiled to wasm with just enough setup to make it easily usable from JavaScript.

This is battle-tested, in production use in both node & browsers as part of HTTP Toolkit, and includes automated build with node & browser tests to make sure.

Getting started

npm install brotli-wasm

You should be able to import this directly into Node, as normal, or in a browser using any bundler that supports ES modules & webassembly (e.g. Webpack v4 or v5, Vite, Rollup, and most others).

For each target (node.js, commonjs bundlers & ESM bundlers) this module exports a different WASM file & setup, with a slightly different entrypoint. These entrypoints all expose a consistent default-export API, in addition to some other exports that may vary (e.g. Node exposes the brotli methods synchronously, while browsers always require an await due to WASM limitations).

In all builds (after waiting for the exported promise in browsers) the module exposes two core methods:

  • compress(Buffer, [options]) - compresses a buffer using Brotli, returning the compressed buffer. An optional options object can be provided. The only currently supported option is quality: a number between 1 and 11.
  • decompress(Buffer) - decompresses a buffer using Brotli, returning the original raw data.

For advanced use data-streaming use cases, CompressStream and DecompressStream classes for streaming compression are also available. See the tests for example usage.

Usage

If you want to support node & browsers with the same code, you can use the await browser-compatible form with the default export everywhere.

In node.js:

const brotli = require('brotli-wasm');

const compressedData = brotli.compress(Buffer.from('some input'));
const decompressedData = brotli.decompress(compressedData);

console.log(Buffer.from(decompressedData).toString('utf8')); // Prints 'some input'

In browsers:

import brotliPromise from 'brotli-wasm'; // Import the default export

const brotli = await brotliPromise; // Import is async in browsers due to wasm requirements!

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

const input = 'some input';

const uncompressedData = textEncoder.encode(input);
const compressedData = brotli.compress(uncompressedData);
const decompressedData = brotli.decompress(compressedData);

console.log(textDecoder.decode(decompressedData)); // Prints 'some input'

You can also load it from a CDN like so:

let brotli = await import("https://unpkg.com/[email protected]/index.web.js?module").then(m => m.default);

The package itself has no runtime dependencies, although if you prefer using Buffer over using TextEncoder/TextDecoder you may want a browser Buffer polyfill.

In browser with streams:

import brotliPromise from 'brotli-wasm'; // Import the default export

const brotli = await brotliPromise; // Import is async in browsers due to wasm requirements!

const input = 'some input';

// Get a stream for your input:
const inputStream = new ReadableStream({
    start(controller) {
        controller.enqueue(input);
        controller.close();
    }
});

// Convert the streaming data to Uint8Arrays, if necessary:
const textEncoderStream = new TextEncoderStream();

// You can use whatever stream chunking size you like here, depending on your use case:
const OUTPUT_SIZE = 100;

// Create a stream to incrementally compress the data as it streams:
const compressStream = new brotli.CompressStream();
const compressionStream = new TransformStream({
    transform(chunk, controller) {
        let resultCode;
        let inputOffset = 0;

        // Compress this chunk, producing up to OUTPUT_SIZE output bytes at a time, until the
        // entire input has been compressed.

        do {
            const input = chunk.slice(inputOffset);
            const result = compressStream.compress(input, OUTPUT_SIZE);
            controller.enqueue(result.buf);
            resultCode = result.code;
            inputOffset += result.input_offset;
        } while (resultCode === brotli.BrotliStreamResultCode.NeedsMoreOutput);
        if (resultCode !== brotli.BrotliStreamResultCode.NeedsMoreInput) {
            controller.error(`Brotli compression failed when transforming with code ${resultCode}`);
        }
    },
    flush(controller) {
        // Once the chunks are finished, flush any remaining data (again in repeated fixed-output
        // chunks) to finish the stream:
        let resultCode;
        do {
            const result = compressStream.compress(undefined, OUTPUT_SIZE);
            controller.enqueue(result.buf);
            resultCode = result.code;
        } while (resultCode === brotli.BrotliStreamResultCode.NeedsMoreOutput)
        if (resultCode !== brotli.BrotliStreamResultCode.ResultSuccess) {
            controller.error(`Brotli compression failed when flushing with code ${resultCode}`);
        }
        controller.terminate();
    }
});

const decompressStream = new brotli.DecompressStream();
const decompressionStream = new TransformStream({
    transform(chunk, controller) {
        let resultCode;
        let inputOffset = 0;

        // Decompress this chunk, producing up to OUTPUT_SIZE output bytes at a time, until the
        // entire input has been decompressed.

        do {
            const input = chunk.slice(inputOffset);
            const result = decompressStream.decompress(input, OUTPUT_SIZE);
            controller.enqueue(result.buf);
            resultCode = result.code;
            inputOffset += result.input_offset;
        } while (resultCode === brotli.BrotliStreamResultCode.NeedsMoreOutput);
        if (
            resultCode !== brotli.BrotliStreamResultCode.NeedsMoreInput &&
            resultCode !== brotli.BrotliStreamResultCode.ResultSuccess
        ) {
            controller.error(`Brotli decompression failed with code ${resultCode}`)
        }
    },
    flush(controller) {
        controller.terminate();
    }
});

const textDecoderStream = new TextDecoderStream();

let output = '';
const outputStream = new WritableStream({
    write(chunk) {
        output += chunk;
    }
});

await inputStream
    .pipeThrough(textEncoderStream)
    .pipeThrough(compressionStream)
    .pipeThrough(decompressionStream)
    .pipeThrough(textDecoderStream)
    .pipeTo(outputStream);
console.log(output); // Prints 'some input'

Note that TransformStream has become available in all browsers as of mid-2022: https://caniuse.com/mdn-api_transformstream. It's also been available in Node.js (experimentally) since v16.5.0.

This is a simplified demo example - you may well want to tweak the specific stream buffer sizes for compression/decompression to your use case, to reuse buffers, or explore further optimizations if you're interested in these streaming use cases.

Alternatives

There's a few other packages that do similar things, but I found they were all unusable and/or unmaintained:

  • brotli-dec-wasm - decompressor only, compiled from Rust just like this package, actively maintained, but no compressor available (by design). If you only need decompression, this package is a good choice.
  • Brotli.js - hand-written JS decompressor that seems to work OK for most cases, but it crashes for some edge cases and the emscripten build of the compressor doesn't work in browsers at all. Last updated in 2017.
  • wasm-brotli - Compiled from Rust like this package, includes decompressor & compressor, but requires a custom async wrapper for Webpack v4 usage and isn't usable at all in Webpack v5. Last updated in 2019.

More Repositories

1

httptoolkit

HTTP Toolkit is a beautiful & open-source tool for debugging, testing and building with HTTP(S) on Windows, Linux & Mac 🎉 Open an issue here to give feedback or ask for help.
2,730
star
2

frida-interception-and-unpinning

Frida scripts to directly MitM all HTTPS traffic from a target mobile application
JavaScript
1,088
star
3

react-reverse-portal

React reparenting ⚛️ Build an element once, move it anywhere
JavaScript
885
star
4

mockttp

Powerful friendly HTTP mock server & proxy library
TypeScript
779
star
5

httptoolkit-desktop

Electron wrapper to build and distribute HTTP Toolkit for the desktop
TypeScript
617
star
6

httptoolkit-android

Automatic Android interception & debugging with HTTP Toolkit, for Android
Java
480
star
7

httptoolkit-server

The backend of HTTP Toolkit
JavaScript
452
star
8

httptoolkit-ui

The UI of HTTP Toolkit
TypeScript
292
star
9

mockrtc

Powerful friendly WebRTC mock peer & proxy
TypeScript
282
star
10

android-ssl-pinning-demo

A tiny demo Android app using SSL pinning to block HTTPS MitM interception
Kotlin
120
star
11

httptoolkit-website

The main website of HTTP Toolkit: beautiful, cross-platform & open-source tools to debug, test and develop with HTTP(S).
MDX
74
star
12

jvm-http-proxy-agent

A JVM agent that automatically forces a proxy for HTTP(S) connections and trusts MitM certificates, for all major JVM HTTP clients
Java
69
star
13

docker-registry-facade

A tiny self-hostable Docker Registry facade - own your image URL without running your own registry
Dockerfile
48
star
14

read-tls-client-hello

A pure-JS module to read TLS client hello data and calculate TLS fingerprints from an incoming socket connection.
TypeScript
40
star
15

mockipfs

Powerful friendly IPFS mock node & proxy
TypeScript
39
star
16

docker-socks-tunnel

A tiny Dockerized SOCKS5 proxy
Dockerfile
32
star
17

mockthereum

Powerful friendly Ethereum mock node & proxy
TypeScript
26
star
18

mockttp-proxy-demo

A tiny demo, showing how to build your own scriptable HTTPS-intercepting proxy with Mockttp
JavaScript
23
star
19

frida-js

Pure-JS bindings to control Frida from node.js & browsers
TypeScript
21
star
20

openapi-directory-js

Building & bundling https://github.com/APIs-guru/openapi-directory for easy use from JS
TypeScript
19
star
21

mobx-shallow-undo

Zero-config undo & redo for Mobx
TypeScript
18
star
22

browser-launcher

Detect the browser versions available on your system, and launch them in an isolated profile for automation & testing purposes.
JavaScript
18
star
23

httpolyglot

Serve http and https connections over the same port with node.js
TypeScript
15
star
24

accounts

The API & dashboard that power HTTP Toolkit account management
TypeScript
10
star
25

mockrtc-extension-example

An example web extension, using MockRTC to intercept & debug your own WebRTC traffic
TypeScript
10
star
26

ios-ssl-pinning-demo

A tiny demo iOS app using SSL pinning to block HTTPS MitM interception
Swift
9
star
27

httpsnippet

HTTP Request snippet generator for many languages & libraries
JavaScript
8
star
28

usbmux-client

A pure-js Node.js library for communicating with iPhones over USB via usbmux
TypeScript
8
star
29

os-proxy-config

Access the operating system proxy configuration from Node.js, for all platforms
TypeScript
6
star
30

http-encoding

Everything you need to handle HTTP message body content-encoding
TypeScript
5
star
31

anonymizing-reverse-proxy

Anonymizing reverse proxy used between HTTP Toolkit end users & 3rd party services
Dockerfile
5
star
32

mac-system-proxy

Access the Mac system proxy settings from Node.js
TypeScript
5
star
33

xz-decompress

XZ decompression for the browser & Node without native code, via WebAssembly
JavaScript
5
star
34

webextension

A browser extension used in HTTP Toolkit
TypeScript
4
star
35

osx-find-executable

Find an app's executable by its bundle id
JavaScript
4
star
36

node-launcher

WIP: An node.js-powered launcher for httptoolkit (try 'npx httptoolkit')
JavaScript
3
star
37

websocket-stream

websockets with the node stream API
JavaScript
3
star
38

act-build-base

A base image for local GitHub Action builds with Act
Shell
3
star
39

demo-scripts

WIP: an script for automatically following (and recording) workflows in HTTP Toolkit
TypeScript
3
star
40

android.httptoolkit.tech

Static site used as infrastructure to support the HTTP Toolkit Android app
2
star
41

evil-package

An npm package demonstrating how packages can steal your data (but not actually doing so!)
JavaScript
2
star
42

windows-system-proxy

Access the Windows system proxy settings from Node.js.
TypeScript
2
star
43

ipfs-openapi-spec

An IPFS OpenAPI spec, automatically generated from the official documentation
TypeScript
2
star
44

testserver

A public test server for HTTP & related protocols, similar to httpbin.org (but actively maintained)
TypeScript
2
star
45

destroyable-server

A tiny Node.js module to make any net.Server force-closeable
TypeScript
1
star
46

amiusing

Microsite to tell you if you're currently being proxied by HTTP Toolkit
HTML
1
star
47

statuspagestatuspage

TypeScript
1
star