Skip to content

@fkn/lib/extension

Thrown when the extension is current but does not serve the operation being asked for.

  • Error
new ExtensionOperationUnsupportedError(operation, abi): ExtensionOperationUnsupportedError;

string

number

ExtensionOperationUnsupportedError

Error.constructor
readonly abi: number;
readonly name: "ExtensionOperationUnsupportedError" = "ExtensionOperationUnsupportedError";
Error.name
readonly operation: string;

Thrown instead of the generic “not installed” message when the extension IS there and merely too old. Carries both numbers so an app can say which, and link its listing.

A named class rather than a message match: only the name survives a structured-clone hop, and the codebase has been bitten before by an error whose identity was its text.

  • Error
new ExtensionOutdatedError(abi, required): ExtensionOutdatedError;

number

number

ExtensionOutdatedError

Error.constructor
readonly abi: number;
readonly name: "ExtensionOutdatedError" = "ExtensionOutdatedError";
Error.name
readonly required: number;
type AttachFrameOptions = object;
optional domains?: string[];
iframe: HTMLIFrameElement;
optional lockdown?: boolean;
optional syncCookies?: boolean;

type CookieDetails = object;
name: string;
url: string;

type Executor = object;
execute: (parts, operation, args) => Promise<unknown>;

SelectorPart[]

string

unknown[]

Promise<unknown>

highlight: (parts, on) => Promise<void>;

SelectorPart[]

boolean

Promise<void>

optional stale?: Promise<void>;

type ExtensionHandshake =
| {
status: "absent";
}
| {
abi: number;
operations: readonly string[] | null;
status: "ok";
}
| {
abi: number;
operations: readonly string[] | null;
required: number;
status: "outdated";
};

Three answers, never a boolean.

operations is null when the extension announced no list, which is NOT the same as announcing an empty one. A pre-versioning extension supports the whole original surface and simply cannot say so, so an absent list has to read as “unknown, assume the old set” rather than “supports nothing”. Reading it the other way would refuse every operation on every currently installed extension.


type FetchInit = RequestInit & object;
optional reason?: string;

type Frame = FrameLocator<LocatorModules> & object;
goto(url, options?): Promise<void>;

string

GotoOptions

Promise<void>

url(): string;

string


type Gate = (request, tools) => void | Promise<void>;

OperationRequest

GateTools

void | Promise<void>


type GateTools = object;
highlight: (on) => Promise<void>;

boolean

Promise<void>


type GotoOptions = object;
optional domains?: string[];
optional waitUntil?: "documentstart" | "load";

type HeaderOperation = object;
header: string;
operation: "set" | "remove";
optional value?: string;

type OperationRequest = object;
args: unknown[];
operation: string;
parts: SelectorPart[];
phase: "execute" | "ensure";

type PermissionGrant = object;
allow: boolean;
key: PermissionScope;
scope: string;

type PermissionRequest = object;
key: PermissionScope;
optional reason?: string;
optional scope?: string;

type RemoteVideoElement = EventTarget & object;
autoplay: boolean;
readonly buffered: TimeRanges;
readonly currentSrc: string;
currentTime: number;
disableRemotePlayback: boolean;
readonly duration: number;
readonly ended: boolean;
readonly error: MediaError | null;
readonly HAVE_ENOUGH_DATA: 4;
readonly HAVE_FUTURE_DATA: 3;
loop: boolean;
muted: boolean;
readonly paused: boolean;
playbackRate: number;
poster: string;
preload: string;
readonly readyState: number;
readonly seekable: TimeRanges;
readonly seeking: boolean;
src: string;
volume: number;
exitPictureInPicture(): Promise<void>;

Promise<void>

load(): void;

void

pause(): void;

void

play(): Promise<void>;

Promise<void>

requestPictureInPicture(): Promise<void>;

Promise<void>


type RequestHeaderRule = object;
domains: string[];
optional reason?: string;
requestHeaders: HeaderOperation[];

type SelectorPart = object;
args: unknown[];
kind: ChainKind;
name: string;

type SiteCookie = object;
name: string;
value: string;
const assertAttachableFrameUrl: (raw) => void;

string

void


const assertFetchableUrl: (raw) => void;

string

void


const attachFrame: ({ iframe, domains, syncCookies, lockdown }) => Promise<Frame>;
{ iframe, domains, syncCookies, lockdown }
Section titled “{ iframe, domains, syncCookies, lockdown }”

AttachFrameOptions

Promise<Frame>


const cookies: object;
get: (details) => Promise<SiteCookie | null>;

CookieDetails

Promise<SiteCookie | null>


const events: TypedEventTarget;

const EXTENSION_ABI: 1 = 1;

What the extension half announces about itself. Increment when the callable surface CHANGES in a way a page could notice: an operation added, removed, renamed, or given different semantics.

This is not the package version and not the manifest version. Those move for reasons that have nothing to do with the protocol (a dependency bump, a store resubmission), and tying the contract to them would make every release look like a protocol change.


const fetch: (input, init?) => Promise<Response>;

RequestInfo | URL

FetchInit

Promise<Response>


const FORGEABLE_HEADERS: string[];

const isExtensionExposed: () => boolean;

Unchanged on purpose, and still exported: a page half built before versioning calls exactly this, and an extension that announces an ABI must keep answering it the same way. Backwards compatibility here runs in BOTH directions, which is the only property that matters when neither end updates on demand.

boolean


const isLocalNetworkUrl: (raw) => boolean;

string

boolean


const isLocatorDenied: (error) => boolean;

unknown

boolean


const isLocatorUnsupported: (error) => boolean;

unknown

boolean


const isTerminalError: (error) => boolean;

unknown

boolean


const LOCATOR_DENIED: "LocatorDeniedError" = "LocatorDeniedError";

const LOCATOR_ERROR: "LocatorError" = "LocatorError";

const LOCATOR_UNSUPPORTED: "LocatorUnsupportedError" = "LocatorUnsupportedError";

const permissions: object;
request: (requests) => Promise<PermissionGrant[]>;

PermissionRequest[]

Promise<PermissionGrant[]>


const readExtensionHandshake: () => ExtensionHandshake;

The three-way answer: absent, present but too old, or usable.

ExtensionHandshake


const removeRequestHeaderRule: (ruleId) => Promise<void>;

number

Promise<void>


const REQUIRED_EXTENSION_ABI: 0 = 0;

The oldest extension the page half will talk to. Raise it ONLY when the page half starts depending on something older extensions cannot do, never merely because EXTENSION_ABI moved: an added operation does not break a page that does not call it.

Deliberately 0 for this release. Every extension currently installed predates versioning and announces no ABI at all, so a floor of 1 would refuse all of them the moment this page half deployed, which is precisely the failure this file exists to prevent.


const setMissingExtensionHandler: (handler) => void;

MissingExtensionHandler | null

void


const setRequestHeaderRule: (rule) => Promise<{
ruleId: number;
}>;

RequestHeaderRule

Promise<{ ruleId: number; }>


const supportsOperation: (handshake, operation) => boolean;

Whether a named operation is callable.

null operations means the extension could not tell us, and the answer is YES: it predates the ops list and supports the original surface. The ABI floor is the mechanism for refusing an extension that is genuinely too old; this one only refines a list that was actually announced.

ExtensionHandshake

string

boolean


const waitForExtensionExposure: (timeout?) => Promise<void>;

number

Promise<void>

function available(): boolean;

boolean


function promptInstall(reason?): Promise<boolean>;

string

Promise<boolean>

Renames and re-exports events