feat: in-memory fallback for storing user profiles (#110)

This commit is contained in:
Alejandro 2023-01-27 22:38:41 +01:00 committed by GitHub
parent 2e3fe92b1f
commit d070185322
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 369 additions and 123 deletions

View File

@ -1,39 +0,0 @@
import { HexKey, TaggedRawEvent, UserMetadata } from "Nostr";
import { hexToBech32 } from "../Util";
export interface MetadataCache extends UserMetadata {
/**
* When the object was saved in cache
*/
loaded: number,
/**
* When the source metadata event was created
*/
created: number,
/**
* The pubkey of the owner of this metadata
*/
pubkey: HexKey,
/**
* bech32 encoded pub key
*/
npub: string
};
export function mapEventToProfile(ev: TaggedRawEvent) {
try {
let data: UserMetadata = JSON.parse(ev.content);
return {
pubkey: ev.pubkey,
npub: hexToBech32("npub", ev.pubkey),
created: ev.created_at,
loaded: new Date().getTime(),
...data
} as MetadataCache;
} catch (e) {
console.error("Failed to parse JSON", ev, e);
}
}

View File

@ -1,16 +1,20 @@
import Dexie, { Table } from "dexie";
import { MetadataCache } from "Db/User";
import { MetadataCache } from "State/Users";
import { hexToBech32 } from "Util";
export const NAME = 'snortDB'
export const VERSION = 2
const STORES = {
users: '++pubkey, name, display_name, picture, nip05, npub'
}
export class SnortDB extends Dexie {
users!: Table<MetadataCache>;
constructor() {
super('snortDB');
this.version(2).stores({
users: '++pubkey, name, display_name, picture, nip05, npub'
}).upgrade(tx => {
super(NAME);
this.version(VERSION).stores(STORES).upgrade(tx => {
return tx.table("users").toCollection().modify(user => {
user.npub = hexToBech32("npub", user.pubkey)
})

View File

@ -1,11 +1,11 @@
import { useMemo } from "react";
import { Link } from "react-router-dom";
import useProfile from "Feed/ProfileFeed";
import { useUserProfile } from "Feed/ProfileFeed";
import { HexKey } from "Nostr";
import { hexToBech32, profileLink } from "Util";
export default function Mention({ pubkey }: { pubkey: HexKey }) {
const user = useProfile(pubkey)?.get(pubkey);
const user = useUserProfile(pubkey)
const name = useMemo(() => {
let name = hexToBech32("npub", pubkey).substring(0, 12);
@ -18,4 +18,4 @@ export default function Mention({ pubkey }: { pubkey: HexKey }) {
}, [user, pubkey]);
return <Link to={profileLink(pubkey)} onClick={(e) => e.stopPropagation()}>@{name}</Link>
}
}

View File

@ -13,7 +13,7 @@ import {
import AsyncButton from "Element/AsyncButton";
import LNURLTip from "Element/LNURLTip";
import Copy from "Element/Copy";
import useProfile from "Feed/ProfileFeed";
import { useUserProfile }from "Feed/ProfileFeed";
import useEventPublisher from "Feed/EventPublisher";
import { debounce, hexToBech32 } from "Util";
import { UserMetadata } from "Nostr";
@ -31,7 +31,7 @@ type ReduxStore = any;
export default function Nip5Service(props: Nip05ServiceProps) {
const navigate = useNavigate();
const pubkey = useSelector<ReduxStore, string>(s => s.login.publicKey);
const user = useProfile(pubkey);
const user = useUserProfile(pubkey);
const publisher = useEventPublisher();
const svc = useMemo(() => new ServiceProvider(props.service), [props.service]);
const [serviceConfig, setServiceConfig] = useState<ServiceConfig>();
@ -194,4 +194,4 @@ export default function Nip5Service(props: Nip05ServiceProps) {
</div>}
</>
)
}
}

View File

@ -9,7 +9,7 @@ import { eventLink, getReactions, hexToBech32 } from "Util";
import NoteFooter from "Element/NoteFooter";
import NoteTime from "Element/NoteTime";
import EventKind from "Nostr/EventKind";
import useProfile from "Feed/ProfileFeed";
import { useUserProfiles } from "Feed/ProfileFeed";
import { TaggedRawEvent, u256 } from "Nostr";
import { useInView } from "react-intersection-observer";
@ -31,7 +31,7 @@ export default function Note(props: NoteProps) {
const { data, isThread, related, highlight, options: opt, ["data-ev"]: parsedEvent } = props
const ev = useMemo(() => parsedEvent ?? new NEvent(data), [data]);
const pubKeys = useMemo(() => ev.Thread?.PubKeys || [], [ev]);
const users = useProfile(pubKeys);
const users = useUserProfiles(pubKeys);
const deletions = useMemo(() => getReactions(related, ev.Id, EventKind.Deletion), [related]);
const { ref, inView } = useInView({ triggerOnce: true });

View File

@ -9,7 +9,7 @@ import useEventPublisher from "Feed/EventPublisher";
import { getReactions, hexToBech32, normalizeReaction, Reaction } from "Util";
import { NoteCreator } from "Element/NoteCreator";
import LNURLTip from "Element/LNURLTip";
import useProfile from "Feed/ProfileFeed";
import { useUserProfile } from "Feed/ProfileFeed";
import { default as NEvent } from "Nostr/Event";
import { RootState } from "State/Store";
import { HexKey, TaggedRawEvent } from "Nostr";
@ -26,7 +26,7 @@ export default function NoteFooter(props: NoteFooterProps) {
const login = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const prefs = useSelector<RootState, UserPreferences>(s => s.login.preferences);
const author = useProfile(ev.RootPubKey)?.get(ev.RootPubKey);
const author = useUserProfile(ev.RootPubKey);
const publisher = useEventPublisher();
const [reply, setReply] = useState(false);
const [tip, setTip] = useState(false);

View File

@ -3,7 +3,7 @@ import "./NoteToSelf.css";
import { Link, useNavigate } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBook, faCertificate } from "@fortawesome/free-solid-svg-icons"
import useProfile from "Feed/ProfileFeed";
import { useUserProfile } from "Feed/ProfileFeed";
import Nip05 from "Element/Nip05";
import { profileLink } from "Util";
@ -15,7 +15,7 @@ export interface NoteToSelfProps {
};
function NoteLabel({pubkey, link}:NoteToSelfProps) {
const user = useProfile(pubkey)?.get(pubkey);
const user = useUserProfile(pubkey);
return (
<div>
Note to Self <FontAwesomeIcon icon={faCertificate} size="xs" />

View File

@ -1,13 +1,13 @@
import "./ProfileImage.css";
import { useMemo } from "react";
import { useNavigate } from "react-router-dom";
import useProfile from "Feed/ProfileFeed";
import { Link, useNavigate } from "react-router-dom";
import { useUserProfile } from "Feed/ProfileFeed";
import { hexToBech32, profileLink } from "Util";
import Avatar from "Element/Avatar"
import Nip05 from "Element/Nip05";
import { HexKey } from "Nostr";
import { MetadataCache } from "Db/User";
import { MetadataCache } from "State/Users";
export interface ProfileImageProps {
pubkey: HexKey,
@ -19,7 +19,7 @@ export interface ProfileImageProps {
export default function ProfileImage({ pubkey, subHeader, showUsername = true, className, link }: ProfileImageProps) {
const navigate = useNavigate();
const user = useProfile(pubkey)?.get(pubkey);
const user = useUserProfile(pubkey);
const name = useMemo(() => {
return getDisplayName(user, pubkey);

View File

@ -3,7 +3,7 @@ import { ReactNode } from "react";
import ProfileImage from "Element/ProfileImage";
import FollowButton from "Element/FollowButton";
import useProfile from "Feed/ProfileFeed";
import { useUserProfile } from "Feed/ProfileFeed";
import { HexKey } from "Nostr";
import { useInView } from "react-intersection-observer";
@ -16,7 +16,7 @@ export interface ProfilePreviewProps {
}
export default function ProfilePreview(props: ProfilePreviewProps) {
const pubkey = props.pubkey;
const user = useProfile(pubkey)?.get(pubkey);
const user = useUserProfile(pubkey);
const { ref, inView } = useInView({ triggerOnce: true });
const options = {
about: true,
@ -34,4 +34,4 @@ export default function ProfilePreview(props: ProfilePreviewProps) {
</>}
</div>
)
}
}

View File

@ -10,7 +10,7 @@ import Invoice from "Element/Invoice";
import Hashtag from "Element/Hashtag";
import Tag from "Nostr/Tag";
import { MetadataCache } from "Db/User";
import { MetadataCache } from "State/Users";
import Mention from "Element/Mention";
import TidalEmbed from "Element/TidalEmbed";
import { useSelector } from 'react-redux';

View File

@ -2,7 +2,6 @@ import "@webscopeio/react-textarea-autocomplete/style.css";
import "./Textarea.css";
import { useState } from "react";
import { useLiveQuery } from "dexie-react-hooks";
import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete";
import emoji from "@jukben/emoji-search";
import TextareaAutosize from "react-textarea-autosize";
@ -10,8 +9,8 @@ import TextareaAutosize from "react-textarea-autosize";
import Avatar from "Element/Avatar";
import Nip05 from "Element/Nip05";
import { hexToBech32 } from "Util";
import { db } from "Db";
import { MetadataCache } from "Db/User";
import { MetadataCache } from "State/Users";
import { useQuery } from "State/Users/Hooks";
interface EmojiItemProps {
name: string
@ -45,16 +44,7 @@ const UserItem = (metadata: MetadataCache) => {
const Textarea = ({ users, onChange, ...rest }: any) => {
const [query, setQuery] = useState('')
const allUsers = useLiveQuery(
() => db.users
.where("npub").startsWithIgnoreCase(query)
.or("name").startsWithIgnoreCase(query)
.or("display_name").startsWithIgnoreCase(query)
.or("nip05").startsWithIgnoreCase(query)
.limit(5)
.toArray(),
[query],
);
const allUsers = useQuery(query)
const userDataProvider = (token: string) => {
setQuery(token)

View File

@ -2,12 +2,13 @@ import "./ZapButton.css";
import { faBolt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useState } from "react";
import useProfile from "Feed/ProfileFeed";
import { useUserProfile } from "Feed/ProfileFeed";
import { HexKey } from "Nostr";
import LNURLTip from "Element/LNURLTip";
const ZapButton = ({ pubkey, svc }: { pubkey?: HexKey, svc?: string }) => {
const profile = useProfile(pubkey)?.get(pubkey ?? "");
const profile = useUserProfile(pubkey!)
const [zap, setZap] = useState(false);
const service = svc ?? (profile?.lud16 || profile?.lud06);

View File

@ -7,9 +7,9 @@ import EventKind from "Nostr/EventKind";
import { Subscriptions } from "Nostr/Subscriptions";
import { addDirectMessage, addNotifications, setFollows, setRelays } from "State/Login";
import { RootState } from "State/Store";
import { db } from "Db";
import { mapEventToProfile, MetadataCache } from "State/Users";
import { getDb } from "State/Users/Db";
import useSubscription from "Feed/Subscription";
import { mapEventToProfile, MetadataCache } from "Db/User";
import { getDisplayName } from "Element/ProfileImage";
import { MentionRegex } from "Const";
@ -87,9 +87,10 @@ export default function useLoginFeed() {
return acc;
}, { created: 0, profile: <MetadataCache | null>null });
if (maxProfile.profile) {
let existing = await db.users.get(maxProfile.profile.pubkey);
const db = getDb()
let existing = await db.find(maxProfile.profile.pubkey);
if ((existing?.created ?? 0) < maxProfile.created) {
await db.users.put(maxProfile.profile);
await db.put(maxProfile.profile);
}
}
})().catch(console.warn);
@ -115,10 +116,12 @@ export default function useLoginFeed() {
}
async function makeNotification(ev: TaggedRawEvent) {
const db = getDb()
switch (ev.kind) {
case EventKind.TextNote: {
const pubkeys = new Set([ev.pubkey, ...ev.tags.filter(a => a[0] === "p").map(a => a[1]!)]);
const users = (await db.users.bulkGet(Array.from(pubkeys))).filter(a => a !== undefined).map(a => a!);
const users = await db.bulkGet(Array.from(pubkeys))
// @ts-ignore
const fromUser = users.find(a => a?.pubkey === ev.pubkey);
const name = getDisplayName(fromUser, ev.pubkey);
const avatarUrl = (fromUser?.picture?.length ?? 0) === 0 ? Nostrich : fromUser?.picture;
@ -159,4 +162,4 @@ async function sendNotification(ev: TaggedRawEvent) {
vibrate: [500]
});
}
}
}

View File

@ -1,27 +1,13 @@
import { useLiveQuery } from "dexie-react-hooks";
import { useEffect } from "react";
import { db } from "Db";
import { MetadataCache } from "Db/User";
import { useEffect, useMemo } from "react";
import { RootState } from "State/Store";
import { MetadataCache } from "State/Users";
import { useKey, useKeys } from "State/Users/Hooks";
import { HexKey } from "Nostr";
import { System } from "Nostr/System";
export default function useProfile(pubKey?: HexKey | Array<HexKey> | undefined): Map<HexKey, MetadataCache> | undefined {
const user = useLiveQuery(async () => {
let userList = new Map<HexKey, MetadataCache>();
if (pubKey) {
if (Array.isArray(pubKey)) {
let ret = await db.users.bulkGet(pubKey);
let filtered = ret.filter(a => a !== undefined).map(a => a!);
return new Map(filtered.map(a => [a.pubkey, a]))
} else {
let ret = await db.users.get(pubKey);
if (ret) {
userList.set(ret.pubkey, ret);
}
}
}
return userList;
}, [pubKey]);
export function useUserProfile(pubKey: HexKey): MetadataCache | undefined {
const users = useKey(pubKey);
useEffect(() => {
if (pubKey) {
@ -30,5 +16,19 @@ export default function useProfile(pubKey?: HexKey | Array<HexKey> | undefined):
}
}, [pubKey]);
return user;
}
return users;
}
export function useUserProfiles(pubKeys: Array<HexKey>): Map<HexKey, MetadataCache> | undefined {
const users = useKeys(pubKeys);
useEffect(() => {
if (pubKeys) {
System.TrackMetadata(pubKeys);
return () => System.UntrackMetadata(pubKeys);
}
}, [pubKeys]);
return users;
}

View File

@ -1,7 +1,7 @@
import { HexKey, TaggedRawEvent } from "Nostr";
import { ProfileCacheExpire } from "Const";
import { db } from "Db";
import { mapEventToProfile, MetadataCache } from "Db/User";
import { mapEventToProfile, MetadataCache, } from "State/Users";
import { getDb } from "State/Users/Db";
import Connection, { RelaySettings } from "Nostr/Connection";
import Event from "Nostr/Event";
import EventKind from "Nostr/EventKind";
@ -167,7 +167,8 @@ export class NostrSystem {
async _FetchMetadata() {
let missing = new Set<HexKey>();
let meta = await db.users.bulkGet(Array.from(this.WantsMetadata));
const db = getDb()
let meta = await db.bulkGet(Array.from(this.WantsMetadata));
let expire = new Date().getTime() - ProfileCacheExpire;
for (let pk of this.WantsMetadata) {
let m = meta.find(a => a?.pubkey === pk);
@ -190,18 +191,18 @@ export class NostrSystem {
sub.OnEvent = async (e) => {
let profile = mapEventToProfile(e);
if (profile) {
let existing = await db.users.get(profile.pubkey);
if ((existing?.created ?? 0) < profile.created) {
await db.users.put(profile);
} else if (existing) {
await db.users.update(profile.pubkey, { loaded: new Date().getTime() });
let existing = await db.find(profile.pubkey);
if((existing?.created ?? 0) < profile.created) {
await db.put(profile);
} else if(existing) {
await db.update(profile.pubkey, { loaded: new Date().getTime() });
}
}
}
let results = await this.RequestSubscription(sub);
let couldNotFetch = Array.from(missing).filter(a => !results.some(b => b.pubkey === a));
console.debug("No profiles: ", couldNotFetch);
await db.users.bulkPut(couldNotFetch.map(a => {
await db.bulkPut(couldNotFetch.map(a => {
return {
pubkey: a,
loaded: new Date().getTime()
@ -213,4 +214,4 @@ export class NostrSystem {
}
}
export const System = new NostrSystem();
export const System = new NostrSystem();

View File

@ -6,7 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGear, faEnvelope } from "@fortawesome/free-solid-svg-icons";
import { useNavigate, useParams } from "react-router-dom";
import useProfile from "Feed/ProfileFeed";
import { useUserProfile } from "Feed/ProfileFeed";
import FollowButton from "Element/FollowButton";
import { extractLnAddress, parseId, hexToBech32 } from "Util";
import Avatar from "Element/Avatar";
@ -33,7 +33,7 @@ export default function ProfilePage() {
const params = useParams();
const navigate = useNavigate();
const id = useMemo(() => parseId(params.id!), [params]);
const user = useProfile(id)?.get(id);
const user = useUserProfile(id);
const loginPubKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const follows = useSelector<RootState, HexKey[]>(s => s.login.follows);
const isMe = loginPubKey === id;

View File

@ -8,7 +8,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faShop } from "@fortawesome/free-solid-svg-icons";
import useEventPublisher from "Feed/EventPublisher";
import useProfile from "Feed/ProfileFeed";
import { useUserProfile } from "Feed/ProfileFeed";
import VoidUpload from "Feed/VoidUpload";
import { logout } from "State/Login";
import { hexToBech32, openFile } from "Util";
@ -22,7 +22,7 @@ export default function ProfileSettings() {
const id = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const privKey = useSelector<RootState, HexKey | undefined>(s => s.login.privateKey);
const dispatch = useDispatch();
const user = useProfile(id)?.get(id || "");
const user = useUserProfile(id!);
const publisher = useEventPublisher();
const [name, setName] = useState<string>();

View File

@ -1,13 +1,15 @@
import { configureStore } from "@reduxjs/toolkit";
import { reducer as LoginReducer } from "State/Login";
import { reducer as UsersReducer } from "State/Users";
const store = configureStore({
reducer: {
login: LoginReducer
login: LoginReducer,
users: UsersReducer,
}
});
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export default store;
export default store;

75
src/State/Users.ts Normal file
View File

@ -0,0 +1,75 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { HexKey, TaggedRawEvent, UserMetadata } from "Nostr";
import { hexToBech32 } from "../Util";
export interface MetadataCache extends UserMetadata {
/**
* When the object was saved in cache
*/
loaded: number,
/**
* When the source metadata event was created
*/
created: number,
/**
* The pubkey of the owner of this metadata
*/
pubkey: HexKey
/**
* The bech32 encoded pubkey
*/
npub: string
};
export function mapEventToProfile(ev: TaggedRawEvent) {
try {
let data: UserMetadata = JSON.parse(ev.content);
return {
pubkey: ev.pubkey,
npub: hexToBech32("npub", ev.pubkey),
created: ev.created_at,
loaded: new Date().getTime(),
...data
} as MetadataCache;
} catch (e) {
console.error("Failed to parse JSON", ev, e);
}
}
export interface UsersDb {
isAvailable(): Promise<boolean>
query(str: string): Promise<MetadataCache[]>
find(key: HexKey): Promise<MetadataCache | undefined>
add(user: MetadataCache): Promise<any>
put(user: MetadataCache): Promise<any>
bulkAdd(users: MetadataCache[]): Promise<any>
bulkGet(keys: HexKey[]): Promise<MetadataCache[]>
bulkPut(users: MetadataCache[]): Promise<any>
update(key: HexKey, fields: Record<string, any>): Promise<any>
}
export interface UsersStore {
/**
* A list of seen users
*/
users: Record<HexKey, MetadataCache>,
};
const InitState = { users: {} } as UsersStore;
const UsersSlice = createSlice({
name: "Users",
initialState: InitState,
reducers: {
setUsers(state, action: PayloadAction<Record<HexKey, MetadataCache>>) {
state.users = action.payload
}
}
});
export const { setUsers } = UsersSlice.actions
export const reducer = UsersSlice.reducer;

149
src/State/Users/Db.ts Normal file
View File

@ -0,0 +1,149 @@
import { HexKey } from "Nostr";
import { db as idb } from "Db";
import { UsersDb, MetadataCache, setUsers } from "State/Users";
import store from "State/Store";
class IndexedDb implements UsersDb {
isAvailable() {
if ("indexedDB" in window) {
return new Promise<boolean>((resolve) => {
const req = window.indexedDB.open("dummy", 1)
req.onsuccess = (ev) => {
resolve(true)
}
req.onerror = (ev) => {
resolve(false)
}
})
}
return Promise.resolve(false)
}
find(key: HexKey) {
return idb.users.get(key);
}
query(q: string) {
return idb.users
.where("npub").startsWithIgnoreCase(q)
.or("name").startsWithIgnoreCase(q)
.or("display_name").startsWithIgnoreCase(q)
.or("nip05").startsWithIgnoreCase(q)
.limit(5)
.toArray()
}
bulkGet(keys: HexKey[]) {
return idb.users.bulkGet(keys).then(ret => ret.filter(a => a !== undefined).map(a => a!));
}
add(user: MetadataCache) {
return idb.users.add(user)
}
put(user: MetadataCache) {
return idb.users.put(user)
}
bulkAdd(users: MetadataCache[]) {
return idb.users.bulkAdd(users)
}
bulkPut(users: MetadataCache[]) {
return idb.users.bulkPut(users)
}
update(key: HexKey, fields: Record<string, any>) {
return idb.users.update(key, fields)
}
}
function groupByPubkey(acc: Record<HexKey, MetadataCache>, user: MetadataCache) {
return { ...acc, [user.pubkey]: user }
}
class ReduxUsersDb implements UsersDb {
async isAvailable() { return true }
async query(q: string) {
const state = store.getState()
const { users } = state.users
return Object.values(users).filter(user => {
const profile = user as MetadataCache
return profile.name?.includes(q)
|| profile.npub?.includes(q)
|| profile.display_name?.includes(q)
|| profile.nip05?.includes(q)
})
}
async find(key: HexKey) {
const state = store.getState()
const { users } = state.users
return users[key]
}
async add(user: MetadataCache) {
const state = store.getState()
const { users } = state.users
store.dispatch(setUsers({...users, [user.pubkey]: user }))
}
async put(user: MetadataCache) {
const state = store.getState()
const { users } = state.users
store.dispatch(setUsers({...users, [user.pubkey]: user }))
}
async bulkAdd(newUserProfiles: MetadataCache[]) {
const state = store.getState()
const { users } = state.users
const newUsers = newUserProfiles.reduce(groupByPubkey, {})
store.dispatch(setUsers({...users, ...newUsers }))
}
async bulkGet(keys: HexKey[]) {
const state = store.getState()
const { users } = state.users
const ids = new Set([...keys])
return Object.values(users).filter(user => {
return ids.has(user.pubkey)
})
}
async update(key: HexKey, fields: Record<string, any>) {
const state = store.getState()
const { users } = state.users
const current = users[key]
const updated = {...current, ...fields }
store.dispatch(setUsers({...users, [key]: updated }))
}
async bulkPut(newUsers: MetadataCache[]) {
const state = store.getState()
const { users } = state.users
const newProfiles = newUsers.reduce(groupByPubkey, {})
store.dispatch(setUsers({ ...users, ...newProfiles }))
}
}
const indexedDb = new IndexedDb()
export const inMemoryDb = new ReduxUsersDb()
let db: UsersDb = inMemoryDb
indexedDb.isAvailable().then((available) => {
if (available) {
console.debug('Using Indexed DB')
db = indexedDb;
} else {
console.debug('Using in-memory DB')
}
})
export function getDb() {
return db
}

60
src/State/Users/Hooks.ts Normal file
View File

@ -0,0 +1,60 @@
import { useSelector } from "react-redux"
import { useLiveQuery } from "dexie-react-hooks";
import { MetadataCache } from "State/Users";
import { getDb, inMemoryDb } from "State/Users/Db";
import type { RootState } from "State/Store"
import { HexKey } from "Nostr";
export function useQuery(query: string, limit: number = 5) {
const db = getDb()
const allUsers = useLiveQuery(
() => db.query(query)
.catch((err) => {
console.error(err)
}).then(() => {
return inMemoryDb.query(query)
}),
[query],
)
return allUsers
}
export function useKey(pubKey: HexKey) {
const db = getDb()
const { users } = useSelector((state: RootState) => state.users)
const defaultUser = users[pubKey]
const user = useLiveQuery(async () => {
if (pubKey) {
try {
return await db.find(pubKey);
} catch (error) {
console.error(error)
return defaultUser
}
}
}, [pubKey, defaultUser]);
return user
}
export function useKeys(pubKeys: HexKey[]): Map<HexKey, MetadataCache> {
const db = getDb()
const dbUsers = useLiveQuery(async () => {
if (pubKeys) {
try {
const ret = await db.bulkGet(pubKeys);
return new Map(ret.map(a => [a.pubkey, a]))
} catch (error) {
console.error(error)
const ret = await inMemoryDb.bulkGet(pubKeys);
return new Map(ret.map(a => [a.pubkey, a]))
}
}
return new Map()
}, [pubKeys]);
return dbUsers!
}