Skip to content

Encryption

Every file is encrypted in the browser before it is uploaded to the account, and your app never handles a key. This page covers the one state an app does see, locked, and the errors that come with a key that changed.

Encryption is always on: there is no unencrypted account and no setting that turns it off. readFile and writeFile work unchanged, because the encryption happens in the broker frame, the hidden fkn.app iframe the library mounts, see how it works. The broker is the connection your app holds into FKN through that frame. The account is the FKN identity a person carries between sites, and the service is the FKN server that keeps its files.

Locked means the broker holds no usable key for this app and account. A key reaches the broker only from a first-party fkn.app window, the connect or unlock popup. The broker keeps it in its own storage partition until that record expires.

A refused read or write does not fail at once. It raises the broker’s unlock card inside the call and waits. Concurrent locked calls share the one card. StorageLockedError arrives only after the card was dismissed, could not be shown, or delivered no usable key:

app.ts
import {
const promises: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
export promises
promises
as
const cloud: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
cloud
,
class StorageLockedError
StorageLockedError
,
const unlock: () => Promise<boolean>
unlock
} from '@fkn/lib/cloud/fs'
const
const readCatalog: (attempt?: number) => Promise<string>
readCatalog
= async (
attempt: number
attempt
= 0):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<string> => {
try {
return
var String: StringConstructor
(value?: any) => string

Allows manipulation and formatting of text strings and determination and location of substrings within strings.

String
(await
const cloud: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
cloud
.
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>
readFile
('library/catalog.json', 'utf8')) // '{"items":[]}', after the unlock card when locked
} catch (
function (local var) error: unknown
error
) {
if (!(
function (local var) error: unknown
error
instanceof
class StorageLockedError
StorageLockedError
)) throw
function (local var) error: unknown
error
// absence, a changed key and unreachable pass through
if (
attempt: number
attempt
> 0 || !(await
function unlock(): Promise<boolean>
unlock
())) throw
function (local var) error: StorageLockedError
error
return
const readCatalog: (attempt?: number) => Promise<string>
readCatalog
(
attempt: number
attempt
+ 1) // once, a second dismissal is not retried
}
}
const
const catalog: string
catalog
= await
const readCatalog: (attempt?: number) => Promise<string>
readCatalog
()

The read retries once and rethrows everything else. Locked is unavailable, not empty, so StorageLockedError and StorageNotFoundError are two classes, see two errors keep a class.

Every cloud.fs call on this page waits for the broker with no deadline, so in a worker nobody relayed none of them returns. The one bounded wait is the availability probe behind the hybrid fs, see connection and lifecycle.

unlock() asks for the key without a read or a write. It answers false with no account connected and true when a usable key is already held. Otherwise it raises the card and answers what the card answered:

app.ts
await
function unlock(): Promise<boolean>
unlock
() // true once unlocked, false after a dismissal or with nothing connected

true means the broker now holds a key, not that the next write will accept it, see key epochs.

The card, Storage locked. Click to unlock., never asks for a secret. Its Unlock button opens a first-party fkn.app window for the password or passkey, which this site never sees.

Two connections never unlock through unlock(): one the service has stopped accepting, where the card comes back after every popup, and one made before FKN began delivering keys, since a key crosses only from a first-party window. Disconnect and connect once to fix either, see disconnecting.

The broker’s availability probe tells a refused connection from a locked account. It is availability() on the cloud.fs behind apiPromise from @fkn/lib/api, and it answers 'disconnected' once the service has refused the connection and 'connected' while the account is merely locked, see the broker’s vocabulary. A broker older than that resolver carries only available(), so check that availability is a function before you call it.

encryption() reports the state without raising a card:

app.ts
const {
const unlocked: boolean
unlocked
,
const enrolled: boolean
enrolled
,
const keyEpoch: number | null
keyEpoch
} = await
function encryption(): Promise<EncryptionStatus>
encryption
() // never shows a card
if (
const enrolled: boolean
enrolled
&& !
const unlocked: boolean
unlocked
) await
function unlock(): Promise<boolean>
unlock
() // the account has a key this browser lacks, so ask now
const keyEpoch: number | null
keyEpoch
// the account's current key epoch, unix seconds at enrolment, or null

The three fields are the EncryptionStatus on TypeScript, { unlocked, enrolled, keyEpoch: number | null }, which the broker’s own vocabulary calls EncryptionState. They come from two places:

FieldWhat it holds
unlockedthis browser holds a usable key for this app and account, the broker’s own answer
enrolledthe service reports a key for the account, keyEpoch != null
keyEpochthe account’s current key epoch from the service, unix seconds at enrolment, or null

Two fields are the service’s answer about the account and one is this browser’s answer about itself, so they can disagree. When the service cannot be reached the call still resolves with enrolled: false and keyEpoch: null beside whatever unlocked says, so { unlocked: true, enrolled: false } is what an outage looks like.

Never read enrolled: false as “this account has no key”. An app that gates writes or shows an enrol prompt on it does so at every outage. The account does hold no key between a settled key reset and its next enrolment, see key epochs.

fs reaches the account through mount() and the read rule, and through pull() and readFileSealed(). On a locked account await fs.mount() is where the card appears. It reads every listed file in turn, and each account read raises the card and blocks on the user. Each dismissal costs one more card for the next path this device holds no copy of.

A path whose read failed stays present but unreadable. existsSync is true and readdirSync lists it from the root, while readFileSync, statSync, lstatSync, writeFileSync, appendFileSync and renameSync throw with code FKN_E2E_LOCKED. Nothing creates its parent directory, so readdirSync on that parent throws ENOENT unless a readable file shares it:

app.ts
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
.
mount: () => Promise<void>
mount
() // on a locked account, the unlock card, one more per unread file after a dismissal
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
.
readdirSync: (path: import("node:fs").PathLike) => string[]
readdirSync
('.') // ['library'], an unread path is still listed
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
.
readdirSync: (path: import("node:fs").PathLike) => string[]
readdirSync
('library') // throws ENOENT, an unread path gets no directory
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
.
existsSync: (path: import("node:fs").PathLike) => boolean
existsSync
('library/catalog.json') // true
try {
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')
} catch (
var error: unknown
error
) {
const {
const code: string | undefined
code
,
const message: string
message
} =
var error: unknown
error
as
namespace NodeJS
NodeJS
.
interface NodeJS.ErrnoException
ErrnoException
const code: string | undefined
code
// 'FKN_E2E_LOCKED'
const message: string
message
// 'storage: library/catalog.json exists but could not be read, retry once its scope is available'
}

The message is storage: <path> exists but could not be read, retry once its scope is available. The code means listed in the account and unreadable here. fs marks a path unreadable on any failed read, whether the account is locked, the file was encrypted under a reset key, its integrity check failed or the service answered an error, and it cannot tell the four apart.

A path still unreadable after an unlock is one of the other three, and only a read that reaches the account names the cause: cloud.fs, pull() and readFileSealed() reject with StorageLockedError while locked, and otherwise with the sentences under the messages.

fs refuses a write to an unreadable path rather than queueing one. Deleting it needs no key. A later callback or promise call re-runs that read after 5 seconds, so a locked file appears on its own after an unlock. adopt() and the drain, the background process that replicates queued writes, stop at the first locked failure and keep the work queued, see adopting files written before sign-in.

Only ENOENT means empty, so the readOr helper on error codes rethrows this code. Its locked branch calls unlock() and then remount(), and the fresh hydrate reads the path:

app.ts
const
const readOr: (path: string, fallback: string) => Promise<string>
readOr
= async (
path: string
path
: string,
fallback: string
fallback
: string):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<string> => {
try {
return
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
(
path: string
path
, 'utf8'))
} catch (
function (local var) error: unknown
error
) {
const {
const code: string | undefined
code
} =
function (local var) error: unknown
error
as
namespace NodeJS
NodeJS
.
interface NodeJS.ErrnoException
ErrnoException
if (
const code: string | undefined
code
=== 'ENOENT') return
fallback: string
fallback
if (
const code: string | undefined
code
=== 'FKN_E2E_LOCKED' && await
function unlock(): Promise<boolean>
unlock
()) {
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
.
remount: () => Promise<void>
remount
() // a fresh hydrate, which reads the path now that a key is held
return
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
(
path: string
path
, 'utf8')) // throws again when the cause was not the lock
}
throw
function (local var) error: unknown
error
// a dismissed card, or a path unreadable for one of the other three reasons
}
}
await
const readOr: (path: string, fallback: string) => Promise<string>
readOr
('library/settings.json', '{}') // '{}' until the app writes one, the file after an unlock

We recommend calling encryption() first, then unlock() on your own terms, and mounting after, so the card does not appear mid-load.

keyEpoch is the account’s key epoch, the unix time in seconds when the key was enrolled. A key generation is that epoch paired with a fingerprint of the key, since a reset and a new enrolment in the same second repeat the epoch. Every stored file records the generation it was encrypted under. So keyEpoch alone is for display and coarse comparison, never a key’s identity.

A write encrypts under the account’s current generation, read fresh from the service, and its commit names it. A commit naming a generation the account has since left is refused with Superseded key. When the held key is behind, the broker raises the unlock card before it can encrypt again:

app.ts
const
const saveCatalog: (catalog: string, attempt?: number) => Promise<void>
saveCatalog
= async (
catalog: string
catalog
: string,
attempt: number
attempt
= 0):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<void> => {
try {
await
const cloud: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
cloud
.
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>
writeFile
('library/catalog.json',
catalog: string
catalog
) // encrypted under the current generation and committed
} catch (
function (local var) error: unknown
error
) {
const
const message: string
message
=
function (local var) error: unknown
error
instanceof
var Error: ErrorConstructor
Error
?
function (local var) error: Error
error
.
Error.message: string
message
: ''
if (
const 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
('Superseded key') &&
attempt: number
attempt
=== 0) return
const saveCatalog: (catalog: string, attempt?: number) => Promise<void>
saveCatalog
(
catalog: string
catalog
,
attempt: 0
attempt
+ 1) // encrypts again, after the card when the held key is behind
throw
function (local var) error: unknown
error
}
}
await
const saveCatalog: (catalog: string, attempt?: number) => Promise<void>
saveCatalog
(
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.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
items: never[]
items
: [] }))

The block retries Superseded key once, the one write refusal here worth a retry. A read accepts any generation the broker still holds.

A generation changes when the user resets the account’s key and enrols again on the Security page of fkn.app. Between the two the account holds no key at all, so encryption() answers enrolled: false and FKN refuses every write rather than storing anything readable.

The old key is gone everywhere, so every file encrypted before the reset answers fkn:e2e-stale-epoch from then on. The only way back is an encrypted export taken before the reset. An app has nothing to retry: report it, and never write a guess over the file.

A hop out of the broker keeps an Error’s name, message, stack and cause only, see what crosses a realm, so the broker throws fixed sentences rather than codes. The library matches the locked one and re-mints StorageLockedError on your side. @fkn/lib/messages exports all five names below. The first three are the messages themselves, matched by prefix, and the last two are codes you read off error.code:

ConstantValueWhat it marks
E2E_LOCKED_MESSAGEfkn:e2e-lockedthe locked signal, re-minted as StorageLockedError before you see it
E2E_STALE_EPOCH_MESSAGEfkn:e2e-stale-epoch: this file is encrypted under a previous key you resetencrypted under a key a reset removed
E2E_INTEGRITY_MESSAGEfkn:e2e-integrity: stored data failed its integrity checkno seal marker, the field naming the envelope layout, an unknown one, a scope or connection that is not this caller’s own, or an envelope whose authentication tag does not verify
STORAGE_NOT_FOUNDFKN_STORAGE_NOT_FOUNDan error code: no object at that path, what isNotFound tests
STORAGE_UNREACHABLEFKN_API_UNREACHABLEa code set beside storage: api unreachable that is not guaranteed to arrive

Test the locked case with StorageLockedError and absence with isNotFound, on reads and writes only, since the re-mint does not run for unlink, see unlink() skips the re-mint. Match stale-epoch, integrity and unreachable by message prefix:

app.ts
const
const cached: () => Promise<Uint8Array | null>
cached
= async ():
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
| null> => {
const
const data: string | Buffer<ArrayBufferLike> | null
data
= await
const opfs: {
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;
... 20 more ...;
remount: () => Promise<void>;
}
opfs
.
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
.
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>
readFile
('backup/library.bin').
Promise<string | Buffer<ArrayBufferLike>>.catch<null>(onrejected?: ((reason: any) => PromiseLike<null> | null) | null | undefined): Promise<string | Buffer<ArrayBufferLike> | null>

Attaches a callback for only the rejection of the Promise.

@paramonrejected The callback to execute when the Promise is rejected.

@returnsA Promise for the completion of the callback.

catch
(() => null) // this device's last good copy, or nothing
if (
const data: string | Buffer<ArrayBufferLike> | null
data
=== null) return null
return
const data: string | Buffer<ArrayBufferLike>
data
instanceof
var Uint8Array: Uint8ArrayConstructor

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
?
const data: Buffer<ArrayBufferLike>
data
: new
var TextEncoder: new () => TextEncoder

The TextEncoder interface takes a stream of code points as input and emits a stream of UTF-8 bytes.

MDN Reference

TextEncoder
().
TextEncoder.encode(input?: string): Uint8Array<ArrayBuffer>

The TextEncoder.encode() method takes a string as input, and returns a Global_Objects/Uint8Array containing the text given in parameters encoded with the specific method for that TextEncoder object.

MDN Reference

encode
(
const data: string
data
)
}
const
const loadBackup: (attempt?: number) => Promise<Uint8Array | null>
loadBackup
= async (
attempt: number
attempt
= 0):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
| null> => {
try {
const
const data: string | Buffer<ArrayBufferLike>
data
= await
const cloud: {
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>;
writeFile: (path: import("node:fs").PathLike, data: WriteData, options?: WriteOptions) => Promise<void>;
unlink: (path: import("node:fs").PathLike) => Promise<void>;
rename: (from: import("node:fs").PathLike, to: import("node:fs").PathLike) => Promise<void>;
readdir: (path: import("node:fs").PathLike) => Promise<string[]>;
mkdir: (_path?: import("node:fs").PathLike, _options?: MakeOptions) => Promise<void>;
... 4 more ...;
access: (path: import("node:fs").PathLike) => Promise<void>;
}
cloud
.
readFile: (path: import("node:fs").PathLike, options?: ReadOptions) => Promise<Buffer | string>
readFile
('backup/library.bin')
return
const data: string | Buffer<ArrayBufferLike>
data
instanceof
var Uint8Array: Uint8ArrayConstructor

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
?
const data: Buffer<ArrayBufferLike>
data
: new
var TextEncoder: new () => TextEncoder

The TextEncoder interface takes a stream of code points as input and emits a stream of UTF-8 bytes.

MDN Reference

TextEncoder
().
TextEncoder.encode(input?: string): Uint8Array<ArrayBuffer>

The TextEncoder.encode() method takes a string as input, and returns a Global_Objects/Uint8Array containing the text given in parameters encoded with the specific method for that TextEncoder object.

MDN Reference

encode
(
const data: string
data
)
} catch (
function (local var) error: unknown
error
) {
if (
function isNotFound(error: unknown): boolean

Whether an error from this module means the path is empty, as opposed to unreadable.

isNotFound
(
function (local var) error: unknown
error
)) return null // nothing stored yet, safe to write a first backup
if (
function (local var) error: unknown
error
instanceof
class StorageLockedError
StorageLockedError
) {
if (
attempt: number
attempt
=== 0 && await
function unlock(): Promise<boolean>
unlock
()) return
const loadBackup: (attempt?: number) => Promise<Uint8Array | null>
loadBackup
(
attempt: 0
attempt
+ 1) // once, after the user unlocked
throw
function (local var) error: StorageLockedError
error
}
const
const message: string
message
=
function (local var) error: unknown
error
instanceof
var Error: ErrorConstructor
Error
?
function (local var) error: Error
error
.
Error.message: string
message
: ''
if (
const 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
(
const E2E_STALE_EPOCH_MESSAGE: "fkn:e2e-stale-epoch: this file is encrypted under a previous key you reset"
E2E_STALE_EPOCH_MESSAGE
)) throw
function (local var) error: unknown
error
// a reset key, nothing to retry
if (
const 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
(
const E2E_INTEGRITY_MESSAGE: "fkn:e2e-integrity: stored data failed its integrity check"
E2E_INTEGRITY_MESSAGE
)) throw
function (local var) error: unknown
error
// unreadable, never overwrite
if (
const 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
('storage: api unreachable')) return
const cached: () => Promise<Uint8Array | null>
cached
() // keep this device's copy, retry later
throw
function (local var) error: unknown
error
}
}
const
const backup: Uint8Array<ArrayBufferLike> | null
backup
= await
const loadBackup: (attempt?: number) => Promise<Uint8Array | null>
loadBackup
() // the bytes, null with nothing stored, or cached while unreachable

isNotFound and instanceof StorageLockedError come first, because those two classes are minted on your side and keep their code. The order to test a mixed handler in is on handling errors.

The service stores the encrypted bytes and, beside them, the five fields of FileEntry: path, size, contentType, updatedAt, and the seal marker in encryption, the field that names the envelope layout and nothing else. cloud.fs.quota() meters usedBytes and objects against their caps, see limits and timeouts.

OPFS, the local half of fs and all of opfs, is not encrypted by the platform and belongs to the origin, not the account, see scope and paths.

The five rows an app meets most:

MessageWhat happened
fkn:e2e-lockedArrives as StorageLockedError. Call unlock() and retry once.
fkn:e2e-stale-epoch: this file is encrypted under a previous key you resetA key a reset removed. Never retry or overwrite.
fkn:e2e-integrity: stored data failed its integrity checkThe seal marker, scope, connection or authentication tag does not verify. Never overwrite.
This account has no encryption keys yetAn account holding no key, between a settled reset and the next enrolment.
This account stores sealed objectsA write with no seal marker, which the broker never sends.

Superseded key and the hybrid’s own sentence are above, and every other message is on every error.