Profile fetcher

This commit is contained in:
Kieran 2023-01-16 00:07:27 +00:00
parent bd247991bc
commit b74f8f33dd
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 80 additions and 66 deletions

View File

@ -1,9 +1,8 @@
import Dexie, { Table } from 'dexie';
import type { User } from './nostr/types';
import { MetadataCache } from './state/Users';
export class SnortDB extends Dexie {
users!: Table<User>;
users!: Table<MetadataCache>;
constructor() {
super('snortDB');

View File

@ -11,12 +11,12 @@ import "./Textarea.css";
// @ts-expect-error
import Nostrich from "../nostrich.jpg";
import { hexToBech32 } from "../Util";
import type { User } from "../nostr/types";
import { db } from "../db";
import { MetadataCache } from "../state/Users";
function searchUsers(query: string, users: User[]) {
function searchUsers(query: string, users: MetadataCache[]) {
const q = query.toLowerCase()
return users.filter(({ name, display_name, about, nip05 }: User) => {
return users.filter(({ name, display_name, about, nip05 }: MetadataCache) => {
return name?.toLowerCase().includes(q)
|| display_name?.toLowerCase().includes(q)
|| about?.toLowerCase().includes(q)
@ -24,7 +24,7 @@ function searchUsers(query: string, users: User[]) {
}).slice(0, 3)
}
const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: User) => {
const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: MetadataCache) => {
return (
<div key={pubkey} className="user-item">
<div className="user-picture">
@ -38,22 +38,22 @@ const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: User) => {
)
}
function normalizeUser({ pubkey, picture, nip05, name, display_name }: User) {
function normalizeUser({ pubkey, picture, nip05, name, display_name }: MetadataCache) {
return { pubkey, nip05, name, picture, display_name }
}
const Textarea = ({ users, onChange, ...rest }: any) => {
const normalizedUsers = Object.keys(users).reduce((acc, pk) => {
return {...acc, [pk]: normalizeUser(users[pk]) }
}, {})
const dbUsers = useLiveQuery(
() => db.users.toArray().then(usrs => {
return usrs.reduce((acc, usr) => {
return { ...acc, [usr.pubkey]: normalizeUser(usr)}
}, {})
})
)
const allUsers: User[] = Object.values({...normalizedUsers, ...dbUsers})
const normalizedUsers = Object.keys(users).reduce((acc, pk) => {
return { ...acc, [pk]: normalizeUser(users[pk]) }
}, {})
const dbUsers = useLiveQuery(
() => db.users.toArray().then(usrs => {
return usrs.reduce((acc, usr) => {
return { ...acc, [usr.pubkey]: normalizeUser(usr) }
}, {})
})
)
const allUsers: MetadataCache[] = Object.values({ ...normalizedUsers, ...dbUsers })
return (
<ReactTextareaAutocomplete

View File

@ -1,17 +1,16 @@
import { useLiveQuery } from "dexie-react-hooks";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { db } from "../db";
import { HexKey } from "../nostr";
import { RootState } from "../state/Store";
import { addPubKey, MetadataCache } from "../state/Users";
import { System } from "../nostr/System";
export default function useProfile(pubKey: HexKey) {
const dispatch = useDispatch();
const user = useSelector<RootState, MetadataCache>(s => s.users.users[pubKey]);
const user = useLiveQuery(async () => {
return await db.users.get(pubKey);
}, [pubKey]);
useEffect(() => {
if (pubKey) {
dispatch(addPubKey(pubKey));
}
System.GetMetadata(pubKey);
}, [pubKey]);
return user;

View File

@ -42,16 +42,13 @@ export default function useUsersCache() {
.filter(a => a !== undefined)
.map(a => a!);
dispatch(setUserData(profiles));
const dbProfiles = results.notes.map(ev => {
return { ...JSON.parse(ev.content), pubkey: ev.pubkey }
});
db.users.bulkPut(dbProfiles);
db.users.bulkPut(profiles);
}, [results]);
return results;
}
export function mapEventToProfile(ev: TaggedRawEvent): MetadataCache | undefined {
export function mapEventToProfile(ev: TaggedRawEvent) {
try {
let data: UserMetadata = JSON.parse(ev.content);
return {
@ -59,7 +56,7 @@ export function mapEventToProfile(ev: TaggedRawEvent): MetadataCache | undefined
created: ev.created_at,
loaded: new Date().getTime(),
...data
};
} as MetadataCache;
} catch (e) {
console.error("Failed to parse JSON", ev, e);
}

View File

@ -1,6 +1,10 @@
import { TaggedRawEvent } from ".";
import { HexKey, TaggedRawEvent } from ".";
import { ProfileCacheExpire } from "../Const";
import { db } from "../db";
import { mapEventToProfile } from "../feed/UsersFeed";
import Connection, { RelaySettings } from "./Connection";
import Event from "./Event";
import EventKind from "./EventKind";
import { Subscriptions } from "./Subscriptions";
/**
@ -22,10 +26,17 @@ export class NostrSystem {
*/
PendingSubscriptions: Subscriptions[];
/**
* List of pubkeys to fetch metadata for
*/
WantsMetadata: Set<HexKey>;
constructor() {
this.Sockets = new Map();
this.Subscriptions = new Map();
this.PendingSubscriptions = [];
this.WantsMetadata = new Set();
this._FetchMetadata()
}
/**
@ -79,11 +90,17 @@ export class NostrSystem {
}
}
GetMetadata(pk: HexKey) {
if (pk.length > 0) {
this.WantsMetadata.add(pk);
}
}
/**
* Request/Response pattern
*/
RequestSubscription(sub: Subscriptions) {
return new Promise((resolve, reject) => {
return new Promise<TaggedRawEvent[]>((resolve, reject) => {
let events: TaggedRawEvent[] = [];
// force timeout returning current results
@ -119,6 +136,37 @@ export class NostrSystem {
this.AddSubscription(sub);
});
}
async _FetchMetadata() {
let missing = new Set<HexKey>();
for (let pk of this.WantsMetadata) {
let meta = await db.users.get(pk);
let now = new Date().getTime();
if (!meta || meta.loaded < now - ProfileCacheExpire) {
missing.add(pk);
} else {
this.WantsMetadata.delete(pk);
}
}
if (missing.size > 0) {
console.debug("Wants: ", missing);
let sub = new Subscriptions();
sub.Id = `profiles:${sub.Id}`;
sub.Kinds = new Set([EventKind.SetMetadata]);
sub.Authors = missing;
sub.OnEvent = (e) => {
let profile = mapEventToProfile(e);
if (profile) {
db.users.put(profile);
}
}
await this.RequestSubscription(sub);
}
setTimeout(() => this._FetchMetadata(), 500);
}
}
export const System = new NostrSystem();

View File

@ -1,9 +0,0 @@
export interface User {
name?: string
about?: string
display_name?: string
nip05?: string
pubkey: string
picture?: string
}

View File

@ -1,6 +1,4 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { ProfileCacheExpire } from '../Const';
import { db } from '../db';
import { HexKey, UserMetadata } from '../nostr';
export interface MetadataCache extends UserMetadata {
@ -27,10 +25,10 @@ export interface UsersStore {
const UsersSlice = createSlice({
name: "Users",
initialState: <UsersStore>{
initialState: {
pubKeys: [],
users: {},
},
} as UsersStore,
reducers: {
addPubKey: (state, action: PayloadAction<string | Array<string>>) => {
let keys = action.payload;
@ -38,31 +36,15 @@ const UsersSlice = createSlice({
keys = [keys];
}
let changes = false;
let fromCache = false;
let temp = new Set(state.pubKeys);
for (let k of keys) {
if (!temp.has(k)) {
changes = true;
temp.add(k);
// load from cache
let cache = window.localStorage.getItem(`user:${k}`);
if (cache) {
let ud: MetadataCache = JSON.parse(cache);
if (ud.loaded > new Date().getTime() - ProfileCacheExpire) {
state.users[ud.pubkey] = ud;
fromCache = true;
}
}
}
}
if (changes) {
state.pubKeys = Array.from(temp);
if (fromCache) {
state.users = {
...state.users
};
}
}
},
setUserData: (state, action: PayloadAction<MetadataCache | Array<MetadataCache>>) => {
@ -84,8 +66,6 @@ const UsersSlice = createSlice({
};
}
state.users[x.pubkey] = x;
db.users.put(x)
state.users = {
...state.users
};