fix(orbit): tear down session on server switch + raise cleanup TTL

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-24 18:16:30 +02:00
parent 728b8f6315
commit f2e4c5b684
2 changed files with 26 additions and 6 deletions
+6 -6
View File
@@ -732,13 +732,13 @@ export async function sweepGuestOutboxes(sid: string, hostUsername: string): Pro
export const ORBIT_HEARTBEAT_ALIVE_MS = 30_000;
/**
* Grace window for the app-start orphan sweep. Has to be comfortably
* larger than the outbox heartbeat interval (10 s) and the session state
* tick (2.5 s) so a session running on the user's other device doesn't
* get deleted on a transient slow tick. 2× ALIVE window is the minimum
* sane value.
* Grace window for the app-start orphan sweep. A session on the user's
* other device or a browser that briefly restarted must NOT be deleted
* by this sweep. 5 min matches the guest-side host-timeout threshold:
* if a session is silent for that long, it's fair to treat it as dead;
* anything shorter is a real restart and must survive.
*/
export const ORBIT_ORPHAN_TTL_MS = 60_000;
export const ORBIT_ORPHAN_TTL_MS = 5 * 60_000;
/**
* Legacy / fallback shuffle cadence. New sessions store their own interval
+20
View File
@@ -1,11 +1,31 @@
import { pingWithCredentials, scheduleInstantMixProbeForServer } from '../api/subsonic';
import type { ServerProfile } from '../store/authStore';
import { useAuthStore } from '../store/authStore';
import { useOrbitStore } from '../store/orbitStore';
import { endOrbitSession, leaveOrbitSession } from './orbit';
export async function switchActiveServer(server: ServerProfile): Promise<boolean> {
try {
const ping = await pingWithCredentials(server.url, server.username, server.password);
if (!ping.ok) return false;
// Tear down any active Orbit session before we actually switch. The
// session's playlists live on the *old* server — once we flip the
// active server, every API call from the orbit hooks would hit the
// wrong backend, heartbeats would silently fail, and the next
// app-start cleanup would prune the still-live session as stale.
// Capped at 1.5 s so a slow network doesn't freeze the UI.
const role = useOrbitStore.getState().role;
if (role === 'host' || role === 'guest') {
const teardown = role === 'host' ? endOrbitSession() : leaveOrbitSession();
await Promise.race([
teardown.catch(() => {}),
new Promise<void>(r => setTimeout(r, 1500)),
]);
// Ensure local store is idle even if the remote call timed out.
useOrbitStore.getState().reset();
}
const identity = {
type: ping.type,
serverVersion: ping.serverVersion,