diff --git a/.changeset/rotten-garlics-beg.md b/.changeset/rotten-garlics-beg.md new file mode 100644 index 000000000..174141ce9 --- /dev/null +++ b/.changeset/rotten-garlics-beg.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add "Proactively authenticate to relays" option to privacy settings, defaults to off diff --git a/.changeset/swift-trainers-fold.md b/.changeset/swift-trainers-fold.md new file mode 100644 index 000000000..077d370c7 --- /dev/null +++ b/.changeset/swift-trainers-fold.md @@ -0,0 +1,5 @@ +--- +"nostrudel": patch +--- + +Fix automatically disconnecting from authenticated relays diff --git a/src/classes/relay-pool.ts b/src/classes/relay-pool.ts index c8a31c807..818b8e5e3 100644 --- a/src/classes/relay-pool.ts +++ b/src/classes/relay-pool.ts @@ -10,6 +10,7 @@ import { offlineMode } from "../services/offline-mode"; import processManager from "../services/process-manager"; import signingService from "../services/signing"; import accountService from "../services/account"; +import localSettings from "../services/local-settings"; export type Notice = { message: string; @@ -20,6 +21,8 @@ export type Notice = { export type RelayAuthMode = "always" | "ask" | "never"; export default class RelayPool { + log = logger.extend("RelayPool"); + relays = new Map(); onRelayCreated = new Subject(); onRelayChallenge = new Subject<[AbstractRelay, string]>(); @@ -35,8 +38,6 @@ export default class RelayPool { authenticated = new SuperMap>(() => new Subject()); - log = logger.extend("RelayPool"); - getRelay(relayOrUrl: string | URL | AbstractRelay) { if (typeof relayOrUrl === "string") { const safeURL = safeRelayUrl(relayOrUrl); @@ -69,10 +70,7 @@ export default class RelayPool { const key = url.toString(); if (!this.relays.has(key)) { const r = new AbstractRelay(key, { verifyEvent: verifyEventMethod }); - r._onauth = (challenge) => { - this.onRelayChallenge.next([r, challenge]); - this.challenges.get(r).next(challenge); - }; + r._onauth = (challenge) => this.handleRelayChallenge(r, challenge); r.onnotice = (notice) => this.handleRelayNotice(r, notice); this.relays.set(key, r); @@ -130,7 +128,7 @@ export default class RelayPool { let relay = this.getRelay(relayOrUrl); if (!relay) return; - const defaultMode = (localStorage.getItem(`default-auth-mode`) as RelayAuthMode) ?? undefined; + const defaultMode = localSettings.defaultAuthenticationMode.value; const mode = (localStorage.getItem(this.getRelayAuthStorageKey(relay)) as RelayAuthMode) ?? undefined; return mode || defaultMode; @@ -189,6 +187,30 @@ export default class RelayPool { return this.authForSubscribe.get(relay).value !== false; } + private automaticallyAuthenticate(relay: AbstractRelay) { + const authMode = this.getRelayAuthMode(relay); + // only automatically authenticate if auth mode is set to "always" + if (authMode === "always") { + const account = accountService.current.value; + if (!account) return; + + this.authenticate(relay, (draft) => { + return signingService.requestSignature(draft, account); + }).then(() => { + this.log(`Automatically authenticated to ${relay.url}`); + }); + } + } + + private handleRelayChallenge(relay: AbstractRelay, challenge: string) { + this.onRelayChallenge.next([relay, challenge]); + this.challenges.get(relay).next(challenge); + + if (localSettings.proactivelyAuthenticate.value) { + this.automaticallyAuthenticate(relay); + } + } + handleRelayNotice(relay: AbstractRelay, message: string) { const subject = this.notices.get(relay); subject.next([...subject.value, { message, date: dayjs().unix(), relay }]); @@ -197,18 +219,8 @@ export default class RelayPool { const authForSubscribe = this.authForSubscribe.get(relay); if (!authForSubscribe.value) authForSubscribe.next(true); - const account = accountService.current.value; - if (account) { - const authMode = this.getRelayAuthMode(relay); - - if (authMode === "always") { - this.authenticate(relay, (draft) => { - return signingService.requestSignature(draft, account); - }).then(() => { - this.log(`Automatically authenticated to ${relay.url}`); - }); - } - } + // try to authenticate + this.automaticallyAuthenticate(relay); } } @@ -216,6 +228,9 @@ export default class RelayPool { for (const [url, relay] of this.relays) { if (!relay.connected) continue; + // don't disconnect from authenticated relays + if (this.authenticated.get(relay).value) continue; + let disconnect = true; for (const process of processManager.processes) { if (process.active && process.relays.has(relay)) { diff --git a/src/services/local-settings.ts b/src/services/local-settings.ts index 8ea06fac9..900aa9a45 100644 --- a/src/services/local-settings.ts +++ b/src/services/local-settings.ts @@ -51,6 +51,10 @@ const addClientTag = new BooleanLocalStorageEntry("add-client-tag", false); const verifyEventMethod = new LocalStorageEntry("verify-event-method", "wasm"); // wasm, internal, none const enableKeyboardShortcuts = new BooleanLocalStorageEntry("enable-keyboard-shortcuts", true); +// privacy +const defaultAuthenticationMode = new LocalStorageEntry("default-relay-auth-mode", "ask"); // ask, always, never +const proactivelyAuthenticate = new BooleanLocalStorageEntry("proactively-authenticate", false); + // display settings const showBrandLogo = new BooleanLocalStorageEntry("show-brand-logo", true); @@ -66,6 +70,8 @@ const localSettings = { verifyEventMethod, enableKeyboardShortcuts, showBrandLogo, + defaultAuthenticationMode, + proactivelyAuthenticate, }; if (import.meta.env.DEV) { diff --git a/src/views/settings/privacy/index.tsx b/src/views/settings/privacy/index.tsx index f6f2e0e0b..b74e912e6 100644 --- a/src/views/settings/privacy/index.tsx +++ b/src/views/settings/privacy/index.tsx @@ -1,4 +1,3 @@ -import { useLocalStorage } from "react-use"; import { Flex, FormControl, @@ -18,6 +17,8 @@ import { createRequestProxyUrl } from "../../../helpers/request"; import { RelayAuthMode } from "../../../classes/relay-pool"; import VerticalPageLayout from "../../../components/vertical-page-layout"; import useSettingsForm from "../use-settings-form"; +import useSubject from "../../../hooks/use-subject"; +import localSettings from "../../../services/local-settings"; async function validateInvidiousUrl(url?: string) { if (!url) return true; @@ -44,9 +45,8 @@ async function validateRequestProxy(url?: string) { export default function PrivacySettings() { const { register, submit, formState } = useSettingsForm(); - const [defaultAuthMode, setDefaultAuthMode] = useLocalStorage("default-relay-auth-mode", "ask", { - raw: true, - }); + const defaultAuthenticationMode = useSubject(localSettings.defaultAuthenticationMode); + const proactivelyAuthenticate = useSubject(localSettings.proactivelyAuthenticate); return ( @@ -58,8 +58,8 @@ export default function PrivacySettings() { w="xs" rounded="md" flexShrink={0} - value={defaultAuthMode || "ask"} - onChange={(e) => setDefaultAuthMode(e.target.value as RelayAuthMode)} + value={defaultAuthenticationMode} + onChange={(e) => localSettings.defaultAuthenticationMode.next(e.target.value as RelayAuthMode)} > @@ -68,6 +68,22 @@ export default function PrivacySettings() { How should the app handle relays requesting identification + + + + Proactively authenticate to relays + + localSettings.proactivelyAuthenticate.next(e.currentTarget.checked)} + /> + + + Authenticate to relays as soon as they send the authentication challenge + + + Nitter instance ( + const [authMode, setAuthMode] = useLocalStorage( relayPoolService.getRelayAuthStorageKey(relay), - "ask", - { raw: true }, + "", + { + raw: true, + }, ); return ( @@ -74,9 +80,10 @@ function RelayAuthCard({ relay }: { relay: AbstractRelay }) { w="auto" rounded="md" flexShrink={0} - value={authMode || "ask"} + value={authMode} onChange={(e) => setAuthMode(e.target.value as RelayAuthMode)} > +