mirror of
https://github.com/dergigi/boris.git
synced 2025-12-18 15:14:20 +01:00
- Add project structure with TypeScript, React, and Vite - Implement nostr authentication using browser extension (NIP-07) - Add NIP-51 compliant bookmark fetching and display - Create minimal UI with login and bookmark components - Integrate applesauce-core and applesauce-react libraries - Add responsive styling with dark/light mode support - Include comprehensive README with setup instructions This is a minimal MVP for a nostr bookmark client that allows users to view their bookmarks according to NIP-51 specification.
159 lines
5.4 KiB
JavaScript
159 lines
5.4 KiB
JavaScript
import { BehaviorSubject } from "rxjs";
|
|
import { ProxySigner } from "./proxy-signer.js";
|
|
export class AccountManager {
|
|
types = new Map();
|
|
active$ = new BehaviorSubject(undefined);
|
|
get active() {
|
|
return this.active$.value;
|
|
}
|
|
accounts$ = new BehaviorSubject([]);
|
|
get accounts() {
|
|
return this.accounts$.value;
|
|
}
|
|
/** Proxy signer for currently active account */
|
|
signer;
|
|
/** Disable request queueing for any accounts added to this manager */
|
|
disableQueue;
|
|
constructor() {
|
|
this.signer = new ProxySigner(this.active$, "No active account");
|
|
}
|
|
// Account type CRUD
|
|
/** Add account type class */
|
|
registerType(accountType) {
|
|
if (!accountType.type)
|
|
throw new Error(`Account class missing static "type" field`);
|
|
if (this.types.has(accountType.type))
|
|
throw new Error(`An account type of ${accountType.type} already exists`);
|
|
this.types.set(accountType.type, accountType);
|
|
}
|
|
/** Remove account type */
|
|
unregisterType(type) {
|
|
this.types.delete(type);
|
|
}
|
|
// Accounts CRUD
|
|
/** gets an account in the manager */
|
|
getAccount(id) {
|
|
if (typeof id === "string")
|
|
return this.accounts$.value.find((a) => a.id === id);
|
|
else if (this.accounts$.value.includes(id))
|
|
return id;
|
|
else
|
|
return undefined;
|
|
}
|
|
/** Return the first account for a pubkey */
|
|
getAccountForPubkey(pubkey) {
|
|
return Object.values(this.accounts$.value).find((account) => account.pubkey === pubkey);
|
|
}
|
|
/** Returns all accounts for a pubkey */
|
|
getAccountsForPubkey(pubkey) {
|
|
return Object.values(this.accounts$.value).filter((account) => account.pubkey === pubkey);
|
|
}
|
|
/** adds an account to the manager */
|
|
addAccount(account) {
|
|
if (this.getAccount(account.id))
|
|
return;
|
|
// copy the disableQueue flag only if its set
|
|
if (this.disableQueue !== undefined && account.disableQueue !== undefined) {
|
|
account.disableQueue = this.disableQueue;
|
|
}
|
|
this.accounts$.next([...this.accounts$.value, account]);
|
|
}
|
|
/** Removes an account from the manager */
|
|
removeAccount(account) {
|
|
const id = typeof account === "string" ? account : account.id;
|
|
this.accounts$.next(this.accounts$.value.filter((a) => a.id !== id));
|
|
// if the removed account was active, clear the active account
|
|
if (this.active$.value?.id === id)
|
|
this.active$.next(undefined);
|
|
}
|
|
/** Replaces an account with another */
|
|
replaceAccount(old, account) {
|
|
this.addAccount(account);
|
|
// if the old account was active, switch to the new one
|
|
const id = typeof account === "string" ? account : account.id;
|
|
if (this.active$.value?.id === id)
|
|
this.setActive(account);
|
|
this.removeAccount(old);
|
|
}
|
|
// Active account methods
|
|
/** Returns the currently active account */
|
|
getActive() {
|
|
return this.active$.value;
|
|
}
|
|
/** Sets the currently active account */
|
|
setActive(id) {
|
|
const account = this.getAccount(id);
|
|
if (!account)
|
|
throw new Error("Cant find account with that ID");
|
|
if (this.active$.value?.id !== account.id) {
|
|
this.active$.next(account);
|
|
}
|
|
}
|
|
/** Clears the currently active account */
|
|
clearActive() {
|
|
this.active$.next(undefined);
|
|
}
|
|
// Metadata CRUD
|
|
/** sets the metadata on an account */
|
|
setAccountMetadata(id, metadata) {
|
|
const account = this.getAccount(id);
|
|
if (!account)
|
|
throw new Error("Cant find account with that ID");
|
|
account.metadata = metadata;
|
|
}
|
|
/** sets the metadata on an account */
|
|
getAccountMetadata(id) {
|
|
const account = this.getAccount(id);
|
|
if (!account)
|
|
throw new Error("Cant find account with that ID");
|
|
return account.metadata;
|
|
}
|
|
/** Removes all metadata on the account */
|
|
clearAccountMetadata(id) {
|
|
const account = this.getAccount(id);
|
|
if (!account)
|
|
throw new Error("Cant find account with that ID");
|
|
account.metadata = undefined;
|
|
}
|
|
// Serialize / Deserialize
|
|
/** Returns an array of serialized accounts */
|
|
toJSON(quite = false) {
|
|
const accounts = [];
|
|
for (const account of this.accounts) {
|
|
try {
|
|
accounts.push(account.toJSON());
|
|
}
|
|
catch (error) {
|
|
if (!quite)
|
|
throw error;
|
|
}
|
|
}
|
|
return accounts;
|
|
}
|
|
/**
|
|
* Restores all accounts from an array of serialized accounts
|
|
* NOTE: this will clear all existing accounts
|
|
*/
|
|
fromJSON(accounts, quite = false) {
|
|
for (const json of accounts) {
|
|
try {
|
|
const AccountType = this.types.get(json.type);
|
|
if (!AccountType) {
|
|
if (!quite)
|
|
throw new Error(`Missing account type ${json.type}`);
|
|
else
|
|
continue;
|
|
}
|
|
const account = AccountType.fromJSON(json);
|
|
this.addAccount(account);
|
|
}
|
|
catch (error) {
|
|
if (!quite)
|
|
throw error;
|
|
else
|
|
console.log(`Failed to load account`, error);
|
|
}
|
|
}
|
|
}
|
|
}
|