diff --git a/packages/app/src/Cache/index.ts b/packages/app/src/Cache/index.ts index 7585f7e..8156b66 100644 --- a/packages/app/src/Cache/index.ts +++ b/packages/app/src/Cache/index.ts @@ -1,9 +1,10 @@ -import { UserProfileCache, UserRelaysCache } from "@snort/system"; +import { UserProfileCache, UserRelaysCache, RelayMetricCache } from "@snort/system"; import { DmCache } from "./DMCache"; import { InteractionCache } from "./EventInteractionCache"; export const UserCache = new UserProfileCache(); export const UserRelays = new UserRelaysCache(); +export const RelayMetrics = new RelayMetricCache(); export { DmCache }; export async function preload(follows?: Array) { @@ -12,6 +13,7 @@ export async function preload(follows?: Array) { DmCache.preload(), InteractionCache.preload(), UserRelays.preload(follows), + RelayMetrics.preload(), ]; await Promise.all(preloads); } diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 3710630..9ffc3fc 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -6,6 +6,7 @@ import { StrictMode } from "react"; import * as ReactDOM from "react-dom/client"; import { Provider } from "react-redux"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import { EventPublisher, NostrSystem, ProfileLoaderService } from "@snort/system"; import * as serviceWorkerRegistration from "serviceWorkerRegistration"; import { IntlProvider } from "IntlProvider"; @@ -33,16 +34,16 @@ import { SubscribeRoutes } from "Pages/subscribe"; import ZapPoolPage from "Pages/ZapPool"; import DebugPage from "Pages/Debug"; import { db } from "Db"; -import { preload, UserCache } from "Cache"; +import { preload, RelayMetrics, UserCache, UserRelays } from "Cache"; import { LoginStore } from "Login"; -import { EventPublisher, NostrSystem, ProfileLoaderService } from "@snort/system"; -import { UserRelays } from "Cache"; /** * Singleton nostr system */ export const System = new NostrSystem({ relayCache: UserRelays, + profileCache: UserCache, + relayMetrics: RelayMetrics, authHandler: async (c, r) => { const { publicKey, privateKey } = LoginStore.snapshot(); if (publicKey) { diff --git a/packages/system/src/Connection.ts b/packages/system/src/Connection.ts index db10938..1fdd261 100644 --- a/packages/system/src/Connection.ts +++ b/packages/system/src/Connection.ts @@ -64,7 +64,7 @@ export class Connection extends ExternalStore { OnConnected?: () => void; OnEvent?: (sub: string, e: TaggedRawEvent) => void; OnEose?: (sub: string) => void; - OnDisconnect?: (id: string) => void; + OnDisconnect?: (code: number) => void; Auth?: AuthHandler; AwaitingAuth: Map; Authed = false; @@ -162,7 +162,7 @@ export class Connection extends ExternalStore { this.ReconnectTimer = undefined; } - this.OnDisconnect?.(this.Id); + this.OnDisconnect?.(e.code); this.#resetQueues(); // reset connection Id on disconnect, for query-tracking this.Id = uuid(); diff --git a/packages/system/src/NostrSystem.ts b/packages/system/src/NostrSystem.ts index c815a34..58d084d 100644 --- a/packages/system/src/NostrSystem.ts +++ b/packages/system/src/NostrSystem.ts @@ -7,7 +7,17 @@ import { Query } from "./Query"; import { RelayCache } from "./GossipModel"; import { NoteStore } from "./NoteCollection"; import { BuiltRawReqFilter, RequestBuilder } from "./RequestBuilder"; -import { MetadataCache, ProfileLoaderService, SystemInterface, SystemSnapshot, UserProfileCache, UserRelaysCache } from "."; +import { RelayMetricHandler } from "./RelayMetricHandler"; +import { + MetadataCache, + ProfileLoaderService, + RelayMetrics, + SystemInterface, + SystemSnapshot, + UserProfileCache, + UserRelaysCache, + RelayMetricCache +} from "."; /** * Manages nostr content retrieval system @@ -40,21 +50,35 @@ export class NostrSystem extends ExternalStore implements System */ #profileCache: FeedCache; + /** + * Storage class for relay metrics (connects/disconnects) + */ + #relayMetricsCache: FeedCache; + /** * Profile loading service */ #profileLoader: ProfileLoaderService; + /** + * Relay metrics handler cache + */ + #relayMetrics: RelayMetricHandler; + constructor(props: { authHandler?: AuthHandler, relayCache?: RelayCache, profileCache?: FeedCache + relayMetrics?: FeedCache }) { super(); this.#handleAuth = props.authHandler; this.#relayCache = props.relayCache ?? new UserRelaysCache(); this.#profileCache = props.profileCache ?? new UserProfileCache(); + this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(); + this.#profileLoader = new ProfileLoaderService(this, this.#profileCache); + this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache); this.#cleanup(); } @@ -80,7 +104,7 @@ export class NostrSystem extends ExternalStore implements System this.#sockets.set(addr, c); c.OnEvent = (s, e) => this.OnEvent(s, e); c.OnEose = s => this.OnEndOfStoredEvents(c, s); - c.OnDisconnect = id => this.OnRelayDisconnect(id); + c.OnDisconnect = (code) => this.OnRelayDisconnect(c, code); await c.Connect(); } else { // update settings if already connected @@ -91,9 +115,10 @@ export class NostrSystem extends ExternalStore implements System } } - OnRelayDisconnect(id: string) { + OnRelayDisconnect(c: Connection, code: number) { + this.#relayMetrics.onDisconnect(c, code); for (const [, q] of this.Queries) { - q.connectionLost(id); + q.connectionLost(c.Id); } } @@ -121,7 +146,7 @@ export class NostrSystem extends ExternalStore implements System this.#sockets.set(addr, c); c.OnEvent = (s, e) => this.OnEvent(s, e); c.OnEose = s => this.OnEndOfStoredEvents(c, s); - c.OnDisconnect = id => this.OnRelayDisconnect(id); + c.OnDisconnect = code => this.OnRelayDisconnect(c, code); await c.Connect(); return c; } diff --git a/packages/system/src/RelayMetricHandler.ts b/packages/system/src/RelayMetricHandler.ts new file mode 100644 index 0000000..2f76011 --- /dev/null +++ b/packages/system/src/RelayMetricHandler.ts @@ -0,0 +1,15 @@ +import { FeedCache } from "@snort/shared"; +import { Connection } from "Connection"; +import { RelayMetrics } from "cache"; + +export class RelayMetricHandler { + readonly #cache: FeedCache; + + constructor(cache: FeedCache) { + this.#cache = cache; + } + + onDisconnect(c: Connection, code: number) { + + } +} \ No newline at end of file diff --git a/packages/system/src/cache/RelayMetricCache.ts b/packages/system/src/cache/RelayMetricCache.ts new file mode 100644 index 0000000..8dd6a9c --- /dev/null +++ b/packages/system/src/cache/RelayMetricCache.ts @@ -0,0 +1,22 @@ +import { db, RelayMetrics } from "."; +import { FeedCache } from "@snort/shared"; + +export class RelayMetricCache extends FeedCache { + constructor() { + super("RelayMetrics", db.relayMetrics); + } + + key(of: RelayMetrics): string { + return of.addr; + } + + override async preload(): Promise { + await super.preload(); + // load everything + await this.buffer([...this.onTable]); + } + + takeSnapshot(): Array { + return [...this.cache.values()]; + } +} \ No newline at end of file diff --git a/packages/system/src/cache/db.ts b/packages/system/src/cache/db.ts index 31b2e02..6201eaf 100644 --- a/packages/system/src/cache/db.ts +++ b/packages/system/src/cache/db.ts @@ -3,11 +3,11 @@ import { NostrEvent } from "../Nostr"; import Dexie, { Table } from "dexie"; const NAME = "snort-system"; -const VERSION = 1; +const VERSION = 2; const STORES = { users: "++pubkey, name, display_name, picture, nip05, npub", - relays: "++addr", + relayMetrics: "++addr", userRelays: "++pubkey", events: "++id, pubkey, created_at" }; diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index 0157234..6168d5a 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -25,6 +25,7 @@ export * from "./impl/nip44"; export * from "./cache"; export * from "./cache/UserRelayCache"; export * from "./cache/UserCache"; +export * from "./cache/RelayMetricCache"; export interface SystemInterface { /**