fix(delete): single signature for admin takedown — stop Amber collision hang (1.2.12)
Admin delete fired THREE signed events (kind:5 deletion, kind:1985 label, NIP-86
banevent). Each opens Amber, but Amber handles one approval at a time — the
extra background signs collided with the primary one on Android and hung the
whole delete ("taking longer than expected"). Now the admin path publishes only
the kind:5 deletion (one Amber approval) and hides locally (no signature); the
optional label/relay-ban are dropped from the interactive flow. Also raise the
awaited timeout 40s→120s to fit a real Amber round-trip. Bump to 1.2.12.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
955a823b02
commit
52fd6f7a81
2 changed files with 21 additions and 58 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "skate-spots",
|
"name": "skate-spots",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.11",
|
"version": "1.2.12",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"homepage": "https://skate-spots.com",
|
"homepage": "https://skate-spots.com",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,18 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useNostrPublish } from '@/hooks/useNostrPublish';
|
import { useNostrPublish } from '@/hooks/useNostrPublish';
|
||||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||||
import { isAdminPubkey } from '@/lib/admins';
|
import { isAdminPubkey } from '@/lib/admins';
|
||||||
import {
|
|
||||||
MODERATION_LABEL_NAMESPACE,
|
|
||||||
MODERATION_LABEL_HIDDEN,
|
|
||||||
} from '@/lib/types';
|
|
||||||
import { addLocalHidden } from '@/lib/localModeration';
|
import { addLocalHidden } from '@/lib/localModeration';
|
||||||
import { banEventOnRelay } from '@/lib/relayAdmin';
|
|
||||||
import type { NostrEvent } from '@nostrify/nostrify';
|
import type { NostrEvent } from '@nostrify/nostrify';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hard ceiling on how long the whole delete can run before we give up and
|
* Hard ceiling on how long the AWAITED part of the delete can run before we give
|
||||||
* surface an error. Guarantees the UI spinner can NEVER hang indefinitely,
|
* up. This must be generous enough for an Amber round-trip: the user has to
|
||||||
* whatever the signer/relay does underneath.
|
* switch to Amber, approve, and switch back — which easily exceeds 40s if they
|
||||||
|
* fumble. Too short a ceiling was surfacing "taking longer than expected" even
|
||||||
|
* when Amber was about to succeed. The signer has its own 90s cap underneath, so
|
||||||
|
* we align to that plus headroom.
|
||||||
*/
|
*/
|
||||||
const DELETE_TIMEOUT_MS = 40_000;
|
const DELETE_TIMEOUT_MS = 120_000;
|
||||||
|
|
||||||
function withHardTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
function withHardTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
|
@ -113,56 +111,21 @@ export function useDeleteEntity() {
|
||||||
tags: deletionTags,
|
tags: deletionTags,
|
||||||
});
|
});
|
||||||
|
|
||||||
// STEP 2 (admin takedown only): record the target locally RIGHT NOW so the
|
// STEP 2 (admin takedown only): hide the target LOCALLY right now so it
|
||||||
// app hides it on this device immediately — no waiting on a second signer
|
// disappears from this device immediately. This needs NO signature.
|
||||||
// round-trip. Then fire the cross-device kind:1985 moderation label in the
|
//
|
||||||
// BACKGROUND (not awaited): a second Amber approval, if needed, happens
|
// CRITICAL UX/BUG FIX: we deliberately do NOT fire additional signed
|
||||||
// after the dialog has already closed and never blocks the UI. This is the
|
// events here (the kind:1985 moderation label or the NIP-86 relay ban).
|
||||||
// fix for "stuck on Deleting…": the mutation resolves as soon as kind:5 is
|
// Each of those calls Amber, and Amber can only handle ONE approval at a
|
||||||
// published, regardless of the label.
|
// time — firing a second/third signer request while the first is still in
|
||||||
|
// flight COLLIDES on Android and makes the whole delete hang ("taking
|
||||||
|
// longer than expected", Amber juggling multiple prompts). The single
|
||||||
|
// kind:5 deletion (step 1) already tells relays to drop the event, and the
|
||||||
|
// local hide covers this device instantly. The optional label / relay-ban
|
||||||
|
// can be performed separately from the relay moderator tools without
|
||||||
|
// blocking or breaking the interactive delete.
|
||||||
if (isAdmin && !isOwner) {
|
if (isAdmin && !isOwner) {
|
||||||
addLocalHidden({ coord, id: event.id });
|
addLocalHidden({ coord, id: event.id });
|
||||||
|
|
||||||
const labelTags: string[][] = [
|
|
||||||
['L', MODERATION_LABEL_NAMESPACE],
|
|
||||||
['l', MODERATION_LABEL_HIDDEN, MODERATION_LABEL_NAMESPACE],
|
|
||||||
['a', coord],
|
|
||||||
['e', event.id],
|
|
||||||
['k', String(event.kind)],
|
|
||||||
['p', event.pubkey],
|
|
||||||
['alt', `SkateSpots admin takedown${reason?.trim() ? `: ${reason.trim()}` : ''}`],
|
|
||||||
];
|
|
||||||
// Intentionally NOT awaited — background best-effort.
|
|
||||||
void publishEvent({
|
|
||||||
kind: 1985,
|
|
||||||
content: reason?.trim() || 'Removed by SkateSpots admin',
|
|
||||||
tags: labelTags,
|
|
||||||
}).catch((labelError) => {
|
|
||||||
console.warn(
|
|
||||||
'[useDeleteEntity] background moderation label failed (content is ' +
|
|
||||||
'already hidden locally; kind:5 was published):',
|
|
||||||
labelError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// REAL takedown on our own relay (NIP-86 `banevent`). Because the admin
|
|
||||||
// is the relay owner/moderator, goskatespots.nostr1.com honors this and
|
|
||||||
// actually removes the event from its database — unlike a cross-author
|
|
||||||
// kind:5 request. Also background/best-effort so it never blocks the UI;
|
|
||||||
// this may prompt one more signer approval (NIP-98 auth event).
|
|
||||||
if (user.signer) {
|
|
||||||
void banEventOnRelay({
|
|
||||||
signer: user.signer,
|
|
||||||
eventId: event.id,
|
|
||||||
reason: reason?.trim() || 'Removed by SkateSpots admin',
|
|
||||||
}).catch((banError) => {
|
|
||||||
console.warn(
|
|
||||||
'[useDeleteEntity] relay banevent failed (content is hidden in-app ' +
|
|
||||||
'regardless; you can retry from the relay moderator portal):',
|
|
||||||
banError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue