SkateSpots/src/components/AmberApprovalHint.tsx
shakespeare.diy 48b127f7b0 Amber UX: honest multi-approval hint for encrypted chat/DM sends
Audit outcome for the two suspected "double-prompt" Amber flows:

• Image upload — CONFIRMED already a single signature. BlossomUploader
  (even at the pinned 0.47.1) signs one kind-24242 auth event and reuses
  it across all servers via Promise.any, so uploads = 1 Amber prompt. No
  change needed.

• DM / crew-chat send — the multiple prompts are protocol-inherent and
  MUST NOT be optimized away: NIP-17 seals+signs once per recipient (and
  to self); a Cordn crew message signs an author proof then encrypts once
  per roster member. Reducing the count would weaken confidentiality or
  authorship. So instead of a code shortcut we add transparency.

New <AmberApprovalHint>: a tiny heads-up shown ONLY to interactive
(Amber) signers before an inherently multi-approval action, telling them
how many Approve screens to expect and that it's normal. Renders nothing
for silent signers (extension/nsec/bunker), where the ops are invisible.

Wired into:
  • CrewChat composer — count derived from transport + roster size
    (Cordn: roster+1; NIP-17: roster*2).
  • ConversationPage (1:1 DM) composer — 4 approvals (encrypt+sign to the
    recipient and to yourself).

Dependencies: reviewed the full manifest; deliberately left unchanged.
Every upgrade candidate is either on the critical crypto/Nostr/payments
path (nostrify, nostr-tools, cashu, bitcoinjs, noble, scure — pinned for
safety) or a documented major bump (lucide v1, react 19, react-router 7)
with breaking changes and no user-facing benefit. No security advisories
force a change, and caret ranges already pull safe patch/minor updates.
"It must work" wins over churn.

Bumps to 1.2.20 (Android versionCode 10220 auto-derived).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 20:08:39 +07:00

64 lines
2.4 KiB
TypeScript

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 (
<p
className={cn(
'flex items-start gap-1.5 text-[11px] leading-snug text-muted-foreground',
className,
)}
>
<KeyRound className="mt-0.5 h-3 w-3 shrink-0 text-amber-400" />
<span>
{multi ? (
<>
Your signer will ask you to approve{' '}
<strong className="font-semibold text-amber-300">{approvals} times</strong> to {action} —
that's normal for encrypted messages (one approval per recipient). Approve each.
</>
) : (
<>Your signer will ask you to approve to {action}.</>
)}
</span>
</p>
);
}