Amber: make crew-notification dismissal local-only for interactive signers
Accepting/declining a crew invite (and handling join-requests) fired an encrypted NIP-78 cross-device sync on every dismissal, costing an extra encrypt + sign Amber approval on top of the required membership signature. For interactive (Amber) signers, dismissal is now kept local-only: • Accept invite → 1 Amber prompt (membership attestation only) • Decline invite → 0 Amber prompts (local dismissal only) The dismissal still applies instantly and permanently on-device; only the cross-device "clear it on my other devices too" nicety is dropped for Amber. Extension/nsec/bunker signers keep the silent encrypted sync (free for them). Bumps to 1.2.19 (Android versionCode 10219 auto-derived). Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
c9b0571de9
commit
56f29e0a29
2 changed files with 35 additions and 5 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "skate-spots",
|
||||
"private": true,
|
||||
"version": "1.2.18",
|
||||
"version": "1.2.19",
|
||||
"type": "module",
|
||||
"homepage": "https://skate-spots.com",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useNostrLogin } from '@nostrify/react/login';
|
|||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { isInteractiveSigner } from '@/lib/signerCapabilities';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* Cross-device "dismissed crew notifications" set.
|
||||
|
|
@ -129,15 +130,36 @@ export function useCrewDismissals() {
|
|||
* Dismiss a notification by its event id. Optimistically updates the cache +
|
||||
* local mirror immediately, then (best-effort) publishes the encrypted synced
|
||||
* list so other devices catch up.
|
||||
*
|
||||
* INTERACTIVE SIGNERS (Amber): the cross-device encrypted sync is skipped
|
||||
* entirely and the dismissal is kept LOCAL-ONLY. Dismissing is a low-stakes,
|
||||
* high-frequency action (fired on accept/decline of every crew invite and on
|
||||
* every handled join-request), and the encrypted sync costs an extra
|
||||
* encrypt + sign — i.e. one or two additional Amber approval screens per tap.
|
||||
*
|
||||
* Since accepting a crew invite ALREADY requires the membership-attestation
|
||||
* signature (the one that actually makes you a member), forcing a *second*
|
||||
* Amber round-trip just to clear the notification on your other devices is
|
||||
* poor UX and not worth it. So for Amber:
|
||||
* • Accept invite → 1 Amber prompt (the membership attestation only)
|
||||
* • Decline invite → 0 Amber prompts (local dismissal only)
|
||||
* The cross-device "also clear it on my laptop" nicety is silently dropped for
|
||||
* Amber; the dismissal still holds instantly and permanently on this device.
|
||||
*
|
||||
* Non-interactive signers (extension / nsec / bunker) still get the silent
|
||||
* encrypted cross-device sync — it's free for them (no approval screens).
|
||||
*/
|
||||
export function useDismissCrewNotification() {
|
||||
const { nostr } = useNostr();
|
||||
const { user } = useCurrentUser();
|
||||
const { logins } = useNostrLogin();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
// 1. Optimistic local update (works for everyone, instantly).
|
||||
const interactive = isInteractiveSigner(user, logins);
|
||||
|
||||
const mutate = useCallback(
|
||||
async (id: string) => {
|
||||
// 1. Optimistic local update (works for everyone, instantly + offline).
|
||||
const key = dismissKey(user?.pubkey);
|
||||
const current = queryClient.getQueryData<Set<string>>(key) ?? new Set(readLocal());
|
||||
const next = new Set(current);
|
||||
|
|
@ -145,7 +167,10 @@ export function useDismissCrewNotification() {
|
|||
queryClient.setQueryData(key, next);
|
||||
writeLocal([...next]);
|
||||
|
||||
// 2. Best-effort sync via an encrypted NIP-78 event.
|
||||
// 2. Interactive signer (Amber): stop here — local-only, NO extra prompt.
|
||||
if (interactive) return;
|
||||
|
||||
// 3. Non-interactive signer: best-effort encrypted NIP-78 cross-device sync.
|
||||
const nip44 = getNip44(user);
|
||||
if (!user?.pubkey || !nip44) return; // no NIP-44: local-only, no throw
|
||||
|
||||
|
|
@ -166,6 +191,11 @@ export function useDismissCrewNotification() {
|
|||
// Publishing failed — the local dismissal still holds on this device.
|
||||
}
|
||||
},
|
||||
[nostr, user, queryClient, interactive],
|
||||
);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: mutate,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: dismissKey(user?.pubkey) });
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue