Allow users to delete their messages

No more than 30 minutes old
This commit is contained in:
dtonon 2026-06-23 22:53:31 +02:00
parent 2daaec15fa
commit 9e26e9dc60
3 changed files with 76 additions and 16 deletions

View file

@ -1,12 +1,24 @@
import { auth } from "$lib/auth.svelte";
import { publishForum } from "$lib/relay";
// Users may delete their own posts only within this window; afterwards the relay
// also refuses (pyramid caps group self-deletes at 2h, we're stricter on top).
export const SELF_DELETE_WINDOW = 30 * 60; // seconds
export function withinSelfDeleteWindow(createdAt: number): boolean {
return Math.floor(Date.now() / 1000) - createdAt <= SELF_DELETE_WINDOW;
}
// What's pending deletion, surfaced to the confirmation modal. `label` is the
// noun shown in the dialog copy ("discussion", "reply", "message").
// noun shown in the dialog copy ("discussion", "reply", "message"). `self` picks
// the mechanism: authors delete via NIP-09 (kind 5), admins moderate via
// NIP-29 (kind 9005). `eventKind` feeds the NIP-09 `k` tag.
type DeleteTarget = {
eventId: string;
groupId: string;
label: string;
self?: boolean;
eventKind?: number;
};
let target = $state<DeleteTarget | null>(null);
@ -45,8 +57,9 @@ export function cancelDelete() {
error = null;
}
// NIP-29 kind:9005 delete-event. The relay enforces the role check and only
// resolves the publish (OK: true) once it has processed the deletion.
// Authors self-delete with NIP-09 (kind 5); admins moderate with NIP-29
// (kind 9005). The relay enforces the matching rule (author match / role) and
// only resolves the publish (OK: true) once it has processed the deletion.
export async function confirmDelete(reason?: string) {
if (!target) return;
if (!auth.signer) {
@ -56,21 +69,28 @@ export async function confirmDelete(reason?: string) {
busy = true;
error = null;
const kind = target.self ? 5 : 9005;
const tags: string[][] = [
["h", target.groupId],
["e", target.eventId],
];
if (target.self && target.eventKind !== undefined) {
tags.push(["k", String(target.eventKind)]);
}
try {
const signed = await auth.signer.signEvent({
kind: 9005,
kind,
created_at: Math.floor(Date.now() / 1000),
tags,
content: reason?.trim() ?? "",
});
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("Relay did not respond in time")), 8000),
setTimeout(
() => reject(new Error("Relay did not respond in time")),
8000,
),
);
await Promise.race([Promise.all(publishForum(signed)), timeout]);