community: the inbox check belongs to the app, not to the page that happened to be open

This commit is contained in:
KeelrLeek committed 2026-09-03 12:38:57 -05:00
1 parent fd216ecd3d
commit b3c121fa94
3 files changed
+77 -18

No files matched your search

+64 -12
View File
@@ -314,12 +314,12 @@ export function toggleRoomFav(name) {
}
// ---- boot -------------------------------------------------------------------------
let booted = false;
/** Idempotent: the first Community mount wires the listeners and pulls the
* initial state; later mounts only refresh. */
export async function initCommunity() {
if (!booted) {
booted = true;
let wired = false;
/** The backend's community events, wired once per session. Both the app-wide
* inbox watch and the Community page need them; whoever arrives first wires. */
function wireEvents() {
if (!wired) {
wired = true;
api.onEvent("freenet:status", (status) => {
if (community.freenet) community.freenet.status = status;
else void refreshFreenet();
@@ -348,10 +348,59 @@ export async function initCommunity() {
if (p.status === 1 || p.status === 2) noteSeen(p.username);
});
}
}
/** Idempotent: pulls the state the Community page draws. */
export async function initCommunity() {
wireEvents();
await Promise.all([refreshFreenet(), loadContacts(), refreshLinks(), refreshFriends(), refreshShares(), refreshPublished(), refreshInbox(), refreshSpaces(), refreshOverlap()]);
await refreshPersona();
}
// ---- the inbox watch ---------------------------------------------------------------
/** Everything sealed — friend requests, shares, proposal pings — arrives on one
* inbox check, and that check used to live in Community.svelte's effect. So
* nothing arrived unless that page happened to be open: a friend request sat
* unseen while you were in the Library, and the proposal toast could only fire
* somewhere it was least needed. The watch belongs to the app.
*
* App.svelte starts it once at boot. The gate is re-read every tick, so a node
* that comes up later — or an inbox published later — simply starts working
* without anything having to restart the timer. */
const INBOX_POLL_MS = 90_000;
let inboxWatch = 0;
let polling = false;
export async function startInboxWatch() {
if (inboxWatch) return;
wireEvents();
inboxWatch = setInterval(() => void pollInbox(), INBOX_POLL_MS);
await refreshFreenet();
await pollInbox();
}
/** One tick, and quiet by design: a node that is down or an inbox that was
* never published is the resting state of this app, not an error to show. */
async function pollInbox() {
if (polling) return;
polling = true;
try {
// If the first read failed we have no status to gate on, and the
// status event only speaks when something changes — so try again
// rather than sit inert for the session.
if (!community.freenet) await refreshFreenet();
await refreshInbox();
if (!adapterConnected(community.freenet?.status)) return;
if (!community.inbox?.published) return;
await readInbox();
} catch {
// A background poll never surfaces; the manual button is the one that
// reports. The backend logs the reason either way.
} finally {
polling = false;
}
}
// ---- native Soulseek cards ---------------------------------------------------------
/** Fetch a person's native card and interests once per session, both in
* flight together; errors are kept per person so the drawer can say so. */
@@ -527,12 +576,15 @@ export const publishInbox = () =>
community.inbox = await api.inboxPublish();
});
/** Read our inbox through the node, act on sealed cargo, prune it. */
export const checkInbox = () =>
run("inboxcheck", async () => {
community.inboxLast = await api.inboxCheck();
await Promise.all([refreshInbox(), refreshFriends(), refreshShares()]);
return community.inboxLast;
});
async function readInbox() {
community.inboxLast = await api.inboxCheck();
await Promise.all([refreshInbox(), refreshFriends(), refreshShares()]);
return community.inboxLast;
}
/** The Check now button: busy state, and a failure the page can show. The
* background watch below deliberately does NOT go through this — a poll that
* fails must not stamp the shared error slot. */
export const checkInbox = () => run("inboxcheck", readInbox);
export const friendRequestByKey = (personaKey) => friendAction("friend:" + personaKey, () => api.friendRequestByKey(personaKey));
export const friendshipForKey = (key) => {
const mine = community.friends.filter((f) => f.persona_key === key);