Skip to content

@fkn/lib/contract

type Account = object;

The signed-in account, or null when nobody is.

image: string | null;
name: string;
premium: boolean;
premiumUntil: string | null;

type AccountChange = object;

What the broker’s account change notification says. switched is true only when the account the broker acts as changed: another account, a sign-out or a sign-in. A renewal of the same account’s token is false. A broker older than this sends no payload at all.

switched: boolean;

type AccountPinOptions = object;

The trailing option of every pinned storage call: a value accountPin() answered. The api refuses the call ACCOUNT_CHANGED when the signed-in account (or the app) is not the one the pin was taken under. Absent, the call is unpinned.

optional accountPin?: string;

type AddressLookupResult = object;
address: string;
family: 0 | 4 | 6;

type AdoptRequest = object;
bytes: number;
files: number;

type AdoptState = object;
bytes: number;
files: number;

type ConflictChoice = "local" | "cloud" | null;

type ConflictRequest = object;
cloud: ConflictSide;
local: ConflictSide;
path: string;

type ConflictSide = object;
size: number;
updatedAt: string | null;

type ConnectAvailability = "connected" | "disconnected" | "unknown";

Three answers where available() has two. disconnected is an ANSWER and unknown is nobody having been asked; the library’s local-first queue drops its cloud obligation only on the first and keeps the write queued on the second.


type ConnectOptions = object;
optional path?: string;

an absolute inner route to mount the package at, e.g. ‘/watch/1’; defaults to its root

optional protocol?: string;

opaque contract tag delivered to the package’s onConnect, e.g. ‘stub-source@1’


type DisplayCause = "not-rendered" | "clipped" | "not-shown";

type DisplayLevel = "full" | "liveness";

full means this engine reported a boolean isVisible; liveness means we only know it paints


type DnsLookup = <T>(hostname, options?) => Promise<T extends true ? AddressLookupResult[] : AddressLookupResult | undefined>;

T extends boolean = false

string

T

0 | 4 | 6

Promise<T extends true ? AddressLookupResult[] : AddressLookupResult | undefined>


type EncryptionState = object;

Encryption state of the account’s cloud storage.

enrolled: boolean;
keyEpoch: number | null;
unlocked: boolean;

type FrameConsentAnswer = "once" | "session" | false;

'once' is a critical row answered Once, a handoff the middle page takes for that attachment only; 'session' a standing row, remembered until removed from the FKN bar, for a category and a critical key alike; false a refusal.


type FrameConsentRequest =
| {
category: "interaction" | "storage" | "network" | "evaluation";
hosts: string[];
}
| {
attach?: string;
hosts: string[];
key: string;
}
| {
hosts: string[];
scope: FrameFetchScope;
};

One row on the cloud consent card.

A category asks for every capability in it; a key asks for one critical permission’s own row, and is refused unless the registry has that key at severity 4 (none today). A critical grant covers that key alone, never another key or its category. attach binds an “Allow once” answer to the attachment that asked, so no other attachment can consume it.


type FrameFetchScope = "frame.fetchRead" | "frame.fetchWrite";

the pre-category fetch scopes. Still exported so a pinned library keeps compiling; the card maps them to network.


type FrameMessageRelay =
| {
attach: string;
data: unknown;
kind: "message";
origin: string;
type: typeof FRAME_MESSAGE;
}
| {
attach: string;
kind: "document";
origin: string;
type: typeof FRAME_MESSAGE;
};

origin is the page’s origin as the site knows it, which the shell derives from the browser-set origin of the page’s own post, never from anything the page wrote.


type HiddenSurface = object;
cause: DisplayCause;
kind: string;
level: DisplayLevel;

type InstalledPackage = object;
optional appId?: string;

the app id this pin last verified as, version-free. Absent until a verified resolve has run.

installedAt: number;
name: string;
uri: string;
version: string | null;

null when the handler addresses code directly and has no version to pin, e.g. a dev server


type InstallOptions = object;
optional latest?: boolean;

Re-pin an existing record to whatever the source now calls latest, instead of answering the record already held. A host page pinning its own package on every load is what this exists for. An unreachable source keeps the record rather than dropping it.

optional noConfirm?: boolean;

skip the confirm prompt and report the install with a notice instead; the record stays scoped to the calling app

optional version?: string;

exact version to pin; defaults to the packument’s latest dist-tag


type IpFamily = "IPv4" | "IPv6";

type MiddleControl = object;

What a middle page exposes to the library, inline (ATTACH_FRAME_KEY) and for a window (ATTACH_WINDOW_KEY). documentHost, where the frame actually is, is absent from a middle page deployed before categories.

optional blank(): Promise<string>;

The url the attachment’s empty page is armed for while the frame still holds that page, '' when none, once the first goto was sent, and once the frame reported any other url, even on the same host. The library sends a blank attach as ?blank= and refuses it by name unless this echoes that url after ready, and reads it again before it skips the Evaluation card on a fresh jar, so it asks whenever this page’s fresh-jar rule has ended. A middle page that serves it refuses ready when its shell does not answer blank() with the same url. Absent from a middle page that predates blank pages, which mounts its shell with no target.

Promise<string>

optional clearCookies(filter): Promise<void>;

Removes the cookies filter matches from the attachment’s jar, among the cookies of the sites the attachment reaches: the middle page hands its shell the boundary’s hosts (declared, plus the host of the attach url and of every goto from its send) as ShellControl.clearCookies’ scope. No options travel as {}, and each option is { equals } for now. Resolves undefined whatever the shell answered, so nothing about the jar crosses back, and the shell takes the same steps when nothing matched, so neither its time nor its refusals say whether the jar held a cookie there. Never gated: it asks for no grant and draws no card, and a frame holding no document is served, since a no-src attachment is what a sign-out runs on.

Refuses, each with the library’s own message and before anything is sent to the shell: a { pattern, flags } option (a TypeError frame.clearCookies: <key> is a RegExp, and clearCookies takes strings for name, domain and path for now), since the shell would test it against every cookie in reach; a filter of any other shape (a TypeError frame.clearCookies: malformed filter); an attachment that reaches no site (LocatorDeniedError); and a shell that predates it (LocatorUnsupportedError). The shell’s own refusals reach the library as they came. Absent from a middle page that predates it, which the library refuses by name.

ClearCookiesWire

Promise<void>

optional documentHost(): Promise<string>;

The host of the document the frame holds now, as the render proxy last reported it, and '' before the first one. A document outside the attachment boundary answers '#outside', which is no hostname: the library refuses on it with its boundary message and never learns where the frame went. The middle page’s own gate keeps checking the real host.

Promise<string>

executeLocator(
parts,
operation,
args): Promise<unknown>;

SelectorPart[]

string

unknown[]

Promise<unknown>

goto(url, options?): Promise<void>;

string

MiddleGotoOptions

Promise<void>

optional messaging(): Promise<true>;

Present on a middle page that gates postMessage and relays the page’s messages (FRAME_MESSAGE). Absent from one deployed before messaging, which the library refuses by name.

Promise<true>

ready(): Promise<boolean>;

Promise<boolean>

optional seed(state): Promise<void>;

Seeds the attachment’s fresh jar and storage namespace from a storageState in the app’s own shape (milliseconds expires), which the middle page checks as the library does, turns into the jar’s shape and hands its shell (ShellControl.seed). Once per attachment, before its first goto: the library mounts a seeded attach with no target and sends the attach url as that goto once this resolves. That goto is the attachment’s first load, so it takes the first load’s rule (ShellGotoOptions.commitFallback). Resolves undefined, so nothing about the jar crosses back.

Refuses, before anything reaches the shell: a seed on the shared jar (the TypeError attachFrame: storageState seeds a fresh jar, so it needs cookies: 'ephemeral'), a malformed one (the library’s own TypeErrors), a second seed, one sent once a goto was, even one still in flight, and one on an attachment mounted on a url, which its shell loads itself (each an Error), and a shell that predates seeding (LocatorUnsupportedError). Absent from a middle page that predates it, which the library refuses by name.

StorageState

Promise<void>


type MiddleGotoOptions = object;

A goto on the middle page’s control channel, as a library sends it; the middle page hands it on to the shell as it came. Its own names, which never change with the public GotoOptions: the public timeout travels as timeoutMs.

optional domains?: string[];
optional timeoutMs?: number;

The goto’s deadline in milliseconds, 30000 when absent.

optional waitUntil?: "load" | "commit" | "documentstart";

'load', the default, once the goto’s document fired load. 'commit', the public value: the goto resolves once its document holds the frame, and rejects when none came by the deadline. A shell that predates it reads 'commit' as 'load', which never resolves before commit. 'documentstart', which no current library sends, keeps its wire meaning for every library that predates 'commit': resolving as soon as the navigation starts.


type MountDescriptor = object;

Everything an app needs to mount a package’s tenant in its OWN document.

from: string;

the connecting app, as the BROKER knows it

name: string;
origin: string;

the tenant origin alone, which is what a postMessage to that frame must target

uri: string;

the version-free identity, echoed to the package in the port message

url: string;

where to point the iframe: the tenant origin plus the package’s path

version: string | null;

type OverlayRect = object;

Viewport rect plus optional corner radii, as the overlay host reports them.

height: number;
optional radius?: [number, number, number, number];
width: number;
x: number;
y: number;

type OverlayState = object;

What the host page pushes to the broker frame so its UI stays clickable through app chrome.

hidden: HiddenSurface[];
inset: object;
top: number;
modal: boolean;
rects: OverlayRect[];
view: object;
height: number;
width: number;

type PackageQuery = object;
optional id?: string;

host app scope, e.g. ‘stub’ - becomes the keyword fkn-<type>--<id>

optional origin?: "npm";

package source; npm is the only origin implemented

optional size?: number;

result count, clamped to 1..100

optional text?: string;

free text mixed into the registry query

type: string;

package kind, e.g. ‘plugin’ - becomes the keyword fkn-type:<type>


type PackageResult = object;
description: string;
downloadsMonthly: number | null;
installed: boolean;

installed by the calling app

keywords: string[];
links: object;
optional homepage?: string;
optional npm?: string;
optional repository?: string;
name: string;
origin: "npm";
publisher: string | null;
uri: string;

normalized version-free uri, e.g. ‘npm:@banou/stub-plugin-foo’

version: string;

latest version per the search index - display only, install re-resolves from the packument


type PackagesErrorCode =
| "invalid"
| "not-installed"
| "unaddressable"
| "timeout"
| "unavailable"
| "denied";

type PackagesFail = object;
error: PackagesErrorCode;
message: string;

type PickOptions = object;
optional multiple?: boolean;
optional title?: string;

untrusted, rendered as text in the picker header


type Placement = object;
optional clip?: SurfaceRect;

the part of rect still visible after the placeholder’s scroll ancestors clip it

optional radius?: Radii;

the placeholder’s corner radii, so the frame follows a rounded container

rect: SurfaceRect;

where the package frame sits, so its own layout gets the full box


type ProxyFetch = (input, init) => Promise<Response>;

ProxyFetchInput

ProxyFetchInit

Promise<Response>


type ProxyFetchInit = RequestInit & object | undefined;

What a cloud.fetch accepts beyond the platform’s own RequestInit.

spread sends THIS request through any healthy node, taken in turn, instead of the node the WebVPN session is on. The default keeps every request on that one node: it is the node the header names, and it is what an upstream that remembers a source address expects. Spreading is for an upstream that meters per address, where it multiplies the budget by the number of healthy nodes. AniList is the measured case: its live bucket answers x-ratelimit-limit: 30 a minute per address, shared by every user behind one node.

A spread request is the app’s own choice and is not announced: the header keeps naming the session’s node, which every request not carrying this flag still goes through. Only the cloud backend reads it; the extension and the desktop fetch from the user’s own address and ignore it.


type ProxyFetchInput = string | URL | Request;

type Quota = object;

Metered proxy usage for the account behind the broker.

bytesPerSecond: number;
limitBytes: number;
overQuota: boolean;
premium: boolean;
remaining: number;
usedBytes: number;

type Radii = [number, number, number, number];

corner radii in css order: top-left, top-right, bottom-right, bottom-left


type Resolvers = object;

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.

account: object;
info: () => Promise<Account | null>;

Promise<Account | null>

login: (consumerOrigin) => Promise<boolean>;

string

Promise<boolean>

logout: () => void;

void

onChange: (listener) => () => void;

(change?) => void

() => void

cloud: object;
dns: object;
lookup: DnsLookup;
fetch: ProxyFetch;
fs: object;
accountPin: () => Promise<string>;

A fresh opaque pin for the account signed in now, under the calling app. It names nobody: every call answers a new value and two pins cannot be compared. A broker without this member cannot pin, and the library sends it no storage call at all.

Promise<string>

availability: () => Promise<ConnectAvailability>;

Promise<ConnectAvailability>

available: () => Promise<boolean>;

Promise<boolean>

encryption: () => Promise<EncryptionState>;

Promise<EncryptionState>

list: (opts?) => Promise<StorageEntry[]>;

AccountPinOptions

Promise<StorageEntry[]>

promptAdopt: (request) => Promise<boolean>;

AdoptRequest

Promise<boolean>

promptConflict: (request) => Promise<ConflictChoice>;

ConflictRequest

Promise<ConflictChoice>

quota: () => Promise<StorageQuota>;

Promise<StorageQuota>

readFile: (path, opts?) => Promise<Uint8Array>;

string

AccountPinOptions

Promise<Uint8Array>

readFileSealed: (path, opts?) => Promise<{
bytes: Uint8Array;
sealedAt: number | null;
}>;

The same read as readFile, plus the seal time the envelope authenticates, in milliseconds, or null for an envelope that carries no such field.

Authenticated means the server can neither forge nor alter it, because it is covered by the envelope’s AAD; it is still the WRITER’s own claim about when it sealed, so on its own it does not prove that this copy is the newest one.

string

AccountPinOptions

Promise<{ bytes: Uint8Array; sealedAt: number | null; }>

remove: (path, opts?) => Promise<void>;

string

AccountPinOptions

Promise<void>

setAdoptSource: (next, run) => void;

AdoptState | null

(() => Promise<void>) | null

void

unlock: () => Promise<boolean>;

Promise<boolean>

writeFile: (path, data, contentType, opts?) => Promise<void>;

string

WriteData

string | null

AccountPinOptions

Promise<void>

quota: () => Promise<Quota>;

Promise<Quota>

webvpn: object;
tcpSocket: (options) => Promise<TcpSocketResult>;

TcpSocketOptions

Promise<TcpSocketResult>

tcpSocketListener: (options) => Promise<TcpSocketListenerResult>;

TcpSocketListenerOptions

Promise<TcpSocketListenerResult>

udpSocket: (options) => Promise<UdpSocketResult>;

UdpSocketOptions

Promise<UdpSocketResult>

connect: object;
prompt: (consumerOrigin) => Promise<boolean>;

string

Promise<boolean>

dnsLookup: DnsLookup;
frameConsent: object;
ensure: (request) => Promise<boolean>;

the legacy single-row entry, kept so a pinned library keeps working

FrameConsentRequest

Promise<boolean>

request: (requests) => Promise<FrameConsentAnswer[]>;

one card with a row per request; answers in request order

FrameConsentRequest[]

Promise<FrameConsentAnswer[]>

hideInstallPrompt: () => void;

void

installPrompt: object;
hide: () => void;

void

show: (reason?) => Promise<void>;

string

Promise<void>

overlay: object;
setHost: SetOverlayHost;
packages: object;
connect: (uri, options?) => Promise<
| PackagesFail
| {
closed: Promise<void>;
port: MessagePort;
}>;

string

ConnectOptions

Promise< | PackagesFail | { closed: Promise<void>; port: MessagePort; }>

frame: (uri) => Promise<PackagesFail | MountDescriptor>;

string

Promise<PackagesFail | MountDescriptor>

hide: (uri) => Promise<
| PackagesFail
| {
ok: true;
}>;

string

Promise< | PackagesFail | { ok: true; }>

install: (uri, options?) => Promise<
| PackagesFail
| {
installed: InstalledPackage;
}
| {
declined: true;
}>;

string

InstallOptions

Promise< | PackagesFail | { installed: InstalledPackage; } | { declined: true; }>

list: () => Promise<{
results: InstalledPackage[];
}>;

Promise<{ results: InstalledPackage[]; }>

pick: (query, options?) => Promise<
| PackagesFail
| {
failed: string[];
results: PackageResult[];
}
| {
declined: true;
}>;

PackageQuery

PickOptions

Promise< | PackagesFail | { failed: string[]; results: PackageResult[]; } | { declined: true; }>

search: (query) => Promise<
| PackagesFail
| {
results: PackageResult[];
}>;

PackageQuery

Promise< | PackagesFail | { results: PackageResult[]; }>

show: (uri, placement) => Promise<
| PackagesFail
| {
ok: true;
}>;

string

Placement

Promise< | PackagesFail | { ok: true; }>

uninstall: (uri) => Promise<
| PackagesFail
| {
ok: true;
}>;

string

Promise< | PackagesFail | { ok: true; }>

proxyFetch: ProxyFetch;
relay: object;
prompt: () => Promise<string>;

Promise<string>

rooms: object;
available: () => Promise<boolean>;

Promise<boolean>

create: (options?) => Promise<RoomsFail | RoomHandle>;

open under a random name

RoomCreateOptions

Promise<RoomsFail | RoomHandle>

join: (invite, options?) => Promise<RoomsFail | RoomHandle>;

string

RoomJoinOptions

Promise<RoomsFail | RoomHandle>

open: (name, options?) => Promise<RoomsFail | RoomHandle>;

a name under the calling app’s scope, or global/<name>; the room comes into being when nothing is there

string

RoomOpenOptions

Promise<RoomsFail | RoomHandle>

setOverlayHost: SetOverlayHost;
shell: object;
applyUpdate: () => Promise<boolean>;

Promise<boolean>

onUpdate: (callback) => () => void;

() => void

() => void

onUpdateTaken: (callback) => () => void;

() => void

() => void

updateReady: () => Promise<boolean>;

Promise<boolean>

showInstallPrompt: (reason?) => Promise<void>;

string

Promise<void>

storage: object;

Objects sealed here under a key the app holds and served to anyone holding the url. put, list and delete act for the calling app under the account its pin was taken under; get needs no account at all. The data and the key cross to this frame and stay in the browser: the api hears only sizes and ids, the store only sealed bytes.

available: () => Promise<boolean>;

whether put, list and delete can run here: storage is configured, the data plane serves it, and an account is connected under the caller

Promise<boolean>

delete: (url, options) => Promise<
| StorageFail
| {
ok: true;
}>;

string

string

Promise< | StorageFail | { ok: true; }>

get: (url, key, options?) => Promise<StorageFail | StoredHandle>;

string

string

AbortSignal

Promise<StorageFail | StoredHandle>

list: (options) => Promise<StorageFail | StoredPage>;

string

string

number

Promise<StorageFail | StoredPage>

put: (data, options) => Promise<
| StorageFail
| StoredObject & object>;

Blob | ReadableStream<Uint8Array>

StoragePutWire

Promise< | StorageFail | StoredObject & object>

webVpnTcpSocket: (options) => Promise<TcpSocketResult>;

TcpSocketOptions

Promise<TcpSocketResult>

webVpnTcpSocketListener: (options) => Promise<TcpSocketListenerResult>;

TcpSocketListenerOptions

Promise<TcpSocketListenerResult>

webVpnUdpSocket: (options) => Promise<UdpSocketResult>;

UdpSocketOptions

Promise<UdpSocketResult>


type RoomBacklog = object;

What a backlog page answered: the highest seq it sent, and whether another page follows.

last: number;
more: boolean;

type RoomClaimOptions = object;

What a claim carries besides the name.

optional description?: string;

What the room is for, in the app’s own words. The account reads it beside the room in its fkn.app settings, where it can clear or unclaim the room, so say what would be lost. One line of plain text: trimmed, then 1 to 200 characters as String.length counts them, with no control character, line or paragraph separator, or bidi control; anything else is refused invalid, never cut short. Not sealed: stored with the claim and shown to the account only. Claiming again with one replaces it: on a room under an app’s scope from the app that claimed the room only (the same app once it is verified), and from another app it is refused denied (rooms: only the app that claimed the room can describe it); on a global room from any app of the account. Claiming without one keeps it.

optional mailbox?: boolean;

Whether the room keeps its messages. false claims the name and stores nothing: a message reaches whoever is present and is kept nowhere, backlog answers an empty page, and edit and delete are refused invalid (rooms: the room keeps no messages). The claim still reserves the name, locks it to the key and keeps the owner, the blocks and the overrides when everyone leaves, and it still needs premium. true or no value keeps a mailbox. Anything else is refused invalid (rooms: malformed frame).

Read only when the call starts the claim: on a room the account already holds it is ignored, so an app that claims on every open never undoes what the owner chose since. A claim landing after a temporary one ran out starts a new claim, so it reads it again. A broker too old to carry it refuses false unavailable (rooms: the mailbox switch is not available) rather than store what the app asked it not to.

optional temporary?: boolean | number;

Makes the claim end by itself, in milliseconds: true is 7 days (ROOM_TEMPORARY_MS.default), a number is that many milliseconds, a whole number from 60,000 (1 minute) to 31,536,000,000 (365 days), and anything else is refused invalid. false or no value makes the claim permanent.

The clock restarts on every claim, so a temporary room ends that long after its LAST claim: a room the app keeps claiming on open stays, and an abandoned one goes. Restarting the clock needs the service: while it cannot be reached, a claim answers unavailable and the room keeps the end it had, so a room in use can still end if an outage outlasts the time it has left. Pick a duration with margin over how often the app is opened. When it ends, it ends exactly as an Unclaim from the account’s fkn.app settings does: the name is given back, the stored messages are deleted, and whoever is present hears a claim event with claimed: false and stays in an ordinary room. It counts toward the account’s room limit while it lasts and frees the slot when it ends.

The latest claim decides: claiming again without it makes the room permanent, and with it makes it temporary again. On a room under an app’s scope only the app that claimed the room (the same app once it is verified) sets or changes it, and from another app it is refused denied (rooms: only the app that claimed the room can set when it expires); on a global room any app of the account does. A claim from another app without it leaves the room as it was, on either kind of room.


type RoomCreateOptions = RoomOpenOptions;

type RoomDefaults = Readonly<{
maxMessageBytes: number;
receive: boolean;
send: boolean;
}>;

What a member gets unless an override says otherwise. maxMessageBytes is measured on the wire: nonce plus ciphertext, base64url.


type RoomEnd = object;

Why the room ended for this app. It ends once.

reason: "left" | "removed" | "blocked" | "ended" | "unavailable";

type RoomEvent =
| {
message: RoomMessage;
replayed: boolean;
type: "message";
}
| {
message: RoomMessage;
type: "edited";
}
| {
from: number;
to: number;
type: "deleted";
}
| {
member: RoomMember;
type: "joined";
}
| {
id: string;
reason: "left" | "removed" | "blocked";
type: "left";
}
| {
id: string;
maxMessageBytes: number;
permissions: RoomPermissions;
type: "permissions";
}
| {
defaults: RoomDefaults;
type: "defaults";
}
| {
claimed: boolean;
mailbox: boolean;
owner: string;
type: "claim";
};
{
message: RoomMessage;
replayed: boolean;
type: "message";
}

replayed when the message predates this connection: a backlog page, never a live send


{
message: RoomMessage;
type: "edited";
}

{
from: number;
to: number;
type: "deleted";
}

stored messages dropped: by a member, or all of them when the account clears the room in its fkn.app settings


{
member: RoomMember;
type: "joined";
}

{
id: string;
reason: "left" | "removed" | "blocked";
type: "left";
}

{
id: string;
maxMessageBytes: number;
permissions: RoomPermissions;
type: "permissions";
}

{
defaults: RoomDefaults;
type: "defaults";
}

{
claimed: boolean;
mailbox: boolean;
owner: string;
type: "claim";
}

the room was claimed, released (by its app, or by the account in its fkn.app settings) or dissolved, or its mailbox was turned off or on; owner is the owner’s member id, or ” while a claimed room’s owner has never joined. mailbox is whether the room now keeps messages, always false while claimed is false. Turning a mailbox off sends no deleted: what the app shows stays.


type RoomHandle = object;

A joined room, held by the broker: it owns the socket, the room key and the derived message key, and hands the app plaintext and member ids only.

Every member that can be refused answers with a RoomsFail rather than throwing, so a refusal survives the osra hop as data. leave and on cannot be refused.

backlog: (after, limit?) => Promise<RoomsFail | RoomBacklog>;

replays stored messages above after as message events marked replayed, one page at a time

number

number

Promise<RoomsFail | RoomBacklog>

block: (id) => Promise<
| RoomsFail
| {
ok: true;
}>;

a member sent out and kept out, with the block permission (denied, rooms: permission denied). From another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), as claim says.

string

Promise< | RoomsFail | { ok: true; }>

claim: (options?) => Promise<
| RoomsFail
| {
ok: true;
}>;

keeps the name for the signed-in premium account, and a mailbox unless mailbox is false; the only thing in rooms that premium buys. description says what the room is for, shown to the account in its fkn.app settings. temporary makes the claim end by itself that many milliseconds after its last claim (true is 7 days); the broker carries it as a number of milliseconds and never as true. mailbox is read only when the call starts the claim.

A room under an app’s scope, which is every room but a global one, is changed by the claim’s account only through the app that claimed it (the same app once it is verified): from another app of the account setDefault, limit, grant, revoke, remove, block, unblock, setMailbox, edit and delete answer denied (rooms: only the app that claimed the room can change it), the account’s own messages included, and unavailable while the room cannot tell whether two apps are one. A claimed global room is changed by any app of the account, which also describes it, sets its temporary and releases it. The account’s fkn.app settings change every room it claimed, and members of other accounts are answered as in any room.

Past the account’s room limit the call waits while fkn.app shows the person their claimed rooms over your app, with Unclaim on each: it answers { ok: true } once one is unclaimed and this claim is made, and full (rooms: too many claims) when they close the card or when fkn.app cannot show it. Nothing about the account’s other rooms reaches your app.

RoomClaimOptions

Promise< | RoomsFail | { ok: true; }>

claimed: boolean;
closed: Promise<RoomEnd>;

settles once, when the room ends for this app. Never rejects.

defaults: () => Promise<RoomDefaults>;

Promise<RoomDefaults>

delete: (from, to?) => Promise<
| RoomsFail
| {
ok: true;
}>;

stored messages dropped: one of the sender’s own, or any range as the owner. From another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), the account’s own messages included, as claim says.

number

number

Promise< | RoomsFail | { ok: true; }>

edit: (seq, text) => Promise<
| RoomsFail
| {
ok: true;
}>;

a stored message’s text, replaced: the sender’s own, or any as the owner. From another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), the account’s own messages included, as claim says.

number

string

Promise< | RoomsFail | { ok: true; }>

grant: (id, permission) => Promise<
| RoomsFail
| {
ok: true;
}>;

a member’s override of a permission, set to true, from the owner only (denied, rooms: not the room owner). From another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), as claim says.

string

RoomPermission

Promise< | RoomsFail | { ok: true; }>

id: string;

<scope>/<name>: the name under the app’s own scope, or under global

invite: string;

<key>.<id> as one string, the thing to put in a link

key: string;

the room key, base64url. The server never sees it. Anyone holding it and the id can join.

leave: () => Promise<void>;

Promise<void>

limit: (maxMessageBytes, id?) => Promise<
| RoomsFail
| {
ok: true;
}>;

the message size cap: the room default when no member is named, that member’s override otherwise, cleared by null. From the owner only (denied, rooms: not the room owner); from another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), as claim says.

number | null

string

Promise< | RoomsFail | { ok: true; }>

mailbox: RoomMailbox | null;
optional mailboxSwitch?: true;

set by a broker that carries mailbox on a claim and sends setMailbox. A broker older than that drops the option and claims with a mailbox, and has no setMailbox, so the lib refuses both on a handle without it rather than store what the app asked it not to.

members: () => Promise<RoomMember[]>;

Promise<RoomMember[]>

name: string;
on: (listener) => () => void;

the account.onChange shape: one broker-side registration, unsubscribed by the returned function

(event) => void

() => void

owner: string;
release: () => Promise<
| RoomsFail
| {
ok: true;
}>;

Promise< | RoomsFail | { ok: true; }>

remove: (id) => Promise<
| RoomsFail
| {
ok: true;
}>;

a member sent out, free to join again, with the remove permission (denied, rooms: permission denied). From another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), as claim says.

string

Promise< | RoomsFail | { ok: true; }>

revoke: (id, permission) => Promise<
| RoomsFail
| {
ok: true;
}>;

a member’s override of a permission, set to false, from the owner only (denied, rooms: not the room owner). From another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), as claim says.

string

RoomPermission

Promise< | RoomsFail | { ok: true; }>

self: RoomMember;
send: (text) => Promise<
| RoomsFail
| {
ok: true;
}>;

sealed before it leaves the browser and refused, never truncated, past this member’s maxMessageBytes on the wire

string

Promise< | RoomsFail | { ok: true; }>

setDefault: (permission, value) => Promise<
| RoomsFail
| {
ok: true;
}>;

the room default for send or receive, from the owner only (denied, rooms: not the room owner). From another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), as claim says.

"send" | "receive"

boolean

Promise< | RoomsFail | { ok: true; }>

setMailbox: (on) => Promise<
| RoomsFail
| {
ok: true;
}>;

the owner turns a claimed room’s mailbox off, deleting what it stored, or on, starting it empty. Answers once the room is in that state and has tried to tell the api its storage figure; the answer does not say whether the api heard it. From another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), as claim says.

boolean

Promise< | RoomsFail | { ok: true; }>

optional temporaryClaims?: true;

set by a broker that carries temporary on a claim. A broker older than that drops the option and makes the claim permanent, so the lib refuses a temporary claim on a handle without it rather than let the room outlive what the app asked for.

unblock: (id) => Promise<
| RoomsFail
| {
ok: true;
}>;

a block lifted, with the block permission (denied, rooms: permission denied). From another app of the claim’s account on a room claimed under an app’s scope, denied (rooms: only the app that claimed the room can change it), as claim says.

string

Promise< | RoomsFail | { ok: true; }>

usage: () => Promise<RoomsFail | RoomMailbox | null>;

Promise<RoomsFail | RoomMailbox | null>


type RoomJoinOptions = object;
optional signal?: AbortSignal;

type RoomMailbox = object;

What a claimed room’s mailbox holds, as the room last said. Null for a room that keeps no messages: one nobody has claimed, or a claim whose mailbox is off. cap is the most it stores: 500 MB, or the size limit the account set in its fkn.app settings.

archived: boolean;
bytes: number;
cap: number;
messages: number;

type RoomMember = object;

A participant as this room sees them. The id is fresh in every room.

id: string;
maxMessageBytes: number;
permissions: RoomPermissions;

type RoomMessage = object;
at: number;
from: string;
seq: number;
text: string;

type RoomOpenOptions = object;
optional defaults?: Partial<RoomDefaults>;
optional key?: string;

the room key, 32 bytes base64url, minted when absent. Deriving one from something people already share is how a name becomes a rendezvous.

optional members?: number;

honoured only by the join that brings the room into being

optional signal?: AbortSignal;

aborting it leaves the room; it never bounds the wait for the broker


type RoomPermission = "send" | "receive" | "remove" | "block";

type RoomPermissions = Readonly<Record<RoomPermission, boolean>>;

type RoomsErrorCode =
| "invalid"
| "not-found"
| "bad-key"
| "full"
| "blocked"
| "denied"
| "rate-limited"
| "too-large"
| "storage"
| "quota"
| "unavailable"
| "closed";

type RoomsFail = object;

A refusal, as data. packages crosses the hop the same way, and for the same reason.

error: RoomsErrorCode;
message: string;

type SetOverlayHost = (push, options?) => void;

(state) => unknown

boolean

void


type ShellControl = object;

The render proxy shell’s control channel (SHELL_CONTROL_KEY), as its middle page drives it. policy is the middle page’s realm policy for the call; flushCookies and sweepStorage are absent from a shell deployed before windows.

optional blank(): Promise<string>;

The url this shell’s empty page is armed for (?blank=), '' when none: a shell mounted with ?url= or nothing, or one whose first goto disarmed it. Absent from a shell that predates blank pages, which reads no target from ?blank= and so loads nothing.

Promise<string>

optional carry(hosts): Promise<void>;

The hosts this shell’s extension carrier may send a blank page’s own fetch and XHR requests to: the attachment’s approved set, each call replacing the last. The shell drops every FKN platform host whatever it is told, and a shell with nothing to carry (a window, an 'ephemeral' jar, a page that is not blank, no extension that announces the carrier) resolves and carries nothing. A TypeError for anything but an array of non-empty strings. Absent from a shell that predates the carrier, which sends every request through WebVPN.

string[]

Promise<void>

optional clearCookies(filter, scope): Promise<void>;

Removes the cookies filter matches from this shell’s jar, among the cookies of scope.hosts’ sites only: a cookie is in reach when its domain’s own registered domain is that of one of the hosts, or its domain is exactly a host that is itself a public suffix, so amazonaws.com reaches no mybucket.s3.amazonaws.com cookie, a site of its own under s3.amazonaws.com. Resolves undefined once its live realms dropped them (waiting at most 2000 ms) and the removal is committed, and takes the same steps when nothing matched, so neither its answer nor its time says whether the jar held a cookie there. That holds for { equals } options, the only ones its middle page sends for now: a { pattern, flags } one is still rebuilt and tested against every cookie in reach, so its running time on them would be part of the call’s.

Refuses, before anything is removed: a filter of any other shape (a TypeError frame.clearCookies: malformed filter), an empty scope.hosts, and a string domain whose registered domain is none of the hosts’ (each a LocatorDeniedError naming why). Rejects with the library’s own message when the jar’s store did not take the removal. Absent from a shell that predates it.

ClearCookiesWire

string[]

Promise<void>

executeLocator(
parts,
operation,
args,
policy?): Promise<unknown>;

SelectorPart[]

string

unknown[]

unknown

Promise<unknown>

optional flushCookies(): Promise<void>;

Resolves once every cookie change made so far is committed to the jar’s store.

Promise<void>

goto(url, options?): Promise<void>;

Pulls the committed jar first, so a sign-in another realm committed rides this navigation.

string

ShellGotoOptions

Promise<void>

ready(): Promise<boolean>;

Promise<boolean>

optional seed(seed): Promise<void>;

Seeds this shell’s jar and storage namespace: the cookies join the jar, recorded as its own, and each origin’s localStorage items are written where that origin’s pages read them, by the frame host serving it before the host takes a navigation. Resolves once the jar holds the cookies and the items of every origin a host serves now are written, so a goto sent afterwards carries them. Only on a jar of the attachment’s own with a namespace of its own: on the shared jar (an empty session, a window on its opener’s) a TypeError attachFrame: storageState seeds a fresh jar, so it needs cookies: 'ephemeral', with nothing seeded. A seed of another shape is a TypeError from the shell’s controller. Absent from a shell that predates it.

JarSeed

Promise<void>

optional sweepStorage(): Promise<void>;

Removes a window shell’s own site storage; resolves at once for a shell whose storage is shared.

Promise<void>


type ShellFrameMessage =
| {
data: unknown;
kind: "message";
origin: string;
targetOrigin: string;
transfer: Transferable[];
type: typeof SHELL_FRAME_MESSAGE;
}
| {
kind: "document";
origin: string;
type: typeof SHELL_FRAME_MESSAGE;
};

targetOrigin is the page’s own, normalized: what it addressed the message to. transfer is what the page moved with it, also the post’s transfer list, for the middle page to move on.


type ShellGotoOptions = MiddleGotoOptions & object;

A goto on the shell’s control channel.

optional commitFallback?: boolean;

For an attachment’s first navigation: past its deadline, a document that committed but never fired load counts as loaded, the rule the inline attachment’s first load follows. Ignored by a shell deployed before windows, which rejects at the deadline instead.


type ShellJarUnreachable = typeof SHELL_JAR_UNREACHABLE[number];

type StorageEntry = object;
contentType: string | null;
encryption: string | null;
path: string;
size: number;
updatedAt: string;

type StorageErrorCode =
| "invalid"
| "not-found"
| "integrity"
| "denied"
| "quota"
| "too-many"
| "account-changed"
| "unavailable";

What a storage refusal is about. Branch on it, never on the message.


type StorageFail = object;

A refusal, as data, the RoomsFail shape. aborted is the app’s own signal, which the library turns back into the signal’s reason rather than a StorageError.

error: StorageErrorCode | "aborted";
message: string;

type StorageProgress = object;

Progress of an upload, ProgressEvent’s two figures, in bytes of the file itself.

loaded: number;
total: number;

type StoragePutOptions = object;
optional key?: string;

the key that opens the object, 32 bytes as canonical unpadded base64url (43 characters), minted when absent. It never reaches FKN’s servers and never appears in the url: whoever holds the url and the key reads the object.

optional onProgress?: (progress) => void;

called as each part lands

StorageProgress

void

optional signal?: AbortSignal;

aborts the upload and releases the storage it reserved

optional size?: number;

required for a ReadableStream: the bytes it will deliver, exactly


type StoragePutWire = StoragePutOptions & object;

What the library hands the broker for a put, resolved at the moment of the call.

accountPin: string;

type StorageQuota = object;
limitBytes: number;
maxObjects: number;
objects: number;
remaining: number;
usedBytes: number;

type StoredHandle = object;

An object the broker opened: its plaintext size, and its bytes from start to end (exclusive) by range, each 1 MiB record checked before it is handed over. The stream errors on the first record that does not open under the key.

read: (start, end) => Promise<StorageFail | ReadableStream<Uint8Array>>;

number

number

Promise<StorageFail | ReadableStream<Uint8Array>>

size: number;

type StoredObject = object;

A stored object as put and list answer it. Every time is epoch milliseconds.

created: number;
size: number;

the file’s own bytes; the account’s storage counts 28 bytes more per 1 MiB

status: "uploading" | "ready";

uploading until every part has landed

url: string;

https://cdn.fkn.app/<uuid> in production: anyone holding it can download the sealed bytes, and nothing more


type StoredPage = object;

One page of list, newest first. cursor is present only while more objects remain.

optional cursor?: string;
objects: StoredObject[];

type SurfaceRect = object;

viewport coordinates, the space both the app and the broker frame measure in

height: number;
width: number;
x: number;
y: number;

type TcpSocketListenerOptions = object;
localAddress: string;
localPort: number;
optional onClose?: (error?) => void | Promise<void>;

Error

void | Promise<void>

onConnection: (connection) => void | Promise<void>;

TcpSocketResult

void | Promise<void>


type TcpSocketListenerResult = object;
close: () => Promise<void>;

Promise<void>

localAddress: string;
localFamily: IpFamily;
localPort: number;

type TcpSocketOptions = object;
remoteAddress: string;
remotePort: number;

type TcpSocketResult = object;
dataReadableStream: ReadableStream<Uint8Array>;
dataWritableStream: WritableStream<Uint8Array>;
destroy: () => Promise<void>;

Promise<void>

destroySoon: () => Promise<void>;

Promise<void>

end: () => Promise<void>;

Promise<void>

localAddress: string;
localFamily: IpFamily;
localPort: number;
remoteAddress: string;
remoteFamily: IpFamily;
remotePort: number;
resetAndDestroy: () => Promise<void>;

Promise<void>

setOption: (option) => Promise<void>;

TcpSocketOption

Promise<void>


type UdpDatagram = object;
address: string;
data: ArrayBuffer;
family: IpFamily;
port: number;
size: number;

type UdpSocketOptions = object;
address: string;
optional dataPort?: boolean;
port: number;
type: "udp4" | "udp6";

type UdpSocketResult = object;
close: () => Promise<void>;

Promise<void>

closed: Promise<{
reason: string;
}>;
connect: (options) => Promise<{
address: string;
family: IpFamily;
local: boolean;
port: number;
}>;

string

number

Promise<{ address: string; family: IpFamily; local: boolean; port: number; }>

dataPort: MessagePort | undefined;
dataPortAcks: true | undefined;
dataReadableStream: ReadableStream<UdpDatagram>;
disconnect: () => Promise<void>;

Promise<void>

localAddress: string;
localFamily: IpFamily;
localPort: number;
send: (options) => Promise<void>;

string

ArrayBuffer

number

Promise<void>

setOption: (option) => Promise<void>;

UdpSocketOption

Promise<void>

socketId: number;

type WindowMiddleControl = MiddleControl & object;

What a window’s middle page adds to its channel.

flushCookies(): Promise<void>;

Resolves once every cookie change made in the window so far is committed to its jar.

Promise<void>

ping(): Promise<true>;

The app’s heartbeat: a window that hears none for long enough stops answering.

Promise<true>

requestConsent(requests): Promise<FrameConsentAnswer[]>;

Draws the consent card in the window and answers each request in order.

FrameConsentRequest[]

Promise<FrameConsentAnswer[]>

sweepStorage(): Promise<void>;

Removes the proxied site storage this window kept to itself, for an app about to close it. Resolves at once for a window on the web origin’s own jar, whose storage is the app’s.

Promise<void>


type WriteData = ArrayBuffer | Uint8Array | string;
const ATTACH_FRAME_KEY: "fkn-attach-frame" = 'fkn-attach-frame';

The osra key of an inline attachment’s channel, which /attach-frame exposes to the library.


const ATTACH_WINDOW_KEY: "fkn-attach-window" = 'fkn-attach-window';

The osra key of a window attachment’s channel, which /attach-window exposes to its opener.


const ATTACH_WINDOW_MESSAGE: object;

What an app and its window post to each other, every message carrying the attach id.

readonly detach: "fkn-attach-window-detach" = 'fkn-attach-window-detach';

app to window, whenever the app ends the attachment (its pagehide included): stop answering this app. unanswered: true when it ended because the window stopped answering its pings.

readonly gone: "fkn-attach-window-gone" = 'fkn-attach-window-gone';

window to app: this window no longer answers the app, because it is going away or was dropped

readonly hello: "fkn-attach-window" = 'fkn-attach-window';

app to window, repeated until the channel connects

readonly refused: "fkn-attach-window-refused" = 'fkn-attach-window-refused';

window to app, { reason }: the window loaded nothing and exposes nothing


const FRAME_MESSAGE: "fkn-frame-message" = 'fkn-frame-message';

Posted by a middle page to its app for the Frame’s listeners (FrameMessageRelay), with the page’s ports as the transfer list. Only for a document inside the attachment, and a message only when the page addressed it to the app’s own origin.


const JAR_ANCHOR_MESSAGE: object;

What a window’s middle page, the jar anchor its app mounted (/attach-jar) and the anchor’s jar host say, all with the attach id.

readonly hello: "fkn-jar-hello" = 'fkn-jar-hello';

window to each frame of its opener: is the anchor for this attachment here?

readonly host: "fkn-jar-host" = 'fkn-jar-host';

jar host to anchor: the answer, the only one a port is relayed to

readonly port: "fkn-jar-port" = 'fkn-jar-port';

window to anchor, and anchor to jar host, carrying exactly one port

readonly ready: "fkn-jar-ready" = 'fkn-jar-ready';

anchor to the window it accepted

readonly who: "fkn-jar-who" = 'fkn-jar-who';

anchor to jar host, after every load of the host frame: are you the jar host?


const ROOM_TEMPORARY_MS: object;

How long a temporary claim may last, in milliseconds: default is what temporary: true means (7 days), and a number outside min (1 minute) to max (365 days) is refused invalid.

readonly default: 604800000 = 604_800_000;
readonly max: 31536000000 = 31_536_000_000;
readonly min: 60000 = 60_000;

const SHELL_ATTACH_ERROR: "sdbx-attach-error" = 'sdbx-attach-error';

Posted by a shell that could not start, { type, message }, to the middle page framing it.


const SHELL_CONTROL_KEY: "sdbx-control" = 'sdbx-control';

The osra key of the render proxy shell’s control channel, which its middle page drives.


const SHELL_FRAME_MESSAGE: "sdbx-frame-message" = 'sdbx-frame-message';

Posted by the render proxy shell to its middle page (ShellFrameMessage), with the page’s ports as the transfer list: what the page posted to an origin not its own, and each document’s arrival. The shell does not know the app, so the middle page decides what reaches it.


const SHELL_JAR_MESSAGE: object;

What a window’s shell and its middle page say about the shell’s cookie jar, all with the attach id.

readonly mode: "sdbx-jar-mode" = 'sdbx-jar-mode';

shell to middle page, { mode, reason? }, before the first navigation

readonly port: "sdbx-jar-port" = 'sdbx-jar-port';

middle page to shell, carrying exactly one port

readonly request: "sdbx-jar-request" = 'sdbx-jar-request';

shell to middle page, once per shell document: send me the port to the opener’s jar host


const SHELL_JAR_UNREACHABLE: readonly ["bad-attach", "no-storage", "bad-jar", "no-parent", "no-jar-host", "no-token", "no-indexeddb", "failed"];

Why a window’s shell reached no jar, sent as the reason of an 'unreachable' mode report. Only these names cross the wire, never a value or an error’s own text.


const STORAGE_REFUSALS: object;

Every storage refusal, worded once for the library and the broker. Branch on code, never on the message. notFound is one answer for an object that was never made, was deleted, or is still uploading.

readonly accountChanged: object;
readonly code: "account-changed" = 'account-changed';
readonly message: "storage: the signed-in account changed" = 'storage: the signed-in account changed';
readonly badCursor: object;
readonly code: "invalid" = 'invalid';
readonly message: "storage: the cursor is not one list answered" = 'storage: the cursor is not one list answered';
readonly badKey: object;
readonly code: "invalid" = 'invalid';
readonly message: "storage: the key is not 32 bytes base64url" = 'storage: the key is not 32 bytes base64url';
readonly badLimit: object;
readonly code: "invalid" = 'invalid';
readonly message: "storage: the limit is out of range" = 'storage: the limit is out of range';
readonly badSize: object;
readonly code: "invalid" = 'invalid';
readonly message: "storage: a stream needs the size it will deliver, a whole number of bytes" = 'storage: a stream needs the size it will deliver, a whole number of bytes';
readonly badUrl: object;
readonly code: "invalid" = 'invalid';
readonly message: "storage: the url does not name a stored object" = 'storage: the url does not name a stored object';
readonly integrity: object;
readonly code: "integrity" = 'integrity';
readonly message: "storage: the object does not match its key" = 'storage: the object does not match its key';
readonly needsAccount: object;
readonly code: "denied" = 'denied';
readonly message: "storage: storing needs an account" = 'storage: storing needs an account';
readonly notAFile: object;
readonly code: "invalid" = 'invalid';
readonly message: "storage: the data is not a Blob or a ReadableStream" = 'storage: the data is not a Blob or a ReadableStream';
readonly notCreator: object;
readonly code: "denied" = 'denied';
readonly message: "storage: only the app that stored the object can delete it" = 'storage: only the app that stored the object can delete it';
readonly notFound: object;
readonly code: "not-found" = 'not-found';
readonly message: "storage: no object at this url" = 'storage: no object at this url';
readonly quota: object;
readonly code: "quota" = 'quota';
readonly message: "storage: storage quota exceeded" = 'storage: storage quota exceeded';
readonly shortStream: object;
readonly code: "invalid" = 'invalid';
readonly message: "storage: the stream did not deliver the size it declared" = 'storage: the stream did not deliver the size it declared';
readonly tooLarge: object;
readonly code: "invalid" = 'invalid';
readonly message: "storage: the file is too large to store" = 'storage: the file is too large to store';
readonly tooMany: object;
readonly code: "too-many" = 'too-many';
readonly message: "storage: too many stored objects" = 'storage: too many stored objects';
readonly tooManyThisHour: object;
readonly code: "too-many" = 'too-many';
readonly message: "storage: too many objects stored this hour, try again later" = 'storage: too many objects stored this hour, try again later';
readonly unavailable: object;
readonly code: "unavailable" = 'unavailable';
readonly message: "storage: storage is unavailable" = 'storage: storage is unavailable';

const WINDOW_HANDSHAKE_TIMEOUT_MS: number;

How long an app waits for a window’s channel. The window exposes it only once it has heard the app’s hello and its shell has reported a jar, each on its own deadline above and one after the other, and says by name when it cannot; the margin covers the window’s own page load and the channel’s handshake after it. Any shorter and the app gives up on a window that is about to connect, or to say why it cannot.


const WINDOW_HELLO_TIMEOUT_MS: 20000 = 20_000;

How long a window waits for its app’s first hello, from when its page runs. The app says hello every 250 ms from the moment it opens the window, so only an app that went away misses it.


const WINDOW_JAR_MODE_TIMEOUT_MS: 30000 = 30_000;

How long a window waits, from mounting its shell, for the shell to report the jar it runs on: the shell’s jar port wait (12 s), its partition token read (5 s) and a controller boot. The window exposes its channel only after that report, so the app waits for both of these and more.

Re-exports TcpSocketOption


Re-exports UdpSocketOption