Add NRC (Nostr Relay Connect) for cross-device sync

Implements NRC listener that allows other user clients to connect
and sync events through a rendezvous relay. Features:
- REQ-only (read) sync for security
- Secret-based and CAT token authentication
- NIP-44 encrypted tunneling
- Device-specific event filtering via d-tag prefix
- Session management with timeouts
- Settings UI with QR code connection flow

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
woikos
2026-01-11 09:16:03 +01:00
parent 08f75a902d
commit ecd7c36400
10 changed files with 1921 additions and 3 deletions

View File

@@ -6,6 +6,7 @@ import EmojiPackList from '@/components/EmojiPackList'
import EmojiPickerDialog from '@/components/EmojiPickerDialog'
import FavoriteRelaysSetting from '@/components/FavoriteRelaysSetting'
import MailboxSetting from '@/components/MailboxSetting'
import NRCSettings from '@/components/NRCSettings'
import NoteList from '@/components/NoteList'
import Tabs from '@/components/Tabs'
import {
@@ -73,6 +74,7 @@ import {
PencilLine,
RotateCcw,
ScanLine,
RefreshCw,
Server,
Settings2,
Smile,
@@ -105,7 +107,7 @@ const NOTIFICATION_STYLES = [
] as const
// Accordion item values for keyboard navigation
const ACCORDION_ITEMS = ['general', 'appearance', 'relays', 'wallet', 'posts', 'emoji-packs', 'messaging', 'system']
const ACCORDION_ITEMS = ['general', 'appearance', 'relays', 'sync', 'wallet', 'posts', 'emoji-packs', 'messaging', 'system']
export default function Settings() {
const { t, i18n } = useTranslation()
@@ -123,7 +125,7 @@ export default function Settings() {
// Get the visible accordion items based on pubkey availability
const visibleAccordionItems = pubkey
? ACCORDION_ITEMS
: ACCORDION_ITEMS.filter((item) => !['wallet', 'posts', 'emoji-packs', 'messaging'].includes(item))
: ACCORDION_ITEMS.filter((item) => !['sync', 'wallet', 'posts', 'emoji-packs', 'messaging'].includes(item))
// Register as a navigation region - Settings decides what "up/down" means
const handleSettingsIntent = useCallback(
@@ -548,6 +550,23 @@ export default function Settings() {
</AccordionItem>
</NavigableAccordionItem>
{/* Sync (NRC) */}
{!!pubkey && (
<NavigableAccordionItem ref={setAccordionRef('sync')} isSelected={isAccordionSelected('sync')}>
<AccordionItem value="sync">
<AccordionTrigger className="px-4 hover:no-underline">
<div className="flex items-center gap-4">
<RefreshCw className="size-4" />
<span>{t('Device Sync')}</span>
</div>
</AccordionTrigger>
<AccordionContent className="px-4">
<NRCSettings />
</AccordionContent>
</AccordionItem>
</NavigableAccordionItem>
)}
{/* Wallet */}
{!!pubkey && (
<NavigableAccordionItem ref={setAccordionRef('wallet')} isSelected={isAccordionSelected('wallet')}>