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.
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 -->
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();
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.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/");
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;
};
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)");