Networking
HTTP and sockets over the host bridge. They read like the web APIs but are not them — the one
thing to internalize: after await fetch(...) resolves, the body is already on the host, so
res.json() / res.text() are synchronous — no second await.
At a glance
const res = await fetch('https://api.example.com/items')
if (res.status === 200) {
const items = res.json<{ id: number, name: string }[]>() // synchronous — no await
console.log(items.length)
}
res.dispose()fetch
fetch(url: string, options?: {
method?: string // 'GET' (default), 'POST', …
headers?: Record<string, string>
body?: any // string, or a FormData
useOnce?: boolean // one-shot body — host may release it after the read
onProgress?: (p: { loaded: number, total?: number }) => void // download progress, bytes
}): Promise<FetchResponse>The promise resolves once the host has the whole response — status and body. An HTTP error
status (404, 500) still resolves; check res.status. Rejection means the request itself
failed (no connection, bad URL).
onProgress reports downloaded bytes; total is absent when the server sends no length. Pass
useOnce: true for fire-and-forget requests whose body you read exactly once — the SDK's own
loaders (Texture2D.load, Model.load) do.
FetchResponse
res.status // HTTP status code (read-only)
res.json<T>(): T // parse body as JSON — SYNCHRONOUS
res.text(): string // body as text — SYNCHRONOUS
res.dispose() // release the host-side body bufferThe body lives in host-side storage; json()/text() pull it across. A FetchResponse is also
accepted directly as an image source (UIImage(res)) and a FormData value.
json()/text() are plain synchronous calls. await res.json() "works" (awaiting a
plain value) but signals the wrong mental model — don't write it.
ownership — the host holds the body until you dispose() (or you fetched with
useOnce: true). Dispose responses you keep around once you're done with them.
fetchLocal
fetchLocal(path: string): FetchResponse // synchronous, status always 200Read a bundled project resource — no promise, no network. Use it with the asset() macro for
data files you ship:
const config = fetchLocal(asset('./config.json')).json<{ levels: number }>()File
file.id // host-side buffer handle (read-only)
file.name // file name (read-only)
file.size // bytes (read-only)A handle to a picked or captured file. You don't construct these — they come from
openFilePicker and CameraViewer.takePhoto(). Append to
a FormData to upload, use as an image source, or share() it.
FormData
const form = new FormData()
form.append(name: string, value: string | number | boolean | File | FetchResponse,
filename?: string) // filename defaults to a File's own name
form.delete(name: string) // removes the first field with that nameBuild a multipart body, then pass it as fetch's body. This is a minimal subset — no get,
has, set, or iteration.
const file = await openFilePicker({ accept: 'image/*' })
if (file) {
const form = new FormData()
form.append('avatar', file)
form.append('userId', 123)
await fetch('https://api.example.com/upload', { method: 'POST', body: form })
}WebSocket
const ws = new WebSocket(url: string, headers?: Record<string, string>) // connects immediately
ws.send(message: string | ArrayBuffer)
ws.close()Browser-like event surface, with one non-standard extra: the optional headers argument goes on
the upgrade request (handy for auth tokens). Listen with addEventListener /
removeEventListener (the shared emitter pattern — see Conventions):
ws.addEventListener('open', () => ws.send('hello'))
ws.addEventListener('message', data => console.log('got', data)) // text or binary frame
ws.addEventListener('close', code => console.log('closed', code))
ws.addEventListener('error', () => console.log('socket error'))After close() the socket is inert — further send() calls are silent no-ops. Close sockets in
a screen's onClose, or they keep running across navigation.
Pitfalls
// ✗ web-fetch habits — body readers are not async here
const data = await (await fetch(url)).json()
// ✓ await the fetch once, then read synchronously
const res = await fetch(url); const data = res.json()
// ✗ treating a 404 as a rejection
try { await fetch(url) } catch { /* expecting 404 here — it won't arrive */ }
// ✓ HTTP statuses resolve; check status yourself
const res = await fetch(url); if (res.status !== 200) handleError(res.status)See also
- Files & share — where
Filehandles come from - Storage — persist small state locally instead of a server
- Conventions — events,
asset()macro