fix: cache preload
feat: Fetch only newest relay list updates
This commit is contained in:
parent
2ccee7bd7c
commit
988416f353
@ -11,8 +11,8 @@ class DMCache extends FeedCache<RawEvent> {
|
|||||||
return of.id;
|
return of.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async preload(): Promise<void> {
|
override async preload(follows?: Array<string>): Promise<void> {
|
||||||
await super.preload();
|
await super.preload(follows);
|
||||||
// load all dms to memory
|
// load all dms to memory
|
||||||
await this.buffer([...this.onTable]);
|
await this.buffer([...this.onTable]);
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,8 @@ class EventInteractionCache extends FeedCache<EventInteraction> {
|
|||||||
return sha256(of.event + of.by);
|
return sha256(of.event + of.by);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async preload(): Promise<void> {
|
override async preload(follows?: Array<string>): Promise<void> {
|
||||||
await super.preload();
|
await super.preload(follows);
|
||||||
|
|
||||||
const data = window.localStorage.getItem("zap-cache");
|
const data = window.localStorage.getItem("zap-cache");
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -12,18 +12,18 @@ interface HookFilter {
|
|||||||
|
|
||||||
export default abstract class FeedCache<TCached> {
|
export default abstract class FeedCache<TCached> {
|
||||||
#name: string;
|
#name: string;
|
||||||
#table: Table<TCached>;
|
|
||||||
#hooks: Array<HookFilter> = [];
|
#hooks: Array<HookFilter> = [];
|
||||||
#snapshot: Readonly<Array<TCached>> = [];
|
#snapshot: Readonly<Array<TCached>> = [];
|
||||||
#changed = true;
|
#changed = true;
|
||||||
#hits = 0;
|
#hits = 0;
|
||||||
#miss = 0;
|
#miss = 0;
|
||||||
|
protected table: Table<TCached>;
|
||||||
protected onTable: Set<string> = new Set();
|
protected onTable: Set<string> = new Set();
|
||||||
protected cache: Map<string, TCached> = new Map();
|
protected cache: Map<string, TCached> = new Map();
|
||||||
|
|
||||||
constructor(name: string, table: Table<TCached>) {
|
constructor(name: string, table: Table<TCached>) {
|
||||||
this.#name = name;
|
this.#name = name;
|
||||||
this.#table = table;
|
this.table = table;
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
debug(this.#name)(
|
debug(this.#name)(
|
||||||
"%d loaded, %d on-disk, %d hooks, %d% hit",
|
"%d loaded, %d on-disk, %d hooks, %d% hit",
|
||||||
@ -35,9 +35,9 @@ export default abstract class FeedCache<TCached> {
|
|||||||
}, 30_000);
|
}, 30_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async preload() {
|
async preload(follows?: Array<string>) {
|
||||||
if (db.ready) {
|
if (db.ready) {
|
||||||
const keys = await this.#table.toCollection().primaryKeys();
|
const keys = await this.table.toCollection().primaryKeys();
|
||||||
this.onTable = new Set<string>(keys.map(a => a as string));
|
this.onTable = new Set<string>(keys.map(a => a as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
|
|
||||||
async get(key?: string) {
|
async get(key?: string) {
|
||||||
if (key && !this.cache.has(key) && db.ready) {
|
if (key && !this.cache.has(key) && db.ready) {
|
||||||
const cached = await this.#table.get(key);
|
const cached = await this.table.get(key);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
this.cache.set(this.key(cached), cached);
|
this.cache.set(this.key(cached), cached);
|
||||||
this.notifyChange([key]);
|
this.notifyChange([key]);
|
||||||
@ -88,7 +88,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
async bulkGet(keys: Array<string>) {
|
async bulkGet(keys: Array<string>) {
|
||||||
const missing = keys.filter(a => !this.cache.has(a));
|
const missing = keys.filter(a => !this.cache.has(a));
|
||||||
if (missing.length > 0 && db.ready) {
|
if (missing.length > 0 && db.ready) {
|
||||||
const cached = await this.#table.bulkGet(missing);
|
const cached = await this.table.bulkGet(missing);
|
||||||
cached.forEach(a => {
|
cached.forEach(a => {
|
||||||
if (a) {
|
if (a) {
|
||||||
this.cache.set(this.key(a), a);
|
this.cache.set(this.key(a), a);
|
||||||
@ -105,7 +105,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
const k = this.key(obj);
|
const k = this.key(obj);
|
||||||
this.cache.set(k, obj);
|
this.cache.set(k, obj);
|
||||||
if (db.ready) {
|
if (db.ready) {
|
||||||
await this.#table.put(obj);
|
await this.table.put(obj);
|
||||||
this.onTable.add(k);
|
this.onTable.add(k);
|
||||||
}
|
}
|
||||||
this.notifyChange([k]);
|
this.notifyChange([k]);
|
||||||
@ -113,7 +113,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
|
|
||||||
async bulkSet(obj: Array<TCached>) {
|
async bulkSet(obj: Array<TCached>) {
|
||||||
if (db.ready) {
|
if (db.ready) {
|
||||||
await this.#table.bulkPut(obj);
|
await this.table.bulkPut(obj);
|
||||||
obj.forEach(a => this.onTable.add(this.key(a)));
|
obj.forEach(a => this.onTable.add(this.key(a)));
|
||||||
}
|
}
|
||||||
obj.forEach(v => this.cache.set(this.key(v), v));
|
obj.forEach(v => this.cache.set(this.key(v), v));
|
||||||
@ -164,7 +164,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
key: a,
|
key: a,
|
||||||
}));
|
}));
|
||||||
const start = unixNowMs();
|
const start = unixNowMs();
|
||||||
const fromCache = await this.#table.bulkGet(mapped.filter(a => a.has).map(a => a.key));
|
const fromCache = await this.table.bulkGet(mapped.filter(a => a.has).map(a => a.key));
|
||||||
const fromCacheFiltered = fromCache.filter(a => a !== undefined).map(a => unwrap(a));
|
const fromCacheFiltered = fromCache.filter(a => a !== undefined).map(a => unwrap(a));
|
||||||
fromCacheFiltered.forEach(a => {
|
fromCacheFiltered.forEach(a => {
|
||||||
this.cache.set(this.key(a), a);
|
this.cache.set(this.key(a), a);
|
||||||
@ -184,7 +184,7 @@ export default abstract class FeedCache<TCached> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async clear() {
|
async clear() {
|
||||||
await this.#table.clear();
|
await this.table.clear();
|
||||||
this.cache.clear();
|
this.cache.clear();
|
||||||
this.onTable.clear();
|
this.onTable.clear();
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,14 @@ class UserProfileCache extends FeedCache<MetadataCache> {
|
|||||||
return of.pubkey;
|
return of.pubkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async preload(follows?: Array<string>): Promise<void> {
|
||||||
|
await super.preload(follows);
|
||||||
|
// load follows profiles
|
||||||
|
if (follows) {
|
||||||
|
await this.buffer(follows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async search(q: string): Promise<Array<MetadataCache>> {
|
async search(q: string): Promise<Array<MetadataCache>> {
|
||||||
if (db.ready) {
|
if (db.ready) {
|
||||||
// on-disk cache will always have more data
|
// on-disk cache will always have more data
|
||||||
|
@ -10,6 +10,19 @@ class UsersRelaysCache extends FeedCache<UsersRelays> {
|
|||||||
return of.pubkey;
|
return of.pubkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async preload(follows?: Array<string>): Promise<void> {
|
||||||
|
await super.preload(follows);
|
||||||
|
if (follows) {
|
||||||
|
await this.buffer(follows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newest(): number {
|
||||||
|
let ret = 0;
|
||||||
|
this.cache.forEach(v => (ret = v.created_at > ret ? v.created_at : ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
takeSnapshot(): Array<UsersRelays> {
|
takeSnapshot(): Array<UsersRelays> {
|
||||||
return [...this.cache.values()];
|
return [...this.cache.values()];
|
||||||
}
|
}
|
||||||
|
@ -52,11 +52,14 @@ export function mapEventToProfile(ev: RawEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function preload() {
|
export async function preload(follows?: Array<string>) {
|
||||||
await UserCache.preload();
|
const preloads = [
|
||||||
await DmCache.preload();
|
UserCache.preload(follows),
|
||||||
await InteractionCache.preload();
|
DmCache.preload(follows),
|
||||||
await UserRelays.preload();
|
InteractionCache.preload(follows),
|
||||||
|
UserRelays.preload(follows),
|
||||||
|
];
|
||||||
|
await Promise.all(preloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { UserCache, DmCache };
|
export { UserCache, DmCache };
|
||||||
|
@ -21,6 +21,7 @@ export interface RelayMetrics {
|
|||||||
|
|
||||||
export interface UsersRelays {
|
export interface UsersRelays {
|
||||||
pubkey: HexKey;
|
pubkey: HexKey;
|
||||||
|
created_at: number;
|
||||||
relays: FullRelaySettings[];
|
relays: FullRelaySettings[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { TaggedRawEvent, Lists, EventKind } from "@snort/nostr";
|
import { TaggedRawEvent, Lists, EventKind } from "@snort/nostr";
|
||||||
|
import debug from "debug";
|
||||||
|
|
||||||
import { bech32ToHex, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils";
|
import { bech32ToHex, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils";
|
||||||
import { makeNotification, sendNotification } from "Notifications";
|
import { makeNotification, sendNotification } from "Notifications";
|
||||||
@ -41,6 +42,7 @@ export default function useLoginFeed() {
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
const dmSince = DmCache.newest();
|
const dmSince = DmCache.newest();
|
||||||
|
debug("LoginFeed")("Loading dms since %s", new Date(dmSince * 1000).toISOString());
|
||||||
b.withFilter().authors([pubKey]).kinds([EventKind.DirectMessage]).since(dmSince);
|
b.withFilter().authors([pubKey]).kinds([EventKind.DirectMessage]).since(dmSince);
|
||||||
b.withFilter().kinds([EventKind.DirectMessage]).tag("p", [pubKey]).since(dmSince);
|
b.withFilter().kinds([EventKind.DirectMessage]).tag("p", [pubKey]).since(dmSince);
|
||||||
return b;
|
return b;
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, FullRelaySettings, TaggedRawEvent, RelaySettings, EventKind } from "@snort/nostr";
|
import { HexKey, FullRelaySettings, TaggedRawEvent, RelaySettings, EventKind } from "@snort/nostr";
|
||||||
|
import debug from "debug";
|
||||||
|
|
||||||
import { sanitizeRelayUrl } from "SnortUtils";
|
import { sanitizeRelayUrl } from "SnortUtils";
|
||||||
import { PubkeyReplaceableNoteStore, RequestBuilder } from "System";
|
import { PubkeyReplaceableNoteStore, RequestBuilder } from "System";
|
||||||
import useRequestBuilder from "Hooks/useRequestBuilder";
|
import useRequestBuilder from "Hooks/useRequestBuilder";
|
||||||
|
import { UserRelays } from "Cache/UserRelayCache";
|
||||||
|
|
||||||
interface RelayList {
|
interface RelayList {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
created: number;
|
created_at: number;
|
||||||
relays: FullRelaySettings[];
|
relays: FullRelaySettings[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList> {
|
export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList> {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
const b = new RequestBuilder(`relays:follows`);
|
const b = new RequestBuilder(`relays:follows`);
|
||||||
b.withFilter().authors(pubkeys).kinds([EventKind.Relays, EventKind.ContactList]);
|
const since = UserRelays.newest();
|
||||||
|
debug("LoginFeed")("Loading relay lists since %s", new Date(since * 1000).toISOString());
|
||||||
|
b.withFilter().authors(pubkeys).kinds([EventKind.Relays, EventKind.ContactList]).since(since);
|
||||||
return b;
|
return b;
|
||||||
}, [pubkeys]);
|
}, [pubkeys]);
|
||||||
|
|
||||||
@ -22,7 +26,7 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList
|
|||||||
return notes.map(ev => {
|
return notes.map(ev => {
|
||||||
return {
|
return {
|
||||||
pubkey: ev.pubkey,
|
pubkey: ev.pubkey,
|
||||||
created: ev.created_at,
|
created_at: ev.created_at,
|
||||||
relays: ev.tags
|
relays: ev.tags
|
||||||
.map(a => {
|
.map(a => {
|
||||||
return {
|
return {
|
||||||
@ -45,7 +49,7 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList
|
|||||||
const relays: Record<string, RelaySettings> = JSON.parse(ev.content);
|
const relays: Record<string, RelaySettings> = JSON.parse(ev.content);
|
||||||
return {
|
return {
|
||||||
pubkey: ev.pubkey,
|
pubkey: ev.pubkey,
|
||||||
created: ev.created_at,
|
created_at: ev.created_at,
|
||||||
relays: Object.entries(relays)
|
relays: Object.entries(relays)
|
||||||
.map(([k, v]) => {
|
.map(([k, v]) => {
|
||||||
return {
|
return {
|
||||||
@ -61,7 +65,7 @@ export default function useRelaysFeedFollows(pubkeys: HexKey[]): Array<RelayList
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
pubkey: ev.pubkey,
|
pubkey: ev.pubkey,
|
||||||
created: 0,
|
created_at: 0,
|
||||||
relays: [],
|
relays: [],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -14,9 +14,7 @@ import useLoginFeed from "Feed/LoginFeed";
|
|||||||
import { totalUnread } from "Pages/MessagesPage";
|
import { totalUnread } from "Pages/MessagesPage";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
import { NoteCreator } from "Element/NoteCreator";
|
import { NoteCreator } from "Element/NoteCreator";
|
||||||
import { db } from "Db";
|
|
||||||
import useEventPublisher from "Feed/EventPublisher";
|
import useEventPublisher from "Feed/EventPublisher";
|
||||||
import { preload } from "Cache";
|
|
||||||
import { useDmCache } from "Hooks/useDmsCache";
|
import { useDmCache } from "Hooks/useDmsCache";
|
||||||
import { mapPlanName } from "./subscribe";
|
import { mapPlanName } from "./subscribe";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
@ -111,29 +109,6 @@ export default function Layout() {
|
|||||||
};
|
};
|
||||||
}, [preferences.theme]);
|
}, [preferences.theme]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// check DB support then init
|
|
||||||
db.isAvailable().then(async a => {
|
|
||||||
db.ready = a;
|
|
||||||
if (a) {
|
|
||||||
await preload();
|
|
||||||
}
|
|
||||||
console.debug(`Using db: ${a ? "IndexedDB" : "In-Memory"}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ("registerProtocolHandler" in window.navigator) {
|
|
||||||
window.navigator.registerProtocolHandler(
|
|
||||||
"web+nostr",
|
|
||||||
`${window.location.protocol}//${window.location.host}/%s`
|
|
||||||
);
|
|
||||||
console.info("Registered protocol handler for 'web+nostr'");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to register protocol handler", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={pageClass}>
|
<div className={pageClass}>
|
||||||
{!shouldHideHeader && (
|
{!shouldHideHeader && (
|
||||||
|
@ -247,7 +247,7 @@ export class Query implements QueryBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ((q.relays?.length ?? 0) === 0 && c.Ephemeral) {
|
if ((q.relays?.length ?? 0) === 0 && c.Ephemeral) {
|
||||||
this.#log("Cant send non-specific REQ to ephemeral connection");
|
this.#log("Cant send non-specific REQ to ephemeral connection %o", q.relays);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (q.filters.some(a => a.search) && !c.SupportsNip(Nips.Search)) {
|
if (q.filters.some(a => a.search) && !c.SupportsNip(Nips.Search)) {
|
||||||
|
@ -221,7 +221,7 @@ export class NostrSystem extends ExternalStore<SystemSnapshot> {
|
|||||||
if (rb.options?.leaveOpen) {
|
if (rb.options?.leaveOpen) {
|
||||||
q.leaveOpen = rb.options.leaveOpen;
|
q.leaveOpen = rb.options.leaveOpen;
|
||||||
}
|
}
|
||||||
if (rb.options?.relays) {
|
if (rb.options?.relays && (rb.options?.relays?.length ?? 0) > 0) {
|
||||||
q.relays = rb.options.relays;
|
q.relays = rb.options.relays;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,9 @@ import Thread from "Element/Thread";
|
|||||||
import { SubscribeRoutes } from "Pages/subscribe";
|
import { SubscribeRoutes } from "Pages/subscribe";
|
||||||
import ZapPoolPage from "Pages/ZapPool";
|
import ZapPoolPage from "Pages/ZapPool";
|
||||||
import DebugPage from "Pages/Debug";
|
import DebugPage from "Pages/Debug";
|
||||||
|
import { db } from "Db";
|
||||||
|
import { preload } from "Cache";
|
||||||
|
import { LoginStore } from "Login";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.__webpack_nonce__ = "ZmlhdGphZiBzYWlkIHNub3J0LnNvY2lhbCBpcyBwcmV0dHkgZ29vZCwgd2UgbWFkZSBpdCE=";
|
window.__webpack_nonce__ = "ZmlhdGphZiBzYWlkIHNub3J0LnNvY2lhbCBpcyBwcmV0dHkgZ29vZCwgd2UgbWFkZSBpdCE=";
|
||||||
@ -42,6 +45,25 @@ export const router = createBrowserRouter([
|
|||||||
{
|
{
|
||||||
element: <Layout />,
|
element: <Layout />,
|
||||||
errorElement: <ErrorPage />,
|
errorElement: <ErrorPage />,
|
||||||
|
loader: async () => {
|
||||||
|
db.ready = await db.isAvailable();
|
||||||
|
if (db.ready) {
|
||||||
|
await preload(LoginStore.takeSnapshot().follows.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ("registerProtocolHandler" in window.navigator) {
|
||||||
|
window.navigator.registerProtocolHandler(
|
||||||
|
"web+nostr",
|
||||||
|
`${window.location.protocol}//${window.location.host}/%s`
|
||||||
|
);
|
||||||
|
console.info("Registered protocol handler for 'web+nostr'");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to register protocol handler", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
...RootRoutes,
|
...RootRoutes,
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user