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 exposes a ProxyFrame class that manages a sandboxed <iframe> element for you. Once you create an instance, you attach the iframe to your page, point it at a Wisp server, and call navigate_to() to start browsing. All proxied pages run inside the iframe with a strict sandbox policy so they cannot access your host page.
1

Import the Sandstone module

Sandstone ships two bundles. Use the ES module if your project uses native import statements, or the UMD bundle if you need a plain <script> tag.
// ES module (recommended)
import * as sandstone from "./dist/sandstone.mjs";
<!-- UMD bundle via a script tag -->
<script src="dist/sandstone.js"></script>
<!-- sandstone is now available as a global variable -->
2

Create a ProxyFrame instance

Instantiate ProxyFrame from the controller namespace. The constructor immediately creates an <iframe> element and registers it internally — no arguments are required.
const main_frame = new sandstone.controller.ProxyFrame();
3

Style and attach the iframe to the DOM

frame.iframe is a standard HTMLIFrameElement. Style it however you like, then append it to a container element in your page.
const frame_container = document.getElementById("frame_container");
frame_container.append(main_frame.iframe);
The iframe is created with the following attributes set automatically:
sandbox="allow-scripts allow-forms allow-modals allow-pointer-lock"
allowfullscreen
This means proxied pages can run scripts, submit forms, and open modals, but they cannot access the top-level browsing context or the host page’s storage.
4

Set the Wisp WebSocket URL

Sandstone routes all network traffic through a Wisp WebSocket server. You must configure the URL before navigating. See the Configure the Wisp server connection guide for details.
sandstone.libcurl.set_websocket("wss://wisp.mercurywork.shop/");
5

Register navigation callbacks

ProxyFrame exposes three lifecycle callbacks you can override. Each is called synchronously at the appropriate point in the navigation lifecycle.
// Called at the start of every navigation, before the page loads
main_frame.on_navigate = () => {
  url_box.value = main_frame.url.href;
  favicon_img.style.display = "none";
  favicon_text.style.display = "initial";
};

// Called once the page has fully loaded inside the frame
main_frame.on_load = async () => {
  url_box.value = main_frame.url.href;
  let favicon_url = await main_frame.get_favicon();
  if (!favicon_url.startsWith("data:")) {
    let response = await sandstone.libcurl.fetch(favicon_url);
    if (!response.ok) return;
    let favicon = await response.blob();
    favicon_url = URL.createObjectURL(favicon);
  }
  favicon_img.src = favicon_url;
  favicon_img.style.display = "initial";
  favicon_text.style.display = "none";
};

// Called when the URL changes due to client-side navigation (no full reload)
main_frame.on_url_change = () => {
  url_box.value = main_frame.url.href;
};
6

Navigate to a URL

Call navigate_to() with a fully-qualified URL. It returns a Promise that resolves once the page is rendered inside the frame.
await main_frame.navigate_to("https://example.com");
If the URL does not start with http:, https:, or sandstone:, prepend the scheme yourself before calling navigate_to().
async function navigate_clicked() {
  let url = url_box.value;
  if (!url.startsWith("http:") && !url.startsWith("https:") && !url.startsWith("sandstone:"))
    url = "https://" + url;
  await main_frame.navigate_to(url);
}

Complete example

The following is the full setup from example/main.mjs, adapted to show a self-contained integration:
import * as sandstone from "./dist/sandstone.mjs";

const url_box = document.getElementById("url_box");
const favicon_img = document.getElementById("favicon_img");
const favicon_text = document.getElementById("favicon_text");
const frame_container = document.getElementById("frame_container");

// 1. Create the frame and add it to the page
const main_frame = new sandstone.controller.ProxyFrame();
frame_container.append(main_frame.iframe);

// 2. Point Sandstone at a Wisp server
sandstone.libcurl.set_websocket("wss://wisp.mercurywork.shop/");

// 3. Register callbacks
main_frame.on_navigate = () => {
  url_box.value = main_frame.url.href;
  favicon_img.style.display = "none";
  favicon_text.style.display = "initial";
};

main_frame.on_load = async () => {
  url_box.value = main_frame.url.href;
  let favicon_url = await main_frame.get_favicon();
  if (!favicon_url.startsWith("data:")) {
    let response = await sandstone.libcurl.fetch(favicon_url);
    if (!response.ok) return;
    let favicon = await response.blob();
    favicon_url = URL.createObjectURL(favicon);
  }
  favicon_img.src = favicon_url;
  favicon_img.style.display = "initial";
  favicon_text.style.display = "none";
};

main_frame.on_url_change = () => {
  url_box.value = main_frame.url.href;
};

// 4. Navigate
await main_frame.navigate_to("https://example.com");

Reading the current URL

After any navigation, frame.url holds the current page location as a standard URL object.
console.log(main_frame.url.href);     // "https://example.com/"
console.log(main_frame.url.hostname); // "example.com"

Getting the page favicon

get_favicon() is an async method that returns the URL of the current page’s favicon. When the favicon is a remote URL rather than a data URI, you need to fetch it through libcurl before displaying it.
main_frame.on_load = async () => {
  let favicon_url = await main_frame.get_favicon();
  if (!favicon_url.startsWith("data:")) {
    let response = await sandstone.libcurl.fetch(favicon_url);
    if (!response.ok) return;
    let favicon = await response.blob();
    favicon_url = URL.createObjectURL(favicon);
  }
  favicon_img.src = favicon_url;
};

Evaluating JavaScript inside the proxied page

eval_js() sends a JavaScript string to the proxied page and executes it in that page’s context. This lets you inspect or modify the proxied page from your host application.
// Read a value from the proxied page
main_frame.eval_js("document.title");

// Trigger an action inside the proxied page
main_frame.eval_js("window.scrollTo(0, 0)");

Build docs developers (and LLMs) love