Skip to content
Ink
Esc
↑↓navigate↵open⌘Jpreview
On this page

Network

Use fetch with native HTTP transport.

Use @ink/network for fetch, response streams, Blob, File, FormData and WebSocket.

Import it in each module that uses these APIs.

For services that require unencrypted HTTP, opt in explicitly in ink.toml:

[network]
allow_cleartext = true

This includes network and permits cleartext traffic throughout the app, including podcast feeds, downloads and audio playback. Audio can also follow redirects between HTTP and HTTPS. Without this setting, remote audio requires HTTPS and cleartext network traffic remains restricted to local development hosts.

Make a request

import "@ink/network";

export async function getDepartureBoard(stopId: string, signal?: AbortSignal) {
  const url = new URL("https://api.example.com/departures");
  url.searchParams.set("stop", stopId);
  const response = await fetch(url, { signal });
  if (!response.ok) throw new Error(`Departures unavailable (${response.status})`);
  const body: unknown = await response.json();
  return decodeDepartures(body);
}

decodeDepartures validates the response for your app.

fetch rejects network failures and cancellation. HTTP errors such as 404 still return a response; check response.ok before reading it.

Cancel or time out

Use AbortSignal.timeout() for timeouts and AbortSignal.any() to combine cancellation signals.

Read a response

fetch resolves when headers arrive. A response body can be read once.

Read the whole body

json(), text(), blob(), formData() and arrayBuffer() read an entire HTTP response into memory, up to 16 MiB.

Stream a response

Body bytes cross the native bridge as typed byte arrays. HTTP, managed-file reads and WebSocket binary messages no longer encode those bytes as base64 inside JSON. This preserves the public APIs and their existing limits; it still copies bytes across runtime and Android ownership boundaries.

Read larger bodies with a reader, async iterator or stream pipeline. Ink reads 32 KiB chunks as needed:

const response = await fetch(url, { signal });
if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`);
const reader = response.body.getReader();
try {
  for (;;) {
    const { value, done } = await reader.read();
    if (done) break;
    processChunk(value);
  }
} finally {
  await reader.cancel();
  reader.releaseLock();
}

Supply url, a cancellation signal and a processChunk handler. Cancel the reader or abort the request when finished.

clone() creates a second stream. Unread data can accumulate in memory, so avoid cloning large responses.

Read a local file

Local managed files behave differently: blob() keeps their data in native storage. text() and arrayBuffer() still load it into JavaScript memory.

Upload data

Request bodies accept text, URLSearchParams, ArrayBuffer, typed arrays, Blob, FormData and ReadableStream<Uint8Array>.

Bodies supplied from JavaScript have a 64 MiB limit. Ink prepares the body before sending it, so stream uploads start only after the stream finishes.

Text, byte and Blob bodies can be sent again after redirects; consumed stream bodies cannot.

Multipart uploads

For a multipart upload, supply photoBlob, uploadUrl and an optional cancellation signal:

const form = new FormData();
form.append("caption", "Station entrance");
form.append("photo", photoBlob, "entrance.jpg");
await fetch(uploadUrl, { method: "POST", body: form, signal });

Let fetch set the multipart Content-Type and boundary.

Upload managed files

Read an attachment with fetch(file.src) and use its blob() in the upload. Blob slices, File objects and FormData retain references to native file data, without loading it into JavaScript memory.

Ink sends one native copy request per managed file range. Android copies into the temporary upload file on its network executor with a bounded 64 KiB buffer and checks cancellation between reads. JavaScript stream bodies still send 32 KiB chunks. Native file data is limited by available storage, not the 64 MiB JavaScript body limit.

Managed sources do not allow arbitrary filesystem access. Earlier saved camera URLs remain readable; use photo.file.src for new captures.

Transfer limits

Resource Limit
Open responses per runtime 16
Pending uploads per runtime 8
Inactive response streams and upload files Expire after 60 seconds without reads or writes.

Use Downloads for transfers that must survive closing a screen.

HTTPS and authentication

Release builds require HTTPS/WSS. Debug builds allow local loopback HTTP/WS.

Redirects to another origin remove credentials. Requests do not share browser cookies or login sessions. Manage provider cookies separately for each account.

There is no browser origin sandbox. npm HTTP clients must work without browser or Node.js APIs. Use Auth for sign-in.

WebSocket connections

WebSocket supports WSS, subprotocols, text and binary messages, binaryType, and open, message, error and close events. Idle sockets wait for native events without polling.

Unencrypted ws:// is restricted to loopback hosts (localhost, 127.0.0.1, ::1) and requires a debug build or the network-cleartext capability. Remote sockets require WSS.

const socket = new WebSocket("wss://api.example.com/events");
socket.addEventListener("open", () => socket.send("subscribe"));
socket.addEventListener("message", event => handleMessage(event.data));
// When the connection is no longer needed:
socket.close(1000, "Finished");

Supply handleMessage to process messages. Open sockets outside rendering and share authentication and reconnection logic across screens.

Limit Maximum
Sockets per runtime 8
Message size 256 KiB
Incoming or outgoing queue 512 KiB

An overflowing incoming queue closes the connection. bufferedAmount includes JavaScript sends and the latest native queue reading.

Reconnect and recover messages

Track message sequence numbers or provider cursors to detect missing data and recover it after reconnecting. Limit queued messages. Sockets are not reliable background delivery; use background work and push where available.

Retry failed requests

Retry failed reads with increasing delays and a retry limit. A server may accept a write even if the response never arrives. Give writes stable operation IDs so the server can recognise retries, or check whether a write succeeded before repeating it.

Save offline data

Save small results with Store. A resource’s in-memory cache does not survive app restarts. Clear private saved content when signing out.

Connection state and downloads

Separate imports provide connection state and managed downloads:

import { connectivity } from "@ink/network/connectivity";
import { downloads } from "@ink/network/downloads";

These imports work independently and do not install web globals. @ink/network alone includes neither feature. Images and maps load without it.