Frontend-to-Backend Communication¶
ctx.sendToBackend(payload)¶
Send a message to your backend runtime.
For backend-spawned, long-lived frontend loops with ready(), heartbeat(), and graceful stop handling, use Frontend Process Lifecycle instead.
ctx.sendToBackend({ type: 'fetch_data', query: 'hello' })
ctx.onBackendMessage(handler)¶
Receive messages from your backend runtime.
const unsub = ctx.onBackendMessage((payload) => {
console.log('Got from backend:', payload)
})
Messages are JSON-serializable objects. A common pattern is to use a type field for routing on both sides.
The transport is runtime-mode independent: process, sandbox, and worker all use the same extension messaging API.
Startup readiness¶
Lumiverse auto-readies legacy frontends as soon as setup(ctx) returns. That preserves existing extensions, but it also means startup messages can be replayed immediately after setup completes.
If your frontend keeps booting asynchronously and is not ready to receive startup traffic yet, opt into manual readiness:
export function setup(ctx: SpindleFrontendContext) {
ctx.deferReady()
const unsub = ctx.onBackendMessage((payload: any) => {
// Register handlers synchronously before calling ready().
})
void initializeUi().finally(() => {
ctx.ready()
})
return () => {
unsub()
}
}
Rules:
- Call
ctx.deferReady()duringsetup()before it returns. - Call
ctx.ready()once your handlers and initial UI shell are safe to receive queued startup messages. - If your startup flow depends on backend replies, call
ctx.ready()as soon as those handlers are installed instead of waiting on the replies themselves. - If you do nothing, Lumiverse falls back to legacy auto-ready behavior.
- If you call
ctx.deferReady()but never callctx.ready(), Lumiverse eventually auto-recovers and flushes the queue after a timeout.