network Stable WINMACLNX
HTTP requests, WebSocket connections, and local network discovery backed by the Rust reqwest (opens in a new tab) and tungstenite (opens in a new tab) libraries. No browser overhead, no Chromium, no Electron net stack.
Requires capability: "network"
Capability config
{
"capabilities": {
"network": {
"allow": ["api.example.com", "cdn.example.com"]
}
}
}Use ["*"] to allow all outbound requests during development. Restrict to specific
domains before shipping — the runtime enforces this at the Rust layer.
Import
import { fetch, ws, mdns } from 'veloxkit'fetch(url, options?)
Browser-compatible fetch API backed by reqwest. Supports JSON, plain-text bodies,
binary uploads via base64, and multipart/form-data.
Signature
fetch(
url: string,
options?: {
method?: string // 'GET' | 'POST' | 'PUT' | 'DELETE' | ...
headers?: Record<string, string>
body?: string // plain text or JSON.stringify(...)
multipart?: MultipartPart[] // mutually exclusive with body
}
): Promise<FetchResponse>
type MultipartPart =
| { name: string; value: string } // text field
| { name: string; filename: string; base64: string; contentType: string } // binary file
type FetchResponse = {
status: number
ok: boolean // true when status 200-299
statusText: string
headers: Record<string, string>
text(): Promise<string>
json(): Promise<any>
}GET / POST JSON
// GET
const res = await fetch('https://api.example.com/notes')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const notes = await res.json()
// POST JSON
await fetch('https://api.example.com/notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Hello', body: 'World' }),
})
// PUT with auth header
await fetch(`https://api.example.com/notes/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(updated),
})
// DELETE
await fetch(`https://api.example.com/notes/${id}`, { method: 'DELETE' })Multipart file upload
import { fs } from 'veloxkit'
// fs.readFileBytes returns the file contents as a base64 string
const base64 = await fs.readFileBytes('/tmp/photo.jpg')
await fetch('https://api.example.com/upload', {
method: 'POST',
multipart: [
{ name: 'description', value: 'My photo' },
{ name: 'file', filename: 'photo.jpg', base64, contentType: 'image/jpeg' },
],
})Reading response headers
const res = await fetch('https://api.example.com/file')
const contentType = res.headers['content-type']
const data = await res.text()ws.connect(url, handlers?)
Open a persistent WebSocket connection. Messages are drained once per frame inside
a React flushSync batch, so setState calls in onmessage trigger an immediate
synchronous re-render without extra scheduling.
Signature
ws.connect(
url: string,
handlers?: {
onmessage?: (ev: { data: string }) => void
onclose?: () => void
onerror?: (err: string) => void
}
): Promise<WebSocketHandle>
type WebSocketHandle = {
id: number
send: (msg: string) => void
close: () => void
}Real-time feed
function LiveFeed() {
const [messages, setMessages] = useState([])
const socketRef = useRef(null)
useEffect(() => {
ws.connect('wss://realtime.example.com/feed', {
onmessage: (ev) => {
setMessages(prev => [...prev, JSON.parse(ev.data)])
},
onclose: () => console.log('disconnected'),
}).then(socket => {
socketRef.current = socket
})
return () => socketRef.current?.close()
}, [])
return (
<ScrollView>
{messages.map((m, i) => <Text key={i}>{m.body}</Text>)}
</ScrollView>
)
}Bi-directional chat
function ChatRoom({ roomId }) {
const [text, setText] = useState('')
const [log, setLog] = useState([])
const socketRef = useRef(null)
useEffect(() => {
ws.connect(`wss://chat.example.com/room/${roomId}`, {
onmessage: (ev) => setLog(prev => [...prev, ev.data]),
onclose: () => setLog(prev => [...prev, '── disconnected ──']),
}).then(s => { socketRef.current = s })
return () => socketRef.current?.close()
}, [roomId])
const send = () => {
socketRef.current?.send(JSON.stringify({ text }))
setText('')
}
return (
<View style={{ flex: 1, gap: 8 }}>
<ScrollView style={{ flex: 1 }}>
{log.map((l, i) => <Text key={i}>{l}</Text>)}
</ScrollView>
<View style={{ flexDirection: 'row', gap: 8 }}>
<TextInput
value={text}
onChangeText={setText}
placeholder="Message"
style={{ flex: 1 }}
/>
<Pressable
onPress={send}
style={{ padding: 12, backgroundColor: '#4a9eff', borderRadius: 8 }}
>
<Text style={{ color: '#fff' }}>Send</Text>
</Pressable>
</View>
</View>
)
}mdns.discover(serviceType, opts?)
Browse for mDNS/Bonjour services on the local network. Useful for discovering printers, local servers, smart-home devices, or other instances of your own app.
Requires: "mdns": true capability in veloxkit.config.json.
Signature
mdns.discover(
serviceType: string, // e.g. '_http._tcp.local.'
opts?: { timeout?: number } // wait time in ms (default 5000)
): Promise<MdnsService[]>
type MdnsService = {
name: string // service instance name (e.g. 'My Printer')
hostname: string // resolved hostname
port: number
addresses: string[] // IPv4 / IPv6 addresses
}Example
function LocalDeviceBrowser() {
const [devices, setDevices] = useState([])
const [scanning, setScanning] = useState(false)
const scan = async () => {
setScanning(true)
try {
const found = await mdns.discover('_http._tcp.local.', { timeout: 4000 })
setDevices(found)
} finally {
setScanning(false)
}
}
return (
<View style={{ gap: 12 }}>
<Pressable
onPress={scan}
style={{ padding: 10, backgroundColor: '#4a9eff', borderRadius: 8 }}
>
<Text style={{ color: '#fff' }}>{scanning ? 'Scanning…' : 'Scan Network'}</Text>
</Pressable>
{devices.map((d, i) => (
<View key={i} style={{ padding: 12, backgroundColor: '#1e1e2e', borderRadius: 8 }}>
<Text style={{ fontWeight: '600' }}>{d.name}</Text>
<Text style={{ color: '#888', fontSize: 13 }}>{d.hostname}:{d.port}</Text>
<Text style={{ color: '#666', fontSize: 12 }}>{d.addresses.join(', ')}</Text>
</View>
))}
</View>
)
}Patterns
Authenticated REST client helper
// lib/api.ts
import { fetch, credentials } from 'veloxkit'
async function apiFetch(path: string, options: RequestInit = {}) {
const token = await credentials.get('auth_token')
const res = await fetch(`https://api.example.com${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
})
if (res.status === 401) throw new Error('Unauthorized')
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`)
return res.json()
}
export const api = {
getNotes: () => apiFetch('/notes'),
createNote: (data) => apiFetch('/notes', { method: 'POST', body: JSON.stringify(data) }),
deleteNote: (id) => apiFetch(`/notes/${id}`, { method: 'DELETE' }),
}WebSocket reconnect with exponential backoff
async function connectWithRetry(url: string, handlers, maxRetries = 5) {
let delay = 1000
async function attempt(retries: number) {
try {
return await ws.connect(url, {
...handlers,
onclose: () => {
handlers.onclose?.()
if (retries > 0) {
setTimeout(() => attempt(retries - 1), delay)
delay = Math.min(delay * 2, 30_000)
}
},
})
} catch {
if (retries === 0) throw new Error('WebSocket connection failed')
await new Promise(r => setTimeout(r, delay))
delay = Math.min(delay * 2, 30_000)
return attempt(retries - 1)
}
}
return attempt(maxRetries)
}