diff --git a/packages/app/src/Cache/GiftWrapCache.ts b/packages/app/src/Cache/GiftWrapCache.ts index 2c999ac9..25a3ee9f 100644 --- a/packages/app/src/Cache/GiftWrapCache.ts +++ b/packages/app/src/Cache/GiftWrapCache.ts @@ -24,7 +24,7 @@ export class GiftWrapCache extends RefreshFeedCache { return [...this.cache.values()]; } - override async onEvent(evs: Array, pub?: EventPublisher) { + override async onEvent(evs: Array, _: string, pub?: EventPublisher) { if (!pub) return; const unwrapped = ( diff --git a/packages/app/src/Cache/Notifications.ts b/packages/app/src/Cache/Notifications.ts index 6f606f19..15ef558a 100644 --- a/packages/app/src/Cache/Notifications.ts +++ b/packages/app/src/Cache/Notifications.ts @@ -1,11 +1,11 @@ import { EventKind, NostrEvent, RequestBuilder, TaggedNostrEvent } from "@snort/system"; import { RefreshFeedCache, TWithCreated } from "./RefreshFeedCache"; import { LoginSession } from "Login"; -import { db } from "Db"; +import { NostrEventForSession, db } from "Db"; import { Day } from "Const"; import { unixNow } from "@snort/shared"; -export class NotificationsCache extends RefreshFeedCache { +export class NotificationsCache extends RefreshFeedCache { #kinds = [EventKind.TextNote, EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt]; constructor() { @@ -14,7 +14,7 @@ export class NotificationsCache extends RefreshFeedCache { buildSub(session: LoginSession, rb: RequestBuilder) { if (session.publicKey) { - const newest = this.newest(); + const newest = this.newest(v => v.tags.some(a => a[0] === "p" && a[1] === session.publicKey)); rb.withFilter() .kinds(this.#kinds) .tag("p", [session.publicKey]) @@ -22,10 +22,15 @@ export class NotificationsCache extends RefreshFeedCache { } } - async onEvent(evs: readonly TaggedNostrEvent[]) { + async onEvent(evs: readonly TaggedNostrEvent[], pubKey: string) { const filtered = evs.filter(a => this.#kinds.includes(a.kind) && a.tags.some(b => b[0] === "p")); if (filtered.length > 0) { - await this.bulkSet(filtered); + await this.bulkSet( + filtered.map(v => ({ + ...v, + forSession: pubKey, + })), + ); this.notifyChange(filtered.map(v => this.key(v))); } } @@ -34,7 +39,7 @@ export class NotificationsCache extends RefreshFeedCache { return of.id; } - takeSnapshot(): TWithCreated[] { + takeSnapshot() { return [...this.cache.values()]; } } diff --git a/packages/app/src/Cache/RefreshFeedCache.ts b/packages/app/src/Cache/RefreshFeedCache.ts index 29372ffb..f1eb75d5 100644 --- a/packages/app/src/Cache/RefreshFeedCache.ts +++ b/packages/app/src/Cache/RefreshFeedCache.ts @@ -6,14 +6,18 @@ export type TWithCreated = (T | Readonly) & { created_at: number }; export abstract class RefreshFeedCache extends FeedCache> { abstract buildSub(session: LoginSession, rb: RequestBuilder): void; - abstract onEvent(evs: Readonly>, pub?: EventPublisher): void; + abstract onEvent(evs: Readonly>, pubKey: string, pub?: EventPublisher): void; /** * Get latest event */ - protected newest() { + protected newest(filter?: (e: TWithCreated) => boolean) { let ret = 0; - this.cache.forEach(v => (ret = v.created_at > ret ? v.created_at : ret)); + this.cache.forEach(v => { + if (!filter || filter(v)) { + ret = v.created_at > ret ? v.created_at : ret; + } + }); return ret; } diff --git a/packages/app/src/Db/index.ts b/packages/app/src/Db/index.ts index ec85977a..11fe611c 100644 --- a/packages/app/src/Db/index.ts +++ b/packages/app/src/Db/index.ts @@ -2,7 +2,7 @@ import Dexie, { Table } from "dexie"; import { HexKey, NostrEvent, TaggedNostrEvent, u256 } from "@snort/system"; export const NAME = "snortDB"; -export const VERSION = 14; +export const VERSION = 15; export interface SubCache { id: string; @@ -35,6 +35,10 @@ export interface UnwrappedGift { tags?: Array>; // some tags extracted } +export type NostrEventForSession = TaggedNostrEvent & { + forSession: string; +}; + const STORES = { chats: "++id", eventInteraction: "++id", @@ -50,7 +54,7 @@ export class SnortDB extends Dexie { eventInteraction!: Table; payments!: Table; gifts!: Table; - notifications!: Table; + notifications!: Table; followsFeed!: Table; constructor() { diff --git a/packages/app/src/Element/User/ProfileImage.tsx b/packages/app/src/Element/User/ProfileImage.tsx index 1a7b97aa..b06e56b8 100644 --- a/packages/app/src/Element/User/ProfileImage.tsx +++ b/packages/app/src/Element/User/ProfileImage.tsx @@ -129,8 +129,10 @@ export default function ProfileImage({
- {/**/}
diff --git a/packages/app/src/Hooks/useRefreshFeedcache.tsx b/packages/app/src/Hooks/useRefreshFeedcache.tsx index 00fb2098..7ad793e3 100644 --- a/packages/app/src/Hooks/useRefreshFeedcache.tsx +++ b/packages/app/src/Hooks/useRefreshFeedcache.tsx @@ -5,6 +5,7 @@ import { NoopStore, RequestBuilder, TaggedNostrEvent } from "@snort/system"; import { RefreshFeedCache } from "Cache/RefreshFeedCache"; import useLogin from "./useLogin"; import useEventPublisher from "./useEventPublisher"; +import { unwrap } from "@snort/shared"; export function useRefreshFeedCache(c: RefreshFeedCache, leaveOpen = false) { const system = useContext(SnortContext); @@ -33,7 +34,7 @@ export function useRefreshFeedCache(c: RefreshFeedCache, leaveOpen = false tBuf = [...evs]; t = setTimeout(() => { t = undefined; - c.onEvent(tBuf, publisher); + c.onEvent(tBuf, unwrap(login.publicKey), publisher); }, 100); } else { tBuf.push(...evs); @@ -46,8 +47,5 @@ export function useRefreshFeedCache(c: RefreshFeedCache, leaveOpen = false releaseOnEvent(); }; } - return () => { - // noop - }; }, [sub]); } diff --git a/packages/app/src/Login/LoginSession.ts b/packages/app/src/Login/LoginSession.ts index 890d566b..766b341e 100644 --- a/packages/app/src/Login/LoginSession.ts +++ b/packages/app/src/Login/LoginSession.ts @@ -128,4 +128,9 @@ export interface LoginSession { * A list of chats which we have joined (NIP-28/NIP-29) */ extraChats: Array; + + /** + * Is login session in stalker mode + */ + stalker: boolean; } diff --git a/packages/app/src/Login/MultiAccountStore.ts b/packages/app/src/Login/MultiAccountStore.ts index 9496961a..a471744b 100644 --- a/packages/app/src/Login/MultiAccountStore.ts +++ b/packages/app/src/Login/MultiAccountStore.ts @@ -53,6 +53,7 @@ const LoggedOut = { timestamp: 0, }, extraChats: [], + stalker: false, } as LoginSession; export class MultiAccountStore extends ExternalStore { @@ -125,6 +126,7 @@ export class MultiAccountStore extends ExternalStore { relays?: Record, remoteSignerRelays?: Array, privateKey?: KeyStorage, + stalker?: boolean, ) { if (this.#accounts.has(key)) { throw new Error("Already logged in with this pubkey"); @@ -143,6 +145,7 @@ export class MultiAccountStore extends ExternalStore { preferences: deepClone(DefaultPreferences), remoteSignerRelays, privateKeyData: privateKey, + stalker: stalker ?? false, } as LoginSession; const pub = createPublisher(newSession); diff --git a/packages/app/src/Pages/Layout.css b/packages/app/src/Pages/Layout.css index 9e7600d1..7b5e9897 100644 --- a/packages/app/src/Pages/Layout.css +++ b/packages/app/src/Pages/Layout.css @@ -90,3 +90,22 @@ header { display: none; } } + +.stalker { + position: fixed; + top: 0; + width: 100vw; + height: 100vh; + box-shadow: 0px 0px 26px 0px rgba(139, 92, 246, 0.7) inset; + pointer-events: none; +} + +.stalker button { + position: absolute; + top: 50px; + right: 50px; + color: black; + background-color: var(--btn-color); + padding: 12px; + pointer-events: all !important; +} diff --git a/packages/app/src/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx index 001d2822..c543f200 100644 --- a/packages/app/src/Pages/Layout.tsx +++ b/packages/app/src/Pages/Layout.tsx @@ -22,10 +22,12 @@ import { useTheme } from "Hooks/useTheme"; import { useLoginRelays } from "Hooks/useLoginRelays"; import { useNoteCreator } from "State/NoteCreator"; import { LoginUnlock } from "Element/PinPrompt"; +import { LoginStore } from "Login"; export default function Layout() { const location = useLocation(); const [pageClass, setPageClass] = useState("page"); + const { id, stalker } = useLogin(s => ({ id: s.id, stalker: s.stalker ?? false })); useLoginFeed(); useTheme(); @@ -60,6 +62,17 @@ export default function Layout() {
+ {stalker && ( +
{ + LoginStore.removeSession(id); + }}> + +
+ )} ); } diff --git a/packages/app/src/Pages/Notifications.tsx b/packages/app/src/Pages/Notifications.tsx index fbbc835e..d7984314 100644 --- a/packages/app/src/Pages/Notifications.tsx +++ b/packages/app/src/Pages/Notifications.tsx @@ -80,23 +80,27 @@ export default function NotificationsPage({ onClick }: { onClick?: (link: NostrL return onHour.toString(); }; + const myNotifications = useMemo(() => { + return orderDescending([...notifications]).filter( + a => !isMuted(a.pubkey) && a.tags.some(b => b[0] === "p" && b[1] === login.publicKey), + ); + }, [notifications, login.publicKey]); + const timeGrouped = useMemo(() => { - return orderDescending([...notifications]) - .filter(a => !isMuted(a.pubkey) && a.tags.some(b => b[0] === "p" && b[1] === login.publicKey)) - .reduce((acc, v) => { - const key = `${timeKey(v)}:${notificationContext(v as TaggedNostrEvent)?.encode()}:${v.kind}`; - if (acc.has(key)) { - unwrap(acc.get(key)).push(v as TaggedNostrEvent); - } else { - acc.set(key, [v as TaggedNostrEvent]); - } - return acc; - }, new Map>()); - }, [notifications]); + return myNotifications.reduce((acc, v) => { + const key = `${timeKey(v)}:${notificationContext(v as TaggedNostrEvent)?.encode()}:${v.kind}`; + if (acc.has(key)) { + unwrap(acc.get(key)).push(v as TaggedNostrEvent); + } else { + acc.set(key, [v as TaggedNostrEvent]); + } + return acc; + }, new Map>()); + }, [myNotifications]); return (
- + {login.publicKey && [...timeGrouped.entries()].map(([k, g]) => )}