From 24978f4e62ea4f68b93edac3a9d70a7766fa6cc3 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 13 Nov 2023 16:51:29 +0000 Subject: [PATCH] feat: appdata --- .../app/src/Element/Embed/MixCloudEmbed.tsx | 2 +- packages/app/src/Element/Embed/PubkeyList.tsx | 4 +- .../app/src/Element/Event/NoteContextMenu.tsx | 14 +++-- .../app/src/Element/Event/NoteCreator.tsx | 2 +- packages/app/src/Element/Event/NoteFooter.tsx | 2 +- packages/app/src/Element/Event/Poll.tsx | 6 ++- .../app/src/Element/Event/RevealMedia.tsx | 11 ++-- .../app/src/Element/Feed/TimelineFollows.tsx | 10 ++-- packages/app/src/Element/PinPrompt.tsx | 8 +-- packages/app/src/Element/SendSats.tsx | 2 +- packages/app/src/Feed/LoginFeed.ts | 14 ++++- packages/app/src/Feed/TimelineFeed.ts | 2 +- packages/app/src/Hooks/useImgProxy.ts | 2 +- packages/app/src/Hooks/useLogin.tsx | 2 +- packages/app/src/Hooks/useTheme.tsx | 2 +- packages/app/src/IntlProvider.tsx | 2 +- packages/app/src/Login/Functions.ts | 23 ++++++-- packages/app/src/Login/LoginSession.ts | 8 +-- packages/app/src/Login/MultiAccountStore.ts | 30 +++++++---- packages/app/src/Pages/HashTagsPage.tsx | 53 +++++++++---------- .../app/src/Pages/Profile/ProfilePage.tsx | 4 +- packages/app/src/Pages/Root.tsx | 5 +- packages/app/src/Pages/ZapPool.tsx | 10 ++-- .../app/src/Pages/settings/Moderation.tsx | 28 ++++------ .../app/src/Pages/settings/Preferences.tsx | 47 ++++++++-------- packages/app/src/Upload/index.ts | 2 +- packages/app/src/index.tsx | 2 +- packages/system/src/event-publisher.ts | 7 +++ packages/system/src/index.ts | 13 +++-- 29 files changed, 181 insertions(+), 136 deletions(-) diff --git a/packages/app/src/Element/Embed/MixCloudEmbed.tsx b/packages/app/src/Element/Embed/MixCloudEmbed.tsx index 0a3cd3eb..ed1229c5 100644 --- a/packages/app/src/Element/Embed/MixCloudEmbed.tsx +++ b/packages/app/src/Element/Embed/MixCloudEmbed.tsx @@ -4,7 +4,7 @@ import useLogin from "Hooks/useLogin"; const MixCloudEmbed = ({ link }: { link: string }) => { const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + (MixCloudRegex.test(link) && RegExp.$2); - const { theme } = useLogin(s => ({ theme: s.preferences.theme })); + const theme = useLogin(s => s.appData.item.preferences.theme); const lightParams = theme === "light" ? "light=1" : "light=0"; return ( <> diff --git a/packages/app/src/Element/Embed/PubkeyList.tsx b/packages/app/src/Element/Embed/PubkeyList.tsx index c6fadf97..839a8e99 100644 --- a/packages/app/src/Element/Embed/PubkeyList.tsx +++ b/packages/app/src/Element/Embed/PubkeyList.tsx @@ -22,7 +22,7 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam for (const pk of ids) { try { const profile = await UserCache.get(pk); - const amtSend = login.preferences.defaultZapAmount; + const amtSend = login.appData.item.preferences.defaultZapAmount; const lnurl = profile?.lud16 || profile?.lud06; if (lnurl) { const svc = new LNURL(lnurl); @@ -72,7 +72,7 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam , + n: , }} /> diff --git a/packages/app/src/Element/Event/NoteContextMenu.tsx b/packages/app/src/Element/Event/NoteContextMenu.tsx index f1794070..aebd54bc 100644 --- a/packages/app/src/Element/Event/NoteContextMenu.tsx +++ b/packages/app/src/Element/Event/NoteContextMenu.tsx @@ -83,7 +83,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { useEffect(() => { const sub = getCurrentSubscription(login.subscriptions); - if (sub?.type === SubscriptionType.Premium && (login.preferences.autoTranslate ?? true)) { + if (sub?.type === SubscriptionType.Premium && (login.appData.item.preferences.autoTranslate ?? true)) { translate(); } }, []); @@ -162,7 +162,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { )} - {login.preferences.enableReactions && !login.readonly && ( + {login.appData.item.preferences.enableReactions && !login.readonly && ( props.react("-")}> @@ -182,12 +182,10 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { - {login.preferences.showDebugMenus && ( - copyEvent()}> - - - - )} + copyEvent()}> + + + {isMine && !login.readonly && ( deleteEvent()}> diff --git a/packages/app/src/Element/Event/NoteCreator.tsx b/packages/app/src/Element/Event/NoteCreator.tsx index 6aa6e083..4d059bb3 100644 --- a/packages/app/src/Element/Event/NoteCreator.tsx +++ b/packages/app/src/Element/Event/NoteCreator.tsx @@ -27,7 +27,7 @@ import { ToggleSwitch } from "Icons/Toggle"; export function NoteCreator() { const { formatMessage } = useIntl(); const uploader = useFileUpload(); - const login = useLogin(s => ({ relays: s.relays, publicKey: s.publicKey, pow: s.preferences.pow })); + const login = useLogin(s => ({ relays: s.relays, publicKey: s.publicKey, pow: s.appData.item.preferences.pow })); const { publisher: pub } = useEventPublisher(); const publisher = login.pow ? pub?.pow(login.pow, GetPowWorker()) : pub; const note = useNoteCreator(); diff --git a/packages/app/src/Element/Event/NoteFooter.tsx b/packages/app/src/Element/Event/NoteFooter.tsx index 523297c7..4e6ff443 100644 --- a/packages/app/src/Element/Event/NoteFooter.tsx +++ b/packages/app/src/Element/Event/NoteFooter.tsx @@ -52,7 +52,7 @@ export default function NoteFooter(props: NoteFooterProps) { publicKey, preferences: prefs, readonly, - } = useLogin(s => ({ preferences: s.preferences, publicKey: s.publicKey, readonly: s.readonly })); + } = useLogin(s => ({ preferences: s.appData.item.preferences, publicKey: s.publicKey, readonly: s.readonly })); const author = useUserProfile(ev.pubkey); const interactionCache = useInteractionCache(publicKey, ev.id); const { publisher, system } = useEventPublisher(); diff --git a/packages/app/src/Element/Event/Poll.tsx b/packages/app/src/Element/Event/Poll.tsx index 482c0a01..2bccf3d1 100644 --- a/packages/app/src/Element/Event/Poll.tsx +++ b/packages/app/src/Element/Event/Poll.tsx @@ -23,7 +23,11 @@ export default function Poll(props: PollProps) { const { formatMessage } = useIntl(); const { publisher } = useEventPublisher(); const { wallet } = useWallet(); - const { preferences: prefs, publicKey: myPubKey, relays } = useLogin(); + const { + preferences: prefs, + publicKey: myPubKey, + relays, + } = useLogin(s => ({ preferences: s.appData.item.preferences, publicKey: s.publicKey, relays: s.relays })); const pollerProfile = useUserProfile(props.ev.pubkey); const [tallyBy, setTallyBy] = useState("pubkeys"); const [error, setError] = useState(""); diff --git a/packages/app/src/Element/Event/RevealMedia.tsx b/packages/app/src/Element/Event/RevealMedia.tsx index 4621a576..3e2b85c2 100644 --- a/packages/app/src/Element/Event/RevealMedia.tsx +++ b/packages/app/src/Element/Event/RevealMedia.tsx @@ -13,12 +13,15 @@ interface RevealMediaProps { } export default function RevealMedia(props: RevealMediaProps) { - const login = useLogin(); - const { preferences: pref, follows, publicKey } = login; + const { preferences, follows, publicKey } = useLogin(s => ({ + preferences: s.appData.item.preferences, + follows: s.follows.item, + publicKey: s.publicKey, + })); - const hideNonFollows = pref.autoLoadMedia === "follows-only" && !follows.item.includes(props.creator); + const hideNonFollows = preferences.autoLoadMedia === "follows-only" && !follows.includes(props.creator); const isMine = props.creator === publicKey; - const hideMedia = pref.autoLoadMedia === "none" || (!isMine && hideNonFollows); + const hideMedia = preferences.autoLoadMedia === "none" || (!isMine && hideNonFollows); const hostname = new URL(props.link).hostname; const url = new URL(props.link); diff --git a/packages/app/src/Element/Feed/TimelineFollows.tsx b/packages/app/src/Element/Feed/TimelineFollows.tsx index bfde55f8..0b99c517 100644 --- a/packages/app/src/Element/Feed/TimelineFollows.tsx +++ b/packages/app/src/Element/Feed/TimelineFollows.tsx @@ -118,9 +118,11 @@ const TimelineFollows = (props: TimelineFollowsProps) => { noteOnClick={props.noteOnClick} noteRenderer={props.noteRenderer} /> - {sortedFeed.length > 0 && await FollowsFeed.loadMore(system, login, sortedFeed[sortedFeed.length - 1].created_at)} - />} + {sortedFeed.length > 0 && ( + await FollowsFeed.loadMore(system, login, sortedFeed[sortedFeed.length - 1].created_at)} + /> + )} ); }; @@ -168,4 +170,4 @@ function weaveTimeline( refTime: main[skip].created_at, }, ].sort((a, b) => (a.refTime > b.refTime ? -1 : 1)); -} \ No newline at end of file +} diff --git a/packages/app/src/Element/PinPrompt.tsx b/packages/app/src/Element/PinPrompt.tsx index 39c48fd4..d33ea647 100644 --- a/packages/app/src/Element/PinPrompt.tsx +++ b/packages/app/src/Element/PinPrompt.tsx @@ -100,8 +100,8 @@ export function LoginUnlock() { const newPin = await PinEncrypted.create(k, pin); const pub = EventPublisher.privateKey(k); - if (login.preferences.pow) { - pub.pow(login.preferences.pow, GetPowWorker()); + if (login.appData.item.preferences.pow) { + pub.pow(login.appData.item.preferences.pow, GetPowWorker()); } LoginStore.setPublisher(login.id, pub); LoginStore.updateSession({ @@ -117,8 +117,8 @@ export function LoginUnlock() { await key.unlock(pin); const pub = createPublisher(login); if (pub) { - if (login.preferences.pow) { - pub.pow(login.preferences.pow, GetPowWorker()); + if (login.appData.item.preferences.pow) { + pub.pow(login.appData.item.preferences.pow, GetPowWorker()); } LoginStore.setPublisher(login.id, pub); LoginStore.updateSession({ diff --git a/packages/app/src/Element/SendSats.tsx b/packages/app/src/Element/SendSats.tsx index 89619697..48c37c43 100644 --- a/packages/app/src/Element/SendSats.tsx +++ b/packages/app/src/Element/SendSats.tsx @@ -247,7 +247,7 @@ function SendSatsInput(props: { onNextStage: (v: SendSatsInputSelection) => Promise; }) { const { defaultZapAmount, readonly } = useLogin(s => ({ - defaultZapAmount: s.preferences.defaultZapAmount, + defaultZapAmount: s.appData.item.preferences.defaultZapAmount, readonly: s.readonly, })); const { formatMessage } = useIntl(); diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts index f7eb16b1..a5042255 100644 --- a/packages/app/src/Feed/LoginFeed.ts +++ b/packages/app/src/Feed/LoginFeed.ts @@ -2,7 +2,7 @@ import { useEffect, useMemo } from "react"; import { TaggedNostrEvent, EventKind, RequestBuilder, NoteCollection, NostrLink } from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; -import { bech32ToHex, findTag, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils"; +import { bech32ToHex, debounce, findTag, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils"; import { makeNotification, sendNotification } from "Notifications"; import useEventPublisher from "Hooks/useEventPublisher"; import useModeration from "Hooks/useModeration"; @@ -40,9 +40,19 @@ export default function useLoginFeed() { useRefreshFeedCache(GiftsCache, true); useEffect(() => { - system.checkSigs = login.preferences.checkSigs; + system.checkSigs = login.appData.item.preferences.checkSigs; }, [login]); + // write appdata after 10s of no changes + useEffect(() => { + return debounce(10_000, async () => { + if (publisher && login.appData.item) { + const ev = await publisher.appData(login.appData.item, "snort"); + await system.BroadcastEvent(ev); + } + }); + }, [login.appData.timestamp]); + const subLogin = useMemo(() => { if (!login || !pubKey) return null; diff --git a/packages/app/src/Feed/TimelineFeed.ts b/packages/app/src/Feed/TimelineFeed.ts index a0f014ee..e76ae280 100644 --- a/packages/app/src/Feed/TimelineFeed.ts +++ b/packages/app/src/Feed/TimelineFeed.ts @@ -28,7 +28,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel window: options.window, now: options.now ?? unixNow(), }); - const pref = useLogin().preferences; + const pref = useLogin(s => s.appData.item.preferences); const createBuilder = useCallback(() => { if (subject.type !== "global" && subject.items.length === 0) { diff --git a/packages/app/src/Hooks/useImgProxy.ts b/packages/app/src/Hooks/useImgProxy.ts index 2f1a04cd..7f2316d5 100644 --- a/packages/app/src/Hooks/useImgProxy.ts +++ b/packages/app/src/Hooks/useImgProxy.ts @@ -10,7 +10,7 @@ export interface ImgProxySettings { } export default function useImgProxy() { - const settings = useLogin().preferences.imgProxyConfig; + const settings = useLogin(s => s.appData.item.preferences.imgProxyConfig); const te = new TextEncoder(); function urlSafe(s: string) { diff --git a/packages/app/src/Hooks/useLogin.tsx b/packages/app/src/Hooks/useLogin.tsx index 2d28da1a..90ce4793 100644 --- a/packages/app/src/Hooks/useLogin.tsx +++ b/packages/app/src/Hooks/useLogin.tsx @@ -2,7 +2,7 @@ import { LoginSession, LoginStore } from "Login"; import { useSyncExternalStore } from "react"; import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector"; -export default function useLogin(selector?: (v: LoginSession) => T) { +export default function useLogin(selector?: (v: LoginSession) => T) { if (selector) { return useSyncExternalStoreWithSelector( s => LoginStore.hook(s), diff --git a/packages/app/src/Hooks/useTheme.tsx b/packages/app/src/Hooks/useTheme.tsx index 15386624..0f44d671 100644 --- a/packages/app/src/Hooks/useTheme.tsx +++ b/packages/app/src/Hooks/useTheme.tsx @@ -2,7 +2,7 @@ import { useEffect } from "react"; import useLogin from "./useLogin"; export function useTheme() { - const { preferences } = useLogin(); + const { preferences } = useLogin(s => ({ preferences: s.appData.item.preferences })); function setTheme(theme: "light" | "dark") { const elm = document.documentElement; diff --git a/packages/app/src/IntlProvider.tsx b/packages/app/src/IntlProvider.tsx index 210e9e11..f4e5bc93 100644 --- a/packages/app/src/IntlProvider.tsx +++ b/packages/app/src/IntlProvider.tsx @@ -95,7 +95,7 @@ class LangStore extends ExternalStore { const LangOverride = new LangStore(); export function useLocale() { - const { language } = useLogin(s => ({ language: s.preferences.language })); + const { language } = useLogin(s => ({ language: s.appData.item.preferences.language })); const loggedOutLang = useSyncExternalStore( c => LangOverride.hook(c), () => LangOverride.snapshot(), diff --git a/packages/app/src/Login/Functions.ts b/packages/app/src/Login/Functions.ts index 92fcad9e..6b4fb918 100644 --- a/packages/app/src/Login/Functions.ts +++ b/packages/app/src/Login/Functions.ts @@ -13,7 +13,7 @@ import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; import { DefaultRelays, SnortPubKey } from "Const"; -import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData } from "Login"; +import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData, Newest } from "Login"; import { generateBip39Entropy, entropyToPrivateKey } from "nip6"; import { bech32ToHex, dedupeById, sanitizeRelayUrl, unwrap } from "SnortUtils"; import { SubscriptionEvent } from "Subscription"; @@ -54,9 +54,13 @@ export function removeRelay(state: LoginSession, addr: string) { LoginStore.updateSession(state); } -export function updatePreferences(state: LoginSession, p: UserPreferences) { - state.preferences = p; - LoginStore.updateSession(state); +export function updatePreferences(id: string, p: UserPreferences) { + updateAppData(id, d => { + return { + item: { ...d, preferences: p }, + timestamp: unixNowMs(), + }; + }); } export function logout(id: string) { @@ -183,6 +187,17 @@ export function setAppData(state: LoginSession, data: SnortAppData, ts: number) LoginStore.updateSession(state); } +export function updateAppData(id: string, fn: (data: SnortAppData) => Newest) { + const session = LoginStore.get(id); + if (session) { + const next = fn(session.appData.item); + if (next.timestamp > session.appData.timestamp) { + session.appData = next; + LoginStore.updateSession(session); + } + } +} + export function addSubscription(state: LoginSession, ...subs: SubscriptionEvent[]) { const newSubs = dedupeById([...(state.subscriptions || []), ...subs]); if (newSubs.length !== state.subscriptions.length) { diff --git a/packages/app/src/Login/LoginSession.ts b/packages/app/src/Login/LoginSession.ts index 766b341e..8b1429e5 100644 --- a/packages/app/src/Login/LoginSession.ts +++ b/packages/app/src/Login/LoginSession.ts @@ -5,7 +5,7 @@ import { SubscriptionEvent } from "Subscription"; /** * Stores latest copy of an item */ -interface Newest { +export interface Newest { item: T; timestamp: number; } @@ -20,6 +20,7 @@ export const enum LoginSessionType { export interface SnortAppData { mutedWords: Array; + preferences: UserPreferences; } export interface LoginSession { @@ -104,11 +105,6 @@ export interface LoginSession { */ readNotifications: number; - /** - * Users cusom preferences - */ - preferences: UserPreferences; - /** * Snort subscriptions licences */ diff --git a/packages/app/src/Login/MultiAccountStore.ts b/packages/app/src/Login/MultiAccountStore.ts index d7860bef..a26f5e25 100644 --- a/packages/app/src/Login/MultiAccountStore.ts +++ b/packages/app/src/Login/MultiAccountStore.ts @@ -7,13 +7,12 @@ import { deepClone, sanitizeRelayUrl, unwrap, ExternalStore } from "@snort/share import { DefaultRelays } from "Const"; import { LoginSession, LoginSessionType, createPublisher } from "Login"; -import { DefaultPreferences } from "./Preferences"; +import { DefaultPreferences, UserPreferences } from "./Preferences"; const AccountStoreKey = "sessions"; const LoggedOut = { id: "default", type: "public_key", - preferences: DefaultPreferences, readonly: true, tags: { item: [], @@ -49,6 +48,7 @@ const LoggedOut = { appData: { item: { mutedWords: [], + preferences: DefaultPreferences, }, timestamp: 0, }, @@ -83,11 +83,11 @@ export class MultiAccountStore extends ExternalStore { v.appData ??= { item: { mutedWords: [], + preferences: DefaultPreferences, }, timestamp: 0, }; v.extraChats ??= []; - v.preferences.checkSigs ??= false; if (v.privateKeyData) { v.privateKeyData = KeyStorage.fromPayload(v.privateKeyData as object); } @@ -102,6 +102,13 @@ export class MultiAccountStore extends ExternalStore { })); } + get(id: string) { + const s = this.#accounts.get(id); + if (s) { + return { ...s }; + } + } + allSubscriptions() { return [...this.#accounts.values()].map(a => a.subscriptions).flat(); } @@ -246,14 +253,6 @@ export class MultiAccountStore extends ExternalStore { #migrate() { let didMigrate = false; - // replace default tab with notes - for (const [, v] of this.#accounts) { - if ((v.preferences.defaultRootTab as string) === "posts") { - v.preferences.defaultRootTab = "notes"; - didMigrate = true; - } - } - // update session types for (const [, v] of this.#accounts) { if (!v.type) { @@ -283,6 +282,15 @@ export class MultiAccountStore extends ExternalStore { } } + // move preferences to appdata + for (const [, v] of this.#accounts) { + if ("preferences" in v) { + v.appData.item.preferences = v.preferences as UserPreferences; + delete v["preferences"]; + didMigrate = true; + } + } + if (didMigrate) { console.debug("Finished migration in MultiAccountStore"); this.#save(); diff --git a/packages/app/src/Pages/HashTagsPage.tsx b/packages/app/src/Pages/HashTagsPage.tsx index 78bcc837..27d8c263 100644 --- a/packages/app/src/Pages/HashTagsPage.tsx +++ b/packages/app/src/Pages/HashTagsPage.tsx @@ -33,8 +33,6 @@ const HashTagsPage = () => { export default HashTagsPage; - - export function HashTagHeader({ tag }: { tag: string }) { const login = useLogin(); const isFollowing = useMemo(() => { @@ -55,35 +53,36 @@ export function HashTagHeader({ tag }: { tag: string }) { const sub = useMemo(() => { const rb = new RequestBuilder(`hashtag-counts:${tag}`); - rb.withFilter() - .kinds([EventKind.CategorizedBookmarks]) - .tag("d", ["follow"]) - .tag("t", [tag.toLowerCase()]); + rb.withFilter().kinds([EventKind.CategorizedBookmarks]).tag("d", ["follow"]).tag("t", [tag.toLowerCase()]); return rb; }, [tag]); const followsTag = useRequestBuilder(NoteCollection, sub); const pubkeys = dedupe((followsTag.data ?? []).map(a => a.pubkey)); - return
-
-

#{tag}

-
- {pubkeys.slice(0, 5).map(a => )} - {pubkeys.length > 5 && - + - } + return ( +
+
+

#{tag}

+
+ {pubkeys.slice(0, 5).map(a => ( + + ))} + {pubkeys.length > 5 && ( + + + + + )} +
+ {isFollowing ? ( + followTags(login.tags.item.filter(t => t !== tag))}> + + + ) : ( + followTags(login.tags.item.concat([tag]))}> + + + )}
- {isFollowing ? ( - followTags(login.tags.item.filter(t => t !== tag))}> - - - ) : ( - followTags(login.tags.item.concat([tag]))}> - - - )} -
-} \ No newline at end of file + ); +} diff --git a/packages/app/src/Pages/Profile/ProfilePage.tsx b/packages/app/src/Pages/Profile/ProfilePage.tsx index 8c427ef7..cc2c2fc3 100644 --- a/packages/app/src/Pages/Profile/ProfilePage.tsx +++ b/packages/app/src/Pages/Profile/ProfilePage.tsx @@ -89,8 +89,8 @@ export default function ProfilePage({ id: propId, state }: ProfilePageProps) { // ignored } })(); - const showBadges = login.preferences.showBadges ?? false; - const showStatus = login.preferences.showStatus ?? true; + const showBadges = login.appData.item.preferences.showBadges ?? false; + const showStatus = login.appData.item.preferences.showStatus ?? true; // feeds const { blocked } = useModeration(); diff --git a/packages/app/src/Pages/Root.tsx b/packages/app/src/Pages/Root.tsx index 4491cec6..b9afdb10 100644 --- a/packages/app/src/Pages/Root.tsx +++ b/packages/app/src/Pages/Root.tsx @@ -181,7 +181,10 @@ export const TagsTab = (params: { tag?: string }) => { }; const DefaultTab = () => { - const { preferences, publicKey } = useLogin(); + const { preferences, publicKey } = useLogin(s => ({ + preferences: s.appData.item.preferences, + publicKey: s.publicKey, + })); const tab = publicKey ? preferences.defaultRootTab ?? `notes` : `trending/notes`; const elm = RootTabRoutes.find(a => a.path === tab)?.element; return elm; diff --git a/packages/app/src/Pages/ZapPool.tsx b/packages/app/src/Pages/ZapPool.tsx index d2f3ae32..84bf4a2a 100644 --- a/packages/app/src/Pages/ZapPool.tsx +++ b/packages/app/src/Pages/ZapPool.tsx @@ -34,7 +34,7 @@ function ZapTarget({ target }: { target: ZapPoolRecipient }) { const login = useLogin(); const profile = useUserProfile(target.pubkey); const hasAddress = profile?.lud16 || profile?.lud06; - const defaultZapMount = Math.ceil(login.preferences.defaultZapAmount * (target.split / 100)); + const defaultZapMount = Math.ceil(login.appData.item.preferences.defaultZapAmount * (target.split / 100)); return ( - + ), }} @@ -119,12 +119,14 @@ export default function ZapPoolPage() { values={{ nIn: ( - + ), nOut: ( - + ), }} diff --git a/packages/app/src/Pages/settings/Moderation.tsx b/packages/app/src/Pages/settings/Moderation.tsx index 4a320213..973c8015 100644 --- a/packages/app/src/Pages/settings/Moderation.tsx +++ b/packages/app/src/Pages/settings/Moderation.tsx @@ -1,6 +1,6 @@ import { unixNowMs } from "@snort/shared"; import useLogin from "Hooks/useLogin"; -import { setAppData } from "Login"; +import { updateAppData } from "Login"; import { appendDedupe } from "SnortUtils"; import { useState } from "react"; import { FormattedMessage } from "react-intl"; @@ -10,32 +10,24 @@ export function ModerationSettings() { const [muteWord, setMuteWord] = useState(""); function addMutedWord() { - login.appData ??= { + updateAppData(login.id, ad => ({ item: { - mutedWords: [], - }, - timestamp: 0, - }; - setAppData( - login, - { - ...login.appData.item, + ...ad, mutedWords: appendDedupe(login.appData.item.mutedWords, [muteWord]), }, - unixNowMs(), - ); + timestamp: unixNowMs(), + })); setMuteWord(""); } function removeMutedWord(word: string) { - setAppData( - login, - { - ...login.appData.item, + updateAppData(login.id, ad => ({ + item: { + ...ad, mutedWords: login.appData.item.mutedWords.filter(a => a !== word), }, - unixNowMs(), - ); + timestamp: unixNowMs(), + })); setMuteWord(""); } diff --git a/packages/app/src/Pages/settings/Preferences.tsx b/packages/app/src/Pages/settings/Preferences.tsx index b2ffbedb..4fe243f9 100644 --- a/packages/app/src/Pages/settings/Preferences.tsx +++ b/packages/app/src/Pages/settings/Preferences.tsx @@ -35,8 +35,7 @@ export const AllLanguageCodes = [ const PreferencesPage = () => { const { formatMessage } = useIntl(); - const login = useLogin(); - const perf = login.preferences; + const { id, perf } = useLogin(s => ({ id: s.id, perf: s.appData.item.preferences })); const { lang } = useLocale(); return ( @@ -53,7 +52,7 @@ const PreferencesPage = () => { - updatePreferences(login, { + updatePreferences(id, { ...perf, theme: e.target.value, } as UserPreferences) @@ -102,7 +101,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, telemetry: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, telemetry: e.target.checked })} />
@@ -149,7 +148,7 @@ const PreferencesPage = () => { className="w-max" value={perf.autoLoadMedia} onChange={e => - updatePreferences(login, { + updatePreferences(id, { ...perf, autoLoadMedia: e.target.value, } as UserPreferences) @@ -180,7 +179,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, checkSigs: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, checkSigs: e.target.checked })} /> @@ -197,7 +196,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, autoTranslate: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, autoTranslate: e.target.checked })} /> @@ -215,7 +214,7 @@ const PreferencesPage = () => { type="number" defaultValue={perf.pow} min={0} - onChange={e => updatePreferences(login, { ...perf, pow: parseInt(e.target.value || "0") })} + onChange={e => updatePreferences(id, { ...perf, pow: parseInt(e.target.value || "0") })} /> @@ -228,7 +227,7 @@ const PreferencesPage = () => { type="number" defaultValue={perf.defaultZapAmount} min={1} - onChange={e => updatePreferences(login, { ...perf, defaultZapAmount: parseInt(e.target.value || "0") })} + onChange={e => updatePreferences(id, { ...perf, defaultZapAmount: parseInt(e.target.value || "0") })} /> @@ -245,7 +244,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, showBadges: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, showBadges: e.target.checked })} /> @@ -262,7 +261,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, showStatus: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, showStatus: e.target.checked })} /> @@ -279,7 +278,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, autoZap: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, autoZap: e.target.checked })} /> @@ -298,7 +297,7 @@ const PreferencesPage = () => { type="checkbox" checked={perf.imgProxyConfig !== null} onChange={e => - updatePreferences(login, { + updatePreferences(id, { ...perf, imgProxyConfig: e.target.checked ? DefaultImgProxy : null, }) @@ -321,7 +320,7 @@ const PreferencesPage = () => { description: "Placeholder text for imgproxy url textbox", })} onChange={e => - updatePreferences(login, { + updatePreferences(id, { ...perf, imgProxyConfig: { ...unwrap(perf.imgProxyConfig), @@ -345,7 +344,7 @@ const PreferencesPage = () => { description: "Hexidecimal 'key' input for improxy", })} onChange={e => - updatePreferences(login, { + updatePreferences(id, { ...perf, imgProxyConfig: { ...unwrap(perf.imgProxyConfig), @@ -369,7 +368,7 @@ const PreferencesPage = () => { description: "Hexidecimal 'salt' input for imgproxy", })} onChange={e => - updatePreferences(login, { + updatePreferences(id, { ...perf, imgProxyConfig: { ...unwrap(perf.imgProxyConfig), @@ -396,7 +395,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, enableReactions: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, enableReactions: e.target.checked })} /> @@ -413,7 +412,7 @@ const PreferencesPage = () => { onChange={e => { const split = e.target.value.match(/[\p{L}\S]{1}/u); console.debug(e.target.value, split); - updatePreferences(login, { + updatePreferences(id, { ...perf, reactionEmoji: split?.[0] ?? "", }); @@ -433,7 +432,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, confirmReposts: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, confirmReposts: e.target.checked })} /> @@ -450,7 +449,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, autoShowLatest: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, autoShowLatest: e.target.checked })} /> @@ -464,7 +463,7 @@ const PreferencesPage = () => { updatePreferences(login, { ...perf, showDebugMenus: e.target.checked })} + onChange={e => updatePreferences(id, { ...perf, showDebugMenus: e.target.checked })} /> diff --git a/packages/app/src/Upload/index.ts b/packages/app/src/Upload/index.ts index fb461810..28e5bc01 100644 --- a/packages/app/src/Upload/index.ts +++ b/packages/app/src/Upload/index.ts @@ -62,7 +62,7 @@ export interface UploadProgress { export type UploadStage = "starting" | "hashing" | "uploading" | "done" | undefined; export default function useFileUpload(): Uploader { - const fileUploader = useLogin().preferences.fileUploader; + const fileUploader = useLogin(s => s.appData.item.preferences.fileUploader); const { publisher } = useEventPublisher(); const [progress, setProgress] = useState>([]); const [stage, setStage] = useState(); diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index a6a4be73..ba49ffe5 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -160,7 +160,7 @@ async function initSite() { // inject analytics script // - if (CONFIG.features.analytics && (login.preferences.telemetry ?? true)) { + if (CONFIG.features.analytics && (login.appData.item.preferences.telemetry ?? true)) { const sc = document.createElement("script"); sc.src = "https://analytics.v0l.io/js/script.js"; sc.defer = true; diff --git a/packages/system/src/event-publisher.ts b/packages/system/src/event-publisher.ts index df1092b4..0ccc0245 100644 --- a/packages/system/src/event-publisher.ts +++ b/packages/system/src/event-publisher.ts @@ -330,6 +330,13 @@ export class EventPublisher { return await this.#sign(eb); } + async appData(data: object, id: string) { + const eb = this.#eb(EventKind.AppData); + eb.content(await this.nip4Encrypt(JSON.stringify(data), this.#pubKey)); + eb.tag(["d", id]); + return await this.#sign(eb); + } + /** * NIP-59 Gift Wrap event with ephemeral key */ diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index 25def55e..c226a857 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -139,21 +139,28 @@ export interface MessageEncryptor { decryptData(payload: MessageEncryptorPayload, sharedSecet: Uint8Array): Promise | string; } -export function decodeEncryptionPayload(p: string) { +export function decodeEncryptionPayload(p: string): MessageEncryptorPayload { if (p.startsWith("{") && p.endsWith("}")) { const pj = JSON.parse(p) as { v: number; nonce: string; ciphertext: string }; return { v: pj.v, nonce: base64.decode(pj.nonce), ciphertext: base64.decode(pj.ciphertext), - } as MessageEncryptorPayload; + }; + } else if (p.includes("?iv=")) { + const [ciphertext, nonce] = p.split("?iv="); + return { + v: MessageEncryptorVersion.Nip4, + nonce: base64.decode(nonce), + ciphertext: base64.decode(ciphertext), + }; } else { const buf = base64.decode(p); return { v: buf[0], nonce: buf.subarray(1, 25), ciphertext: buf.subarray(25), - } as MessageEncryptorPayload; + }; } }