2023-04-14 11:33:19 +00:00
|
|
|
import * as secp from "@noble/secp256k1";
|
|
|
|
import { HexKey, RelaySettings } from "@snort/nostr";
|
|
|
|
|
|
|
|
import { DefaultRelays } from "Const";
|
|
|
|
import ExternalStore from "ExternalStore";
|
|
|
|
import { LoginSession } from "Login";
|
|
|
|
import { deepClone, sanitizeRelayUrl, unwrap } from "Util";
|
|
|
|
import { DefaultPreferences, UserPreferences } from "./Preferences";
|
|
|
|
|
|
|
|
const AccountStoreKey = "sessions";
|
|
|
|
const LoggedOut = {
|
|
|
|
preferences: DefaultPreferences,
|
|
|
|
tags: {
|
|
|
|
item: [],
|
|
|
|
timestamp: 0,
|
|
|
|
},
|
|
|
|
follows: {
|
|
|
|
item: [],
|
|
|
|
timestamp: 0,
|
|
|
|
},
|
|
|
|
muted: {
|
|
|
|
item: [],
|
|
|
|
timestamp: 0,
|
|
|
|
},
|
|
|
|
blocked: {
|
|
|
|
item: [],
|
|
|
|
timestamp: 0,
|
|
|
|
},
|
|
|
|
bookmarked: {
|
|
|
|
item: [],
|
|
|
|
timestamp: 0,
|
|
|
|
},
|
|
|
|
pinned: {
|
|
|
|
item: [],
|
|
|
|
timestamp: 0,
|
|
|
|
},
|
|
|
|
relays: {
|
|
|
|
item: Object.fromEntries([...DefaultRelays.entries()].map(a => [unwrap(sanitizeRelayUrl(a[0])), a[1]])),
|
|
|
|
timestamp: 0,
|
|
|
|
},
|
|
|
|
latestNotification: 0,
|
|
|
|
readNotifications: 0,
|
2023-04-14 15:02:15 +00:00
|
|
|
subscriptions: [],
|
2023-04-14 11:33:19 +00:00
|
|
|
} as LoginSession;
|
|
|
|
const LegacyKeys = {
|
|
|
|
PrivateKeyItem: "secret",
|
|
|
|
PublicKeyItem: "pubkey",
|
|
|
|
NotificationsReadItem: "notifications-read",
|
|
|
|
UserPreferencesKey: "preferences",
|
|
|
|
RelayListKey: "last-relays",
|
|
|
|
FollowList: "last-follows",
|
|
|
|
};
|
|
|
|
|
|
|
|
export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|
|
|
#activeAccount?: HexKey;
|
|
|
|
#accounts: Map<string, LoginSession>;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
const existing = window.localStorage.getItem(AccountStoreKey);
|
|
|
|
if (existing) {
|
|
|
|
this.#accounts = new Map((JSON.parse(existing) as Array<LoginSession>).map(a => [unwrap(a.publicKey), a]));
|
|
|
|
} else {
|
|
|
|
this.#accounts = new Map();
|
|
|
|
}
|
|
|
|
this.#migrate();
|
|
|
|
if (!this.#activeAccount) {
|
|
|
|
this.#activeAccount = this.#accounts.keys().next().value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getSessions() {
|
|
|
|
return [...this.#accounts.keys()];
|
|
|
|
}
|
|
|
|
|
|
|
|
loginWithPubkey(key: HexKey, relays?: Record<string, RelaySettings>) {
|
|
|
|
if (this.#accounts.has(key)) {
|
|
|
|
throw new Error("Already logged in with this pubkey");
|
|
|
|
}
|
|
|
|
const initRelays = relays ?? Object.fromEntries(DefaultRelays.entries());
|
|
|
|
const newSession = {
|
|
|
|
...LoggedOut,
|
|
|
|
publicKey: key,
|
|
|
|
relays: {
|
|
|
|
item: initRelays,
|
|
|
|
timestamp: 1,
|
|
|
|
},
|
|
|
|
preferences: deepClone(DefaultPreferences),
|
|
|
|
} as LoginSession;
|
|
|
|
|
|
|
|
this.#accounts.set(key, newSession);
|
|
|
|
this.#activeAccount = key;
|
|
|
|
this.#save();
|
|
|
|
return newSession;
|
|
|
|
}
|
|
|
|
|
2023-04-14 15:02:15 +00:00
|
|
|
loginWithPrivateKey(key: HexKey, entropy?: string, relays?: Record<string, RelaySettings>) {
|
2023-04-14 11:33:19 +00:00
|
|
|
const pubKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(key));
|
|
|
|
if (this.#accounts.has(pubKey)) {
|
|
|
|
throw new Error("Already logged in with this pubkey");
|
|
|
|
}
|
2023-04-14 15:02:15 +00:00
|
|
|
const initRelays = relays ?? Object.fromEntries(DefaultRelays.entries());
|
|
|
|
const newSession = {
|
2023-04-14 11:33:19 +00:00
|
|
|
...LoggedOut,
|
|
|
|
privateKey: key,
|
|
|
|
publicKey: pubKey,
|
|
|
|
generatedEntropy: entropy,
|
2023-04-14 15:02:15 +00:00
|
|
|
relays: {
|
|
|
|
item: initRelays,
|
|
|
|
timestamp: 1,
|
|
|
|
},
|
2023-04-14 11:33:19 +00:00
|
|
|
preferences: deepClone(DefaultPreferences),
|
2023-04-14 15:02:15 +00:00
|
|
|
} as LoginSession;
|
|
|
|
this.#accounts.set(pubKey, newSession);
|
2023-04-14 11:33:19 +00:00
|
|
|
this.#activeAccount = pubKey;
|
|
|
|
this.#save();
|
2023-04-14 15:02:15 +00:00
|
|
|
return newSession;
|
2023-04-14 11:33:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updateSession(s: LoginSession) {
|
|
|
|
const pk = unwrap(s.publicKey);
|
|
|
|
if (this.#accounts.has(pk)) {
|
|
|
|
this.#accounts.set(pk, s);
|
|
|
|
this.#save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
removeSession(k: string) {
|
|
|
|
if (this.#accounts.delete(k)) {
|
|
|
|
this.#save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
takeSnapshot(): LoginSession {
|
|
|
|
const s = this.#activeAccount ? this.#accounts.get(this.#activeAccount) : undefined;
|
|
|
|
if (!s) return LoggedOut;
|
|
|
|
|
|
|
|
return deepClone(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
#migrate() {
|
|
|
|
let didMigrate = false;
|
|
|
|
const oldPreferences = window.localStorage.getItem(LegacyKeys.UserPreferencesKey);
|
|
|
|
const pref: UserPreferences = oldPreferences ? JSON.parse(oldPreferences) : deepClone(DefaultPreferences);
|
|
|
|
window.localStorage.removeItem(LegacyKeys.UserPreferencesKey);
|
|
|
|
|
|
|
|
const privKey = window.localStorage.getItem(LegacyKeys.PrivateKeyItem);
|
|
|
|
if (privKey) {
|
|
|
|
const pubKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(privKey));
|
|
|
|
this.#accounts.set(pubKey, {
|
|
|
|
...LoggedOut,
|
|
|
|
privateKey: privKey,
|
|
|
|
publicKey: pubKey,
|
|
|
|
preferences: pref,
|
|
|
|
} as LoginSession);
|
|
|
|
window.localStorage.removeItem(LegacyKeys.PrivateKeyItem);
|
|
|
|
window.localStorage.removeItem(LegacyKeys.PublicKeyItem);
|
|
|
|
didMigrate = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const pubKey = window.localStorage.getItem(LegacyKeys.PublicKeyItem);
|
|
|
|
if (pubKey) {
|
|
|
|
this.#accounts.set(pubKey, {
|
|
|
|
...LoggedOut,
|
|
|
|
publicKey: pubKey,
|
|
|
|
preferences: pref,
|
|
|
|
} as LoginSession);
|
|
|
|
window.localStorage.removeItem(LegacyKeys.PublicKeyItem);
|
|
|
|
didMigrate = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
window.localStorage.removeItem(LegacyKeys.RelayListKey);
|
|
|
|
window.localStorage.removeItem(LegacyKeys.FollowList);
|
|
|
|
window.localStorage.removeItem(LegacyKeys.NotificationsReadItem);
|
|
|
|
if (didMigrate) {
|
|
|
|
console.debug("Finished migration to MultiAccountStore");
|
|
|
|
this.#save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#save() {
|
|
|
|
if (!this.#activeAccount && this.#accounts.size > 0) {
|
|
|
|
this.#activeAccount = [...this.#accounts.keys()][0];
|
|
|
|
}
|
|
|
|
window.localStorage.setItem(AccountStoreKey, JSON.stringify([...this.#accounts.values()]));
|
|
|
|
this.notifyChange();
|
|
|
|
}
|
|
|
|
}
|