TypeScript
Most of the types you will reach for are the types of values you already hold: Frame is what attachFrame resolves to, Locator is what locator() returns, and QuotaStatus is what cloud.quota() resolves to. This page covers which entry point exports each name, how to spell the shapes @fkn/lib declares but does not export, what the broker’s own vocabulary in @fkn/lib/contract is for (the broker is the connection your app holds into FKN), and how to tell an error you can test with instanceof from one you match by message.
The three you will meet first, with the entry each comes from:
import { const attachFrame: (options: AttachFrameOptions) => Promise<Frame>
attachFrame, (alias) namespace cloudimport cloud
cloud } from '@fkn/lib'import type { type Frame = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Frame, type Locator = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Locator } from '@fkn/lib'import type { type QuotaStatus = { premium: boolean; overQuota: boolean; throttled: boolean; usedBytes: number; limitBytes: number; remainingBytes: number; bytesPerSecond: number; bitsPerSecond: number;}
QuotaStatus } from '@fkn/lib/cloud'
const const frame: Frame
frame: type Frame = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Frame = await function attachFrame(options: AttachFrameOptions): Promise<Frame>
attachFrame({ iframe: HTMLIFrameElement
iframe: var document: Document
window.document returns a reference to the document contained in the window.
document.ParentNode.querySelector<"iframe">(selectors: "iframe"): HTMLIFrameElement | null (+4 overloads)
Returns the first element that is a descendant of node that matches selectors.
querySelector('iframe')! }) // the extension backend when it is exposed, the cloud backend otherwiseconst const rows: Locator
rows: type Locator = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Locator = const frame: Frame
frame.locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve: (_context: LocatorContext) => Element[]; readonly render: () => { fragment: string; separator: "up"; }; }; }; readonly operations: { readonly addStyleTag: { readonly resolve: (context: LocatorContext, options: AddStyleTagOptions$1) => void; }; readonly fetch: { readonly kind: ChainKind; readonly ...
locator('.result') // a chain, nothing touches the page yetconst const quota: cloud.QuotaStatus
quota: type QuotaStatus = { premium: boolean; overQuota: boolean; throttled: boolean; usedBytes: number; limitBytes: number; remainingBytes: number; bytesPerSecond: number; bitsPerSecond: number;}
QuotaStatus = await (alias) namespace cloudimport cloud
cloud.cloud_d_exports.quota(): Promise<QuotaStatus>export cloud_d_exports.quota
quota()const const total: number
total = await const rows: Locator
rows.count: (_options?: LocatorOptions | undefined) => Promise<number>
count() // the first call that touches the pageconst quota: cloud.QuotaStatus
quota.throttled: boolean
transfers are actually being rate-limited right now (overQuota and not premium)
throttled // false while today's free volume is leftEach annotation names a type the call already infers, so the imports are the only new thing. The namespaces carry their types too, so cloud.QuotaStatus, fs.Stats and packages.PackagesError all resolve from a root import. A named export usually has an entry in the generated API reference, the pages built from the declarations @fkn/lib ships. A shape declared under no exported name never does, and this page shows how to derive each of those.
Frames and locators
Section titled “Frames and locators”Frame and Locator come from the root and from @fkn/lib/extension, with AttachFrameOptions, GotoOptions, RemoteVideoElement and VideoElementState beside them. The option and result shapes are declared but not exported by name, so derive them from the members that take or return them:
import type { type Frame = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Frame, type Locator = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Locator } from '@fkn/lib'
type type ReadOptions = OperationOptions & { reason?: string;}
ReadOptions = type NonNullable<T> = T & {}
Exclude null and undefined from T
NonNullable<type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tuple
Parameters<type Locator = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Locator['textContent']>[0]> // { timeout?: number; reason?: string }type type ClickOptions = OperationOptions & { reason?: string;} & { position?: { x?: number; y?: number; };}
ClickOptions = type NonNullable<T> = T & {}
Exclude null and undefined from T
NonNullable<type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tuple
Parameters<type Locator = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Locator['click']>[0]> // the same plus position?: { x?: number; y?: number }type type FrameFetchResult = { status: number; statusText: string; ok: boolean; url: string; redirected: boolean; type: ResponseType; headers: [string, string][]; body: ArrayBuffer;}
FrameFetchResult = type Awaited<T> = T extends null | undefined ? T : T extends object & { then(onfulfilled: infer F, ...args: infer _): any;} ? F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never : T
Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to never. This emulates the behavior of await.
Awaited<type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function type
ReturnType<type Frame = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Frame['fetch']>> // { status, statusText, ok, url, redirected, type, headers: [string, string][], body: ArrayBuffer }
const const readAll: (list: Locator, options?: ReadOptions) => Promise<string[]>
readAll = async (list: Locator
list: type Locator = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Locator, options: OperationOptions & { reason?: string;}
options: type ReadOptions = OperationOptions & { reason?: string;}
ReadOptions = {}): interface Promise<T>
Represents the completion of an asynchronous operation
Promise<string[]> => { const const total: number
total = await list: Locator
list.count: (_options?: LocatorOptions | undefined) => Promise<number>
count() const const out: string[]
out: string[] = [] for (let let i: number
i = 0; let i: number
i < const total: number
total; let i: number
i++) const out: string[]
out.Array<string>.push(...items: string[]): number
Appends new elements to the end of an array, and returns the new length of the array.
push(await list: Locator
list.nth: (index: number) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve: (_context: LocatorContext) => Element[]; readonly render: () => { fragment: string; separator: "up"; }; }; }; readonly operations: { readonly addStyleTag: { readonly resolve: (context: LocatorContext, options: AddStyleTagOptions$1) => void; }; readonly fetch: { readonly kind: ChainKind; readonly ...
nth(let i: number
i).textContent: (_options?: LocatorOptions | undefined) => Promise<string>
textContent(options: OperationOptions & { reason?: string;}
options)) return const out: string[]
out}const const pressRight: (button: Locator, options?: ClickOptions) => Promise<void>
pressRight = (button: Locator
button: type Locator = { readonly locator: (selector: string) => Locator$1<Extended<{ readonly element: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly getByRole: { readonly to: "element"; readonly resolve: (context: LocatorContext, role: string) => Element[]; readonly render: (role: unknown) => { fragment: string; }; }; readonly getByText: { readonly to: "element"; readonly resolve: (context: LocatorContext, text: string) => Element[]; readonly render: (text: unknown) => { fragment: string; }; }; readonly getByTestId: { readonly to: "element"; readonly resolve: (context: LocatorContext, testId: string) => Element[]; readonly render: (testId: unknown) => { fragment: string; }; }; readonly first: { readonly to: "element"; readonly resolve: (context: LocatorContext) => Element[]; readonly render: () => { fragment: string; }; }; readonly nth: { readonly to: "element"; readonly resolve: (context: LocatorContext, index: number) => Element[]; readonly render: (index: unknown) => { fragment: string; }; }; }; readonly operations: { readonly click: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly fill: { readonly resolve: (context: LocatorContext, value: string, _options?: OperationOptions) => void; }; readonly hover: { readonly resolve: (context: LocatorContext, options?: PositionOptions$1) => void; }; readonly textContent: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => string; }; readonly getAttribute: { readonly resolve: (context: LocatorContext, name: string, _options?: OperationOptions) => string | null; }; readonly isVisible: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; readonly count: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => number; }; readonly exists: { readonly resolve: (context: LocatorContext, _options?: OperationOptions) => boolean; }; }; }; readonly frame: { readonly selectors: { readonly locator: { readonly to: "element"; readonly css: true; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "descend"; }; }; readonly frameLocator: { readonly to: "frame"; readonly css: true; readonly barrier: "down"; readonly resolve: (context: LocatorContext, selector: string) => Element[]; readonly render: (selector: unknown) => { fragment: string; separator: "down"; }; }; readonly owner: { readonly to: "frame"; readonly barrier: "up"; readonly resolve ...
Locator, options: OperationOptions & { reason?: string;} & { position?: { x?: number; y?: number; };}
options: type ClickOptions = OperationOptions & { reason?: string;} & { position?: { x?: number; y?: number; };}
ClickOptions = {}) => button: Locator
button.click: (options?: PositionOptions | undefined) => Promise<void>
click({ ...options: OperationOptions & { reason?: string;} & { position?: { x?: number; y?: number; };}
options, position?: { x?: number; y?: number;} | undefined
position: { x?: number | undefined
x: 0.9, y?: number | undefined
y: 0.5 } }) // fractions of the element's box, so this lands near its right edgeconst const text: (result: FrameFetchResult) => string
text = (result: FrameFetchResult
result: type FrameFetchResult = { status: number; statusText: string; ok: boolean; url: string; redirected: boolean; type: ResponseType; headers: [string, string][]; body: ArrayBuffer;}
FrameFetchResult): string => new var TextDecoder: new (label?: string, options?: TextDecoderOptions) => TextDecoder
The TextDecoder interface represents a decoder for a specific text encoding, such as UTF-8, ISO-8859-2, KOI8-R, GBK, etc.
TextDecoder().TextDecoder.decode(input?: AllowSharedBufferSource, options?: TextDecodeOptions): string
The TextDecoder.decode() method returns a string containing text decoded from the buffer passed as a parameter.
decode(result: FrameFetchResult
result.body: ArrayBuffer
body) // the whole body arrived as one ArrayBufferSpelling a type is worth it when the type goes back into a signature, and each of the three does here. textContent() is typed Promise<string>, never string | null. An Element’s textContent is never null at runtime either, unlike a Node’s.
The root Frame has no click, getByRole or first, since those are element members and appear only after the first locator(). Those members, and Locator itself, which has no entry of its own in the generated API reference, are on locators and actions. What attachFrame takes is on frames.
Permissions
Section titled “Permissions”PermissionRequest is { key, scope?, reason? } and PermissionGrant is { key, scope, allow }, both from the root and from @fkn/lib/extension. permissions runs on the extension backend: the call is carried out by the FKN browser extension, not by the cloud. Every permissions.request therefore waits for the extension to expose itself.
On a page without the extension, the missing-extension handler runs first (the install card by default), and then the call rejects with The FKN WebExtension is not installed, enabled or not exposed on this page.. setMissingExtensionHandler(null) removes that card. The rejection then lands after the waitForExtensionExposure wait instead of after a dismissal, so the call is bounded.
The union of the twenty keys is declared but not exported, so index it off the request type:
import { const permissions: { request: (requests: PermissionRequest[]) => Promise<PermissionGrant[]>;}
permissions, const setMissingExtensionHandler: (handler: MissingExtensionHandler | null) => void
setMissingExtensionHandler } from '@fkn/lib'import type { type PermissionRequest = { key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"; scope?: string; reason?: string;}
PermissionRequest, type PermissionGrant = { key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"; scope: string; allow: boolean;}
PermissionGrant } from '@fkn/lib'
type type PermissionKey = "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"
PermissionKey = type PermissionRequest = { key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"; scope?: string; reason?: string;}
PermissionRequest['key'] // 'read.text' | 'read.info' | 'read.visible' | ... twenty keysconst const wanted: ("read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders")[]
wanted: type PermissionKey = "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"
PermissionKey[] = ['read.text', 'act.click']const const requests: PermissionRequest[]
requests: type PermissionRequest = { key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"; scope?: string; reason?: string;}
PermissionRequest[] = const wanted: ("read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders")[]
wanted.Array<"read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | ... 8 more ... | "network.modifyRequestHeaders">.map<{ key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"; scope: string; reason: string;}>(callbackfn: (value: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | ... 10 more ... | "network.modifyRequestHeaders", index: number, array: ("read.text" | ... 18 more ... | "network.modifyRequestHeaders")[]) => { key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"; scope: string; reason: string;}, thisArg?: any): { key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"; scope: string; reason: string;}[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
map(key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"
key => ({ key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"
key, scope: string
scope: '*', reason: string
reason: 'Read the results and press Search for you' }))
function setMissingExtensionHandler(handler: MissingExtensionHandler | null): void
setMissingExtensionHandler(null) // a page without the extension rejects after the wait instead of opening the install cardconst const grants: PermissionGrant[]
grants: type PermissionGrant = { key: "read.text" | "read.info" | "read.visible" | "read.check" | "read.count" | "act.click" | "act.type" | "act.hover" | "media.video" | "media.appear" | "media.appearU" | "embed.iframe" | "embed.open" | "frame.fetchRead" | "frame.fetchWrite" | "network.fetch" | "network.fetchCredentialed" | "network.fetchLocal" | "network.readCookie" | "network.modifyRequestHeaders"; scope: string; allow: boolean;}
PermissionGrant[] = await const permissions: { request: (requests: PermissionRequest[]) => Promise<PermissionGrant[]>;}
permissions.request: (requests: PermissionRequest[]) => Promise<PermissionGrant[]>
request(const requests: PermissionRequest[]
requests) // [{ key: 'read.text', scope: '*', allow: true }, ...] once granted, in the order askedThe consent sheet is what the extension shows the user before it carries out a gated action. What each key gates, what the scope means and what the sheet shows are on permissions and consent.
Account and quota
Section titled “Account and quota”An account is the FKN identity a person carries between sites. AccountInfo is what account.info() resolves to, or null when no account is connected. It comes from @fkn/lib/account and holds { name: string, image: string | null, premium: boolean, premiumUntil: number | null } and nothing else: no id and no email, by design.
QuotaStatus is what cloud.quota() resolves to, from @fkn/lib/cloud. It holds the three booleans premium, overQuota and throttled, and the five numbers usedBytes, limitBytes, remainingBytes, bytesPerSecond and bitsPerSecond, each defined on account and quota.
The example reads throttled and bitsPerSecond, the two fields a rate label needs:
import type { type AccountInfo = { name: string; image: string | null; premium: boolean; premiumUntil: number | null;}
AccountInfo } from '@fkn/lib/account'
const const label: (info: AccountInfo | null) => string
label = (info: account.AccountInfo | null
info: type AccountInfo = { name: string; image: string | null; premium: boolean; premiumUntil: number | null;}
AccountInfo | null): string =>
const { const throttled: boolean
transfers are actually being rate-limited right now (overQuota and not premium)
throttled, const bitsPerSecond: number
current effective egress rate cap, bits per second (for display)
bitsPerSecond } = await (alias) namespace cloudimport cloud
cloud.cloud_d_exports.quota(): Promise<cloud.QuotaStatus>export cloud_d_exports.quota
quota() // false and the free rate while today's free volume is leftconst const rate: string
rate = const throttled: boolean
transfers are actually being rate-limited right now (overQuota and not premium)
throttled ? `${var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math.Math.round(x: number): number
Returns a supplied numeric expression rounded to the nearest integer.
round(const bitsPerSecond: number
current effective egress rate cap, bits per second (for display)
bitsPerSecond / 1e6)} Mbit/s` : 'full rate' // 'full rate', since throttled is falselabel takes AccountInfo | null, so the disconnected case is typed. The rest of the account surface is on account and quota.
Storage
Section titled “Storage”The storage types split by entry: @fkn/lib/fs exports the shapes of the hybrid fs, @fkn/lib/cloud/fs exports the account’s, and Stats comes from all three file systems:
| Type | From | What it holds |
|---|---|---|
Stats | fs, opfs, cloud/fs | { size, mtimeMs, mtime, atimeMs, ctimeMs, mode, isFile(), isDirectory(), isSymbolicLink() }, and isSymbolicLink() is always false |
SealedRead | fs, cloud/fs | { data: Uint8Array, sealedAt: string | null } |
StorageQuota | cloud/fs | { usedBytes, limitBytes, remaining, objects, maxObjects } |
EncryptionStatus | cloud/fs | { unlocked, enrolled, keyEpoch: number | null } |
FileEntry | cloud/fs | { path, size, contentType: string | null, updatedAt, encryption: string | null }, which no exported function produces |
ConflictSide | fs | { size, updatedAt: string | null } |
FileConflict | fs | { path, local, cloud, readLocal(), readCloud() }, what a resolver receives |
ConflictResolution | fs | Blob | ArrayBuffer | Uint8Array | string | null, what a resolver returns |
ConflictResolver | fs | (conflict: FileConflict) => ConflictResolution | Promise<ConflictResolution> |
AdoptSummary | fs | uploaded, kept, unresolved and deleted as string[], resolved: { path, choice: 'local' | 'cloud' | 'merged' }[] and failed: { path, error }[], what adopt() resolves to |
A read is typed Buffer | string whatever encoding you pass, because the declarations carry no overloads. Coerce the result before JSON.parse. The option types (ReadOptions, WriteOptions, WriteData, MakeOptions, Callback) are not exported by name from any storage entry, and the WriteData on @fkn/lib/contract is a different type (see the broker’s vocabulary). Derive them from the functions instead:
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) => Stats; ... 28 more ...; onConflict: (next: ConflictResolver) => (() => void);}
fs from '@fkn/lib/fs'
type type WriteOptions = BufferEncoding | { encoding?: BufferEncoding | null; mode?: number; flag?: string; contentType?: string;} | null | undefined
WriteOptions = type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tuple
Parameters<typeof 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) => Stats; ... 28 more ...; onConflict: (next: ConflictResolver) => (() => void);}
fs.promises: { readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>; writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>; appendFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>; stat: (path: import("node:fs").PathLike) => Promise<Stats>; lstat: (path: import("node:fs").PathLike) => Promise<Stats>; ... 6 more ...; access: (path: import("node:fs").PathLike) => Promise<void>;}
promises.writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>
writeFile>[2] // BufferEncoding | { encoding?, mode?, flag?, contentType? } | null | undefinedtype type WriteData = string | ArrayBuffer | Uint8Array<ArrayBufferLike> | DataView<ArrayBufferLike>
WriteData = type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
Obtain the parameters of a function type in a tuple
Parameters<typeof 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) => Stats; ... 28 more ...; onConflict: (next: ConflictResolver) => (() => void);}
fs.promises: { readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>; writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>; appendFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>; stat: (path: import("node:fs").PathLike) => Promise<Stats>; lstat: (path: import("node:fs").PathLike) => Promise<Stats>; ... 6 more ...; access: (path: import("node:fs").PathLike) => Promise<void>;}
promises.writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>
writeFile>[1] // string | Uint8Array | ArrayBuffer | DataView, and never a Blob
const const options: WriteOptions
options: type WriteOptions = BufferEncoding | { encoding?: BufferEncoding | null; mode?: number; flag?: string; contentType?: string;} | null | undefined
WriteOptions = { contentType?: string | undefined
contentType: 'application/json' }const const data: WriteData
data: type WriteData = string | ArrayBuffer | Uint8Array<ArrayBufferLike> | DataView<ArrayBufferLike>
WriteData = var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
stringify({ items: never[]
items: [] })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) => Stats; ... 28 more ...; onConflict: (next: ConflictResolver) => (() => void);}
fs.promises: { readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>; writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>; appendFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>; stat: (path: import("node:fs").PathLike) => Promise<Stats>; lstat: (path: import("node:fs").PathLike) => Promise<Stats>; ... 6 more ...; access: (path: import("node:fs").PathLike) => Promise<void>;}
promises.writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>
writeFile('library/catalog.json', const data: string
data, const options: { encoding?: BufferEncoding | null; mode?: number; flag?: string; contentType?: string;}
options)const const catalog: any
catalog = var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON.JSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): any
Converts a JavaScript Object Notation (JSON) string into an object.
parse(var String: StringConstructor(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(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) => Stats; ... 28 more ...; onConflict: (next: ConflictResolver) => (() => void);}
fs.readFileSync: (path: import("node:fs").PathLike, options?: ReadOptions) => Buffer | string
readFileSync('library/catalog.json', 'utf8'))) // Buffer | string in the type, a string at runtimeThe read still needs String(). The string is what arrives, and the union is what the declaration says.
@fkn/lib/cloud/fs has no synchronous members at all, so a synchronous call there is a compile error rather than a runtime one:
import * as import cloudFs
cloudFs from '@fkn/lib/cloud/fs'
type type CloudMember = "STORAGE_NOT_FOUND" | "StorageLockedError" | "StorageNotFoundError" | "available" | "encryption" | "isNotFound" | "mkdir" | "promises" | "quota" | "readFile" | "readFileSealed" | "readdir" | "rename" | "rm" | "stat" | "unlink" | "unlock" | "writeFile"
CloudMember = keyof typeof import cloudFs
cloudFs // 'available' | 'readFile' | 'writeFile' | 'quota' | 'encryption' | 'unlock' | ... eighteen names, none ending in Syncimport cloudFs
cloudFs.readFileSync('library/catalog.json')Error ts(2551) ― The key union is the whole surface, and mount, flush and appendFile are missing from it as well. The rest is on storage, sync and conflicts and encryption.
Sockets, HTTP and DNS
Section titled “Sockets, HTTP and DNS”Socket and Server from @fkn/lib/net are classes that implement Node’s net.Socket and net.Server. The option and address types around them (SocketConnectOpts, ListenOptions, AddressInfo) are Node’s own, from @types/node. @fkn/lib/dgram and @fkn/lib/http are typed the same way, so code written against Node’s declarations compiles against these, with three exceptions.
net.connect and net.createConnection hand back a Socket before the relay, the server that holds the real socket at the far end, has answered. The connect method on that Socket accepts every Node overload. The two functions accept only the options form:
const const socket: net.Socket
socket = import net
net.function connect(options: SocketConnectOpts, connectionListener?: () => void): net.Socketexport connect
connect({ TcpSocketConnectOpts.host?: string | undefined
host: 'example.org', TcpSocketConnectOpts.port: number
port: 6881 }) // a Socket, handed back before the relay answersimport net
net.function connect(options: SocketConnectOpts, connectionListener?: () => void): net.Socketexport connect
connect(6881, 'example.org')Error ts(2345) ― The positional call fails at the call site rather than on the wire.
The dgram.Socket constructor requires its options, where Node’s does not, so build sockets with createSocket:
import { class Socket
Socket, const createSocket: (options: SocketType | SocketOptions, callback?: (msg: Buffer, rinfo: RemoteInfo) => void) => Socket
createSocket } from '@fkn/lib/dgram'
const const socket: Socket
socket = function createSocket(options: SocketType | SocketOptions, callback?: (msg: Buffer, rinfo: RemoteInfo) => void): Socket
createSocket('udp4') // a Socket built as new Socket({ type: 'udp4' })new new Socket(options: SocketOptions & EventEmitterOptions & { connection?: ReturnType<(options: UdpSocketOptions) => Promise<UdpSocketResult>>;}): Socket
Socket()Error ts(2554) ― createSocket supplies that object, so code written against Node’s createSocket needs no change.
The third exception is a name rather than a narrowing. OutgoingMessage, the base of ClientRequest and ServerResponse, sits on the default export of @fkn/lib/http only. http.OutgoingMessage resolves, and an import of it by name does not compile:
import const http: { METHODS: string[]; STATUS_CODES: Record<number, string>; Agent: typeof Agent; globalAgent: Agent; Server: typeof ServerImpl; ServerResponse: typeof ServerResponse; IncomingMessage: typeof IncomingMessage; ClientRequest: typeof ClientRequest; OutgoingMessage: typeof OutgoingMessage; createServer: (options?: object | RequestListener, requestListener?: RequestListener) => Server; request: (...args: [options: string | URL | ClientRequestArgs, callback?: (res: IncomingMessage) => void] | [url: string | URL, options: ClientRequestArgs, callback?: (res: IncomingMessage) => void]) => ClientRequest; get: (...args: [options: string | URL | ClientRequestArgs, callback?: (res: IncomingMessage) => void] | [url: string | URL, options: ClientRequestArgs, callback?: (res: IncomingMessage) => void]) => ClientRequest;}
http from '@fkn/lib/http'import { OutgoingMessage } from '@fkn/lib/http'Error ts(2614) ―
type type Out = OutgoingMessage
Out = type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any
Obtain the return type of a constructor function type
InstanceType<typeof const http: { METHODS: string[]; STATUS_CODES: Record<number, string>; Agent: typeof Agent; globalAgent: Agent; Server: typeof ServerImpl; ServerResponse: typeof ServerResponse; IncomingMessage: typeof IncomingMessage; ClientRequest: typeof ClientRequest; OutgoingMessage: typeof OutgoingMessage; createServer: (options?: object | RequestListener, requestListener?: RequestListener) => Server; request: (...args: [options: string | URL | ClientRequestArgs, callback?: (res: IncomingMessage) => void] | [url: string | URL, options: ClientRequestArgs, callback?: (res: IncomingMessage) => void]) => ClientRequest; get: (...args: [options: string | URL | ClientRequestArgs, callback?: (res: IncomingMessage) => void] | [url: string | URL, options: ClientRequestArgs, callback?: (res: IncomingMessage) => void]) => ClientRequest;}
http.type OutgoingMessage: typeof OutgoingMessage
OutgoingMessage> // the base both ClientRequest and ServerResponse extendconst const sent: (message: Out) => boolean
sent = (message: OutgoingMessage
message: type Out = OutgoingMessage
Out): boolean => message: OutgoingMessage
message.OutgoingMessage.headersSent: boolean
headersSentOut is the base class a signature over both a request and a response needs.
@fkn/lib/dns exports lookup, generic over its all option, and AddressLookupResult = { address: string; family: 0 | 4 | 6 }. @fkn/lib/wire exports the socket option codes as literal types and the two unions TcpSocketOption and UdpSocketOption. The rest is on TCP and UDP sockets and HTTP and DNS.
Packages
Section titled “Packages”A package is an npm module FKN loads on a sandbox origin of its own. @fkn/lib/packages exports every shape the packages surface uses, so nothing there has to be derived:
| Type | What it covers |
|---|---|
PackageQuery, PackageResult | what search and pick take, and one hit they answer |
InstalledPackage | what install resolves to, and one row of list |
PickOptions, InstallOptions | { title?, multiple? } and { version?, noConfirm? } |
ConnectOptions, AppConnectOptions | { protocol? }, and the app’s payload?, signal? and raw? on top |
Connect | the overloaded call type behind packages.connect |
PackageConnection, RawPackageConnection | { remote: Remote<T>, closed, port }, and the same without remote |
MountOptions, MountedPackage | AppConnectOptions plus your iframe, and the connection plus frame and unmount() |
ShowOptions, PackageView | { element?, rect? }, and the { hide(), refresh() } that show resolves to |
IncomingConnectionInfo, IncomingConnection | who connected, and that plus the connection |
ConnectPayload | the first argument of onConnect |
SurfaceRect, Radii, Placement | the geometry behind show |
PackagesError, PackagesErrorCode | Error & { code }, the six codes on packages |
The type argument on connect, mount and attach is the payload the other side exposed, mapped through osra’s Remote<T>. Every function on it becomes async, and plain data stays itself:
import { const onConnect: <T = unknown>(createPayload: ConnectPayload, handler?: (connection: IncomingConnection<T>) => void) => { unsubscribe: () => void;}
Serve connections from apps that installed this package. The first argument is called once per
incoming connection with the connection info and returns the payload exposed to that app (its
remote). The latest registration receives new connections; existing connections are unaffected.
onConnect } from '@fkn/lib/packages'import type { type IncomingConnection<T = unknown> = IncomingConnectionInfo & PackageConnection<T>
IncomingConnection } from '@fkn/lib/packages'
type type HostApi = { appVersion: string;}
HostApi = { appVersion: string
appVersion: string }const const payload: { search: (text: string) => Promise<string[]>;}
payload = { search: (text: string) => Promise<string[]>
search: async (text: string
text: string) => [`result for ${text: string
text}`] }export type type SourceApi = { search: (text: string) => Promise<string[]>;}
SourceApi = typeof const payload: { search: (text: string) => Promise<string[]>;}
payload
onConnect<HostApi>(createPayload: ConnectPayload, handler?: ((connection: IncomingConnection<HostApi>) => void) | undefined): { unsubscribe: () => void;}
Serve connections from apps that installed this package. The first argument is called once per
incoming connection with the connection info and returns the payload exposed to that app (its
remote). The latest registration receives new connections; existing connections are unaffected.
onConnect<type HostApi = { appVersion: string;}
HostApi>( () => const payload: { search: (text: string) => Promise<string[]>;}
payload, (connection: IncomingConnection<HostApi>
connection: type IncomingConnection<T = unknown> = IncomingConnectionInfo & PackageConnection<T>
IncomingConnection<type HostApi = { appVersion: string;}
HostApi>) => { connection: IncomingConnection<HostApi>
connection.protocol: string | null
the contract tag the app passed to connect(), e.g. 'stub-source@1'
protocol // 'example-source@1', the tag the app passed connection: IncomingConnection<HostApi>
connection.remote: { appVersion: string;}
the package's exposed payload
remote.appVersion: string
appVersion // '1.2.0', plain data on the app's payload, so no await },)import type { type PackageConnection<T = unknown> = { remote: Remote<T>; closed: Promise<void>; port: MessagePort;}
PackageConnection, type PackagesError = Error & { code: packages.PackagesErrorCode;}
PackagesError } from '@fkn/lib/packages'import type { type SourceApi = { search: (text: string) => Promise<string[]>;}
SourceApi } from './package'
try { const const connection: packages.PackageConnection<{ search: (text: string) => Promise<string[]>;}>
connection: type PackageConnection<T = unknown> = { remote: Remote<T>; closed: Promise<void>; port: MessagePort;}
PackageConnection<type SourceApi = { search: (text: string) => Promise<string[]>;}
SourceApi> = await (alias) namespace packagesimport packages
packages.packages_d_exports.connect<{ search: (text: string) => Promise<string[]>;}>(uri: string, options?: packages.AppConnectOptions & { raw?: false;}): Promise<packages.PackageConnection<{ search: (text: string) => Promise<string[]>;}>> (+1 overload)export packages_d_exports.connect
Connect to an installed package. Throws a PackagesError with code 'not-installed' when it is not.
connect<type SourceApi = { search: (text: string) => Promise<string[]>;}
SourceApi>('npm:@example/subtitles-plugin', { protocol?: string | undefined
opaque contract tag delivered to the package's onConnect, e.g. 'stub-source@1'
protocol: 'example-source@1', payload?: unknown
exposed to the package as ITS remote
payload: { appVersion: string
appVersion: '1.2.0' }, // what the package sees as its remote }) const const found: string[]
found = await const connection: packages.PackageConnection<{ search: (text: string) => Promise<string[]>;}>
connection.remote: { search: (text: string) => Promise<string[]>;}
the package's exposed payload
remote.search: (text: string) => Promise<string[]>
search('naruto') // ['result for naruto'], answered by the package} catch (var error: unknown
error) { const { const code: packages.PackagesErrorCode
code } = var error: unknown
error as type PackagesError = Error & { code: packages.PackagesErrorCode;}
PackagesError // 'timeout' when the package never registered onConnect}Each side names the other’s payload as its type argument. The app imports what the package exposes, and the package names what the app passed as payload. The rest is on packages.
A room is a realtime channel several browsers join from an invite. @fkn/lib/rooms exports every shape the surface uses:
| Type | What it covers |
|---|---|
Room | what create and join resolve to: id, key, invite, self, owner, and every method |
RoomMember, RoomPermissions, RoomPermission | one member as this room sees them, the four booleans, and their names |
RoomDefaults | the room-wide send and receive defaults create takes and setDefault changes |
RoomMessage, RoomEvent | one delivered message, and the five events on delivers |
RoomEnd | what closed settles with |
CreateOptions, JoinOptions | { signal?, members?, defaults? } and { signal? } |
RoomsError, RoomsErrorCode | the rejection every member of the entry throws, and its ten codes |
const const render: (event: RoomEvent) => string
render = (event: rooms.RoomEvent
event: type RoomEvent = { type: "message"; message: rooms.RoomMessage;} | { type: "joined"; member: rooms.RoomMember;} | { type: "left"; id: string; reason: "left" | "removed" | "blocked";} | { type: "permissions"; id: string; permissions: rooms.RoomPermissions;} | { type: "defaults"; defaults: rooms.RoomDefaults;}
RoomEvent) => event: rooms.RoomEvent
event.type: "message" | "joined" | "left" | "permissions" | "defaults"
type === 'message' ? event: { type: "message"; message: rooms.RoomMessage;}
event.message: rooms.RoomMessage
message.text: string
text : event: { type: "joined"; member: rooms.RoomMember;} | { type: "left"; id: string; reason: "left" | "removed" | "blocked";} | { type: "permissions"; id: string; permissions: rooms.RoomPermissions;} | { type: "defaults"; defaults: rooms.RoomDefaults;}
event.type: "joined" | "left" | "permissions" | "defaults"
type
try { const const room: rooms.Room
room: type Room = { readonly id: string; readonly key: string; readonly invite: string; readonly self: rooms.RoomMember; readonly owner: string; defaults: () => rooms.RoomDefaults; members: () => Promise<rooms.RoomMember[]>; send: (text: string) => Promise<void>; setDefault: (permission: "send" | "receive", value: boolean) => Promise<void>; grant: (id: string, permission: rooms.RoomPermission) => Promise<void>; revoke: (id: string, permission: rooms.RoomPermission) => Promise<void>; ... 5 more ...; readonly closed: Promise<rooms.RoomEnd>;}
A joined room. The same object survives a broker replacement, so it is safe to hold for as long
as the chat lasts.
Room = await import rooms
rooms.function join(invite: string, options?: rooms.JoinOptions): Promise<Room>export join
invite is room.invite, or an id and a key joined by a dot.
join(var location: Location
The Window.location read-only property returns a Location object with information about the current location of the document.
location.Location.hash: string
The hash property of the Location interface is a string containing a '#' followed by the fragment identifier of the location URL.
hash.String.slice(start?: number, end?: number): string
Returns a section of a string.
slice(1)) await const room: rooms.Room
room.on: (listener: (event: RoomEvent) => void) => Promise<() => void>
Await the returned unsubscribe in cleanup, the account.onChange shape.
on(const render: (event: RoomEvent) => string
render)} catch (var error: unknown
error) { const { const code: rooms.RoomsErrorCode
code } = var error: unknown
error as type RoomsError = Error & { code: rooms.RoomsErrorCode;}
Thrown by every member of this namespace. Match on code, never on the message.
RoomsError // 'not-found' once the room has ended}RoomsError is a type, never a class, so you test error.code on a rejection rather than instanceof, exactly like PackagesError. The rest is on rooms.
The broker’s vocabulary
Section titled “The broker’s vocabulary”Resolvers is the whole surface the broker exposes over osra: the namespaces cloud, overlay, installPrompt, frameConsent, connect, relay, packages, rooms, account and shell, plus flat legacy members kept so an older published @fkn/lib keeps working. It lives on @fkn/lib/contract, a type-only entry whose .js is empty, and @fkn/lib/api re-exports it beside apiPromise. The alias Api = Remote<Resolvers> is declared inside @fkn/lib/api and not exported, so spell it through the promise:
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'
type type 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 = type Awaited<T> = T extends null | undefined ? T : T extends object & { then(onfulfilled: infer F, ...args: infer _): any;} ? F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never : T
Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to never. This emulates the behavior of await.
Awaited<typeof 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> // Remote<Resolvers>, osra's mapping, with every resolver an async functionconst 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: type 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 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 // every resolver on the newest broker, from a promise that never rejectsconst { 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, on the broker's Quota rather than QuotaStatusThe call comes back as the broker’s own shape: Quota has remaining where QuotaStatus has remainingBytes, and @fkn/lib owns the mapping between the two. The wrappers are what an app calls. @fkn/lib/api is for code that observes the connection itself (see entry points).
The rest of the entry is the vocabulary those resolvers exchange, one line each:
| Type | What it holds |
|---|---|
ConnectAvailability | 'connected' | 'disconnected' | 'unknown', the storage availability probe’s three answers |
Account | { name, image, premium, premiumUntil: string | null }, behind AccountInfo, with the ISO string |
Quota | { overQuota, remaining, usedBytes, limitBytes, premium, bytesPerSecond }, behind QuotaStatus |
StorageEntry | the same shape @fkn/lib/cloud/fs exports as FileEntry, one listed object |
StorageQuota | the same shape @fkn/lib/cloud/fs exports |
EncryptionState | { unlocked, enrolled, keyEpoch: number | null }, behind EncryptionStatus |
WriteData | ArrayBuffer | Uint8Array | string, what a write carries to the broker |
AdoptState, AdoptRequest | { files: number, bytes: number }, what the adopt card is told |
ConflictSide, ConflictRequest | { size, updatedAt: string | null } and { path, local, cloud }, what the conflict card is told |
ConflictChoice | 'local' | 'cloud' | null, the card’s answer |
AddressLookupResult, DnsLookup | { address, family: 0 | 4 | 6 } and the generic lookup signature |
FrameFetchScope, FrameConsentRequest | 'frame.fetchRead' | 'frame.fetchWrite' and { scope, hosts: string[] }, what the cloud frame consent card is told |
OverlayState, OverlayRect, HiddenSurface, DisplayCause, DisplayLevel, SetOverlayHost | what the broker pushes so the overlay frame clips itself to its cards, see how it works |
TcpSocketOptions, TcpSocketResult, TcpSocketListenerOptions, TcpSocketListenerResult, UdpSocketOptions, UdpDatagram, UdpSocketResult, IpFamily | what net and dgram exchange with the broker for one socket |
ProxyFetch, ProxyFetchInput, ProxyFetchInit | the signature behind cloud.fetch, taking string | URL | Request and an optional RequestInit widened with render?: boolean |
MountDescriptor, PackagesFail | the broker’s answer to packages.frame, which is what packages.mount calls, and the refusal @fkn/lib re-mints as a PackagesError |
It also re-exports the package shapes and the two socket option unions from @fkn/lib/wire, which their own sections cover.
Read the @fkn/lib/fs name whenever your code calls fs, and the contract name only when it reads the broker directly.
Errors
Section titled “Errors”An error that crosses the broker keeps only its name, message, stack and cause. instanceof therefore works for the classes @fkn/lib constructs in your realm, the JavaScript execution context your code runs in, and for nothing from the other side. See what crosses a realm.
An error is recognised by one of five things: a class, a predicate, a code, a name or a message prefix. Each family answers to just one:
| Error | Recognise it by |
|---|---|
BrokerUnreachableError, from @fkn/lib/api | instanceof, minted in your realm by apiWithin and on the error event of net.connect, Server.listen and dgram.bind |
StorageLockedError, StorageNotFoundError, from @fkn/lib/cloud/fs | instanceof or isNotFound(), with code FKN_E2E_LOCKED or FKN_STORAGE_NOT_FOUND, re-minted on reads and writes only, see unlink() skips the re-mint |
ExtensionOutdatedError, ExtensionOperationUnsupportedError, from the root | instanceof, on The FKN WebExtension is installed but too old for this page and The FKN WebExtension does not support "<operation>", the first reachable only if the required ABI ever rises and the second constructed by nothing |
ENOENT and its family, and FKN_E2E_LOCKED, on fs and opfs | code, on <CODE>: <text>, <syscall> '<path>' and on storage: <path> exists but could not be read, retry once its scope is available, see storage |
PackagesError | code, one of PackagesErrorCode, on every packages.* call and the local attach |
PermissionDeniedError | error.name, on every gated extension call, the message starting Permission denied: <key>, matched by neither locator guard, see a refusal neither guard matches |
LocatorDeniedError, LocatorUnsupportedError, LocatorInvalidError | error.name, or from the root isLocatorDenied, isLocatorUnsupported and isTerminalError, which matches all three, the invalid name having no constant or guard, see the name with no constant |
LocatorError | error.name or LOCATOR_ERROR from the root, with no guard, the one locator name an action retries rather than stops on |
fkn:e2e-locked, fkn:e2e-stale-epoch…, fkn:e2e-integrity… | message.startsWith(...) with the three E2E_* constants from @fkn/lib/messages, the first re-minted as StorageLockedError before you see it |
storage: api unreachable | code === STORAGE_UNREACHABLE in the realm that set it, else the message prefix |
FKN: the broker was replaced while this call was pending; retry it | the exact message, on a call left pending across a replacement, see connection and lifecycle |
getaddrinfo ENOTFOUND <hostname>, tcp connect to <address>:<port> timed out after 12000ms | the message on a socket’s error event, the relay’s own refusal text included, the Node code not surviving the hop |
A class test is true only for an error constructed on your side. A name or prefix test is what remains for one that crossed:
import { const API_DEADLINE_MS: 8000
apiPromise bounded by a deadline, for call sites that must not park forever.
apiPromise NEVER REJECTS: epochs.first settles only when a broker connection exists, so a
broker frame that never bridges leaves it pending for the life of the realm. Awaiting it directly
is correct wherever hanging is the honest answer, and wrong wherever the caller owns a socket, a
timer or a UI that has to say something.
That distinction is not academic. In a WORKER realm the osra transport is {receive: self, emit: self}, which is inert until the page bridges it, so an unbridged worker parks every socket
call here with no listening, no error and no rejection. The engine then reports a listener
that neither succeeded nor failed, its reopen counters stay at 0 because reopen only runs from an
error or close that never arrives, and the relay is never contacted at all. That state cost a
long diagnosis: it presents as a transport fault and is invisible from every counter.
The latch mirrors storage.ts: once the broker has missed one deadline, later calls stop paying
the full wait. net.ts needs it especially, because its listen path is bind('::').catch(() => bind('0.0.0.0')), so an unbounded-then-rejecting version would charge the deadline twice.
API_DEADLINE_MS, class BrokerUnreachableError
BrokerUnreachableError } from '@fkn/lib/api'
const const minted: BrokerUnreachableError
minted = new new BrokerUnreachableError(what: string, ms: number): BrokerUnreachableError
BrokerUnreachableError('the quota readout', const API_DEADLINE_MS: 8000
apiPromise bounded by a deadline, for call sites that must not park forever.
apiPromise NEVER REJECTS: epochs.first settles only when a broker connection exists, so a
broker frame that never bridges leaves it pending for the life of the realm. Awaiting it directly
is correct wherever hanging is the honest answer, and wrong wherever the caller owns a socket, a
timer or a UI that has to say something.
That distinction is not academic. In a WORKER realm the osra transport is {receive: self, emit: self}, which is inert until the page bridges it, so an unbridged worker parks every socket
call here with no listening, no error and no rejection. The engine then reports a listener
that neither succeeded nor failed, its reopen counters stay at 0 because reopen only runs from an
error or close that never arrives, and the relay is never contacted at all. That state cost a
long diagnosis: it presents as a transport fault and is invisible from every counter.
The latch mirrors storage.ts: once the broker has missed one deadline, later calls stop paying
the full wait. net.ts needs it especially, because its listen path is bind('::').catch(() => bind('0.0.0.0')), so an unbounded-then-rejecting version would charge the deadline twice.
API_DEADLINE_MS)const minted: BrokerUnreachableError
minted instanceof class BrokerUnreachableError
BrokerUnreachableError // true, constructed in this realm
const const crossed: Error & { name: string;}
crossed = var Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
Object.ObjectConstructor.assign<Error, { name: string;}>(target: Error, source: { name: string;}): Error & { name: string;} (+3 overloads)
Copy the values of all of the enumerable own properties from one or more source objects to a
target object. Returns the target object.
assign(new var Error: ErrorConstructornew (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error('Permission denied: act.click (#play)'), { name: string
name: 'PermissionDeniedError' }) // the shape a hop hands youconst crossed: Error & { name: string;}
crossed instanceof var Error: ErrorConstructor
Error // true, and nothing narrower is left to testconst crossed: Error & { name: string;}
crossed.name: string
name === 'PermissionDeniedError' // true, the name survivesconst crossed: Error & { name: string;}
crossed.Error.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('Permission denied: ') // true, and so does the prefixThe class test holds only because the error was built on this side. The order to test a mixed handler in is on handling errors, and every message with its cause is on every error.
Requirements
Section titled “Requirements”Every entry point ships one bundled .d.ts, so a type import needs nothing installed beyond @fkn/lib and its own dependencies. @types/node is one of those, a dependency rather than a peer, since the socket and file system surfaces are typed against Node’s own declarations. @fkn/lib/react imports its types from react, an optional peer at >=18, so that one entry needs React’s type declarations installed beside it.
Every twoslash example on this site is checked with TypeScript 5.9 under strict, with an ESNext target and module, bundler module resolution and the DOM library. Top-level await is used freely.
From here, entry points says which import path carries each name. The generated API reference carries the exact declaration of most named exports, Locator and VideoElementState among the ones it misses. The vocabulary above is under @fkn/lib/contract.