Files
psysonic/src/hooks/useOrbitOutboxHeartbeat.ts
T
Frank Stellmacher 946528350c refactor(orbit + shortcuts): unify host/guest heartbeat, document shortcut contract (Phase I) (#693)
* refactor(orbit): unify host/guest outbox heartbeat into a shared hook (Phase I)

The outbox-heartbeat effect was duplicated near-verbatim in useOrbitHost
and useOrbitGuest — same 10 s interval, same writeOrbitHeartbeat call,
same cleanup; the only difference is whose name owns the outbox
(OrbitState.host vs the active-server username).

Extract it into useOrbitOutboxHeartbeat(active, outboxPlaylistId,
sessionId, ownName). Host and guest each pass their own name source.
The push/pull state-tick logic stays untouched — that asymmetry is the
real host/guest difference, not duplication.

Behaviour-preserving: the owner name is now a reactive hook arg instead
of a getState() read inside the effect, so the heartbeat starts as soon
as the name is available rather than waiting for an unrelated dep to
change — a strict improvement, unreachable in practice since host name
and username are fixed per session.

* docs(shortcuts): document the shortcut-actions contract (Phase I)

Add a contract reference block to the shortcutActions barrel — the three
independent trigger surfaces (inApp / global / runInMiniWindow), the
surface-independent cli + run fields, the dispatch entry points — and
per-field doc comments on ShortcutActionMeta / ShortcutSlot /
ActionContext / CliContext in shortcutTypes.ts.

Pure documentation, no code change.
2026-05-14 15:37:26 +02:00

39 lines
1.4 KiB
TypeScript

import { useEffect } from 'react';
import { writeOrbitHeartbeat } from '../utils/orbit';
import { orbitOutboxPlaylistName } from '../api/orbit';
const HEARTBEAT_TICK_MS = 10_000;
/**
* Shared Orbit outbox heartbeat — used by both the host and guest tick hooks.
*
* Refreshes the caller's own outbox playlist comment with a fresh timestamp
* every 10 s so the host's participant sweep sees the user as alive (and so
* the host's own outbox is refreshed symmetrically). Host and guest differ
* only in whose name owns the outbox: `ownName` is `OrbitState.host` for the
* host and the active server username for a guest.
*
* Best-effort — a transient Navidrome outage just skips a beat; the next
* interval tick retries.
*/
export function useOrbitOutboxHeartbeat(
active: boolean,
outboxPlaylistId: string | null,
sessionId: string | null,
ownName: string | null | undefined,
): void {
useEffect(() => {
if (!active || !outboxPlaylistId || !sessionId || !ownName) return;
const outboxName = orbitOutboxPlaylistName(sessionId, ownName);
const beat = async () => {
try { await writeOrbitHeartbeat(outboxPlaylistId, outboxName); }
catch { /* best-effort */ }
};
void beat();
const id = window.setInterval(() => { void beat(); }, HEARTBEAT_TICK_MS);
return () => window.clearInterval(id);
}, [active, outboxPlaylistId, sessionId, ownName]);
}