@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:
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:
search, pick, install, list, uninstall, show, hide, connect, mount, attach, onConnect, isVisible, onVisibilityChange, their types, and the PackagesError type you match on error.code
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:
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
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.
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:
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:
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:
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:
The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
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.
@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:
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 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
@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 {
constavailable: () =>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.
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
@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:
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.
"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.
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.
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.
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.
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(
constE2E_STALE_EPOCH_MESSAGE:"fkn:e2e-stale-epoch: this file is encrypted under a previous key you reset"
"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.
@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 {
constTCP_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.
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.
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.
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.
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:
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.