# Embedding There are two distinct dimensions: embedding the frontend into your app, and embedding `blit server` into your own service. ## Your app, our components: `@blit-sh/react` / `@blit-sh/solid` `@blit-sh/react` and `@blit-sh/solid` are workspace-first. Both are thin wrappers over `@blit-sh/core`'s `BlitTerminalSurface`. A `BlitWorkspace` owns connections, each connection owns terminals, and each `BlitTerminal` renders a terminal by ID. ```tsx import { BlitTerminal, BlitWorkspaceProvider, useBlitFocusedSession, useBlitSessions, useBlitWorkspace, } from "@blit-sh/react"; import { BlitWorkspace, WebSocketTransport } from "@blit-sh/core"; import { useEffect, useMemo } from "react"; function EmbeddedBlit({ wasm, passphrase }: { wasm: any; passphrase: string }) { const transport = useMemo( () => new WebSocketTransport("wss://example.com/blit", passphrase), [passphrase], ); const workspace = useMemo( () => new BlitWorkspace({ wasm, connections: [{ id: "default", transport }], }), [transport, wasm], ); useEffect(() => () => workspace.dispose(), [workspace]); return ( ); } function TerminalScreen() { const workspace = useBlitWorkspace(); const sessions = useBlitSessions(); const focusedSession = useBlitFocusedSession(); useEffect(() => { if (sessions.length > 0) return; void workspace.createSession({ connectionId: "default", rows: 24, cols: 80, }); }, [sessions.length, workspace]); return ( ); } ``` ### React API | API | Purpose | | ------------------------------------------------------ | -------------------------------------------------------- | | `new BlitWorkspace({ wasm, connections })` | Create a workspace with one or more transports | | `BlitWorkspaceProvider` | Put the workspace, palette, and font settings in context | | `useBlitWorkspace()` | Get the imperative workspace object | | `useBlitWorkspaceState()` | Read the full reactive workspace snapshot | | `useBlitConnection(connectionId?)` | Read one connection snapshot | | `useBlitSessions()` | Read all terminals | | `useBlitFocusedSession()` | Read the currently focused terminal | | `useBlitWorkspaceConnection(workspace, id, transport)` | Manage a connection lifecycle with cleanup | | `BlitTerminal` | Render one terminal by `sessionId` | ### Solid API ```tsx import { BlitTerminal, BlitWorkspaceProvider, createBlitWorkspace, createBlitWorkspaceState, createBlitSessions, useBlitFocusedSession, } from "@blit-sh/solid"; import { BlitWorkspace } from "@blit-sh/core"; import { createSignal, onCleanup, createEffect } from "solid-js"; function EmbeddedBlit(props: { wasm: any; passphrase: string }) { const workspace = new BlitWorkspace({ wasm: props.wasm, connections: [ { id: "default", transport: { type: "websocket", url: "wss://example.com/blit", passphrase: props.passphrase, }, }, ], }); onCleanup(() => workspace.dispose()); return ( ); } function TerminalScreen() { const workspace = createBlitWorkspace(); const sessions = createBlitSessions(); const focusedSession = () => useBlitFocusedSession(workspace); createEffect(() => { if (sessions().length > 0) return; workspace.createSession({ connectionId: "default", rows: 24, cols: 80 }); }); return ( ); } ``` | API | Purpose | | --------------------------------------------------------- | -------------------------------------------------------- | | `new BlitWorkspace({ wasm, connections })` | Create a workspace with one or more transports | | `BlitWorkspaceProvider` | Put the workspace, palette, and font settings in context | | `createBlitWorkspace()` | Get the imperative workspace object from context | | `createBlitWorkspaceState(workspace?)` | Reactive signal tracking the workspace snapshot | | `createBlitSessions(workspace?)` | Reactive signal tracking all terminals | | `useBlitSession(workspace, sessionId)` | Look up a single terminal by ID (non-reactive) | | `useBlitFocusedSession(workspace)` | Look up the focused terminal (non-reactive) | | `useBlitConnection(workspace, sessionId)` | Look up a connection snapshot (non-reactive) | | `createBlitWorkspaceConnection(workspace, id, transport)` | Manage a connection lifecycle with `onCleanup` | | `BlitTerminal` | Render one terminal by `sessionId` | ### Wayland surface rendering (experimental) `BlitSurfaceView` renders a single Wayland surface from a terminal's compositor. The server encodes each surface as H.264 or AV1; the component decodes via WebCodecs and draws to a canvas. ```tsx import { BlitSurfaceView } from "@blit-sh/react"; function AppWindow({ connectionId, surfaceId, }: { connectionId: string; surfaceId: number; }) { return ( ); } ``` Every terminal has an experimental Wayland compositor available. Any command — shell, TUI, or GUI — can open Wayland surfaces: ```tsx workspace.createSession({ connectionId: "default", rows: 24, cols: 80, command: "my-gui-app", }); ``` Surfaces created by the terminal appear in the connection's `surfaceStore`, keyed by the terminal's PTY ID. Each surface has a `surfaceId`, `parentId`, `title`, `appId`, `width`, and `height`. ### Workspace operations - `createSession({ connectionId, rows, cols, tag?, command?, cwdFromSessionId? })` - `closeSession(sessionId)` - `restartSession(sessionId)` - `focusSession(sessionId | null)` - `search(query, { connectionId? })` - `setVisibleSessions(sessionIds)` - `addConnection(...)` / `removeConnection(connectionId)` / `reconnectConnection(connectionId)` ### Transports All transports share a common set of options (`BlitTransportOptions`): | Option | Default | Description | | ------------------- | -------------------------------- | ---------------------------- | | `reconnect` | `true` | Auto-reconnect on disconnect | | `reconnectDelay` | `500` | Initial reconnect delay (ms) | | `maxReconnectDelay` | `10000` | Maximum reconnect delay (ms) | | `reconnectBackoff` | `1.5` | Backoff multiplier | | `connectTimeoutMs` | none (WS) / `10000` (WT, WebRTC) | Connection timeout (ms) | ```ts // WebSocket new WebSocketTransport(url, passphrase, { reconnect, reconnectDelay, connectTimeoutMs, ... }) // WebTransport (QUIC/HTTP3) new WebTransportTransport(url, passphrase, { serverCertificateHash, ... }) // WebRTC data channel createWebRtcDataChannelTransport(peerConnection, { label, displayRateFps, ... }) ``` Or implement your own: ```ts interface BlitTransport { connect(): void; send(data: Uint8Array): void; close(): void; readonly status: ConnectionStatus; readonly authRejected: boolean; readonly lastError: string | null; addEventListener(type: "message" | "statuschange", listener: Function): void; removeEventListener( type: "message" | "statuschange", listener: Function, ): void; } ``` ## Server-side: a Node/Bun client over a unix socket You can also run a `@blit-sh/core` client **server-side** (Node/Bun/Deno) to drive a local `blit server` over its unix-domain socket — e.g. to script terminals or run headless commands. The non-browser building blocks live under the `@blit-sh/core/node` subpath (kept out of the package root so `node:net` and runtime globals never leak into browser bundles): ```ts import { BlitWorkspace, exitCodeFromStatus, nullLogger } from "@blit-sh/core"; import { NodeUnixSocketTransport, loadBlitWasm } from "@blit-sh/core/node"; // `loadBlitWasm()` initializes the @blit-sh/browser WASM off-browser: it reads // the colocated blit_browser_bg.wasm from disk and feeds it to init(), so you // never touch raw wasm bytes. (If you depend on a self-initializing // `@blit-sh/browser/node` build it is returned as-is.) const wasm = await loadBlitWasm(); const transport = new NodeUnixSocketTransport( process.env.BLIT_SOCK ?? "/tmp/blit.sock", ); const workspace = new BlitWorkspace({ wasm, logger: nullLogger, // no-op logger; omit to log lifecycle events to console connections: [{ id: "default", transport }], }); const session = await workspace.createSession({ connectionId: "default", rows: 24, cols: 80, command: "my-command", }); ``` The unix transport speaks blit's framing protocol (4-byte little-endian length-prefixed frames) for you — there is no need to re-implement the wire format. `BunUnixSocketTransport` and `DenoUnixSocketTransport` are the runtime-native equivalents. ### Exit status When a session's process exits, its `BlitSession.state` becomes `"exited"` and `BlitSession.exitStatus` carries the raw status from the server: - `>= 0` — normal exit; the value is the exit code. - `< 0` — terminated by a signal; the value is the negated signal number. - `EXIT_STATUS_UNKNOWN` — not yet collected. `exitCodeFromStatus(status)` maps that to a conventional shell exit code (unknown → `1`, signalled → `128 + signal`), and `formatExitStatus(status)` renders `"exited()"` / `"signal()"`. Both mirror the `blit` CLI. ```ts import { exitCodeFromStatus } from "@blit-sh/core"; workspace.subscribe(() => { for (const s of workspace.getSnapshot().sessions) { if (s.state === "exited" && s.exitStatus !== null) { console.log(`${s.id} exited with code`, exitCodeFromStatus(s.exitStatus)); } } }); ``` ## Your service, our server: `fd-channel` mode `fd-channel` lets an external process own `blit server`'s lifecycle and control which clients connect via `SCM_RIGHTS` fd passing. See [ARCHITECTURE.md](ARCHITECTURE.md) for the protocol details and the working examples: - [Python](examples/fd-channel-python.py) - [Bun](examples/fd-channel-bun.ts)