Getting started
@fkn/lib gives a browser app access to FKN’s cloud networking, storage, account, and page-automation APIs. Cloud networking and isolated page automation need no extension or account; cloud storage requires a connected account. Install the optional FKN browser extension when your app needs to use the user’s browser, cookies, or logged-in session.
Install
Section titled “Install”npm install @fkn/libUse ESM imports in your app. The package includes TypeScript declarations, and the examples on this site are type-checked against the published release.
Make a cloud request
Section titled “Make a cloud request”The explicit cloud.* namespace pins a call to FKN’s hosted backend:
const const response: Response
response = await (alias) namespace cloudimport cloud
cloud.cloud_d_exports.fetch(input: string | Request | URL, init?: (RequestInit & { render?: boolean;}) | undefined): Promise<Response>export cloud_d_exports.fetch
fetch('https://example.com/api/catalog')const const data: any
data = await const response: Response
response.Body.json(): Promise<any>
json()No account is required. Anonymous cloud use is metered per IP; connecting an account moves metering to that account. See Quota and throttling.
For a request that can use either the extension or cloud, import the root fetch() instead. The exact selection rules are documented under API backends.
Attach a frame with cloud fallback
Section titled “Attach a frame with cloud fallback”If the extension is exposed, attachFrame() uses it; otherwise it starts the cloud backend with no install prompt. Pass syncCookies: false when the frame should use an isolated per-attach cloud session instead of the persistent cloud jar.
import { const attachFrame: (options: AttachFrameOptions) => Promise<Frame>
attachFrame } from '@fkn/lib'
const const 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')!const const frame: Frame
frame = await function attachFrame(options: AttachFrameOptions): Promise<Frame>
attachFrame({ iframe: HTMLIFrameElement
iframe, syncCookies?: boolean | undefined
syncCookies: false })
await const frame: Frame
frame.function goto(url: string, options?: GotoOptions): Promise<void>
goto('https://example.com', { waitUntil?: "documentstart" | "load" | undefined
waitUntil: 'load' })const const title: string
title = await 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; }; }; };}, { ...;}>>
locator('h1').textContent: (_options?: LocatorOptions | undefined) => Promise<string>
textContent()A frame that must use the user’s existing browser session needs the extension backend: pin it with extension.attachFrame(), which waits for exposure and surfaces the install flow when the extension is missing. On the cloud backend the user’s session comes from logging in inside the proxied page itself, and the default cookie mode keeps that login in the persistent cloud jar across attachments.
Detect the extension
Section titled “Detect the extension”The extension announces itself asynchronously to each page. Check for it before showing extension-specific UI:
import { const isExtensionExposed: () => boolean
isExtensionExposed, const waitForExtensionExposure: (timeout?: number) => Promise<void>
waitForExtensionExposure } from '@fkn/lib'
var console: Console
console.Console.log(...data: any[]): void
The console.log() static method outputs a message to the console.
log('FKN extension is available')}
waitForExtensionExposure() rejects when the extension is not installed. By default, an extension-required call also opens FKN’s install prompt. Apps with their own absence UI or cloud fallback can suppress that prompt:
import { const promptInstall: (reason?: string) => Promise<boolean>
promptInstall, const setMissingExtensionHandler: (handler: MissingExtensionHandler | null) => void
setMissingExtensionHandler } from '@fkn/lib'
function setMissingExtensionHandler(handler: MissingExtensionHandler | null): void
setMissingExtensionHandler(null)
// Call this later from your own install button.Use an extension-only capability
Section titled “Use an extension-only capability”Extension APIs are explicit under extension.*. Sensitive operations prompt for per-app consent; severity-0 operations are granted automatically and recorded once per capability and scope during a visit.
const const response: Response
response = await (alias) namespace extensionimport extension
extension.extension_d_exports.fetch(input: RequestInfo | URL, init?: extension.FetchInit): Promise<Response>export extension_d_exports.fetch
fetch('https://example.com/api/me', { RequestInit.credentials?: RequestCredentials | undefined
A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials.
credentials: 'include', reason?: string | undefined
reason: 'Load your profile from your existing session',})See it run
Section titled “See it run”The guided demo below embeds Wikipedia, restyles it, reads the heading, fills a search, and clicks Search. It uses an isolated cloud session with no install. If the FKN extension is already exposed, it uses the extension instead and asks once for the three sensitive actions.
Continue with API backends for routing behavior or attachFrame() for the complete frame API.