Documentation Index
Fetch the complete documentation index at: https://iii.dev/docs/llms.txt
Use this file to discover all available pages before exploring further.
Connect a browser application directly to the iii engine over WebSocket so frontend code can call backend functions and receive live invocations — without HTTP endpoints in between.
Why the browser SDK instead of HTTP calls
With a traditional HTTP setup, every interaction follows the same pattern: the browser sends a request, waits for a response, and repeats. Real-time features require polling or bolting on a separate WebSocket layer.
The browser SDK turns your frontend into a first-class iii worker:
- Persistent connection — one WebSocket replaces many HTTP round-trips. No per-request handshake, no CORS preflight on every call.
- Bi-directional — the engine can invoke functions registered in the browser. Backend workers push data to the frontend with
trigger(), enabling real-time patterns without polling.
- Same primitives —
registerFunction, trigger, registerTrigger, and createChannel work the same way they do server-side.
- Type-safe — the same TypeScript types (
ISdk, TriggerRequest, ApiRequest) are shared between iii-sdk and iii-browser-sdk.
Prerequisites
- A running iii engine with the iii-worker-manager enabled and RBAC configured (see Worker RBAC).
iii-browser-sdk installed in your frontend project.
1. Install the browser SDK
pnpm add iii-browser-sdk
# or: npm install iii-browser-sdk
2. Enable the Worker Manager with RBAC
Browser workers connect through the RBAC port, not the internal bridge. Add an iii-worker-manager entry to your engine config:
workers:
# ... your existing workers ...
- name: iii-worker-manager
config:
port: 49135
rbac:
expose_functions:
- match("api::*")
- match("stream::*")
- match("state::*")
For production, add an auth_function_id to validate tokens from the browser. See Worker RBAC for full auth setup.
3. Connect from the browser
import { registerWorker } from 'iii-browser-sdk'
const iii = registerWorker('ws://localhost:49135')
export { iii }
4. Register functions and triggers
import { iii } from './iii'
// Register a function the backend can call
iii.registerFunction(
{ id: 'ui::show-notification' },
async (data: { title: string; body: string }) => {
showToast(data.title, data.body)
return { displayed: true }
},
)
// Call a backend function directly
const users = await iii.trigger({
function_id: 'api::get::users',
payload: {},
})
Example: Real-time dashboard
A metrics dashboard that updates instantly when backend data changes — no polling.
Backend worker registers a function that pushes metrics to all browser workers:
import { registerWorker, TriggerAction } from 'iii-sdk'
const iii = registerWorker('ws://localhost:49134')
iii.registerFunction('metrics::collect', async () => {
const metrics = await collectSystemMetrics()
// Push to every browser that registered ui::update-dashboard
await iii.trigger({
function_id: 'ui::update-dashboard',
payload: metrics,
action: TriggerAction.Void(),
})
return { collected: true }
})
// Run every 5 seconds
iii.registerTrigger({
type: 'cron',
function_id: 'metrics::collect',
config: { expression: '*/5 * * * * * *' },
})
Browser worker receives dashboard updates:
import { registerWorker } from 'iii-browser-sdk'
const iii = registerWorker('ws://localhost:49135')
iii.registerFunction(
{ id: 'ui::update-dashboard' },
async (metrics: { cpu: number; memory: number; requests: number }) => {
document.getElementById('cpu')!.textContent = `${metrics.cpu}%`
document.getElementById('memory')!.textContent = `${metrics.memory}MB`
document.getElementById('requests')!.textContent = `${metrics.requests}/s`
return null
},
)
Compare this to the HTTP alternative: a setInterval polling GET /metrics every 5 seconds, getting stale data and wasting bandwidth when nothing changes. With iii, the backend pushes only when metrics actually update.
Example: Collaborative task board
A task board where multiple browser tabs see changes in real time — both reading and writing through iii.
Backend worker handles task persistence:
import { registerWorker } from 'iii-sdk'
const iii = registerWorker('ws://localhost:49134')
iii.registerFunction(
{ id: 'tasks::create' },
async (input: { title: string; assignee: string }) => {
const taskId = crypto.randomUUID()
await iii.trigger({
function_id: 'state::set',
payload: { scope: 'tasks', key: taskId, value: { ...input, taskId, status: 'open' } },
})
return { taskId }
},
)
iii.registerFunction(
{ id: 'tasks::list' },
async () => {
return await iii.trigger({
function_id: 'state::list',
payload: { scope: 'tasks' },
})
},
)
Browser worker creates tasks and reacts to state changes:
import { registerWorker } from 'iii-browser-sdk'
const iii = registerWorker('ws://localhost:49135')
// Create a task — calls the backend function directly
async function createTask(title: string, assignee: string) {
const { taskId } = await iii.trigger<
{ title: string; assignee: string },
{ taskId: string }
>({
function_id: 'tasks::create',
payload: { title, assignee },
})
console.log('Task created', { taskId })
return taskId
}
// Register a function the backend can call when tasks change
iii.registerFunction(
{ id: 'ui::tasks-updated' },
async (data: { tasks: Array<{ taskId: string; title: string; status: string }> }) => {
renderTaskList(data.tasks)
return null
},
)
// Subscribe to state changes so we get live updates
iii.registerTrigger({
type: 'state',
function_id: 'ui::tasks-updated',
config: { scope: 'tasks' },
})
Every browser tab connected to the engine sees task changes the moment they happen. Tab A creates a task, Tab B’s ui::tasks-updated function fires instantly.
Your browser is a full iii worker. It registers functions, calls backend functions with trigger(), and receives live invocations over a single WebSocket connection. No REST endpoints, no polling, no separate real-time layer.
The browser SDK exposes the same ISdk interface as the Node SDK. See the Browser SDK Reference for the full API.