fix(client): safeBack helper + prevent self-contact-request

1. GO_BACK warning & stuck screens

   When a deep link or direct push put the user on /feed/[id],
   /profile/[address], /compose, or /settings without any prior stack
   entry, tapping the header chevron emitted:
     "ERROR The action 'GO_BACK' was not handled by any navigator"
   and did nothing — user was stuck.

   New helper lib/utils.safeBack(fallback = '/(app)/chats') wraps
   router.canGoBack() — when there's history it pops; otherwise it
   replace-navigates to a sensible fallback (chats list by default,
   '/' for auth screens so we land back at the onboarding).

   Applied to every header chevron and back-from-detail flow:
   app/(app)/chats/[id], app/(app)/compose, app/(app)/feed/[id]
   (header + onDeleted), app/(app)/feed/tag/[tag],
   app/(app)/profile/[address], app/(app)/new-contact (header + OK
   button on request-sent alert), app/(app)/settings,
   app/(auth)/create, app/(auth)/import.

2. Prevent self-contact-request

   new-contact.tsx now compares the resolved address against
   keyFile.pub_key at two points:
     - right after resolveUsername + getIdentity in search() — before
       the profile card even renders, so the user doesn't see the
       "Send request" CTA for themselves.
     - inside sendRequest() as a belt-and-braces guard in case the
       check was somehow bypassed.
   The search path shows an inline error ("That's you. You can't
   send a contact request to yourself."); sendRequest falls back to
   an Alert with the same meaning. Both compare case-insensitively
   against the pubkey hex so mixed-case pastes work.

   Technically the server would still accept a self-request (the
   chain stores it under contact_in:<self>:<self>), but it's a dead-
   end UX-wise — the user can't chat with themselves — so the client
   blocks it preemptively instead of letting users pay the fee for
   nothing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
vsecoder
2026-04-18 23:45:19 +03:00
parent f7a849ddcb
commit e62b72b5be
10 changed files with 54 additions and 15 deletions

View File

@@ -10,6 +10,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { generateKeyFile } from '@/lib/crypto';
import { saveKeyFile } from '@/lib/storage';
import { useStore } from '@/lib/store';
import { safeBack } from '@/lib/utils';
import { Header } from '@/components/Header';
import { IconButton } from '@/components/IconButton';
@@ -37,7 +38,7 @@ export default function CreateAccountScreen() {
<View style={{ flex: 1, backgroundColor: '#000000', paddingTop: insets.top }}>
<Header
title="Create account"
left={<IconButton icon="chevron-back" size={36} onPress={() => router.back()} />}
left={<IconButton icon="chevron-back" size={36} onPress={() => safeBack('/')} />}
/>
<ScrollView contentContainerStyle={{ padding: 14, paddingBottom: 40 }}>
<Text style={{ color: '#ffffff', fontSize: 17, fontWeight: '700', marginBottom: 4 }}>

View File

@@ -15,6 +15,7 @@ import * as DocumentPicker from 'expo-document-picker';
import * as Clipboard from 'expo-clipboard';
import { saveKeyFile } from '@/lib/storage';
import { useStore } from '@/lib/store';
import { safeBack } from '@/lib/utils';
import type { KeyFile } from '@/lib/types';
import { Header } from '@/components/Header';
@@ -96,7 +97,7 @@ export default function ImportKeyScreen() {
<View style={{ flex: 1, backgroundColor: '#000000', paddingTop: insets.top }}>
<Header
title="Import key"
left={<IconButton icon="chevron-back" size={36} onPress={() => router.back()} />}
left={<IconButton icon="chevron-back" size={36} onPress={() => safeBack('/')} />}
/>
<ScrollView
contentContainerStyle={{ padding: 14, paddingBottom: 40 }}