API reference › @evolu/common › local‑first/Evolu › unlockWrappedDeviceSecret
function unlockWrappedDeviceSecret(__namedParameters: {
appName: string & Brand<"UrlSafeString"> & Brand<"AppName">;
keyProvider: DeviceSecretKeyProvider;
}): Task<
Uint8Array<ArrayBufferLike> &
Brand<"Entropy"> &
Brand<"Length32"> &
Brand<"DeviceSecret">,
| DeviceSecretKeyError
| DeviceSecretUnwrapError
| WrappedDeviceSecretNotPersistedError,
CreateDbWorkerDep &
CreateBroadcastChannelDep &
CreateMessageChannelDep &
DevicePersistenceDep &
LockManagerDep &
ReloadAppDep &
Partial<ConsoleDep> &
Partial<CreateMemoryInstanceWorkerDep> &
Partial<FlushSyncDep> &
Partial<RequestPersistentStorageDep> &
StoredWorkerDep
>;
Defined in: packages/common/src/local-first/Evolu.ts:2980
Unlocks the app's DeviceSecret with a key from the DeviceSecretKeyProvider, and on the first use creates them: a key, a DeviceSecret, and its WrappedDeviceSecret, which Evolu stores in a database of its own whose key is public. Pass the DeviceSecret to createEvoluApp as DeviceSecretAuth.
On the first use, it runs under a lock per app, so a second tab waits and
then unlocks what the first one created. Once a wrapped secret is stored, it
only unlocks, without the lock, so no tab waits for another tab's prompt, and
an error, such as a dismissed prompt, never starts another setup, which would
orphan the device database. A setup that stopped after creating its key
resumes with that key, also after the provider refused it, so the user gets
no second passkey; only when the provider can't provide it does the next
setup create another one. When its database is refused, as evoluError
reports, it fails with an UnknownError, the refusal itself or one
wrapping it. Like createEvoluApp, it stays pending while another build of
the app keeps it waiting or the shared worker fails to load. Some browsers
allow a prompt only in a user gesture, so call it from one, such as a button
click, and use hasWrappedDeviceSecret to tell which button to show.
After an error, keep the app usable and let the user try again, rather than
blocking it on the prompt.
WebAuthn reports a deleted passkey like a dismissed prompt, and a stored
secret whose key is gone for good fails every unlock, so after any refusal an
app can offer to start over with forgetWrappedDeviceSecret. Its
onlyPending mode forgets a setup's pending key, which loses nothing and
needs no warning, and fails when it finds a stored secret, whose loss the
user confirms first. When unlocking still fails right after a start over, the
provider likely refuses its PRF output, so stop offering to forget a stored
secret, but keep offering onlyPending, which forgets only the pending key,
loses nothing, and lets the user try another provider. Where the platform
keeps nothing, it fails with WrappedDeviceSecretNotPersistedError
before anything else.