Skip to content

Entry points

@fkn/lib is one package with 29 exports: 28 import paths that carry code, plus its manifest. This page lists every entry, what it gives you, what importing it starts, and the guide that explains it.

The subpaths let you carry one piece of the library at a time. Every one of the 28 ships an ESM .js, a CJS .cjs and one bundled .d.ts. What each one pulls in, and whether importing it opens the broker (the connection your app holds into FKN), is under what each entry reaches.

A subpath and the matching root namespace are one implementation. The choice between them is about what the bundle carries and what the import starts, never about what a call does:

app.ts
import {
(alias) namespace cloud
import cloud
cloud
} from '@fkn/lib'
import {
const fetch: (input: ProxyFetchInput, init?: ProxyFetchInit) => Promise<Response>
export fetch
fetch
as
const cloudFetch: (input: ProxyFetchInput, init?: ProxyFetchInit) => Promise<Response>
cloudFetch
} from '@fkn/lib/cloud/fetch'
(alias) namespace cloud
import cloud
cloud
.
const cloud_d_exports.fetch: (input: ProxyFetchInput, init?: ProxyFetchInit) => Promise<Response>
export cloud_d_exports.fetch
fetch
===
const cloudFetch: (input: ProxyFetchInput, init?: ProxyFetchInit) => Promise<Response>
cloudFetch
// true, one function behind both paths

The narrow entry gives you the root’s cloud.fetch and nothing else. The root’s bare fetch is a different function: it picks a backend on every call. A backend is where a call runs: the FKN cloud, the FKN browser extension or the desktop. See backends.

Several entries name the account and the account copy. The account is the FKN identity a person carries between sites, and the account copy is the copy of each file kept in the account. Here is every export:

ExportWhat it gives youGuide
@fkn/libfetch, attachFrame, relayWorker, promptInstall, connect, promptRelay, connectButtonUrl, the namespaces cloud, extension, desktop, fs, opfs, net, dgram, http, dns, account, packages and shell, and everything the extension exposes to a pagebackends
@fkn/lib/fsNode’s fs over this device’s OPFS and the account copy: sync, callback and promise forms, available, mount, flush, remount, pull, readFileSealed, adopt, adoptable, pending, pendingDeletes, replicating, onConflictstorage, sync and conflicts
@fkn/lib/fs/promisesthe promise members of @fkn/lib/fs, and only thosestorage
@fkn/lib/opfsthe same shape over this device’s OPFS alone, with available, mount, flush, remountstorage
@fkn/lib/opfs/promisesthe promise members of @fkn/lib/opfs, and only thosestorage
@fkn/lib/cloudfetch, attachFrame, quota, available, the namespaces fs, net, dgram, http, dns, and the type QuotaStatusbackends
@fkn/lib/cloud/fetchfetch alonefetch()
@fkn/lib/cloud/fsthe account’s storage, async only: callback and promise forms, available, quota, encryption, unlock, readFileSealed, isNotFound, StorageLockedError, StorageNotFoundError, STORAGE_NOT_FOUNDstorage, encryption
@fkn/lib/cloud/fs/promisesthe promise members of @fkn/lib/cloud/fs, and only thosestorage
@fkn/lib/cloud/net, @fkn/lib/cloud/dgram, @fkn/lib/cloud/http, @fkn/lib/cloud/dnsthe same objects as @fkn/lib/net, @fkn/lib/dgram, @fkn/lib/http and @fkn/lib/dnsTCP and UDP sockets, HTTP and DNS
@fkn/lib/netSocket, Server, connect, createConnection, createServer, isIP, isIPv4, isIPv6, and a default objectTCP and UDP sockets
@fkn/lib/dgramSocket, createSocket, and a default objectTCP and UDP sockets
@fkn/lib/httprequest, get, createServer, Server, ClientRequest, IncomingMessage, ServerResponse, Agent, globalAgent, HTTPParser, METHODS, STATUS_CODES, and a default object, with its types in the generated reference for @fkn/lib/httpHTTP and DNS
@fkn/lib/dnslookup and the type AddressLookupResultHTTP and DNS
@fkn/lib/extensionfetch, attachFrame, available, events, promptInstall, cookies, permissions, setRequestHeaderRule, removeRequestHeaderRule, waitForExtensionExposure, setMissingExtensionHandler, the permission descriptors, isLocatorDenied, isLocatorUnsupported, isTerminalError, the types Frame, Locator and AttachFrameOptions, and the rest in the generated reference for @fkn/lib/extensionfetch(), frames, permissions and consent, locators and actions
@fkn/lib/desktopavailable and fs.available, which answer false, and placeholders that throwbackends
@fkn/lib/accountlogin, logout, info, onChange, and the type AccountInfoaccount and quota
@fkn/lib/packagessearch, pick, install, list, uninstall, show, hide, connect, mount, attach, onConnect, isVisible, onVisibilityChange, their types, and the PackagesError type you match on error.codepackages
@fkn/lib/roomsavailable, create and join, the Room they resolve with its send, on, members, defaults, setDefault, grant, revoke, remove, block, unblock, leave and closed, and the types RoomMember, RoomMessage, RoomEvent, RoomPermission, RoomPermissions, RoomDefaults, RoomEnd, CreateOptions, JoinOptions, RoomsErrorCode and the RoomsError type you match on error.coderooms
@fkn/lib/reactConnectButton, connectButtonUrl, and the type ConnectButtonPropsaccount and quota
@fkn/lib/contracttypes only: Resolvers and the broker’s data vocabularyTypeScript
@fkn/lib/wirethe sixteen socket option codes and the unions TcpSocketOption, UdpSocketOptionTCP and UDP sockets
@fkn/lib/messagesfive string constants: E2E_LOCKED_MESSAGE, E2E_STALE_EPOCH_MESSAGE, E2E_INTEGRITY_MESSAGE, STORAGE_NOT_FOUND, STORAGE_UNREACHABLEencryption
@fkn/lib/attach-policynormalizeDeclaredHosts, frameFetchVerdict, and the types FrameFetchPolicy, FrameFetchVerdictframes
@fkn/lib/apiapiPromise, apiWithin, API_DEADLINE_MS, BrokerUnreachableError, hasTransport, onApiEpoch, currentApiEpoch, apiLastCall, apiInFlight, and the type Resolverserrors and lifecycle
@fkn/lib/package.jsonthe generated manifestinstall

The package exports its manifest for tooling that reads the version. Nothing else in it is reachable by path. The exact signature of every name above is in the generated API reference.

The root is the whole library under one import, and importing it is not free. When the module evaluates in a window, it mounts the broker frame or adopts one the page already holds. The broker frame is the hidden fkn.app iframe that carries the broker.

The one exception is a nested realm whose parent broker hands it a port. A realm is one JavaScript execution context, such as a window or a worker. The exact conditions are on how it works.

Importing also registers the handler that opens the install card when an extension call finds no extension:

app.ts
import {
(alias) namespace cloud
import cloud
cloud
,
(alias) namespace shell
import shell
shell
} from '@fkn/lib'
(alias) namespace shell
import shell
shell
.
shell_d_exports.busyReasons(): string[]
export shell_d_exports.busyReasons

What a shell reload would sever right now, as a list of human-readable reasons, empty when nothing is bound to the broker connection.

This is the evidence for deciding whether to call applyUpdate yet: open sockets and listening servers, a streaming proxy response, a mounted package, a live frame attachment, unflushed write-behind. An empty list is not a promise that a reload is free, only that this realm holds nothing the lib knows about, so an app with state of its own should weigh that too.

It is per REALM, and that distinction has already caused a wrong reading once. A worker that imports @fkn/lib/net keeps its own tally, which this function cannot see from the window. An app whose transfers live in a worker should ask the worker, not the page.

busyReasons
() // [] while nothing in this realm holds a busy token
(alias) namespace cloud
import cloud
cloud
.
cloud_d_exports.available(): boolean
export cloud_d_exports.available
available
() // true in any window or worker, connected or not
const {
const remainingBytes: number

bytes of free-tier volume left today

remainingBytes
} = await
(alias) namespace cloud
import cloud
cloud
.
cloud_d_exports.quota(): Promise<cloud.QuotaStatus>
export cloud_d_exports.quota
quota
() // the free volume left today, answered over the frame the import mounted
var document: Document

window.document returns a reference to the document contained in the window.

MDN Reference

document
.
ParentNode.querySelector<Element>(selectors: string): Element | null (+4 overloads)

Returns the first element that is a descendant of node that matches selectors.

MDN Reference

querySelector
('iframe[title="FKN"]') // the broker frame, mounted by the import

No line above creates the frame. A top-level page holds it from the first microtask after the module evaluated, whether or not a call is ever made.

Importing starts only two timers: a poll every 16 ms for the frame to commit, capped at 10,000 ms, and in a nested realm a 2,000 ms wait for the parent’s port. Each timer is cleared once it settles.

The root’s fetch, attachFrame and extension are the library’s own, not the ones @fkn/lib/extension exports under the same names. Of that entry, only events and available are missing from the root by name, and both are still reachable as extension.events and extension.available.

shell exists only in the root: the package publishes no @fkn/lib/shell subpath. Its busyReasons() lists the busy tokens held in this realm, each one a reason the realm reports itself busy, such as an open socket or a relayed worker. A relayed worker keeps its own tally, which the page never sees.

Three file systems share one Node shape, and the entry you pick decides where the bytes live:

app.ts
import
const fs: {
available: () => Promise<boolean>;
readFileSync: (path: import("node:fs").PathLike, options?: ReadOptions) => Buffer | string;
writeFileSync: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => void;
appendFileSync: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => void;
existsSync: (path: import("node:fs").PathLike) => boolean;
statSync: (path: import("node:fs").PathLike) => opfs.Stats;
... 28 more ...;
onConflict: (next: ConflictResolver) => (() => void);
}
fs
from '@fkn/lib/fs'
import {
const readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>
readFile
} from '@fkn/lib/fs/promises'
import * as
import opfs
opfs
from '@fkn/lib/opfs'
import {
const promises: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
export promises
promises
as
const cloudFs: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
cloudFs
} from '@fkn/lib/cloud/fs'
await
const fs: {
available: () => Promise<boolean>;
readFileSync: (path: import("node:fs").PathLike, options?: ReadOptions) => Buffer | string;
writeFileSync: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => void;
appendFileSync: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => void;
existsSync: (path: import("node:fs").PathLike) => boolean;
statSync: (path: import("node:fs").PathLike) => opfs.Stats;
... 28 more ...;
onConflict: (next: ConflictResolver) => (() => void);
}
fs
.
mount: () => Promise<void>
mount
() // fills the in-memory layer, synchronous reads work after this
await
function readFile(path: import("node:fs").PathLike, options?: ReadOptions): Promise<Buffer | string>
readFile
('library/catalog.json', 'utf8') // the catalog text, from the layer fs.readFileSync reads
await
import opfs
opfs
.
function mount(): Promise<void>
export mount
mount
() // a second layer, filled from this device's OPFS alone
import opfs
opfs
.
function existsSync(path: import("node:fs").PathLike): boolean
export existsSync
existsSync
('cache/poster.png') // false until this device wrote it, the broker is never asked
await
const cloudFs: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
cloudFs
.
readdir: (path: import("node:fs").PathLike) => Promise<string[]>
readdir
('library') // ['catalog.json'] once the app wrote it, always a broker round trip

@fkn/lib/fs keeps its bytes in this device’s OPFS and in the account copy, behind one in-memory layer. The root’s fs namespace reads and writes that same layer. See storage.

@fkn/lib/opfs keeps them in this device’s OPFS alone and never reaches the broker frame.

@fkn/lib/cloud/fs keeps them in the account alone and has no in-memory layer, so it is async only. The *Sync members, the default export, and mount, flush, remount, pull, appendFile and exists are all absent from it.

Every read above is typed Buffer | string whatever encoding you pass. See TypeScript.

The /promises paths export the promise members by name. On the two device-side entries the same object is also the default export. @fkn/lib/cloud/fs/promises has no default export.

@fkn/lib/cloud is the cloud backend pinned. Its fetch never consults the extension, and its attachFrame always uses the render proxy, the cloud backend for frames, even when the extension is installed. The four Node subpaths under it re-export @fkn/lib/net, @fkn/lib/dgram, @fkn/lib/http and @fkn/lib/dns, so there is exactly one implementation behind every spelling:

app.ts
import {
(alias) namespace cloud
import cloud
cloud
} from '@fkn/lib'
import {
const quota: () => Promise<cloud.QuotaStatus>
quota
} from '@fkn/lib/cloud'
import * as
import net
net
from '@fkn/lib/cloud/net'
const {
const remainingBytes: number

bytes of free-tier volume left today

remainingBytes
} = await
function quota(): Promise<cloud.QuotaStatus>
quota
() // the free volume left today, the same call as cloud.quota()
(alias) namespace cloud
import cloud
cloud
.
namespace cloud_d_exports.net
export cloud_d_exports.net
net
.
class net_d_exports.Socket
export net_d_exports.Socket
Socket
===
import net
net
.
class Socket
export Socket
Socket
// true, one class behind both paths

The namespace and the subpath answer the same call. @fkn/lib/cloud/fetch is the narrow one: fetch alone, with no stream shim behind it, for when you cannot carry @fkn/lib/cloud.

cloud.fetch sends every request through the proxy. What the proxy answers is on fetch().

Four entries give you Node’s net, dgram, http and dns shapes. The sockets run over the relay, which holds the real socket at the far end, and the lookup goes through the broker. They work on the page and inside a worker the page relayed:

app.ts
import * as
import net
net
from '@fkn/lib/net'
import {
const lookup: <T extends boolean = false>(hostname: string, options?: {
all?: T;
family?: 0 | 4 | 6;
}) => Promise<T extends true ? AddressLookupResult[] : AddressLookupResult | undefined>
lookup
} from '@fkn/lib/dns'
import net
net
.
function isIP(input: string): 0 | 4 | 6
export isIP
isIP
('::1') // 6, decided locally
await
lookup<false>(hostname: string, options?: {
all?: false | undefined;
family?: 0 | 4 | 6;
} | undefined): Promise<AddressLookupResult | undefined>
lookup
('example.org') // { address, family } or undefined, through the broker

isIP answers locally. lookup waits on the broker with no deadline of its own. See HTTP and DNS.

The package has no https entry. See HTTP and DNS. What a relayed worker can use is on workers.

Not every entry needs the same shims. The published files import buffer, events and stream as bare specifiers only where a shape uses them, and the four shapes above are where events and stream are used:

EntryWhat it reaches
@fkn/lib, @fkn/lib/cloud, @fkn/lib/cloud/http, @fkn/lib/httpbuffer, events, stream, ip-address, osra
@fkn/lib/net, @fkn/lib/cloud/netevents, stream, ip-address, osra
@fkn/lib/dgram, @fkn/lib/cloud/dgrambuffer, events, ip-address, osra
@fkn/lib/fs, @fkn/lib/fs/promises, @fkn/lib/cloud/fs, @fkn/lib/cloud/fs/promisesbuffer, osra
@fkn/lib/opfs, @fkn/lib/opfs/promisesbuffer
@fkn/lib/cloud/fetch, @fkn/lib/dns, @fkn/lib/cloud/dns, @fkn/lib/account, @fkn/lib/packages, @fkn/lib/rooms, @fkn/lib/extension, @fkn/lib/apiosra
@fkn/lib/reactreact
@fkn/lib/desktop, @fkn/lib/contract, @fkn/lib/wire, @fkn/lib/messages, @fkn/lib/attach-policynone

Every entry that reaches osra mounts or adopts the broker frame when it evaluates in a window, the way the root does. The others never do. So @fkn/lib/cloud/fetch is narrow in what it carries, not in what it starts.

osra, ip-address and react are ordinary npm packages a bundler resolves on its own. The shims a browser bundle has to supply are buffer, events and stream. @fkn/vite-plugin aliases the Node names to these paths and supplies the shims, with no alias for dns.

@fkn/lib/extension is everything the FKN browser extension exposes to a page, plus available, events and promptInstall. @fkn/lib/desktop is a typed placeholder for a backend that does not exist yet:

app.ts
import {
(alias) namespace extension
import extension
extension
,
(alias) namespace desktop
import desktop
desktop
} from '@fkn/lib'
import {
const events: TypedEventTarget<ExtensionEventMap>
events
} from '@fkn/lib/extension'
(alias) namespace extension
import extension
extension
.
extension_d_exports.available(): boolean
export extension_d_exports.available
available
() // true once the content script marked this page, false in a worker
const events: TypedEventTarget<ExtensionEventMap>
events
.
TypedEventTarget<ExtensionEventMap>.addEventListener<"statuschange">(type: "statuschange", listener: ((event: CustomEvent<{
enabled: boolean;
}>) => void) | null, options?: boolean | AddEventListenerOptions): void (+1 overload)

The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.

MDN Reference

addEventListener
('statuschange',
event: CustomEvent<{
enabled: boolean;
}>
event
=> {
event: CustomEvent<{
enabled: boolean;
}>
event
.
CustomEvent<{ enabled: boolean; }>.detail: {
enabled: boolean;
}

The read-only detail property of the CustomEvent interface returns any data passed when initializing the event.

MDN Reference

detail
.
enabled: boolean
enabled
// true when the extension exposed itself, false when it stopped
})
(alias) namespace desktop
import desktop
desktop
.
desktop_d_exports.available(): boolean
export desktop_d_exports.available

Typed placeholder for the planned desktop backend. Always false in this release.

available
() // false

available() answers on both namespaces, and only desktop answers false everywhere. extension.available() reads whether the content script has marked this page at the moment you call it, so it is false in a worker and before that mark lands. See backends.

On desktop, every callable member other than available and fs.available throws synchronously, fetch and every method under net, dgram, dns and fs included. The message is The FKN desktop app is not connected, desktop.<name> is unavailable. desktop.fs.available() resolves false rather than throwing.

@fkn/lib/account and @fkn/lib/packages hold the same functions as the root namespaces. A package, an npm module FKN loads on a sandbox origin of its own, imports onConnect from the second at boot. @fkn/lib/react is the only entry that imports react, an optional peer at >=18:

app.ts
import {
function createElement(type: "input", props?: (React.InputHTMLAttributes<HTMLInputElement> & React.ClassAttributes<HTMLInputElement>) | null, ...children: React.ReactNode[]): React.DetailedReactHTMLElement<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> (+6 overloads)
createElement
} from 'react'
import {
const info: () => Promise<AccountInfo | null>
info
} from '@fkn/lib/account'
import {
const install: (uri: string, options?: InstallOptions) => Promise<InstalledPackage | null>

Install a package for this app behind an FKN-rendered confirm, or with { noConfirm: true } for a notice instead of a prompt. Resolves null when the user declines.

install
} from '@fkn/lib/packages'
import {
const ConnectButton: ({ className, style }?: ConnectButtonProps) => React.ReactElement
ConnectButton
,
const connectButtonUrl: () => string
connectButtonUrl
, type
type ConnectButtonProps = {
className?: string;
style?: React.CSSProperties;
}
ConnectButtonProps
} from '@fkn/lib/react'
await
function info(): Promise<AccountInfo | null>
info
() // null when this site is not connected, in a worker, or on any error
await
function install(uri: string, options?: InstallOptions): Promise<InstalledPackage | null>

Install a package for this app behind an FKN-rendered confirm, or with { noConfirm: true } for a notice instead of a prompt. Resolves null when the user declines.

install
('npm:@example/subtitles-plugin') // the install record, or null when the person pressed Not now
function connectButtonUrl(): string
connectButtonUrl
() // 'https://fkn.app/connect-button'
createElement<ConnectButtonProps>(type: React.FunctionComponent<ConnectButtonProps>, props?: (React.Attributes & ConnectButtonProps) | null | undefined, ...children: React.ReactNode[]): React.FunctionComponentElement<ConnectButtonProps> (+6 overloads)
createElement
<
type ConnectButtonProps = {
className?: string;
style?: React.CSSProperties;
}
ConnectButtonProps
>(
const ConnectButton: ({ className, style }?: ConnectButtonProps) => React.ReactElement
ConnectButton
, {
className?: string | undefined
className
: 'connect' }) // a react element, an <iframe> of that url

ConnectButton renders an <iframe> of that URL at 150 by 40 pixels, and a style prop overrides both. PackagesError is a type, never a class, so you test error.code on a rejection rather than instanceof. See handling errors.

@fkn/lib/rooms holds available, create and join, and the Room they resolve carries everything else. It is also the rooms namespace on the root:

app.ts
import {
const available: () => Promise<boolean>

Whether this realm can join a room: false in Node, false in a worker nothing bridged, false against a shell older than rooms. Answers rather than rejecting.

available
,
const create: (options?: RoomCreateOptions) => Promise<Room>

Open a room and become its owner. Share room.invite to let anyone else in.

create
} from '@fkn/lib/rooms'
if (await
function available(): Promise<boolean>

Whether this realm can join a room: false in Node, false in a worker nothing bridged, false against a shell older than rooms. Answers rather than rejecting.

available
()) { // false in Node, and in a worker nobody relayed
const
const room: Room
room
= await
function create(options?: RoomCreateOptions): Promise<Room>

Open a room and become its owner. Share room.invite to let anyone else in.

create
()
const room: Room
room
.
invite: string

id and key as one string, the thing to put in a link

invite
// the id and the key, joined by a dot, for a URL fragment
}

The key is minted in the browser and the platform never holds it, and a member’s id is fresh in every room. See rooms and run a chat room.

@fkn/lib/contract is types only, and its .js is empty. Resolvers is the surface the broker exposes, and the rest is the data vocabulary those resolvers exchange. @fkn/lib/messages is five string constants: three message prefixes you match with startsWith, two of which continue into a sentence, and two error codes:

app.ts
import type {
type Resolvers = {
cloud: {
fetch: ProxyFetch;
quota: () => Promise<Quota>;
dns: {
lookup: DnsLookup;
};
fs: {
available: () => Promise<boolean>;
availability: () => Promise<ConnectAvailability>;
list: () => Promise<StorageEntry[]>;
quota: () => Promise<StorageQuota>;
readFile: (path: string) => Promise<Uint8Array>;
readFileSealed: (path: string) => Promise<{
bytes: Uint8Array;
sealedAt: number | null;
}>;
writeFile: (path: string, data: WriteData, contentType: string | null) => Promise<void>;
remove: (path: string) => Promise<void>;
encryption: () => Promise<EncryptionState>;
unlock: () => Promise<boolean>;
promptAdopt: (request: AdoptRequest) => Promise<boolean>;
promptConflict: (request: ConflictRequest) => Promise<ConflictChoice>;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => void;
};
webvpn: {
tcpSocket: (options: TcpSocketOptions) => Promise<TcpSocketResult>;
tcpSocketListener: (options: TcpSocketListenerOptions) => Promise<TcpSocketListenerResult>;
udpSocket: (options: UdpSocketOptions) => Promise<UdpSocketResult>;
};
};
... 16 more ...;
hideInstallPrompt: () => void;
}

Everything the broker exposes over osra.

The flat members at the bottom duplicate members of cloud and overlay. They predate the namespaced form and are kept because a published consumer may still be calling them: they are contract, not dead code.

Resolvers
} from '@fkn/lib/contract'
import {
const E2E_LOCKED_MESSAGE: "fkn:e2e-locked"
E2E_LOCKED_MESSAGE
,
const E2E_STALE_EPOCH_MESSAGE: "fkn:e2e-stale-epoch: this file is encrypted under a previous key you reset"
E2E_STALE_EPOCH_MESSAGE
,
const STORAGE_UNREACHABLE: "FKN_API_UNREACHABLE"

"The api never answered", as a thing a caller can TEST, for the same reason as above and one more: a caller that relaxes anything when the server cannot be reached must never relax it on an ANSWERED error, because an answered 500 lands exactly when a key rotation or a setting flip may be propagating. The MESSAGE prefix is the wire contract: osra's error boxer ships name, message, stack and cause only, so a custom code does NOT survive the SharedWorker hop (the E2E_LOCKED_MESSAGE prefix match exists for exactly this reason). The code is still set for same-realm callers, which is the cheaper test when it is available.

STORAGE_UNREACHABLE
} from '@fkn/lib/messages'
import {
const promises: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
export promises
promises
as
const cloudFs: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
cloudFs
} from '@fkn/lib/cloud/fs'
import {
const apiPromise: Promise<{
cloud: {
fetch: (input: ProxyFetchInput, init: ProxyFetchInit) => Promise<Response>;
quota: () => Promise<{
overQuota: boolean;
remaining: number;
usedBytes: number;
limitBytes: number;
premium: boolean;
bytesPerSecond: number;
}>;
dns: {
lookup: (hostname: string, options?: {
all?: boolean | undefined;
family?: 0 | 4 | 6;
} | undefined) => Promise<{
address: string;
family: 0 | 4 | 6;
} | {
address: string;
family: 0 | 4 | 6;
}[] | undefined>;
};
fs: {
available: () => Promise<boolean>;
... 11 more ...;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => Promise<...>;
};
webvpn: {
...;
};
};
... 16 more ...;
hideInstallPrompt: () => Promise<...>;
}>

The broker api. Settles when the first connection exists, exactly as before, but resolves with a stable facade that always routes to the NEWEST connection, so holding the resolved value across a broker replacement is safe. A call in flight at the moment of replacement rejects with a named error instead of hanging.

apiPromise
} from '@fkn/lib/api'
const
const cloudFetch: ProxyFetch
cloudFetch
:
type Resolvers = {
cloud: {
fetch: ProxyFetch;
quota: () => Promise<Quota>;
dns: {
lookup: DnsLookup;
};
fs: {
available: () => Promise<boolean>;
availability: () => Promise<ConnectAvailability>;
list: () => Promise<StorageEntry[]>;
quota: () => Promise<StorageQuota>;
readFile: (path: string) => Promise<Uint8Array>;
readFileSealed: (path: string) => Promise<{
bytes: Uint8Array;
sealedAt: number | null;
}>;
writeFile: (path: string, data: WriteData, contentType: string | null) => Promise<void>;
remove: (path: string) => Promise<void>;
encryption: () => Promise<EncryptionState>;
unlock: () => Promise<boolean>;
promptAdopt: (request: AdoptRequest) => Promise<boolean>;
promptConflict: (request: ConflictRequest) => Promise<ConflictChoice>;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => void;
};
webvpn: {
tcpSocket: (options: TcpSocketOptions) => Promise<TcpSocketResult>;
tcpSocketListener: (options: TcpSocketListenerOptions) => Promise<TcpSocketListenerResult>;
udpSocket: (options: UdpSocketOptions) => Promise<UdpSocketResult>;
};
};
... 16 more ...;
hideInstallPrompt: () => void;
}

Everything the broker exposes over osra.

The flat members at the bottom duplicate members of cloud and overlay. They predate the namespaced form and are kept because a published consumer may still be calling them: they are contract, not dead code.

Resolvers
['cloud']['fetch'] = (await
const apiPromise: Promise<{
cloud: {
fetch: (input: ProxyFetchInput, init: ProxyFetchInit) => Promise<Response>;
quota: () => Promise<{
overQuota: boolean;
remaining: number;
usedBytes: number;
limitBytes: number;
premium: boolean;
bytesPerSecond: number;
}>;
dns: {
lookup: (hostname: string, options?: {
all?: boolean | undefined;
family?: 0 | 4 | 6;
} | undefined) => Promise<{
address: string;
family: 0 | 4 | 6;
} | {
address: string;
family: 0 | 4 | 6;
}[] | undefined>;
};
fs: {
available: () => Promise<boolean>;
... 11 more ...;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => Promise<...>;
};
webvpn: {
...;
};
};
... 16 more ...;
hideInstallPrompt: () => Promise<...>;
}>

The broker api. Settles when the first connection exists, exactly as before, but resolves with a stable facade that always routes to the NEWEST connection, so holding the resolved value across a broker replacement is safe. A call in flight at the moment of replacement rejects with a named error instead of hanging.

apiPromise
).
cloud: {
fetch: (input: ProxyFetchInput, init: ProxyFetchInit) => Promise<Response>;
quota: () => Promise<{
overQuota: boolean;
remaining: number;
usedBytes: number;
limitBytes: number;
premium: boolean;
bytesPerSecond: number;
}>;
dns: {
lookup: (hostname: string, options?: {
all?: boolean | undefined;
family?: 0 | 4 | 6;
} | undefined) => Promise<{
address: string;
family: 0 | 4 | 6;
} | {
address: string;
family: 0 | 4 | 6;
}[] | undefined>;
};
fs: {
available: () => Promise<boolean>;
... 11 more ...;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => Promise<...>;
};
webvpn: {
...;
};
}
cloud
.
fetch: (input: ProxyFetchInput, init: ProxyFetchInit) => Promise<Response>
fetch
// the broker's own signature behind cloud.fetch
try {
await
const cloudFs: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
cloudFs
.
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>
readFile
('backup/library.bin')
} catch (
var error: unknown
error
) {
const
const message: string
message
=
var error: unknown
error
instanceof
var Error: ErrorConstructor
Error
?
var error: Error
error
.
Error.message: string
message
: ''
const message: string
message
.
String.startsWith(searchString: string, position?: number): boolean

Returns true if the sequence of elements of searchString converted to a String is the same as the corresponding elements of this object (converted to a String) starting at position. Otherwise returns false.

startsWith
(
const E2E_STALE_EPOCH_MESSAGE: "fkn:e2e-stale-epoch: this file is encrypted under a previous key you reset"
E2E_STALE_EPOCH_MESSAGE
) // true for a file sealed under a key the user reset
}
const E2E_LOCKED_MESSAGE: "fkn:e2e-locked"
E2E_LOCKED_MESSAGE
// 'fkn:e2e-locked'
const STORAGE_UNREACHABLE: "FKN_API_UNREACHABLE"

"The api never answered", as a thing a caller can TEST, for the same reason as above and one more: a caller that relaxes anything when the server cannot be reached must never relax it on an ANSWERED error, because an answered 500 lands exactly when a key rotation or a setting flip may be propagating. The MESSAGE prefix is the wire contract: osra's error boxer ships name, message, stack and cause only, so a custom code does NOT survive the SharedWorker hop (the E2E_LOCKED_MESSAGE prefix match exists for exactly this reason). The code is still set for same-realm callers, which is the cheaper test when it is available.

STORAGE_UNREACHABLE
// 'FKN_API_UNREACHABLE', a code rather than a prefix

The three E2E_* constants are prefixes because a message survives the hop behind the broker where a custom code does not. See handling errors.

STORAGE_NOT_FOUND holds on error.code, because the library re-mints that error in your realm. STORAGE_UNREACHABLE is set behind the hop and can arrive without it, so the test that crosses is the prefix storage: api unreachable.

What each constant marks is on encryption, and the vocabulary is on TypeScript.

@fkn/lib/wire is the numeric codes a socket option carries to the relay. @fkn/lib/attach-policy is the pure half of the cloud backend’s frame.fetch check. Both run locally, and neither touches the broker:

app.ts
import {
const TCP_OPTION_NODELAY: 0

Socket option codes as they appear on the WebVPN wire, and the option shapes built from them.

These live in the library rather than beside the packet codecs because BOTH sides need them and only one side may own them. @fkn/lib already imported these constants at value level (see webvpn/net.ts and webvpn/dgram.ts), so the previous arrangement had the published package reaching into src/api/webvpn/packets/* for its own vocabulary, which dragged those modules and their imports into the library's type program.

Owning them here inverts that: the codecs in src/api/webvpn/packets/ import from this file, and the library needs nothing from the app to describe a socket option.

The VALUES are protocol. They are the u8 discriminant of a tagged union nested inside a TcpSetOption or UdpSetOption client packet (metadata-stream tag 8), and a relay built against these numbers is deployed independently of any browser holding this file. Renumbering one is a wire break, not a refactor. Booleans go on the wire as u8, 0 for false and anything else true.

TCP_OPTION_NODELAY
} from '@fkn/lib/wire'
import type {
type TcpSocketOption = {
type: typeof TCP_OPTION_NODELAY;
value: boolean;
} | {
type: 1;
value: number;
} | {
type: 2;
enabled: boolean;
initialDelaySeconds: number;
} | {
type: 3;
value: number;
} | {
type: 4;
value: number;
}
TcpSocketOption
} from '@fkn/lib/wire'
import {
const normalizeDeclaredHosts: (raw: string[]) => string[]

Bare hostnames only: lowercase, no scheme, no port, no path. Anything else is dropped rather than repaired, so a malformed declaration narrows the app's reach instead of widening it.

normalizeDeclaredHosts
,
const frameFetchVerdict: (policy: FrameFetchPolicy, url: unknown) => FrameFetchVerdict
frameFetchVerdict
} from '@fkn/lib/attach-policy'
const
const option: TcpSocketOption
option
:
type TcpSocketOption = {
type: typeof TCP_OPTION_NODELAY;
value: boolean;
} | {
type: 1;
value: number;
} | {
type: 2;
enabled: boolean;
initialDelaySeconds: number;
} | {
type: 3;
value: number;
} | {
type: 4;
value: number;
}
TcpSocketOption
= {
type: 0
type
:
const TCP_OPTION_NODELAY: 0

Socket option codes as they appear on the WebVPN wire, and the option shapes built from them.

These live in the library rather than beside the packet codecs because BOTH sides need them and only one side may own them. @fkn/lib already imported these constants at value level (see webvpn/net.ts and webvpn/dgram.ts), so the previous arrangement had the published package reaching into src/api/webvpn/packets/* for its own vocabulary, which dragged those modules and their imports into the library's type program.

Owning them here inverts that: the codecs in src/api/webvpn/packets/ import from this file, and the library needs nothing from the app to describe a socket option.

The VALUES are protocol. They are the u8 discriminant of a tagged union nested inside a TcpSetOption or UdpSetOption client packet (metadata-stream tag 8), and a relay built against these numbers is deployed independently of any browser holding this file. Renumbering one is a wire break, not a refactor. Booleans go on the wire as u8, 0 for false and anything else true.

TCP_OPTION_NODELAY
,
value: boolean
value
: true } // what setNoDelay(true) sends
const
const declaredHosts: string[]
declaredHosts
=
function normalizeDeclaredHosts(raw: string[]): string[]

Bare hostnames only: lowercase, no scheme, no port, no path. Anything else is dropped rather than repaired, so a malformed declaration narrows the app's reach instead of widening it.

normalizeDeclaredHosts
(['Example.org', 'https://cdn.example.org/clip.mp4', 'cdn.example.org']) // ['example.org', 'cdn.example.org'], lowercased
function frameFetchVerdict(policy: FrameFetchPolicy, url: unknown): FrameFetchVerdict
frameFetchVerdict
({
session: string

'' means the shared render-proxy jar; non-empty is an isolated per-attach session

session
: 'attach-1',
declaredHosts: string[]

bare hostnames the app declared at attach; matched on hostname alone, any scheme or port

declaredHosts
,
approvedOrigins: ReadonlySet<string>

exact origins this attachment has legitimately visited: the attach url plus every goto target

approvedOrigins
: new
var Set: SetConstructor
new <string>(iterable?: Iterable<string> | null | undefined) => Set<string> (+1 overload)
Set
() }, 'https://example.org/api/me') // { kind: 'consent', targetHost: 'example.org', promptHosts: ['example.org', 'cdn.example.org'] }

setNoDelay and setKeepAlive on a TCP socket, and setTTL and setBroadcast on a UDP one, send these codes for you. See socket options. The numbers are public because renumbering one would break compatibility with the relay.

normalizeDeclaredHosts drops a scheme, port or path rather than repairing it, so a malformed declaration narrows what your frame may reach.

frameFetchVerdict answers a refusal or a consent request from the facts of the attached frame, the Frame that attachFrame returns. The cloud backend runs it before every frame.fetch leaves the page. See fetching as the frame.

@fkn/lib/api is the broker connection itself, for an app that needs to observe it rather than use it through a wrapper. apiPromise settles once a broker connection exists and never rejects. It resolves the facade, the object that carries the broker’s resolvers and routes every call to the newest broker connection.

apiWithin is the bounded form that net and dgram use internally, and it resolves the same facade. It waits 8,000 ms the first time, and 1,000 ms for every later wait once any deadline was missed:

app.ts
import {
const apiWithin: (what: string) => Promise<{
cloud: {
fetch: (input: ProxyFetchInput, init: ProxyFetchInit) => Promise<Response>;
quota: () => Promise<{
overQuota: boolean;
remaining: number;
usedBytes: number;
limitBytes: number;
premium: boolean;
bytesPerSecond: number;
}>;
dns: {
lookup: (hostname: string, options?: {
all?: boolean | undefined;
family?: 0 | 4 | 6;
} | undefined) => Promise<{
address: string;
family: 0 | 4 | 6;
} | {
address: string;
family: 0 | 4 | 6;
}[] | undefined>;
};
fs: {
available: () => Promise<boolean>;
... 11 more ...;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => Promise<...>;
};
webvpn: {
...;
};
};
... 16 more ...;
hideInstallPrompt: () => Promise<...>;
}>
apiWithin
,
class BrokerUnreachableError
BrokerUnreachableError
} from '@fkn/lib/api'
try {
const
const api: {
cloud: {
fetch: (input: ProxyFetchInput, init: ProxyFetchInit) => Promise<Response>;
quota: () => Promise<{
overQuota: boolean;
remaining: number;
usedBytes: number;
limitBytes: number;
premium: boolean;
bytesPerSecond: number;
}>;
dns: {
lookup: (hostname: string, options?: {
all?: boolean | undefined;
family?: 0 | 4 | 6;
} | undefined) => Promise<{
address: string;
family: 0 | 4 | 6;
} | {
address: string;
family: 0 | 4 | 6;
}[] | undefined>;
};
fs: {
available: () => Promise<boolean>;
... 11 more ...;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => Promise<...>;
};
webvpn: {
...;
};
};
... 16 more ...;
hideInstallPrompt: () => Promise<...>;
}
api
= await
function apiWithin(what: string): Promise<{
cloud: {
fetch: (input: ProxyFetchInput, init: ProxyFetchInit) => Promise<Response>;
quota: () => Promise<{
overQuota: boolean;
remaining: number;
usedBytes: number;
limitBytes: number;
premium: boolean;
bytesPerSecond: number;
}>;
dns: {
lookup: (hostname: string, options?: {
all?: boolean | undefined;
family?: 0 | 4 | 6;
} | undefined) => Promise<{
address: string;
family: 0 | 4 | 6;
} | {
address: string;
family: 0 | 4 | 6;
}[] | undefined>;
};
fs: {
available: () => Promise<boolean>;
... 11 more ...;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => Promise<...>;
};
webvpn: {
...;
};
};
... 16 more ...;
hideInstallPrompt: () => Promise<...>;
}>
apiWithin
('the quota readout') // the facade, once a broker answered within the deadline
const {
const remaining: number
remaining
} = await
const api: {
cloud: {
fetch: (input: ProxyFetchInput, init: ProxyFetchInit) => Promise<Response>;
quota: () => Promise<{
overQuota: boolean;
remaining: number;
usedBytes: number;
limitBytes: number;
premium: boolean;
bytesPerSecond: number;
}>;
dns: {
lookup: (hostname: string, options?: {
all?: boolean | undefined;
family?: 0 | 4 | 6;
} | undefined) => Promise<{
address: string;
family: 0 | 4 | 6;
} | {
address: string;
family: 0 | 4 | 6;
}[] | undefined>;
};
fs: {
available: () => Promise<boolean>;
... 11 more ...;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => Promise<...>;
};
webvpn: {
...;
};
};
... 16 more ...;
hideInstallPrompt: () => Promise<...>;
}
api
.
cloud: {
fetch: (input: ProxyFetchInput, init: ProxyFetchInit) => Promise<Response>;
quota: () => Promise<{
overQuota: boolean;
remaining: number;
usedBytes: number;
limitBytes: number;
premium: boolean;
bytesPerSecond: number;
}>;
dns: {
lookup: (hostname: string, options?: {
all?: boolean | undefined;
family?: 0 | 4 | 6;
} | undefined) => Promise<{
address: string;
family: 0 | 4 | 6;
} | {
address: string;
family: 0 | 4 | 6;
}[] | undefined>;
};
fs: {
available: () => Promise<boolean>;
... 11 more ...;
setAdoptSource: (next: AdoptState | null, run: (() => Promise<void>) | null) => Promise<...>;
};
webvpn: {
...;
};
}
cloud
.
quota: () => Promise<{
overQuota: boolean;
remaining: number;
usedBytes: number;
limitBytes: number;
premium: boolean;
bytesPerSecond: number;
}>
quota
() // bytes of free volume left today
} catch (
var error: unknown
error
) {
if (
var error: unknown
error
instanceof
class BrokerUnreachableError
BrokerUnreachableError
)
var console: Console
console
.
Console.warn(...data: any[]): void

The console.warn() static method outputs a warning message to the console at the 'warning' log level.

MDN Reference

warn
(
var error: BrokerUnreachableError
error
.
Error.message: string
message
) // '@fkn/lib: no broker connection within 8000ms, so the quota readout could not be requested'
}

The message names the deadline that was missed, so after the first miss it reads within 1000ms instead. See @fkn/lib: no broker connection within <ms>ms, so <what> could not be requested.

onApiEpoch and currentApiEpoch expose the broker epochs, each a broker generation. When a broker is replaced, the library re-sends on the new epoch every registration that died with the old one. See how it works and errors and lifecycle.

apiLastCall() answers a millisecond timestamp of the last call through the facade, and 0 before any. apiInFlight() answers the number of calls pending.

What supplying the shims above takes, with or without the plugin, is on install. Every type these paths export is on TypeScript.