Skip to content

LFHttp ​

Global:LFHttp · Enable:// @require http

Background-proxied HTTP: plain requests, streaming, WebSocket, local filesystem access, plus shortcut routes for the PluginX client/server. The target domain's existing browser cookies are attached by default (credentials: 'include'; pass 'omit' or withCredentials: false to disable). You can also set cookies explicitly with headers.Cookie, which replaces the cookies the browser would attach (see headers below).

URL policy: rejects only the local PluginX client port — port 41720 on a loopback host (127.0.0.0/8, 0.0.0.0, localhost, *.localhost, [::1], [::], and IPv6 forms embedding a loopback IPv4 such as [::ffff:127.0.0.1], [::127.0.0.1], and [64:ff9b::7f00:1]); use LFHttp.client.* to reach the local client. It also rejects chrome-extension://, file://, and non-http(s) targets (non-ws(s) for WebSocket). Other localhost ports, LAN addresses, and the PluginX server domains are allowed.

The extension-proxy transports also re-check the final address after redirects against the same policy: a redirect to the local client port rejects with LFHttpUrlBlocked (its message ends with after redirect) and the body is never handed to the script. Note that the redirected request itself is still sent by the browser (fetch follows redirects automatically); only the result is blocked. The policy checks literal hostnames/IPs only and does no DNS resolution.

Methods ​

ts
LFHttp.request(url, options?): Promise<Response>

Sends a request; returns a Response-like object

ts
LFHttp.text(url, options?): Promise<string>

Fetches text

ts
LFHttp.json(url, options?): Promise<T>

Fetches and parses JSON

ts
LFHttp.stream(url, options?): Promise<StreamResponse>

Streaming response: for await (const chunk of stream.chunks())

ts
LFHttp.websocket(url, options?): Promise<WebSocketConnection>

Opens a WebSocket (directly from the page, or proxied through the background)

ts
LFHttp.page.download(url, options?): Promise<PageDownloadReceipt>

Downloads from page MAIN world to OPFS (default) or client.fs

ts
LFHttp.page.saveAs(url, options?): Promise<PageSaveAsReceipt>

Delivers a file to the browser download manager, automatically choosing page or extension-proxy transport; returns a download receipt.

ts
LFHttp.page.fetchToOPFS(url, pathOrOptions?): Promise<PageDownloadReceipt>

Downloads to the current page origin's OPFS

ts
LFHttp.page.fetchToClientFile(url, pathOrOptions, options?): Promise<PageDownloadReceipt>

Writes to the PluginX client filesystem in bounded chunks

ts
LFHttp.client.request / text / json(route, options?)

Shortcut routes to the PluginX local client

ts
LFHttp.client.isAvailable(options?): Promise<boolean>

Probes whether the local client is available

ts
LFHttp.client.open(): boolean

Attempts to launch the local client

ts
LFHttp.client.ensure(options?): Promise<boolean>

Ensures the client is running (probe → launch → poll)

ts
LFHttp.client.getDownloadUrl(): Promise<string>

Gets the local client download URL

ts
LFHttp.client.fs.* (readFile / writeFile / readdir / stat / find / requestAccess / ...)

Local filesystem I/O executed by the local client, guarded by per-app permissions

ts
LFHttp.server.request / text / json(route, options?)

Shortcut routes to the PluginX server

Request options (LFHttpRequestOptions) ​

request / text / json / stream accept every field below; client.* / server.* share most of them, with the differences listed after the table. Every field is optional, and passing null as options is the same as omitting it:

FieldTypeDefaultDescription
methodstringGETHTTP method, auto-uppercased
headersRecord<string, string>{}Forwarded verbatim. fetch itself cannot set browser-reserved headers (Cookie / Referer / User-Agent / Origin / Sec-* / DNT / Accept-Charset / Date / Access-Control-Request-*), so the extension-proxy transports write them on the wire with a one-shot declarativeNetRequest rule; the response's headerOverrides reports the result. A Cookie header replaces the cookies the browser would attach rather than adding to them. The rule matches only the original URL, so requests after a redirect no longer carry these headers
credentials'include' / 'omit' / 'same-origin''include'Attaches the target domain's existing browser cookies by default; pass 'omit' (or withCredentials: false) for a cookieless request. client.* never sends cookies and server.* always does; both ignore this field
bodystring / object—Strings are sent as-is; any other value is JSON.stringify'd, so FormData / URLSearchParams / Blob / ArrayBuffer / typed arrays turn into {} or similar garbage and are not supported. Content-Type is never set for you (the browser defaults a string body to text/plain;charset=UTF-8): add 'content-type': 'application/json' for JSON, and for forms call new URLSearchParams(...).toString() yourself and set application/x-www-form-urlencoded. GET/HEAD bodies are dropped
throwOnHTTPErrorbooleantrueSet false to resolve on non-2xx too, so you can read 4xx/5xx business error bodies; takes precedence over validateStatus. Ignored by stream()
validateStatus(status, response) => boolean—Custom success predicate; only controls whether HTTPError is thrown, never flips the ok field. Ignored by stream()
maxBodyBytesnumber67108864 (64MB)Buffered-read cap; oversized bodies reject (never silently truncated); 0 or Infinity disables the cap. On stream() it only limits text() / json() / arrayBuffer() / bytes(); chunks() is uncapped
onProgress({ loaded, total, ratio }) => void—Download progress callback; total/ratio are null without Content-Length. Works for request / text / json and for the buffered readers of stream() (not for chunks()); ignored by client.* / server.*
streambooleanfalserequest / text / json only: true forces the streaming transport and still returns the same Response-like object once the body is read. The runtime switches automatically when the response declares a large body, so you rarely need it
signalAbortSignal—Available on request / text / json / stream. request / text / json with a signal switch to the streaming transport automatically; aborting really cancels the request and the Promise rejects with an error whose name is 'AbortError', code is 'ABORTED', and cause is signal.reason. An already-aborted signal rejects immediately without sending anything. Ignored by client.* / server.*

Unsupported options: timeout, mode, redirect, cache, referrer, keepalive, and other remaining fetch options are not forwarded; redirects are always followed. For a timeout pass signal: AbortSignal.timeout(ms); note that the browser throttles that timer while the tab is hidden (another tab active, window covered or minimized), so it can fire seconds or even a minute late — treat it as an upper bound, not a precise clock. client.* / server.* cannot be cancelled once sent.

How client.* / server.* differ: the cookie policy is fixed (see credentials), and onProgress, stream, and signal are ignored (always a one-shot transfer); every other field behaves the same.

Response object (LFHttpResponseLike) ​

FieldTypeDescription
okbooleanTrue for 200-299, always derived from the real status
status / statusTextnumber / stringHTTP status / reason phrase. A transport failure (DNS failure, connection refused, offline …) never produces a response object; it throws NetworkError
headersRecord<string, string>Lowercase-keyed response headers; multi-value headers joined with commas
urlstringFinal URL after redirects
bodyType'text' / 'base64'text for text-like content (JSON/HTML/XML …), base64 for binary. A response without Content-Type is treated as text, so arrayBuffer() returns the UTF-8 re-encoded bytes and binary content may be corrupted
headerOverrides{ applied: string[], dropped: string[] }Present only when the request carried browser-reserved headers: applied lists headers written on the wire, dropped lists headers that could not be written (non-empty means they were not sent). Provided on both one-shot and streaming responses (stream(), onProgress / signal / stream: true)
text() / json() / arrayBuffer()() => PromiseRead the in-memory body; text()/json() reject on binary responses (use arrayBuffer()); json() throws SyntaxError on empty/non-JSON bodies

LFHttp.stream(url, options?) ​

Resolves as soon as response headers arrive (before the body downloads), so you can inspect ok / status / total (Content-Length bytes, null when unknown) first. chunks() returns an incremental async iterator (for await (const bytes of res.chunks()); each chunk is a Uint8Array, decode text with TextDecoder; no buffer cap) and is mutually exclusive with text() / json() / arrayBuffer() / bytes() — the body can be consumed once. Note stream() never throws HTTPError (throwOnHTTPError / validateStatus have no effect); check ok yourself.

Two ways to cancel:

  • options.signal: aborting before the headers arrive rejects stream() itself with code: 'ABORTED'; aborting mid-body makes the chunks() loop (and buffered readers such as text()) throw an error with name: 'AbortError' and code: 'ABORTED', so a truncated body is never mistaken for a complete one.
  • res.abort(): cancels the download and sets res.aborted to true; a chunks() loop in progress simply ends without throwing, so check res.aborted when you need to tell the cases apart.

Call res.abort() whenever you decide not to consume the body: the background has no backpressure, so unconsumed data keeps downloading and piles up in page memory.

LFHttp.page.download(url, options?) ​

Starts native fetch in the target tab's MAIN world, preserving page-owned Cookie/Referer/Origin behavior and page CORS. The complete body never travels through a runInMainWorld result: it streams into origin-private file storage (OPFS, the default), then optionally copies to an authorized client.fs absolute path in bounded chunks. Aliases: page.fetchToOPFS(url, pathOrOptions?) and page.fetchToClientFile(url, pathOrOptions, options?).

OptionTypeDefaultDescription
target'opfs' / 'client.fs''opfs'OPFS paths are relative to the current page origin; client.fs requires an authorized absolute path
path / namestringFile name derived from the URLDestination name/path; OPFS rejects .., client.fs reuses the app's file permissions
signalAbortSignal—Cancels the page fetch and any remaining client.fs chunks; temporary/partial files are cleaned up on a best-effort basis
onProgress({loaded,total,ratio,stage})=>void—stage is fetch or client.fs; total/ratio are null when Content-Length is unknown
maxBytesnumberunlimitedExceeding it throws MAX_BYTES_EXCEEDED and keeps no partial download; must be a positive integer, 0 / Infinity mean unlimited
throwOnHTTPErrorbooleantrueThrows HTTP_ERROR on non-2xx; set false to save the body anyway and read status from the receipt
method / headers / body / credentials / redirect / referrer …as page fetchcredentials: 'include'Passed to the native fetch in the page (you can also group them in a fetch: { ... } sub-object). This is the page's own request, so browser-reserved headers (Cookie/Referer/Origin …) cannot be overridden; body must be a string; mode: 'no-cors' throws INVALID_OPTIONS (opaque bodies are unreadable)

Returns a small receipt (no file body): { ok, target, path, name, bytesWritten, status, statusText, contentType, url, nonWorkspaceSideEffect, advisory }. nonWorkspaceSideEffect.rollbackSupported is always false so Agent rollback previews can warn that OPFS/client.fs live outside the editor workspace and are not deleted or restored by a session rollback.

page.download never creates a browser Downloads entry. OPFS and client.fs are its only destinations, and target:'browser' now throws INVALID_TARGET. Use page.saveAs below when the user is supposed to receive the file.

LFHttp.page.saveAs(url, options?) ​

The only API that reaches the browser download manager, and the only one that can deliver a file the page itself cannot fetch. It is not slower than a hand-rolled stream + Blob download, so never hand-roll one for speed. Bytes never cross a world boundary, and by default they never touch disk either: the proxy transport streams them straight into the plugin's own world, and a response that fits in a 64 MB memory budget is delivered exactly the way stream + Blob would deliver it — collected in memory, wrapped in an object URL, handed to a synthetic a[download], with no disk write and no cross-world call. Only a response beyond that budget (or one where path asks for a kept OPFS copy) spills to OPFS, in large coalesced slabs written while the download keeps running, so a multi-GB file stays bounded in memory without paying per-chunk disk latency; that case reads the staged file back into an object URL and crosses worlds once, carrying just a path and a file name. The page transport instead writes in the page MAIN world where the fetch runs.

Staging has two transports, selected by transport (default 'auto'): it first tries the page-identity fetch, and on PAGE_CORS_OR_NETWORK — typically a same-origin signed URL that 302s to a CDN with no Access-Control-Allow-Origin — it automatically re-stages through the extension proxy, which page CORS does not constrain. The page attempt fails at the response-header stage, so the re-route wastes no body transfer. In other words, you never need to predict CORS behavior before calling saveAs, and you should not fall back to a hand-rolled whole-file Blob just because a CDN redirect might be opaque.

OptionTypeDefaultDescription
fileNamestringDerived from the URLDownload name handed to the browser (the a[download] value); name is accepted as an alias. The browser may still add a counter on name clashes; because the bytes come from an object URL, Content-Disposition has no effect
transport'auto' / 'page' / 'proxy''auto'auto tries the page fetch and re-routes through the extension proxy when CORS blocks it; page keeps page Cookie/Origin/Referer and surfaces the CORS error directly (use it when the request is only valid with page identity); proxy skips the page attempt (use it for a self-signed URL known to land on a CDN). Any other value throws INVALID_OPTIONS
pathstringno disk copyPath of an extra OPFS copy to keep. Passing it forces the disk route and the copy is not deleted — do not pass it just to be safe, it gives up the faster memory-only delivery. Without it, responses that fit the memory budget never touch disk and larger ones are staged to .lf-http-page-saveas/*.part and deleted after a delay
cleanupDelayMsnumber120000How long the object URL (and the temporary file when no path is given) survives the click. Browsers read blobs lazily, so revoking too early truncates or fails the download; raise it for large files or slow disks
Other optionsas page.download—signal / maxBytes / onProgress / throwOnHTTPError / method / headers / body are passed to whichever staging transport runs; signal works on both transports and rejects with code: 'ABORTED'. The proxy transport only honors these, so page fetch options such as credentials / referrer do not apply to it; its headers behave like LFHttp.stream(), writing browser-reserved headers on the wire. onProgress reports stage: 'fetch' (page transport) or 'proxy' (proxy transport); target / destination are ignored

The receipt's transport field tells you which transport actually ran ('page' / 'proxy'); report it truthfully rather than assuming the page fetch succeeded.

With several candidate URLs, prefer the same-origin self-signed one. The proxy transport sends the browser's existing cookies for the target domain but not the page identity (the page's Origin/Referer are not attached, only headers you pass explicitly), so a hotlink-protected CDN URL (bound to a signature or page identity) can still 403 even with a Referer. When a resource offers several candidates — for example a media url_list holding both a same-origin self-signed endpoint (like /aweme/v1/play/?...) and third-party CDN links (*vod* / *.cdn domains) — use the same-origin self-signed URL: it works on both the page and proxy transports. Taking url_list[0] blindly often hits the CDN link and 403s. If only a hotlink-protected CDN URL is available, pass the required Referer/headers; if it still 403s, report the identity requirement honestly instead of cycling through URL spellings or transports.

Returns the staging receipt plus { target:'browser', transport:'page'|'proxy', fileName, dispatched:true, stagedPath }. Dispatch is not completion: the receipt proves the browser accepted the request, not that the file reached disk — Chrome may still prompt, rename, block, or require permission for repeated automatic downloads. Requires a real page (run --tab, liveDebug, or a published bundle); a plain run script with no tab cannot use it. The page transport additionally needs LFRuntime.runInMainWorld: without it transport:'page' fails before any bytes are transferred, and transport:'auto' goes straight to the proxy transport.

LFHttp.websocket(url, options?) ​

OptionTypeDefaultDescription
mode'auto' / 'direct' / 'background''auto'direct uses the native browser WebSocket (no custom handshake headers); background connects from the extension service worker and can inject handshake headers; auto picks background when headers are set or when the execution environment is not page. Any other value throws a plain Error
headersRecord<string, string>—Custom handshake headers (e.g. Authorization); requires background mode. Passing headers with an explicit mode: 'direct' throws a plain Error
protocolsstring / string[]—Sec-WebSocket-Protocol values
binaryType'arraybuffer' / 'blob'—Binary message type, direct mode only; background mode always delivers binary messages as ArrayBuffer
timeoutnumberno timeoutConnect timeout in ms; 0 disables. A timeout rejects with a plain Error (not LFHttpWebSocketError)

Returns a connection object:

  • Methods: send(data) (string / ArrayBuffer / typed array; background mode does not accept Blob), sendJson(value), waitMessage({ timeout?, json?, predicate? }), close(code?, reason?), on(type, cb) / off(type, cb) (aliases addEventListener / removeEventListener; type is 'open' / 'message' / 'error' / 'close', and on() returns an unsubscribe function). There is no onMessage-style method — receive messages with socket.on('message', cb).
  • Read-only properties: readyState (0 connecting / 1 open / 3 closed; direct mode reports the native socket's value), mode (the 'direct' / 'background' actually chosen), url, lastError, rawSocket (the native WebSocket in direct mode, otherwise null).

Handshake failures reject with LFHttpWebSocketError (fields in the error table below): phase: 'open' (with the server's close code) when a close arrives first, or phase: 'error' with code: 0 when an error arrives first — in direct mode the browser usually fires error first, so 'error' is the common case.

The message event has one shape everywhere: every transport delivers { data, bodyType? } to the callback, so read event.data; waitMessage() resolves to the payload itself. Transport internals (relay envelope fields and the like) never appear on the event object.

waitMessage() takes messages from a queue. Every incoming message enters an internal queue, and waitMessage() takes the oldest message not yet taken by a waitMessage call that satisfies predicate(data) — including messages that arrived before the call and messages already delivered to on('message'). With json: true the data is JSON.parsed. A timeout rejects with a plain Error; if the connection is closed and nothing matches it rejects with an LFHttpWebSocketError whose phase is 'close'; after a connection error it rejects with that error. The queue keeps only the most recent 1000 messages and drops the oldest beyond that, so on a busy connection waitMessage() may not find much older messages; to handle every message in real time use on('message', cb).

direct only works in a page. The background-app sandbox and the no-tab run editor sandbox are opaque origins where CSP blocks native WebSocket — even a hand-written new WebSocket(...) cannot connect. Keep the default mode: 'auto' there (it selects background for you); if you ask for direct explicitly, the error's reason names the limiting execution environment and points at background mode.

The close event may not arrive immediately after close(). In direct mode (and background mode under run --tab), close(code, reason) only sends the close request and the close event waits for the real closing handshake, which can take seconds against a remote server. Background mode inside an app (liveDebug / installed / background app) fires close right after close(), with the code/reason you passed. Either way readyState becomes 3 when the event fires. Await the event (or waitMessage() rejecting with phase: 'close') rather than asserting after a fixed sleep.

client / server dedicated routes ​

MethodDescription
client.request/text/json(route, options?)Always targets the local PluginX client at http://127.0.0.1:41720; route is a path only (e.g. /api/xxx, query allowed), absolute URLs throw LFHttpUrlBlocked; the background auto-injects the clientid header; never sends cookies
client.isAvailable({ route?, method?, timeout? })Probes whether the client is running (default timeout 3000ms): any HTTP response (incl. 4xx/5xx) counts as running
client.open()Launches the local client via a custom protocol (hidden iframe, no navigation); returns whether the trigger fired
client.ensure({ timeout?, interval?, route?, probeTimeout? })Probe, launch if needed, then poll every interval (default 500ms) until timeout (default 15000ms); resolves to readiness
client.getDownloadUrl()Gets the client installer download URL for guiding users without the client
server.request/text/json(route, options?)The background resolves and prepends the PluginX server base URL; route is a path only; always attaches the domain's cookies (credentials has no effect), no clientid

Every client.request/text/json and server.request/text/json call automatically carries app identity headers: x-lf-app-id (app id) and x-lf-app-status (liveDebug / dev / published), force-injected by the runtime — same-named headers in options.headers are overwritten. The background additionally performs browser-attested attribution and appends x-lf-app-attested (1 = attested / 0 = unattributable), letting the local client and server reject dangerous requests per app and status. Fixed-route helpers carry none of these headers: the root-path availability probe used by isAvailable / ensure (GET/HEAD to / with no body), open (protocol launch), getDownloadUrl and fs.* (separate background channels), and all LFUser methods.

Local filesystem (LFHttp.client.fs.*) ​

Disk I/O is executed by the local PluginX client over a WebSocket channel. Paths must be absolute and pass the app's whitelist/blacklist permission check — call requestAccess() first to ask the user.

Security boundary: fs requests are attributed to the browser-attested app identity — the background checks the whitelist/blacklist of the app that actually sent the request, so a script cannot borrow another app's grants by claiming its bundleId. client.fs is the only local-file channel available to plugin apps; the editor's internal local file access, local command execution (localexec), and CLI registry requests accept only the PluginX editor page, and plugin code sending those messages directly is rejected (EACCES_PERMISSION) — to reach the editor workspace use LFRuntime.cli.register({ workspace: [...] }) + ctx.workspace.*.

MethodDescription
readFile(path, { offset?, length?, encoding?, maxBytes? })Reads a file, resolves { data, bytesRead, eof, size }; whole-file cap 16MB (throws EMSGSIZE beyond); encoding defaults to utf8 — always pass 'binary' for binary files (data becomes a Uint8Array)
writeFile(path, data, { offset?, append?, encoding?, chunkSize? })Writes a file (full overwrite by default, append: true to append); resolves { bytesWritten }, or { bytesWritten, chunks } when large data is chunked automatically; appendFile() equals append:true
readdir(path) / stat(path) / exists(path)List a directory ({ name, path, isDir, size, mtimeMs }[]) / read metadata ({ size, isDir, mtimeMs, mode } — mode is a permission string like -rw-rw-rw-) / existence check (never throws)
mkdir / remove / rename / copy / findCreate dirs (recursive by default) / delete (non-empty dirs need recursive: true) / rename-move / copy (existing target needs overwrite: true) / glob search by filename (e.g. find(dir, { pattern: '*.json', maxResults?, maxDepth? }))
open / read / write / seek / closeHandle-based sequential streams for very large files; open(path, flags) returns a handle — always close(handle) in a finally block; read(handle, { length?, encoding? }) defaults to encoding: 'binary' and 2MB per call
readFileChunked / writeFileChunkedChunked large-file helpers (default 2MB per chunk; files over 16MB must use these); for binary always pass encoding: 'binary' or use the onChunk callback — aggregate utf8 mode is lossy for binary bytes
requestAccess(paths, { reason? })Asks the user for directory access; resolves { granted, mode, grantedPaths }; denial/close/timeout (120s) resolves granted:false without throwing
getPermissions()Reads the app's { mode, whitelist, blacklist } permission config

fs failures throw LFHttpFsError (check with instanceof LFHttp.FsError, branch on error.code): EACCES_PERMISSION (denied by the permission list), CLIENT_OFFLINE (client not connected — ensure() first; ensure() only probes the client's HTTP service, so the fs channel may still be connecting right after it returns true — retry briefly on CLIENT_OFFLINE), CONNECTION_LOST (channel dropped mid-request), TIMEOUT (30s per request), EMSGSIZE (over the size cap — switch to chunked), ENOENT / EEXIST / EACCES_OS and other OS errors, EBADF (invalid/cross-app handle), USER_DENIED (authorization refused).

Error types ​

ErrorFieldsDescription
HTTPErrorstatus / statusText / headers / url / responseThrown by default on non-2xx; await error.response.text() still reads the error body; check with instanceof LFHttp.HTTPError (error.name === 'HTTPError')
LFHttpUrlBlockedreason / host / protocolURL rejected by policy. This is a plain Error with no exported class, so check error.name === 'LFHttpUrlBlocked'. reason is a stable enum: invalid url / unsupported protocol / missing host / local client port. Only errors from the page-side pre-check are guaranteed to carry reason / host; absolute URLs passed to client.* / server.*, and blocks raised by the extension background (such as the post-redirect re-check), only guarantee name and message (the post-redirect message ends with after redirect)
LFHttpWebSocketErrorcode / reason / wasClean / phase / urlphase is 'open' (a close arrived during the handshake), 'error' (connection error, code 0), or 'close' (the connection closed while waitMessage() was waiting); check with instanceof LFHttp.WebSocketError
LFHttpPageDownloadErrorcode / target / pathpage.download / page.saveAs failure; check with instanceof LFHttp.PageDownloadError. On cancellation name === 'AbortError' and code === 'ABORTED'. Codes: ABORTED, PAGE_CORS_OR_NETWORK, HTTP_ERROR, MAX_BYTES_EXCEEDED, QUOTA_EXCEEDED, INVALID_TARGET, INVALID_OPTIONS, OPFS_UNAVAILABLE, OPFS_READ_FAILED, CLIENT_OFFLINE, TAB_UNAVAILABLE, TAB_NAVIGATED, TAB_OR_BACKGROUND_UNAVAILABLE, MAIN_WORLD_UNAVAILABLE, MAIN_WORLD_EXEC_FAILED, DISPATCH_FAILED, with PAGE_DOWNLOAD_FAILED as the fallback
LFHttpFsErrorcode / path / opclient.fs.* failure; check with instanceof LFHttp.FsError; codes are listed in the previous section
NetworkError—DNS failure / connection refused / offline. A plain Error; check error.name === 'NetworkError'. It never shows up as a response with status: 0
AbortErrorcode / causerequest / text / json / stream cancelled through options.signal: name === 'AbortError', code === 'ABORTED', and cause is signal.reason

Other failures (body over maxBodyBytes, consuming a stream body twice, WebSocket connect or waitMessage timeouts, …) throw a plain Error whose message explains the cause.

Example

js
// @require http

const config = await LFHttp.json("https://example.com/config.json");
const page = await LFHttp.text("https://example.com/page.html");

// Sending JSON: an object body is JSON.stringify'd, but you must set Content-Type yourself
const created = await LFHttp.json("https://example.com/api/items", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: { name: "demo" },
});

// Streaming response: each chunk is a Uint8Array
const stream = await LFHttp.stream("https://example.com/events");
if (!stream.ok) {
  stream.abort(); // always abort when you won't consume the body
} else {
  const decoder = new TextDecoder();
  for await (const chunk of stream.chunks()) {
    console.log(decoder.decode(chunk, { stream: true }));
  }
}

// WebSocket
const socket = await LFHttp.websocket("wss://example.com/ws");
const off = socket.on("message", (event) => console.log(event.data));
socket.send("ping");
const firstReply = await socket.waitMessage({ timeout: 5000 });
off();
socket.close();