mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): land live presence updates for not-yet-cached pubkeys (#947)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -4,6 +4,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import { getPresence } from "@/shared/api/tauri";
|
||||
import { normalizePubkey } from "@/shared/lib/pubkey";
|
||||
import {
|
||||
mergePresenceUpdate,
|
||||
parseLivePresenceEvent,
|
||||
presenceQueryWantsPubkey,
|
||||
} from "@/features/presence/lib/presence";
|
||||
import type { PresenceLookup, PresenceStatus } from "@/shared/api/types";
|
||||
|
||||
const PRESENCE_HEARTBEAT_INTERVAL_MS = 30_000;
|
||||
@@ -103,25 +108,18 @@ export function usePresenceSubscription() {
|
||||
let isCancelled = false;
|
||||
let retryTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function handlePresenceEvent(event: {
|
||||
pubkey: string;
|
||||
content: string;
|
||||
tags?: string[][];
|
||||
}) {
|
||||
function handlePresenceEvent(event: { pubkey: string; content: string }) {
|
||||
if (isCancelled) return;
|
||||
const status = event.content;
|
||||
if (status !== "online" && status !== "away" && status !== "offline")
|
||||
return;
|
||||
const pubkey = (
|
||||
event.tags?.find((t) => t[0] === "p")?.[1] ?? event.pubkey
|
||||
).toLowerCase();
|
||||
const parsed = parseLivePresenceEvent(event);
|
||||
if (!parsed) return;
|
||||
const { pubkey, status } = parsed;
|
||||
queryClient.setQueriesData<PresenceLookup>(
|
||||
{ queryKey: ["presence"] },
|
||||
(old) => {
|
||||
if (!old || !(pubkey in old)) return old;
|
||||
if (old[pubkey] === status) return old;
|
||||
return { ...old, [pubkey]: status };
|
||||
{
|
||||
queryKey: ["presence"],
|
||||
predicate: (query) =>
|
||||
presenceQueryWantsPubkey(query.queryKey, pubkey),
|
||||
},
|
||||
(old) => mergePresenceUpdate(old, pubkey, status),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,14 +174,13 @@ export function useSetPresenceMutation(pubkey?: string) {
|
||||
},
|
||||
onSuccess: ({ status }) => {
|
||||
if (normalizedPubkey.length === 0) return;
|
||||
// Update all cached presence queries containing this pubkey.
|
||||
queryClient.setQueriesData<PresenceLookup>(
|
||||
{ queryKey: ["presence"] },
|
||||
(old) => {
|
||||
if (!old || !(normalizedPubkey in old)) return old;
|
||||
if (old[normalizedPubkey] === status) return old;
|
||||
return { ...old, [normalizedPubkey]: status };
|
||||
{
|
||||
queryKey: ["presence"],
|
||||
predicate: (query) =>
|
||||
presenceQueryWantsPubkey(query.queryKey, normalizedPubkey),
|
||||
},
|
||||
(old) => mergePresenceUpdate(old, normalizedPubkey, status),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
mergePresenceUpdate,
|
||||
parseLivePresenceEvent,
|
||||
presenceQueryWantsPubkey,
|
||||
} from "./presence.ts";
|
||||
|
||||
const WILL = "8e39cba681211b3782d0e4483e9343719b9b7be66515252da5491f26421896b1";
|
||||
const OTHER =
|
||||
"44b8e82baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
|
||||
test("merge adds an absent pubkey going online (the core bug)", () => {
|
||||
const old = {};
|
||||
const next = mergePresenceUpdate(old, WILL, "online");
|
||||
assert.deepEqual(next, { [WILL]: "online" });
|
||||
});
|
||||
|
||||
test("merge updates an existing pubkey", () => {
|
||||
const next = mergePresenceUpdate({ [WILL]: "offline" }, WILL, "online");
|
||||
assert.deepEqual(next, { [WILL]: "online" });
|
||||
});
|
||||
|
||||
test("merge returns same reference when status is unchanged", () => {
|
||||
const old = { [WILL]: "online" };
|
||||
assert.equal(mergePresenceUpdate(old, WILL, "online"), old);
|
||||
});
|
||||
|
||||
test("merge leaves other pubkeys untouched", () => {
|
||||
const next = mergePresenceUpdate({ [OTHER]: "away" }, WILL, "online");
|
||||
assert.deepEqual(next, { [OTHER]: "away", [WILL]: "online" });
|
||||
});
|
||||
|
||||
test("merge is a no-op on an undefined cache", () => {
|
||||
assert.equal(mergePresenceUpdate(undefined, WILL, "online"), undefined);
|
||||
});
|
||||
|
||||
test("query wants a pubkey it requested", () => {
|
||||
assert.equal(presenceQueryWantsPubkey(["presence", WILL, OTHER], WILL), true);
|
||||
});
|
||||
|
||||
test("query does not want a pubkey it did not request", () => {
|
||||
assert.equal(presenceQueryWantsPubkey(["presence", OTHER], WILL), false);
|
||||
});
|
||||
|
||||
test("bare presence key (no pubkeys) wants nothing", () => {
|
||||
assert.equal(presenceQueryWantsPubkey(["presence"], WILL), false);
|
||||
});
|
||||
|
||||
test("live event keys off the author, not a p tag", () => {
|
||||
const event = { pubkey: OTHER, content: "online", tags: [["p", WILL]] };
|
||||
assert.deepEqual(parseLivePresenceEvent(event), {
|
||||
pubkey: OTHER,
|
||||
status: "online",
|
||||
});
|
||||
});
|
||||
|
||||
test("spoof attempt cannot mark a victim: foreign p tag is ignored", () => {
|
||||
const event = { pubkey: OTHER, content: "offline", tags: [["p", WILL]] };
|
||||
const parsed = parseLivePresenceEvent(event);
|
||||
assert.notEqual(parsed.pubkey, WILL);
|
||||
assert.equal(parsed.pubkey, OTHER);
|
||||
});
|
||||
|
||||
test("live event with unknown status is rejected", () => {
|
||||
assert.equal(
|
||||
parseLivePresenceEvent({ pubkey: WILL, content: "lurking" }),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test("live event lowercases the author pubkey", () => {
|
||||
const parsed = parseLivePresenceEvent({
|
||||
pubkey: WILL.toUpperCase(),
|
||||
content: "away",
|
||||
});
|
||||
assert.equal(parsed.pubkey, WILL);
|
||||
});
|
||||
@@ -1,4 +1,40 @@
|
||||
import type { PresenceStatus } from "@/shared/api/types";
|
||||
import type { PresenceLookup, PresenceStatus } from "@/shared/api/types";
|
||||
|
||||
// Live kind:20001 events are self-signed by their author; the subject is
|
||||
// always the event author. A p tag is NOT trusted here — a client could forge
|
||||
// one to spoof another user. The relay-signed REST/seed path is the only place
|
||||
// a p-tag subject is trusted. Returns null for unknown statuses.
|
||||
export function parseLivePresenceEvent(event: {
|
||||
pubkey: string;
|
||||
content: string;
|
||||
}): { pubkey: string; status: PresenceStatus } | null {
|
||||
const status = event.content;
|
||||
if (status !== "online" && status !== "away" && status !== "offline") {
|
||||
return null;
|
||||
}
|
||||
return { pubkey: event.pubkey.toLowerCase(), status };
|
||||
}
|
||||
|
||||
// Presence query keys are ["presence", ...normalizedSortedPubkeys]; a query
|
||||
// "wants" an update only for a pubkey it actually requested.
|
||||
export function presenceQueryWantsPubkey(
|
||||
queryKey: readonly unknown[],
|
||||
pubkey: string,
|
||||
): boolean {
|
||||
return queryKey.length > 1 && queryKey.includes(pubkey);
|
||||
}
|
||||
|
||||
// get_presence omits offline/unknown pubkeys, so a live online event often
|
||||
// targets a pubkey absent from the lookup — merge it in rather than dropping it.
|
||||
export function mergePresenceUpdate(
|
||||
old: PresenceLookup | undefined,
|
||||
pubkey: string,
|
||||
status: PresenceStatus,
|
||||
): PresenceLookup | undefined {
|
||||
if (!old) return old;
|
||||
if (old[pubkey] === status) return old;
|
||||
return { ...old, [pubkey]: status };
|
||||
}
|
||||
|
||||
export function getPresenceLabel(status: PresenceStatus) {
|
||||
switch (status) {
|
||||
|
||||
Reference in New Issue
Block a user