Skip to content

Account Manager

The AccountManager class is the core of the library, as its name suggests its used to manage multiple accounts

Account types

By default the account manager comes with no account types. you have to manually add them when you create the instance. luckily there is a handy method to add the most common types registerCommonAccountTypes

ts
import { AccountManager, registerCommonAccountTypes, AmberClipboardAccount } from "applesauce-accounts";

// create an account manager instance
const manager = new AccountManager();

// register common account types
registerCommonAccountTypes(manager);

// manually add account type
manager.registerType(AmberClipboardAccount);

Adding and removing accounts

ts
import { AccountManager, registerCommonAccountTypes, PrivateKeyAccount } from "applesauce-accounts";

// create an account manager instance
const manager = new AccountManager();

// register common account types
registerCommonAccountTypes(manager);

// subscribe to the active account
manager.active$.subscribe((account) => {
  if (account) console.log(`${account.id} is now active`);
  else console.log("no account is active");

  updateUI();
});

// create an account
const account = PrivateKeyAccount.fromKey("788229e1801c4576391d39a03610293ea7e6645c9d39aca54c62fc6d71cbc385");

// add it to the manager
manager.addAccount(account);

// set it as active
manager.setActive(account);

// later, remove the account and the active account will also update
manager.removeAccount(account.id);

Active account

The AccountManager class exposes a set of methods to track which account is active and switch the active account

  • AccountManager.active gets the currently active account
  • AccountManager.active$ an observable of the active account, can be used to subscribe to changes
  • AccountManager.setActive(id: string | Account) set the active account

Persisting accounts

The account manager exposes two methods that can be used to persist accounts between app reloads. toJSON and fromJSON

ts
import { AccountManager } from "applesauce-accounts";
import { registerCommonAccountTypes } from "applesauce-accounts/accounts";

// create an account manager instance
const manager = new AccountManager();

// register common account types
registerCommonAccountTypes(manager);

// first load all accounts from localStorage
const json = JSON.parse(localStorage.getItem("accounts") || "[]");
await manager.fromJSON(json);

// next, subscribe to any accounts added or removed
manager.accounts$.subscribe((accounts) => {
  // save all the accounts into the "accounts" field
  localStorage.setItem("accounts", JSON.stringify(manager.toJSON()));
});

// load active account from storage
const active = localStorage.getItem("active");
if (active) manager.setActive(active);

// subscribe to active changes
manager.active$.subscribe((account) => {
  if (account) localStorage.setItem("active", account.id);
  else localStorage.clearItem("active");
});

Integration

With EventFactory

The AccountManager's signer property automatically points to the active account:

ts
const factory = new EventFactory({ signer: manager.signer });

manager.setActive(account1);
await factory.sign(draft); // Uses account1

manager.setActive(account2);
await factory.sign(draft); // Uses account2

With ActionRunner

ts
const factory = new EventFactory({ signer: manager.signer });
const actions = new ActionRunner(eventStore, factory, publishFn);

await actions.run(FollowUser, pubkey); // Uses active account

manager.setActive(differentAccount);
await actions.run(FollowUser, pubkey); // Automatically uses new account

With ProxySigner

Create custom proxy signers for specific accounts:

ts
const specificSigner = new ProxySigner(
  manager.accounts$.pipe(map((accounts) => accounts.find((a) => a.id === specificId)?.signer)),
);

With React

tsx
function AccountSwitcher() {
  const accounts = use$(manager.accounts$);
  const active = use$(manager.active$);

  return (
    <select value={active?.id} onChange={(e) => manager.setActive(e.target.value)}>
      {accounts.map((a) => (
        <option key={a.id} value={a.id}>
          {a.metadata?.name}
        </option>
      ))}
    </select>
  );
}

Best Practices

Single Instance

ts
// accounts.ts
export const manager = new AccountManager();
registerCommonAccountTypes(manager);

Auto-save Pattern

ts
// Load from storage
await manager.fromJSON(JSON.parse(localStorage.getItem("accounts") || "[]"));

// Auto-save on changes
manager.accounts$.subscribe(() => {
  localStorage.setItem("accounts", JSON.stringify(manager.toJSON()));
});

// Save/restore active account
manager.active$.subscribe((account) => {
  if (account) localStorage.setItem("active", account.id);
});

const activeId = localStorage.getItem("active");
if (activeId) manager.setActive(activeId);

Register Types Early

ts
// ✅ Good - register before loading
registerCommonAccountTypes(manager);
await manager.fromJSON(saved);

// ❌ Bad - types not registered
await manager.fromJSON(saved);
registerCommonAccountTypes(manager);

Type-Safe Metadata

ts
interface AccountMeta {
  name: string;
  color: string;
}

const manager = new AccountManager<AccountMeta>();
const account = PrivateKeyAccount.generateNew<AccountMeta>();
account.metadata = { name: "Alice", color: "#ff0000" };

Error Handling

ts
const account = manager.getAccount(accountId);
if (!account) {
  console.error("Account not found");
  return;
}
manager.setActive(account);