UNPKG

accounts

Version:

Tempo Accounts SDK

137 lines 5.55 kB
import * as z from 'zod/mini'; import { persist } from 'zustand/middleware'; import { subscribeWithSelector } from 'zustand/middleware'; import { createStore } from 'zustand/vanilla'; import * as core_AccessKey from './AccessKey.js'; import * as core_Keystore from './Keystore.js'; import * as Storage from './Storage.js'; const supportsStructuredClone = Symbol.for('accounts.storage.supportsStructuredClone'); /** * Creates a Zustand vanilla store with `subscribeWithSelector` and `persist` middleware. */ export function create(options) { const { chainId, keystores = core_Keystore.defaults, maxAccounts, persistCredentials = true, schema, storage = typeof window !== 'undefined' ? Storage.idb({ key: 'tempo' }) : Storage.memory({ key: 'tempo' }), } = options; const state = createStore(subscribeWithSelector(persist(() => ({ accessKeys: [], accounts: [], activeAccount: 0, chainId, }), { merge: (persisted, current) => hydrate(persisted, current, { schema }), name: 'store', partialize: (state) => serialize(state, { keystores, maxAccounts, persistCredentials, structuredClone: canStructuredClone(storage), }), storage, version: 0, }))); const store = state; store.accessKeys = core_AccessKey.createManager({ keystores, state }); store.disconnect = () => state.setState({ accessKeys: [], accounts: [], activeAccount: 0, auth: undefined }); return store; } /** Converts runtime provider state into the persisted refresh snapshot. */ function serialize(state, options = {}) { const { keystores, maxAccounts, persistCredentials = true, structuredClone = false } = options; const accounts = maxAccounts && state.accounts.length > maxAccounts ? state.accounts.slice(0, maxAccounts) : state.accounts; return { accounts, activeAccount: state.activeAccount, ...(persistCredentials ? { accessKeys: state.accessKeys.map((accessKey) => serializeAccessKey(accessKey, { keystores, structuredClone })), } : {}), ...(state.auth ? { auth: state.auth } : {}), chainId: state.chainId, }; } /** Restores runtime provider state from a persisted refresh snapshot. */ function hydrate(persisted, current, options = {}) { const state = persisted && typeof persisted === 'object' ? persisted : {}; const accounts_persisted = Array.isArray(state.accounts) ? state.accounts.filter(isStoredAccount) : undefined; const accounts = accounts_persisted?.map((persisted) => { const account = current.accounts.find((a) => a.address.toLowerCase() === persisted.address.toLowerCase()); return account ?? persisted; }) ?? current.accounts; const accounts_valid = options.schema ? accounts.filter((account) => z.safeParse(options.schema, account).success) : accounts; return { ...state, ...current, accounts: accounts_valid, activeAccount: accounts_valid.length === 0 ? 0 : Math.min(state.activeAccount ?? current.activeAccount, accounts_valid.length - 1), accessKeys: normalizeAccessKeys(state.accessKeys) ?? current.accessKeys, chainId: state.chainId ?? current.chainId, }; } function normalizeAccessKeys(accessKeys) { if (!accessKeys) return undefined; return accessKeys.filter((key) => { if (!key || typeof key !== 'object') return false; const value = key; return (typeof value.access === 'string' && typeof value.address === 'string' && typeof value.chainId === 'number' && (value.keyType === 'secp256k1' || value.keyType === 'p256' || value.keyType === 'webAuthn' || value.keyType === 'webCrypto')); }); } function isStoredAccount(account) { if (!account || typeof account !== 'object') return false; return typeof account.address === 'string'; } function serializeAccessKey(accessKey, options) { // Live key material (e.g. WebCrypto key pairs) survives structured-clone // stores inline; everywhere else it is stripped so the key is session-only. if (options.structuredClone) return accessKey; const { keyPair: _keyPair, ...metadata } = accessKey; if ('handle' in metadata && requiresStructuredClone(options.keystores, metadata.keyType)) { const { handle: _handle, ...rest } = metadata; return rest; } return metadata; } /** Returns whether the keystore for `keyType` writes structured-clone-only handles. */ function requiresStructuredClone(keystores, keyType) { if (keyType !== 'p256' && keyType !== 'secp256k1') return false; return keystores?.[keyType]?.requiresStructuredClone === true; } function canStructuredClone(storage) { return (storage[supportsStructuredClone] === true); } /** * Waits for the store to finish hydrating from storage. * * Returns immediately if the store has already hydrated. Otherwise, waits * for the `onFinishHydration` callback with a 100ms safety timeout fallback. */ export async function waitForHydration(store) { if (store.persist.hasHydrated()) return; await new Promise((resolve) => { store.persist.onFinishHydration(() => resolve()); setTimeout(() => resolve(), 100); }); } //# sourceMappingURL=Store.js.map