diff --git a/package.json b/package.json index 18de607..ba99631 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.2.19", + "version": "1.2.20", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/components/AmberApprovalHint.tsx b/src/components/AmberApprovalHint.tsx new file mode 100644 index 0000000..225f28f --- /dev/null +++ b/src/components/AmberApprovalHint.tsx @@ -0,0 +1,64 @@ +import { KeyRound } from 'lucide-react'; + +import { useCurrentUser } from '@/hooks/useCurrentUser'; +import { useNostrLogin } from '@nostrify/react/login'; +import { isInteractiveSigner } from '@/lib/signerCapabilities'; +import { cn } from '@/lib/utils'; + +interface AmberApprovalHintProps { + /** + * How many separate signer approvals this action will require. When known and + * > 1 it's shown to set expectations (e.g. an encrypted group message signs + * once per recipient). Omit for a generic "may ask you to approve" note. + */ + approvals?: number; + /** Short label for the action, e.g. "send this message". */ + action?: string; + className?: string; +} + +/** + * A tiny, honest heads-up shown ONLY to interactive-signer (Amber) users before + * an action that is inherently multi-approval. + * + * Some flows are cryptographically obliged to hit the signer several times — a + * NIP-17 DM seals + signs once per recipient (and once to self); an encrypted + * crew message signs an author proof then encrypts once per member. With a + * silent signer (extension / nsec / bunker) these are instant and invisible, so + * there's nothing to warn about. With Amber, each op is a foreground Approve + * screen, so without this the user sees Amber "pop up several times" for one + * Send and reasonably assumes something is broken. + * + * We DON'T try to reduce the count (that would weaken the encryption/authorship + * model) — we just tell the truth up front. Renders nothing for silent signers. + */ +export function AmberApprovalHint({ approvals, action = 'send', className }: AmberApprovalHintProps) { + const { user } = useCurrentUser(); + const { logins } = useNostrLogin(); + + if (!isInteractiveSigner(user, logins)) return null; + + const multi = typeof approvals === 'number' && approvals > 1; + + return ( +

+ + + {multi ? ( + <> + Your signer will ask you to approve{' '} + {approvals} times to {action} — + that's normal for encrypted messages (one approval per recipient). Approve each. + + ) : ( + <>Your signer will ask you to approve to {action}. + )} + +

+ ); +} diff --git a/src/components/CrewChat.tsx b/src/components/CrewChat.tsx index 5cf6f90..7585c7e 100644 --- a/src/components/CrewChat.tsx +++ b/src/components/CrewChat.tsx @@ -14,6 +14,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useNostrLogin } from '@nostrify/react/login'; import { hasNip44 as checkNip44 } from '@/lib/signerCapabilities'; +import { AmberApprovalHint } from '@/components/AmberApprovalHint'; import { useAuthor } from '@/hooks/useAuthor'; import { useToast } from '@/hooks/useToast'; import { @@ -249,6 +250,14 @@ export function CrewChat({ const canSendNip44 = checkNip44(user, logins); + // How many signer approvals one Send will cost — used to warn Amber users so + // the repeated Approve screens don't look like a bug. Roster = founder + all + // members (deduped, includes self). Cordn v2: one author-proof signature plus + // one encryption per roster member. NIP-17: each target (others + self) is an + // encrypt + a seal signature, i.e. two ops per member. + const rosterSize = Math.max(participants.length, 1); + const approvalCount = usingCordn ? rosterSize + 1 : rosterSize * 2; + const handleSend = async (e: React.FormEvent) => { e.preventDefault(); const text = draft.trim(); @@ -427,6 +436,11 @@ export function CrewChat({ {isPending ? : } + )} diff --git a/src/pages/ConversationPage.tsx b/src/pages/ConversationPage.tsx index 3ef7da4..668e4cb 100644 --- a/src/pages/ConversationPage.tsx +++ b/src/pages/ConversationPage.tsx @@ -25,6 +25,7 @@ import { RefreshCw, } from 'lucide-react'; import { cn } from '@/lib/utils'; +import { AmberApprovalHint } from '@/components/AmberApprovalHint'; import NotFound from './NotFound'; // Single message bubble @@ -294,6 +295,9 @@ export default function ConversationPage() {

Messages are end-to-end encrypted using NIP-17

+ {/* A 1:1 NIP-17 DM encrypts + signs to the recipient AND to yourself + (so you keep a copy) = up to 4 signer approvals. Warn Amber users. */} +