Production client state
A Tacenta client is a device identity with live encrypted sessions. Its exported state is private key material, not a cache: persist it from the first signed-in session and treat its protection as part of the integration.
The rule to start with
Keep the newest exported state after sign-in and after every send or receive. On the next launch, restore that state for the same user and device. Restoring an older copy can rewind session state; the sealed paths on Swift and Kotlin are designed to refuse a rollback when the app supplies a secure store.
const currentState = await client.exportState();
await appPrivateStore.save(userId, currentState);
// On the next launch, restore the same device identity.
const restoredState = await appPrivateStore.load(userId);
const restoredClient = await tenant.signInWithState(user, password, restoredState); TypeScript spelling shown. Use the equivalent state methods in Swift, Kotlin or Rust; the language guides name each one.
Choose storage for the platform
- TypeScript
- The browser head has no sealed-state or secure-store hook yet. IndexedDB is neither encrypted nor private from scripts running on the same origin. Protect the bytes under an app-controlled key, keep third-party scripts out of the page and use a strict content-security policy. The TypeScript README explains the browser trust boundary.
- Swift
-
Use the sealed state methods with a
SecureStorebacked by the Keychain. Keep the wrapping key and rollback counter separate from the exported blob, choose a device-only accessibility class and decide your backup and migration policy. The Swift guide includes a starting implementation. - Kotlin
-
Use the sealed state methods with a Keystore-backed
SecureStore. The wrapping key and monotonic counter must not be stored beside the state blob, and backup must not restore an older counter. The Android guide covers the Keystore and backup considerations. - Rust
-
Rust also exposes the sealed state path through a
SecureStore. Supply it from the host's secret store, keep its wrapping key and counter separate from the exported blob and retain only the latest successful checkpoint.
Receive without competing with yourself
Run one receive loop per client. A message is delivered to whichever
receive() or inbound() call is waiting, so two
concurrent consumers make delivery ownership unclear in the application.
A pending receive does not block another send on the same client.
for await (const message of client.inbound()) {
await saveLatestState(client);
await handleMessage(message);
} Handle failures by kind
Every SDK exposes a stable error kind and a human-readable detail. Branch on the kind, keep the detail for a log line, and leave unknown future kinds to a safe fallback. Do not delete persisted state after a state error: it may be altered, stale or protected by a store that is temporarily unavailable. The SDK surface lists the shared error kinds and their meaning.
Before you ship
- Persist the latest client state after every state-changing operation.
- Restore a state only for the user and device that created it.
- Use sealed state and a platform secure store where the SDK supports it.
- Run one inbound loop for each client and make its ownership explicit.
- Test sign-in, restart, send, receive and restore on the devices you ship.
- Read the assurance boundary before relying on a security claim in your own product.
Continue
This guide covers the state an application owns. The SDK pages show the exact spelling in each language, and the Assurance page describes the evidence behind the protocol and product claims.