accounts
Version:
Tempo Accounts SDK
137 lines • 5.55 kB
JavaScript
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