forked from Keelrleek/SoulStream
community: the inbox check belongs to the app, not to the page that happened to be open
This commit is contained in:
1 parent
fd216ecd3d
commit
b3c121fa94
3 files changed
+77
-18
No files matched your search
@@ -37,6 +37,7 @@
|
|||||||
refreshRecents,
|
refreshRecents,
|
||||||
restoreSession,
|
restoreSession,
|
||||||
} from "./lib/state.svelte.js";
|
} from "./lib/state.svelte.js";
|
||||||
|
import { startInboxWatch } from "./lib/community.svelte.js";
|
||||||
import { floatScroll } from "./lib/scrollbar.js";
|
import { floatScroll } from "./lib/scrollbar.js";
|
||||||
import { initMiniBridge } from "./lib/miniBridge.svelte.js";
|
import { initMiniBridge } from "./lib/miniBridge.svelte.js";
|
||||||
import { applyGlass, armGlassResizeGuard } from "./lib/glass.js";
|
import { applyGlass, armGlassResizeGuard } from "./lib/glass.js";
|
||||||
@@ -134,6 +135,9 @@
|
|||||||
// this way the mode can be toggled mid-drag without leaving stale state.
|
// this way the mode can be toggled mid-drag without leaving stale state.
|
||||||
armGlassResizeGuard();
|
armGlassResizeGuard();
|
||||||
autoConnect();
|
autoConnect();
|
||||||
|
// Sealed cargo arrives on the inbox check, and that has to keep happening
|
||||||
|
// wherever the user is — not only while the Community page is mounted.
|
||||||
|
void startInboxWatch();
|
||||||
refreshLibrary();
|
refreshLibrary();
|
||||||
refreshPlaylists();
|
refreshPlaylists();
|
||||||
refreshRecents();
|
refreshRecents();
|
||||||
|
|||||||
+64
-12
@@ -314,12 +314,12 @@ export function toggleRoomFav(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- boot -------------------------------------------------------------------------
|
// ---- boot -------------------------------------------------------------------------
|
||||||
let booted = false;
|
let wired = false;
|
||||||
/** Idempotent: the first Community mount wires the listeners and pulls the
|
/** The backend's community events, wired once per session. Both the app-wide
|
||||||
* initial state; later mounts only refresh. */
|
* inbox watch and the Community page need them; whoever arrives first wires. */
|
||||||
export async function initCommunity() {
|
function wireEvents() {
|
||||||
if (!booted) {
|
if (!wired) {
|
||||||
booted = true;
|
wired = true;
|
||||||
api.onEvent("freenet:status", (status) => {
|
api.onEvent("freenet:status", (status) => {
|
||||||
if (community.freenet) community.freenet.status = status;
|
if (community.freenet) community.freenet.status = status;
|
||||||
else void refreshFreenet();
|
else void refreshFreenet();
|
||||||
@@ -348,10 +348,59 @@ export async function initCommunity() {
|
|||||||
if (p.status === 1 || p.status === 2) noteSeen(p.username);
|
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 Promise.all([refreshFreenet(), loadContacts(), refreshLinks(), refreshFriends(), refreshShares(), refreshPublished(), refreshInbox(), refreshSpaces(), refreshOverlap()]);
|
||||||
await refreshPersona();
|
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 ---------------------------------------------------------
|
// ---- native Soulseek cards ---------------------------------------------------------
|
||||||
/** Fetch a person's native card and interests once per session, both in
|
/** 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. */
|
* 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();
|
community.inbox = await api.inboxPublish();
|
||||||
});
|
});
|
||||||
/** Read our inbox through the node, act on sealed cargo, prune it. */
|
/** Read our inbox through the node, act on sealed cargo, prune it. */
|
||||||
export const checkInbox = () =>
|
async function readInbox() {
|
||||||
run("inboxcheck", async () => {
|
community.inboxLast = await api.inboxCheck();
|
||||||
community.inboxLast = await api.inboxCheck();
|
await Promise.all([refreshInbox(), refreshFriends(), refreshShares()]);
|
||||||
await Promise.all([refreshInbox(), refreshFriends(), refreshShares()]);
|
return community.inboxLast;
|
||||||
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 friendRequestByKey = (personaKey) => friendAction("friend:" + personaKey, () => api.friendRequestByKey(personaKey));
|
||||||
export const friendshipForKey = (key) => {
|
export const friendshipForKey = (key) => {
|
||||||
const mine = community.friends.filter((f) => f.persona_key === key);
|
const mine = community.friends.filter((f) => f.persona_key === key);
|
||||||
|
|||||||
@@ -134,6 +134,11 @@
|
|||||||
const hue = $derived(key ? hueOf(key) : null);
|
const hue = $derived(key ? hueOf(key) : null);
|
||||||
const freenetOn = $derived(!!community.freenet?.enabled);
|
const freenetOn = $derived(!!community.freenet?.enabled);
|
||||||
const nodeUp = $derived(adapterConnected(community.freenet?.status));
|
const nodeUp = $derived(adapterConnected(community.freenet?.status));
|
||||||
|
// A boolean, deliberately: reading community.inbox?.published straight from
|
||||||
|
// an effect makes that effect depend on the whole record, and a check
|
||||||
|
// REPLACES the record — which re-ran the effect, which checked again. A
|
||||||
|
// derived only notifies when its value actually flips.
|
||||||
|
const inboxLive = $derived(!!community.inbox?.published);
|
||||||
const online = $derived(app.connection === "online");
|
const online = $derived(app.connection === "online");
|
||||||
const me = $derived(app.username ?? "");
|
const me = $derived(app.username ?? "");
|
||||||
const ownLink = $derived(community.links?.own ?? null);
|
const ownLink = $derived(community.links?.own ?? null);
|
||||||
@@ -449,14 +454,12 @@
|
|||||||
const id = await shareImport(card);
|
const id = await shareImport(card);
|
||||||
if (id) goToView("playlist", { playlistId: id, label: card.name });
|
if (id) goToView("playlist", { playlistId: id, label: card.name });
|
||||||
}
|
}
|
||||||
// The inbox is polled while this page is open and the node is up: on
|
// The app polls the inbox for the whole session (startInboxWatch, in
|
||||||
// arrival and every 90 s. Sealed cargo becomes friend requests and share
|
// community.svelte.js) — arriving here only asks for a fresh read now, so
|
||||||
// cards through the same stores the PM carry feeds.
|
// the page opens on current state instead of waiting out the interval.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!nodeUp || !key || !community.inbox?.published) return;
|
if (!nodeUp || !key || !inboxLive) return;
|
||||||
untrack(() => void checkInbox());
|
untrack(() => void checkInbox());
|
||||||
const t = setInterval(() => void checkInbox(), 90_000);
|
|
||||||
return () => clearInterval(t);
|
|
||||||
});
|
});
|
||||||
let addKey = $state("");
|
let addKey = $state("");
|
||||||
// ---- spaces ------------------------------------------------------------
|
// ---- spaces ------------------------------------------------------------
|
||||||
|
|||||||
Reference in new issue
Block a user