Submissions persistence: publish to nos.lol + admin re-broadcast mirror

Submissions are signed by throwaway ephemeral keys and public relays prune
events from pubkeys with no social graph, so the queue quietly evaporated.

- Forms: also publish to nos.lol (better retention, already in the app's
  query set) alongside snort/damus/primal.
- submissionFacade: on each fetch, re-broadcast the still-pending signed
  events back to our relays (publishEventToRelays) so pruning doesn't drop
  them. Best-effort, fire-and-forget — never blocks or fails the fetch;
  no-ops when no relay pool is available.
- test: fetch still returns the pending queue when a pool is present.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-07-07 15:48:48 -04:00
1 parent d091ce8bac
commit 5eb7545a3f
7 files changed
+51 -1

No files matched your search

+33 -1
View File
@@ -14,6 +14,7 @@ import {
SUBMISSION_KIND,
parseSubmissionEvent,
} from "./submissionService.js";
import { publishEventToRelays } from "../nostrPublish.js";
import { devLogger } from "../utils/logger.js";
export const RESOLVED_LIST_KIND = 30000;
@@ -33,6 +34,28 @@ function getManagerAndRelays(client) {
return { manager, relays };
}
// Keep pending submissions alive. They're authored by throwaway ephemeral keys
// and public relays prune events from pubkeys with no social graph, so the queue
// quietly evaporates over time. A signed event can be re-published by anyone, so
// on each fetch we re-broadcast the still-pending events back to our relays to
// refresh them. Best-effort and fire-and-forget — never blocks or fails a fetch.
function mirrorPendingSubmissions(rawEvents, client) {
try {
const pool = client?.pool;
const relays = Array.isArray(client?.relays) ? client.relays : [];
if (!pool || !relays.length || !Array.isArray(rawEvents) || !rawEvents.length) {
return;
}
for (const event of rawEvents) {
Promise.resolve()
.then(() => publishEventToRelays(pool, relays, event))
.catch(() => {});
}
} catch (error) {
// best effort — mirroring must never break the submissions view
}
}
function newestByCreatedAt(events) {
let newest = null;
for (const event of Array.isArray(events) ? events : []) {
@@ -93,7 +116,16 @@ export async function fetchPendingSubmissions({
byApplicant.set(key, submission);
}
}
return [...byApplicant.values()];
const pending = [...byApplicant.values()];
// Refresh the still-pending events on our relays so they don't get pruned.
const pendingIds = new Set(pending.map((submission) => submission.eventId));
const rawPending = (Array.isArray(subEvents) ? subEvents : []).filter(
(event) => event && pendingIds.has(event.id),
);
mirrorPendingSubmissions(rawPending, client);
return pending;
}
/**