@hyperbrowser/ui
v0.3.4
Published
React UI components for Hyperbrowser features.
Readme
Hyperbrowser UI Components
TypeScript-first React component package scaffold with:
- Dual outputs: ESM and CommonJS
- Generated TypeScript declarations
- Minimal dependency surface
- Browser-based visual test harness for manual component checks
- Browser-based sandbox terminal primitives
- Reusable Hyperbrowser HLS playback hook for native
<video>consumers
Project layout
src/: Component source files and public exports.scripts/: Build/support scripts.tests/visual/: Manual visual harness and scenarios.
Commands
npm run typecheck: Validate TypeScript.npm run build: Build ESM, CJS, and declaration outputs.npm run test:visual: Start the visual harness server.
Sandbox terminal usage
The public terminal path is built from three pieces:
useSandboxTerminalConnection(...): creates a Hyperbrowser-backed terminal connectionBaseTerminal: renders a ready-to-use xterm viewportuseTerminal(...): lower-level hook when you want to own the surrounding shell chrome
Import the packaged stylesheet once in your app entrypoint:
import "@hyperbrowser/ui/styles.css";The package also exposes @hyperbrowser/ui/terminal-core.css and
@hyperbrowser/ui/terminal.css if you want a narrower stylesheet.
Backend auth contract
Your frontend should not embed a long-lived Hyperbrowser API key. Instead, your backend should use its server-side credentials to return a short-lived runtime auth payload:
{
"bootstrapUrl": "https://sandbox.region.hyperbrowser.run/_hb/runtime-auth?grant=..."
}The runtime origin is derived from that bootstrap URL.
useSandboxTerminalConnection(...) uses that payload to:
- call
bootstrapUrl - let the browser store the runtime cookie from
Set-Cookie - create the PTY session
- connect the runtime websocket
Basic BaseTerminal example
This is the simplest customer-facing terminal setup.
import { useCallback } from "react";
import {
BaseTerminal,
type HyperbrowserPtyBrowserAuthParams,
useSandboxTerminalConnection,
} from "@hyperbrowser/ui";
import "@hyperbrowser/ui/styles.css";
export function SandboxTerminal({
sandboxId,
}: {
sandboxId: string;
}) {
const getRuntimeBrowserAuth = useCallback(
async ({
browserAuthEndpoint,
sandboxId: resolvedSandboxId,
signal,
}: HyperbrowserPtyBrowserAuthParams) => {
if (!browserAuthEndpoint) {
throw new Error("Sandbox terminal auth endpoint is unavailable.");
}
// requires hyperbrowser ai key, recommended as backend call
const response = await fetch(browserAuthEndpoint, {
method: "POST",
signal,
});
if (!response.ok) {
throw new Error(
`Failed to get terminal auth for sandbox ${resolvedSandboxId ?? sandboxId}.`,
);
}
return response.json();
},
[sandboxId],
);
const connection = useSandboxTerminalConnection({
getRuntimeBrowserAuth,
sandboxId,
command: "bash",
cwd: "/workspace",
});
return (
<BaseTerminal
appearance="dark"
autoFocus
connection={connection}
preset="graphite"
style={{ height: 520 }}
/>
);
}Custom shell with useTerminal
Use useTerminal(...) when you want to control the surrounding card, toolbar,
status text, and actions yourself.
import { useCallback } from "react";
import {
createTerminalTheme,
type HyperbrowserPtyBrowserAuthParams,
useSandboxTerminalConnection,
useTerminal,
} from "@hyperbrowser/ui";
import "@hyperbrowser/ui/styles.css";
export function SandboxTerminalPanel({
sandboxId,
}: {
sandboxId: string;
}) {
const getRuntimeBrowserAuth = useCallback(
async ({
browserAuthEndpoint,
sandboxId: resolvedSandboxId,
signal,
}: HyperbrowserPtyBrowserAuthParams) => {
if (!browserAuthEndpoint) {
throw new Error("Sandbox terminal auth endpoint is unavailable.");
}
// requires hyperbrowser ai key, recommended as backend call
const response = await fetch(browserAuthEndpoint, {
method: "POST",
signal,
});
if (!response.ok) {
throw new Error(
`Failed to get terminal auth for sandbox ${resolvedSandboxId ?? sandboxId}.`,
);
}
return response.json();
},
[sandboxId],
);
const connection = useSandboxTerminalConnection({
getRuntimeBrowserAuth,
sandboxId,
command: "bash",
cwd: "/workspace",
});
const terminalConfig = createTerminalTheme("breeze", {
appearance: "dark",
terminalTheme: {
cursor: "#f8b84e",
selectionBackground: "rgba(248, 184, 78, 0.18)",
},
terminalOptions: {
fontSize: 13,
},
});
const { errorMessage, status, terminal, viewportRef } = useTerminal({
autoFocus: false,
connection,
...terminalConfig,
});
return (
<section className="terminal-panel">
<header className="terminal-toolbar">
<strong>Build Sandbox</strong>
<div>
<span>{status}</span>
<button type="button" onClick={() => terminal?.focus()}>
Focus
</button>
</div>
</header>
<div
ref={viewportRef}
className="hb-terminal-base"
style={{ height: 520 }}
/>
<footer>{errorMessage ?? "Connected terminal session."}</footer>
</section>
);
}Notes:
BaseTerminalalready renders the terminal mount node for you.useSandboxTerminalConnection(...)should be givensandboxIdfor the standard customer path.getRuntimeBrowserAuth(...)receives{ signal, sandboxId, browserAuthEndpoint }.- When
sandboxIdis provided touseSandboxTerminalConnection(...), the hook passes the library-defined Hyperbrowser browser-auth endpoint for that sandbox. apiBaseUrlis an optional override for local development or staging. It only changes the base server URL; the browser-auth path suffix is still owned by the library.- With
useTerminal(...), the element usingviewportRefshould includeclassName="hb-terminal-base"if you want the packaged base terminal layout styles. statusis one ofidle,connecting,ready,closed, orerror.- The hook returns the raw
xterminstance asterminalfor optional imperative actions likefocus().
Terminal theming
Terminal primitives support:
preset: one ofbasic,atlas,paper,ember,graphite,skyline,breezeappearance:"dark"or"light"terminalTheme: partialxtermIThemeoverridesterminalOptions: font and cursor options such asfontFamily,fontSize,cursorBlink, andcursorStyle
You can also create a reusable spreadable config with createTerminalTheme(...).
Filesystem workspace usage
The public filesystem path is built from two layers:
HyperbrowserFileWorkspace: ready-to-use Hyperbrowser-backed filesystem browserFileWorkspace: lower-level browser shell when you want to bring your own adapter
Import the packaged stylesheet once in your app entrypoint:
import "@hyperbrowser/ui/styles.css";Basic HyperbrowserFileWorkspace example
import { useCallback } from "react";
import {
createFileWorkspaceTheme,
HyperbrowserFileWorkspace,
type HyperbrowserFilesystemBrowserAuthParams,
} from "@hyperbrowser/ui";
import "@hyperbrowser/ui/styles.css";
export function SandboxFiles({
sandboxId,
}: {
sandboxId: string;
}) {
const getRuntimeBrowserAuth = useCallback(
async ({
browserAuthEndpoint,
sandboxId: resolvedSandboxId,
signal,
}: HyperbrowserFilesystemBrowserAuthParams) => {
if (!browserAuthEndpoint) {
throw new Error("Sandbox filesystem auth endpoint is unavailable.");
}
const response = await fetch(browserAuthEndpoint, {
method: "POST",
signal,
});
if (!response.ok) {
throw new Error(
`Failed to get filesystem auth for sandbox ${resolvedSandboxId ?? sandboxId}.`,
);
}
return response.json();
},
[sandboxId],
);
const filesystemTheme = createFileWorkspaceTheme("basic", {
appearance: "dark",
});
return (
<HyperbrowserFileWorkspace
{...filesystemTheme}
getRuntimeBrowserAuth={getRuntimeBrowserAuth}
sandboxId={sandboxId}
style={{ minHeight: 720 }}
title="Sandbox Files"
workspacePath="/workspace"
/>
);
}Notes:
getRuntimeBrowserAuth(...)receives{ signal, sandboxId, browserAuthEndpoint }.sandboxIdis the standard customer path.runtimeBaseUrl + bootstrapUrlis also supported for already-bootstrapped runtime sessions.apiBaseUrlis only needed when you want the library to call the control-plane endpoint directly or when you need a non-default control-plane base.
Filesystem theming
Filesystem theming supports:
preset: one ofbasic,atlas,ledgerappearance:"dark"or"light"chromeTheme: partial chrome overrideseditorTheme: partial editor typography overrides
You can create a reusable spreadable config with createFileWorkspaceTheme(...).
VNC component usage
HyperbrowserVncViewer renders a noVNC viewer using a Hyperbrowser session token and
connect base URL.
import { HyperbrowserVncViewer } from "@hyperbrowser/ui";
export function SessionLiveView({
token,
connectUrl,
}: {
token: string;
connectUrl: string;
}) {
return (
<HyperbrowserVncViewer
token={token}
connectUrl={connectUrl}
height={560}
rewriteCmdAsCtrl
useComputerActionClipboard
onConnectionError={(message) => {
console.error("VNC connection error:", message);
}}
/>
);
}Notes:
- Required props:
token,connectUrl. rewriteCmdAsCtrlremaps macOS command shortcuts to control for the remote session.useComputerActionClipboardroutes copy/paste through computer actions.disableFocusOnConnectcan be used to prevent automatic VNC keyboard focus.
HLS hook usage
useHyperbrowserHlsPlayback is designed as an MP4-compatible integration path:
you still render your own <video> element and the hook wires source loading.
import { useRef } from "react";
import { useHyperbrowserHlsPlayback } from "@hyperbrowser/ui";
export function SessionVideo({
sessionId,
sessionToken,
}: {
sessionId: string;
sessionToken: string;
}) {
const videoRef = useRef<HTMLVideoElement | null>(null);
const { reloadSource, sourceError, isHlsSource } = useHyperbrowserHlsPlayback({
videoRef,
sourceType: "hls",
sessionId,
sessionToken,
apiBaseUrl: "https://api.hyperbrowser.ai",
});
return (
<>
<video ref={videoRef} controls playsInline preload="auto" />
{sourceError ? <p>{sourceError}</p> : null}
<button type="button" onClick={reloadSource}>
Reload
</button>
<p>{isHlsSource ? "HLS" : "Non-HLS"}</p>
</>
);
}Notes:
- HLS mode requires both
sessionIdandsessionToken. - HLS mode does not require an explicit playlist URL.
- The hook rewrites requests to:
https://api.hyperbrowser.ai/api/session/{sessionId}/video-playlist.m3u8andhttps://api.hyperbrowser.ai/api/session/{sessionId}/video-segment/{assetName}. - Requests use
Authorization: Bearer <sessionToken>and omit credential cookies. sourceis optional and only needed for non-HLS (for example MP4 playback).
Publishing behavior
package.json exports map supports both import styles:
- ESM:
import { ... } from '@hyperbrowser/ui' - CJS:
const ui = require('@hyperbrowser/ui')
