mirror of
https://github.com/block/buzz.git
synced 2026-11-01 15:01:12 +00:00
## Summary - replace the desktop's global kind-20001 presence subscription with one author-filtered subscription derived from active TanStack presence queries - reconcile changing demand without a delivery gap: promote only after relay EOSE, keep the last confirmed subscription on failure, discard stale opens, and close entirely when demand is empty - preserve REST presence as the initial seed and TTL/crash-recovery backstop - add transport-seam and lifecycle tests for readiness, normalization, churn, retries, close failures, reconnect ownership assumptions, and disposal ## Why The desktop currently receives presence heartbeats from every identity on the relay. A live tap measured roughly 2,700 events/minute (45/sec), about 1 MB/minute and 71.5% of readable traffic, from approximately 1,300 distinct fleet identities. Most are discarded only after WebSocket, Tauri IPC, and JS parsing. This change applies normal Nostr author filtering at relay fan-out, before those costs. It deliberately does not introduce a relay digest protocol or client-side event batching; relevant-author traffic should be small after scoping, and the existing signed-delta/REST-TTL model remains intact. ## Correctness model - active query observers are the demand source; inactive cached queries retain no authors - replacement opens before old closes and is promoted only after EOSE - timeout/CLOSED rejects and closes the candidate while preserving the last good subscription - rapid A→B→C and A→B→A churn cannot unseat current A with stale B - empty demand never sends an unfiltered subscription - RelayClient continues to own reconnect replay; the reconciler does not duplicate subscriptions on reconnect ## Validation Exact pushed head: `8845093aec0330be16efe52d3459ff67f1000ff4` Pre-push hooks passed: - desktop check and file-size ratchet - desktop TypeScript - desktop unit suite: 4,791/4,791 - branch-skew check Focused lifecycle/transport suite: 34/34 passed before commit. Independent Royal Court review found and blocked two prototype flaws (timeout-as-success and starvation-prone trailing debounce); both were fixed and the final worktree was cleared with no remaining correctness or lifecycle blockers. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
import type {
|
|
LiveSubscriptionReadiness,
|
|
RelaySubscriptionFilter,
|
|
} from "@/shared/api/relayClientShared";
|
|
import type { RelayEvent } from "@/shared/api/types";
|
|
import { KIND_PRESENCE_UPDATE } from "@/shared/constants/kinds";
|
|
|
|
export type OpenLiveSubscription = (
|
|
filter: RelaySubscriptionFilter,
|
|
onEvent: (event: RelayEvent) => void,
|
|
onReady: (readiness: LiveSubscriptionReadiness) => void,
|
|
readinessTimeoutMs: number,
|
|
) => Promise<() => Promise<void>>;
|
|
|
|
/** Open an author-scoped presence subscription and require relay EOSE. */
|
|
export async function openPresenceSubscription(
|
|
pubkeys: string[],
|
|
onEvent: (event: RelayEvent) => void,
|
|
openLive: OpenLiveSubscription,
|
|
) {
|
|
const authors = [...new Set(pubkeys.map((pubkey) => pubkey.toLowerCase()))]
|
|
.filter(Boolean)
|
|
.sort();
|
|
if (authors.length === 0) {
|
|
throw new Error("Presence subscriptions require at least one author.");
|
|
}
|
|
|
|
const readiness: { value: LiveSubscriptionReadiness } = { value: "timeout" };
|
|
const unsubscribe = await openLive(
|
|
{ kinds: [KIND_PRESENCE_UPDATE], authors, limit: 0 },
|
|
onEvent,
|
|
(nextReadiness) => {
|
|
readiness.value = nextReadiness;
|
|
},
|
|
5_000,
|
|
);
|
|
if (readiness.value === "eose") return unsubscribe;
|
|
|
|
await unsubscribe();
|
|
throw new Error(
|
|
readiness.value === "closed"
|
|
? "Relay rejected the presence subscription."
|
|
: "Timed out confirming the presence subscription.",
|
|
);
|
|
}
|