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 Dexie, { Table } from 'dexie';
|
||||||
|
import { MetadataCache } from './state/Users';
|
||||||
import type { User } from './nostr/types';
|
|
||||||
|
|
||||||
export class SnortDB extends Dexie {
|
export class SnortDB extends Dexie {
|
||||||
users!: Table<User>;
|
users!: Table<MetadataCache>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super('snortDB');
|
super('snortDB');
|
||||||
|
@ -11,12 +11,12 @@ import "./Textarea.css";
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
import Nostrich from "../nostrich.jpg";
|
import Nostrich from "../nostrich.jpg";
|
||||||
import { hexToBech32 } from "../Util";
|
import { hexToBech32 } from "../Util";
|
||||||
import type { User } from "../nostr/types";
|
|
||||||
import { db } from "../db";
|
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()
|
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)
|
return name?.toLowerCase().includes(q)
|
||||||
|| display_name?.toLowerCase().includes(q)
|
|| display_name?.toLowerCase().includes(q)
|
||||||
|| about?.toLowerCase().includes(q)
|
|| about?.toLowerCase().includes(q)
|
||||||
@ -24,7 +24,7 @@ function searchUsers(query: string, users: User[]) {
|
|||||||
}).slice(0, 3)
|
}).slice(0, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: User) => {
|
const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: MetadataCache) => {
|
||||||
return (
|
return (
|
||||||
<div key={pubkey} className="user-item">
|
<div key={pubkey} className="user-item">
|
||||||
<div className="user-picture">
|
<div className="user-picture">
|
||||||
@ -38,7 +38,7 @@ 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 }
|
return { pubkey, nip05, name, picture, display_name }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ const Textarea = ({ users, onChange, ...rest }: any) => {
|
|||||||
}, {})
|
}, {})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const allUsers: User[] = Object.values({...normalizedUsers, ...dbUsers})
|
const allUsers: MetadataCache[] = Object.values({ ...normalizedUsers, ...dbUsers })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactTextareaAutocomplete
|
<ReactTextareaAutocomplete
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { db } from "../db";
|
||||||
import { HexKey } from "../nostr";
|
import { HexKey } from "../nostr";
|
||||||
import { RootState } from "../state/Store";
|
import { System } from "../nostr/System";
|
||||||
import { addPubKey, MetadataCache } from "../state/Users";
|
|
||||||
|
|
||||||
export default function useProfile(pubKey: HexKey) {
|
export default function useProfile(pubKey: HexKey) {
|
||||||
const dispatch = useDispatch();
|
const user = useLiveQuery(async () => {
|
||||||
const user = useSelector<RootState, MetadataCache>(s => s.users.users[pubKey]);
|
return await db.users.get(pubKey);
|
||||||
|
}, [pubKey]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pubKey) {
|
System.GetMetadata(pubKey);
|
||||||
dispatch(addPubKey(pubKey));
|
|
||||||
}
|
|
||||||
}, [pubKey]);
|
}, [pubKey]);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
@ -42,16 +42,13 @@ export default function useUsersCache() {
|
|||||||
.filter(a => a !== undefined)
|
.filter(a => a !== undefined)
|
||||||
.map(a => a!);
|
.map(a => a!);
|
||||||
dispatch(setUserData(profiles));
|
dispatch(setUserData(profiles));
|
||||||
const dbProfiles = results.notes.map(ev => {
|
db.users.bulkPut(profiles);
|
||||||
return { ...JSON.parse(ev.content), pubkey: ev.pubkey }
|
|
||||||
});
|
|
||||||
db.users.bulkPut(dbProfiles);
|
|
||||||
}, [results]);
|
}, [results]);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapEventToProfile(ev: TaggedRawEvent): MetadataCache | undefined {
|
export function mapEventToProfile(ev: TaggedRawEvent) {
|
||||||
try {
|
try {
|
||||||
let data: UserMetadata = JSON.parse(ev.content);
|
let data: UserMetadata = JSON.parse(ev.content);
|
||||||
return {
|
return {
|
||||||
@ -59,7 +56,7 @@ export function mapEventToProfile(ev: TaggedRawEvent): MetadataCache | undefined
|
|||||||
created: ev.created_at,
|
created: ev.created_at,
|
||||||
loaded: new Date().getTime(),
|
loaded: new Date().getTime(),
|
||||||
...data
|
...data
|
||||||
};
|
} as MetadataCache;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to parse JSON", ev, 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 Connection, { RelaySettings } from "./Connection";
|
||||||
import Event from "./Event";
|
import Event from "./Event";
|
||||||
|
import EventKind from "./EventKind";
|
||||||
import { Subscriptions } from "./Subscriptions";
|
import { Subscriptions } from "./Subscriptions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,10 +26,17 @@ export class NostrSystem {
|
|||||||
*/
|
*/
|
||||||
PendingSubscriptions: Subscriptions[];
|
PendingSubscriptions: Subscriptions[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of pubkeys to fetch metadata for
|
||||||
|
*/
|
||||||
|
WantsMetadata: Set<HexKey>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.Sockets = new Map();
|
this.Sockets = new Map();
|
||||||
this.Subscriptions = new Map();
|
this.Subscriptions = new Map();
|
||||||
this.PendingSubscriptions = [];
|
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
|
* Request/Response pattern
|
||||||
*/
|
*/
|
||||||
RequestSubscription(sub: Subscriptions) {
|
RequestSubscription(sub: Subscriptions) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<TaggedRawEvent[]>((resolve, reject) => {
|
||||||
let events: TaggedRawEvent[] = [];
|
let events: TaggedRawEvent[] = [];
|
||||||
|
|
||||||
// force timeout returning current results
|
// force timeout returning current results
|
||||||
@ -119,6 +136,37 @@ export class NostrSystem {
|
|||||||
this.AddSubscription(sub);
|
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();
|
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 { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
import { ProfileCacheExpire } from '../Const';
|
|
||||||
import { db } from '../db';
|
|
||||||
import { HexKey, UserMetadata } from '../nostr';
|
import { HexKey, UserMetadata } from '../nostr';
|
||||||
|
|
||||||
export interface MetadataCache extends UserMetadata {
|
export interface MetadataCache extends UserMetadata {
|
||||||
@ -27,10 +25,10 @@ export interface UsersStore {
|
|||||||
|
|
||||||
const UsersSlice = createSlice({
|
const UsersSlice = createSlice({
|
||||||
name: "Users",
|
name: "Users",
|
||||||
initialState: <UsersStore>{
|
initialState: {
|
||||||
pubKeys: [],
|
pubKeys: [],
|
||||||
users: {},
|
users: {},
|
||||||
},
|
} as UsersStore,
|
||||||
reducers: {
|
reducers: {
|
||||||
addPubKey: (state, action: PayloadAction<string | Array<string>>) => {
|
addPubKey: (state, action: PayloadAction<string | Array<string>>) => {
|
||||||
let keys = action.payload;
|
let keys = action.payload;
|
||||||
@ -38,31 +36,15 @@ const UsersSlice = createSlice({
|
|||||||
keys = [keys];
|
keys = [keys];
|
||||||
}
|
}
|
||||||
let changes = false;
|
let changes = false;
|
||||||
let fromCache = false;
|
|
||||||
let temp = new Set(state.pubKeys);
|
let temp = new Set(state.pubKeys);
|
||||||
for (let k of keys) {
|
for (let k of keys) {
|
||||||
if (!temp.has(k)) {
|
if (!temp.has(k)) {
|
||||||
changes = true;
|
changes = true;
|
||||||
temp.add(k);
|
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) {
|
if (changes) {
|
||||||
state.pubKeys = Array.from(temp);
|
state.pubKeys = Array.from(temp);
|
||||||
if (fromCache) {
|
|
||||||
state.users = {
|
|
||||||
...state.users
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setUserData: (state, action: PayloadAction<MetadataCache | Array<MetadataCache>>) => {
|
setUserData: (state, action: PayloadAction<MetadataCache | Array<MetadataCache>>) => {
|
||||||
@ -84,8 +66,6 @@ const UsersSlice = createSlice({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
state.users[x.pubkey] = x;
|
state.users[x.pubkey] = x;
|
||||||
db.users.put(x)
|
|
||||||
|
|
||||||
state.users = {
|
state.users = {
|
||||||
...state.users
|
...state.users
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user