🚧 VeloxKit is pre-release software. APIs may change before v1.0. Get started →
Documentation
APIs & Bindings
network

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)
}