forked from Kieran/snort
Profile fetcher
This commit is contained in:
parent
bd247991bc
commit
b74f8f33dd
@ -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');
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
@ -1,9 +0,0 @@
|
||||
export interface User {
|
||||
name?: string
|
||||
about?: string
|
||||
display_name?: string
|
||||
nip05?: string
|
||||
pubkey: string
|
||||
picture?: string
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user