An account is the FKN identity a person carries from site to site, with the same subscription, storage allowance and daily volume on each. This page covers how your app asks for it with connect(), what it may read and follow once it has it, and how it reads the metered volume with cloud.quota().
The account, relay and quota calls on this page go through the broker, the connection your page holds into FKN, reached through the broker frame, a hidden fkn.app iframe the library mounts. None of them uses the FKN browser extension or has a deadline: a call made before the broker is reachable waits for it, and account.onChange returns at once instead (see connection and lifecycle). The two button helpers, connectButtonUrl() and ConnectButton, build a URL and an element locally and never wait.
The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
the account's display name (a username, not necessarily a real name)
name??''// the username, on screen
})
When the site already holds a connection, which a site under fkn.app does from load, connect() resolves true at once and shows nothing. Otherwise the broker draws a card over your page, titled Connect your account, with one Connect button.
On another site that button opens a fkn.app window where the person confirms. Under fkn.app it connects in place first, and opens the window only when that does not answer in time.
It resolves true once the account is accepted, and false for everything else: a dismissed card, a call from a worker, or any failure on the way, since the library turns a broker rejection into false. So false is not proof that the person declined.
The click that opens the window is the one inside the card, so connect() may run from a click handler or on page load. Concurrent callers share the one card, and account.login() is the same call.
A package, an npm module FKN loads on a sandbox origin of its own, cannot connect for itself. Inside one, connect() connects the host app that installed it, and answers false with no card when that host is unknown or is itself a package (see packages).
The token never reaches your app: it stays in the broker’s storage on fkn.app.
AccountInfo carries four fields, and @fkn/lib/account exports the type (see TypeScript):
Field
What it holds
name
The account’s display name, a username.
image
The profile image URL, or null when none is set.
premium
Whether a paid subscription is active right now.
premiumUntil
When the subscription lapses, a millisecond epoch, or null when not subscribed.
It carries no id and no email, by design. The connect card promises that this site sees only the username and premium status, and the service, the FKN server that keeps account files and meters cloud egress, issues nothing more.
The broker caches the answer for 5 seconds and drops it on every connection change, so calling info() often is cheap. It resolves null on any failure and in a worker, never rejects, and inside a package answers for the host app.
the account's display name (a username, not necessarily a real name)
name:null)
}) // the unsubscribe, returned at once, without waiting for the broker
// later: stop() removes this callback only, the broker keeps its registration
The callback receives no argument, so call info() again for the new state. A connect through connect() or the connect button fires it too, and a callback that throws is swallowed without stopping the others.
onChange returns its unsubscribe through a promise. Hold that promise and await it in your cleanup, or the cleanup can run before the unsubscribe exists.
The library holds one registration in the broker per broker connection, renews it when the broker is replaced, and fans it out to every callback locally, so your unsubscribe only removes your callback from the local set (see a replaced broker).
info() has no deadline, so any cap is yours, such as a Promise.race against a timer. A timer that wins means the call was not observed, never that the account is gone, so keep the state you had.
It is not a global sign-out: the person stays signed in on fkn.app and every other site. The broker remembers the choice, so a site under fkn.app, which otherwise connects on load, stays disconnected next time. A package that calls logout() disconnects its host app (see packages).
logout() resolves void, has no deadline and reports no failure, so info() going null is the signal to read. It also clears what the connection unlocked: cloud.fs calls answer storage: not connected afterwards, and the encryption keys the broker remembered for this app are forgotten (see encryption).
The availability probe behind fs, the file system with one copy on this device and one in the account, reports a sign-out as 'disconnected'. Of the three ConnectAvailability answers, 'connected' | 'disconnected' | 'unknown', 'disconnected' is the only one that drops work owed to the account (see the write rule).
@fkn/lib/react ships a ConnectButton for React apps. It renders an iframe of fkn.app’s connect button, which opens a fkn.app window the way connect() does:
width: 160 } })) // a 160 by 40 iframe, over the 150 by 40 default
React is an optional peer dependency that only this entry point imports (see install). Your style merges over the defaults: no border, 150 by 40 pixels, a transparent background and color-scheme: normal, so reserve that box in your layout. With createElement the props type has to be named, since the optional parameter gives TypeScript nothing to infer it from.
The button obtains the connection token only, never the encryption key, so the account connects locked and the first cloud.fs read or write raises the unlock card (see encryption).
Its labels are Connect FKN, Connecting… and, after a blocked popup, Allow popups, retry. It never reflects the account state, so swap it for your own signed-in widget from an onChange callback.
Without React, connectButtonUrl() gives you the same page to frame by hand:
In an HTML document, the document.createElement() method creates the HTML element specified by localName, or an HTMLUnknownElement if localName isn't recognized.
It takes no argument on purpose: however it is framed, the button reads which site it is on from the browser-set referrer, never from your page. The default referrer policy sends the origin, and a page whose policy strips it gets an inert button and a console warning. A package’s referrer leaves it inert too, since a package connects through its host.
promptRelay() opens the broker’s relay picker, the card where a person chooses which relay carries their sockets and proxied requests. The relay holds the real socket at the far end of net and dgram:
It resolves with the preference in effect when the card closes, by a row, Done or the close button: 'auto', the default, or a relay id. Auto picks the closest relay with the most free capacity, and a choice applies to this site and reconnects active transfers. In a worker, and on any failure, it resolves null.
Rows appear only when a relay beyond the built-in fallback was measured. With nothing but the fallback the card says so, and Done still resolves with the stored preference. A package cannot draw the card, so the picker is forwarded to its host app’s broker.
Traffic through FKN’s relay and proxy is metered: net, dgram and http sockets through the relay, and cloud.fetch through the proxy, the server it sends every request through. The free volume is 5,000,000,000 bytes per UTC day, per connected account or per anonymized IP address when nobody is connected. Past it transfers are throttled until the day resets, and a subscription lifts the throttle. cloud.quota() reads the state:
The rate the service reports, in bytes per second.
bitsPerSecond
The same rate in bits, for display.
usedBytessaturates at limitBytes, so a meter never shows more than full. limitBytes is the same free figure on a premium account, so a premium account past the free volume reads overQuota: true, usedBytes === limitBytes and remainingBytes: 0 while throttled stays false. Key a meter on throttled, and hide a remaining label when premium.
bytesPerSecond is the rate the service reports for this account or anonymized address, which the relay and the proxy are meant to apply, so never hard-code a platform constant. As deployed that is 10,485,760 bytes per second under the free volume, 1,048,576 once throttled and 1,073,741,824 with a subscription.
Extension traffic is never metered, and the desktop app handles no traffic. quota lives under cloud, with no root alias.
The answer follows the caller’s own connection: a connected app reads the account’s counter, and an unconnected caller, app or package, reads its anonymized IP address’s counter. An account has one counter, shared by every app it connects. A package meters under its own scope, never the host’s (see packages).
Unlike the account calls above, quota() rejects. A failed query rejects, and so does a call still pending when the shell, the FKN surface that can update and reload the page, replaces the broker frame during an update. The second case reads FKN: the broker was replaced while this call was pending; retry it (see a replaced broker), which the poll below catches.
The broker caches the answer for 5 seconds per caller and drops it on every connection change, so a poll costs at most one request every 5 seconds whatever its interval:
The HTMLProgressElement interface provides special properties and methods (beyond the regular HTMLElement interface it also has available to it by inheritance) for manipulating the layout and presentation of progress elements.
bytes egressed through FKN relays today (per connected account or anonymized IP), saturates at limitBytes:
consumption past the free-tier volume is private and never reported
Attaches a callback for only the rejection of the Promise.
@param ― onrejected The callback to execute when the Promise is rejected.
@returns ― A Promise for the completion of the callback.
catch(() => {}) }, 2_000) // at most one request every 5 seconds, the cache answers the rest
// later: clearInterval(timer) stops the poll, not the metering
Branch on throttled and premium before the remaining label, since a premium account past the free volume reads remainingBytes: 0 while not throttled.
quota() has no window check, so it works in a relayed worker (see workers). In a worker nobody relayed it waits forever, where the account calls above answer false, null or a no-op.