ProxDocs

bare-mux and proxy-transports

If you have read any proxy tutorial from the last three years, you have seen this line:

const connection = new BareMux.BareMuxConnection("/baremux/worker.js");
await connection.setTransport("/libcurl/index.mjs", [{ wisp: wispUrl }]);

This page explains how it makes transports swappable.


The problem bare-mux solves

A proxied page makes requests from many places at once: the top-level document, nested iframes, web workers, and the service worker itself. Every one of those needs to reach the network.

Without coordination each context builds its own transport. That means:

  • Several TLS stacks in memory. Each wasm instance is megabytes.
  • Several Wisp WebSockets to your server, per user.
  • Split state. Connection reuse, cookies, and settings do not line up between contexts.
  • Nothing to switch. Changing transports would mean rebuilding it everywhere, in contexts you do not hold a reference to.

bare-mux ("bare multiplexer") fixes this by putting the transport in one SharedWorker and giving every context a thin client that talks to it over a MessagePort.

   window          iframe          service worker
      │               │                   │
      └───────────────┴───────────────────┘
                      │  MessagePort
              ┌───────▼────────┐
              │  SharedWorker  │  ← one transport instance lives here
              └───────┬────────┘
                      │  one Wisp WebSocket
                  your server

The SharedWorker provides one transport instance and one place to switch it.

What the two arguments mean

await connection.setTransport("/libcurl/index.mjs", [{ wisp: wispUrl }]);

The first argument is a module path and the array contains constructor arguments. The transport is constructed inside the SharedWorker, so it cannot receive an instance created by the page.

That also explains a common confusion: the transport module gets fetched by the worker, so the path must be one your server serves, not a bundler specifier.


proxy-transports: the replacement

@mercuryworkshop/bare-mux 2.1.9 carries a deprecation notice pointing at @mercuryworkshop/proxy-transports. Scramjet 2.x depends on proxy-transports directly and does not use bare-mux at all.

The name change reflects a scope change:

  • bare-mux was "swap out your bare client", the name is a fossil from when bare was the only option. It owned both the interface and the SharedWorker multiplexing.
  • proxy-transports is just the interface: the ProxyTransport type that transports implement. Multiplexing is the engine's problem now.

In Scramjet 2.x the Controller owns the transport and distributes it to frames and the service worker itself, over its own message channels. So you construct the transport directly, in your own code, and hand it over:

const { default: LibcurlClient } = await import("/libcurl/index.mjs");
const transport = new LibcurlClient({ wisp: wispUrl });

const controller = new Controller({ serviceworker, transport });

controller.setTransport(await buildTransport("epoxy", wispUrl));

This is simpler and easier to debug. The transport is a normal object in your page, so you can inspect it, wrap it, or log through it.


Which one do I use?

You are usingUse
Ultraviolet 3.xbare-mux. UV has no other option; it is what UV 3.x is built on.
Scramjet 1.xbare-mux. Same generation.
Scramjet 2.xproxy-transports. Already a dependency; do not add bare-mux.

Do not mix them. bare-mux-era transports (libcurl ^1, epoxy ^2) and proxy-transports-era transports (libcurl ^2, epoxy ^3) are separate package majors. See Version matrix.

The interfaces are close enough that mismatched versions sometimes appear to work, which is worse than failing outright. Pin the pair that matches your engine.


Serving it

bare-mux is the one package here that still has a clean Node path helper:

import { baremuxPath } from "@mercuryworkshop/bare-mux/node";

app.use("/baremux/", express.static(baremuxPath));

That directory contains index.js (the BareMux global for your page) and worker.js (the SharedWorker). Both must be reachable at the paths you pass to BareMuxConnection.


Debugging

"BareMux is not defined". /baremux/index.js is not loaded, or loaded after your script. It is a classic script, not a module, so ordering in your HTML matters.

Requests hang with no error. The SharedWorker probably failed to construct the transport. SharedWorker errors do not appear in the page console; open chrome://inspect/#workers (or Firefox's about:debugging) and inspect it directly.

Transport switch appears to do nothing. setTransport resolves when the worker has swapped it, but pages already loaded keep their existing connections. Reload the frame to see the change.

Safari. SharedWorker support has historically been the shakiest here. If a setup works everywhere but Safari, this is the first thing to check.

Profile Views