Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ading2210/sandstone/llms.txt

Use this file to discover all available pages before exploring further.

Sandstone routes all proxied network requests through a Wisp WebSocket server. Wisp is a lightweight multiplexing protocol that allows the in-browser libcurl.js runtime to open TCP and UDP connections by tunneling them over a single WebSocket connection. Without a reachable Wisp server, navigate_to() cannot fetch any remote pages.

Setting the Wisp server URL

Call sandstone.network.set_websocket(url) — or equivalently sandstone.libcurl.set_websocket(url) — before making any navigation calls. Both functions are aliases for the same underlying libcurl.js call.
import * as sandstone from "./dist/sandstone.mjs";

// Using the network namespace
sandstone.network.set_websocket("wss://wisp.mercurywork.shop/");

// Equivalent using the libcurl alias
sandstone.libcurl.set_websocket("wss://wisp.mercurywork.shop/");
The public Wisp server hosted at wss://wisp.mercurywork.shop/ is available for testing and development. It is operated by the MercuryWorkshop team.

Detecting the right server at runtime

In the example application, Sandstone chooses between a local Wisp endpoint and the public server based on the current location object. This pattern lets the same build work both in local development (where a server is running on the same host) and when deployed to a static hosting platform like Cloudflare Pages.
let wisp_url = "wss://wisp.mercurywork.shop/";

if (
  location.hostname.endsWith(".pages.dev") ||
  (location.protocol !== "http:" && location.protocol !== "https:")
) {
  // Running from a CDN or directly from the filesystem — use the public server
  sandstone.libcurl.set_websocket(wisp_url);
} else {
  // Running behind a local web server — derive the WebSocket URL from the page origin
  wisp_url = location.origin.replace("http", "ws");
  sandstone.libcurl.set_websocket(wisp_url);
}
When the page protocol is neither http: nor https: (for example, file:), there is no local server to connect to, so the code falls back to the public Wisp server.

Local Wisp server with the example backend

The example server (example/server.mjs) uses the @mercuryworkshop/wisp-js package to handle WebSocket upgrade requests and expose a local Wisp endpoint on the same port as the Express static file server.
import { server as wisp } from "@mercuryworkshop/wisp-js/server";
import express from "express";

const app = express();
const port = process.env.PORT || 5001;
const host = process.env.HOST || "0.0.0.0";

app.use(express.static("./"));

const server = app.listen(port, host);

server.on("upgrade", (request, socket, head) => {
  wisp.routeRequest(request, socket, head);
});
With this setup, all HTTP upgrade requests are routed to the Wisp handler, and the WebSocket URL becomes the same origin as the page (e.g. ws://localhost:5001/).
The full Wisp protocol specification is available at github.com/MercuryWorkshop/wisp-protocol. The repository also lists several community-maintained server implementations.
For production deployments, run your own Wisp server instead of relying on the public instance. This gives you control over rate limits, geographic latency, and availability. Any server that implements the Wisp protocol works — point set_websocket() at your server’s WebSocket URL and the rest of Sandstone’s behavior is unchanged.

Build docs developers (and LLMs) love