mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
121 lines
3.4 KiB
TypeScript
121 lines
3.4 KiB
TypeScript
import { normalizeRelayUrl } from "@/features/profile/lib/selfProfileStorage";
|
|
|
|
export type ThreadActivityItem = {
|
|
id: string;
|
|
kind: number;
|
|
pubkey: string;
|
|
content: string;
|
|
createdAt: number;
|
|
channelId: string;
|
|
channelName: string;
|
|
tags: string[][];
|
|
};
|
|
|
|
const ACTIVITY_STORAGE_PREFIX = "buzz-thread-activity.v1";
|
|
const MAX_ACTIVITY_ITEMS = 100;
|
|
|
|
// Scoped to relay+pubkey. The legacy pubkey-only key is intentionally not read
|
|
// — rows from an unknown relay cannot be safely attributed.
|
|
export function activityStorageKey(pubkey: string, relayUrl: string): string {
|
|
return `${ACTIVITY_STORAGE_PREFIX}:${normalizeRelayUrl(relayUrl)}:${pubkey}`;
|
|
}
|
|
|
|
/**
|
|
* Stable identity string for the in-memory thread-activity buffer.
|
|
* A single definition avoids the `${pubkey}:${relay}` expression being
|
|
* repeated at reset, both writers, and the render fence.
|
|
* Returns `""` when either value is absent — the empty string never matches a
|
|
* valid loaded scope, so the fence returns `[]` until the buffer is seeded.
|
|
*/
|
|
export function activityScopeKey(
|
|
pubkey: string | null,
|
|
relayUrl: string,
|
|
): string {
|
|
if (!pubkey || !relayUrl) return "";
|
|
return `${pubkey}:${normalizeRelayUrl(relayUrl)}`;
|
|
}
|
|
|
|
/**
|
|
* Pure projection helper used at the hook return and in tests.
|
|
*
|
|
* Returns `items` only when both scopes are non-empty and identical.
|
|
* An empty `currentScope` (absent pubkey or relay) is always rejected —
|
|
* `activityScopeKey()` returns `""` in that case, and `""` must never
|
|
* compare-equal to a valid loaded scope even if the ref was initialised to
|
|
* `""` before the first reset effect commits.
|
|
*/
|
|
export function projectActivityForScope(
|
|
loadedScope: string,
|
|
currentScope: string,
|
|
items: ThreadActivityItem[],
|
|
): ThreadActivityItem[] {
|
|
if (!currentScope || loadedScope !== currentScope) return [];
|
|
return items;
|
|
}
|
|
|
|
export function readActivityFromStorage(
|
|
pubkey: string,
|
|
relayUrl: string,
|
|
): ThreadActivityItem[] {
|
|
try {
|
|
const raw = window.localStorage.getItem(
|
|
activityStorageKey(pubkey, relayUrl),
|
|
);
|
|
if (!raw) return [];
|
|
const parsed = JSON.parse(raw);
|
|
if (!Array.isArray(parsed)) return [];
|
|
return parsed.filter(
|
|
(item): item is ThreadActivityItem =>
|
|
typeof item === "object" &&
|
|
item !== null &&
|
|
typeof item.id === "string",
|
|
);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function writeActivityToStorage(
|
|
pubkey: string,
|
|
relayUrl: string,
|
|
items: ThreadActivityItem[],
|
|
): void {
|
|
try {
|
|
const capped =
|
|
items.length > MAX_ACTIVITY_ITEMS
|
|
? items.slice(items.length - MAX_ACTIVITY_ITEMS)
|
|
: items;
|
|
window.localStorage.setItem(
|
|
activityStorageKey(pubkey, relayUrl),
|
|
JSON.stringify(capped),
|
|
);
|
|
} catch {
|
|
// Ignore storage errors.
|
|
}
|
|
}
|
|
|
|
export function addThreadActivityItems(
|
|
existing: ThreadActivityItem[],
|
|
items: ThreadActivityItem[],
|
|
) {
|
|
if (items.length === 0) {
|
|
return { didAdd: false, items: existing };
|
|
}
|
|
|
|
const existingIds = new Set(existing.map((item) => item.id));
|
|
const newItems = items.filter((item) => !existingIds.has(item.id));
|
|
if (newItems.length === 0) {
|
|
return { didAdd: false, items: existing };
|
|
}
|
|
|
|
const merged = [...existing, ...newItems].sort(
|
|
(left, right) => left.createdAt - right.createdAt,
|
|
);
|
|
const capped =
|
|
merged.length > MAX_ACTIVITY_ITEMS
|
|
? merged.slice(merged.length - MAX_ACTIVITY_ITEMS)
|
|
: merged;
|
|
|
|
return { didAdd: true, items: capped };
|
|
}
|