mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
chore(orbit): auto-leave on guest side after prolonged host silence
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,21 +22,26 @@ export default function OrbitExitModal() {
|
||||
const sessionName = useOrbitStore(s => s.state?.name);
|
||||
const hostName = useOrbitStore(s => s.state?.host);
|
||||
|
||||
const isEnded = phase === 'ended';
|
||||
const isKicked = phase === 'error' && errorMessage === 'kicked';
|
||||
const isRemoved = phase === 'error' && errorMessage === 'removed';
|
||||
if (!isEnded && !isKicked && !isRemoved) return null;
|
||||
const isEnded = phase === 'ended';
|
||||
const isKicked = phase === 'error' && errorMessage === 'kicked';
|
||||
const isRemoved = phase === 'error' && errorMessage === 'removed';
|
||||
const isHostTimeout = phase === 'error' && errorMessage === 'host-timeout';
|
||||
if (!isEnded && !isKicked && !isRemoved && !isHostTimeout) return null;
|
||||
|
||||
const title = isKicked
|
||||
? t('orbit.exitKickedTitle')
|
||||
: isRemoved
|
||||
? t('orbit.exitRemovedTitle')
|
||||
: t('orbit.exitEndedTitle');
|
||||
: isHostTimeout
|
||||
? t('orbit.exitHostTimeoutTitle')
|
||||
: t('orbit.exitEndedTitle');
|
||||
const body = isKicked
|
||||
? t('orbit.exitKickedBody', { host: hostName ?? '', name: sessionName ?? '' })
|
||||
: isRemoved
|
||||
? t('orbit.exitRemovedBody', { host: hostName ?? '', name: sessionName ?? '' })
|
||||
: t('orbit.exitEndedBody', { name: sessionName ?? '' });
|
||||
: isHostTimeout
|
||||
? t('orbit.exitHostTimeoutBody', { host: hostName ?? '', name: sessionName ?? '' })
|
||||
: t('orbit.exitEndedBody', { name: sessionName ?? '' });
|
||||
|
||||
const onOk = async () => {
|
||||
try {
|
||||
|
||||
@@ -28,6 +28,13 @@ import { orbitOutboxPlaylistName, estimateLivePosition, type OrbitState } from '
|
||||
|
||||
const STATE_READ_TICK_MS = 2_500;
|
||||
const HEARTBEAT_TICK_MS = 10_000;
|
||||
/**
|
||||
* Host must be quiet (no state writes) for this long before we treat the
|
||||
* session as dead and auto-leave. Well above any normal network blip —
|
||||
* reconnects inside this window are silent. Tuned per user decision:
|
||||
* manual exits have priority, short reconnects never trigger auto-close.
|
||||
*/
|
||||
const HOST_TIMEOUT_MS = 5 * 60_000;
|
||||
|
||||
export function useOrbitGuest(): void {
|
||||
const role = useOrbitStore(s => s.role);
|
||||
@@ -129,6 +136,16 @@ export function useOrbitGuest(): void {
|
||||
|
||||
useOrbitStore.getState().setState(state);
|
||||
|
||||
// Auto-leave after prolonged host silence. We keep polling as long as
|
||||
// state reads succeed (short reconnects are silent), but if the host
|
||||
// hasn't written a fresh state blob for > HOST_TIMEOUT_MS we treat the
|
||||
// session as effectively dead and surface the exit modal. Manual exit
|
||||
// still works instantly — the bar's X button short-circuits this path.
|
||||
if (state.positionAt > 0 && (Date.now() - state.positionAt) > HOST_TIMEOUT_MS) {
|
||||
useOrbitStore.getState().setError('host-timeout');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reconcile pending guest suggestions against the host's *playable*
|
||||
// queue — NOT `state.queue`, which is the suggestion history (every
|
||||
// submission lands there immediately, even under manual-approval mode
|
||||
|
||||
@@ -1566,6 +1566,8 @@ export const deTranslation = {
|
||||
exitKickedTitle: 'Du wurdest aus der Session gebannt',
|
||||
exitRemovedTitle: 'Du wurdest aus der Session entfernt',
|
||||
exitEndedTitle: 'Der Gastgeber hat die Session beendet',
|
||||
exitHostTimeoutTitle: 'Host nicht mehr erreichbar',
|
||||
exitHostTimeoutBody: '{{host}} hat sich länger nicht mehr gemeldet — „{{name}}" wurde auf deiner Seite beendet. Über den Einladungslink kannst du jederzeit wieder beitreten.',
|
||||
exitKickedBody: '{{host}} hat dich aus „{{name}}" gebannt. Beitritt nicht mehr möglich.',
|
||||
exitRemovedBody: '{{host}} hat dich aus „{{name}}" entfernt. Du kannst jederzeit über den Einladungslink wieder beitreten.',
|
||||
exitEndedBody: '„{{name}}" ist zu Ende. Hoffentlich war\'s schön.',
|
||||
|
||||
@@ -1569,6 +1569,8 @@ export const enTranslation = {
|
||||
exitKickedTitle: 'You were banned from the session',
|
||||
exitRemovedTitle: 'You were removed from the session',
|
||||
exitEndedTitle: 'The host ended the session',
|
||||
exitHostTimeoutTitle: 'Host went silent',
|
||||
exitHostTimeoutBody: "{{host}} hasn't sent an update for a while, so \"{{name}}\" was closed on your end. You can re-join any time with the invite link.",
|
||||
exitKickedBody: '{{host}} banned you from "{{name}}". You can\'t re-join.',
|
||||
exitRemovedBody: '{{host}} removed you from "{{name}}". You can re-join any time via the invite link.',
|
||||
exitEndedBody: '"{{name}}" has ended. Hope you had fun.',
|
||||
|
||||
Reference in New Issue
Block a user