// Party REST + realtime WebSocket - same origin as everything else, auth is // the session cookie (see embyApi.js), so no headers/tokens to pass around // here at all. async function apiFetch(path, options = {}) { const res = await fetch(path, { credentials: 'same-origin', ...options, headers: { 'Content-Type': 'application/json', ...(options.headers || {}) } }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.error || `Request failed: ${res.status}`); } return res.json(); } export async function listActiveParties() { const data = await apiFetch('/api/parties'); return data.parties || []; } export function createParty(itemId) { return apiFetch('/api/parties', { method: 'POST', body: JSON.stringify({ itemId }) }); } export function joinParty(joinCode) { return apiFetch(`/api/parties/${encodeURIComponent(joinCode)}/join`, { method: 'POST' }); } export function leaveParty(partyId) { return apiFetch(`/api/parties/${partyId}/leave`, { method: 'POST' }); } export function getParty(partyId) { return apiFetch(`/api/parties/${partyId}`); } /** * Opens the realtime connection for a party. Auth rides along on the * WebSocket's HTTP upgrade handshake as the session cookie (browsers attach * cookies to that handshake automatically for a same-origin URL) - so all * this needs to send is which party to join. */ export function connectParty(partyId, { onMembers, onPlaystate, onError, onClose } = {}) { const proto = location.protocol === 'https:' ? 'wss://' : 'ws://'; const ws = new WebSocket(proto + location.host + '/ws'); ws.addEventListener('open', () => { ws.send(JSON.stringify({ type: 'join', partyId })); }); ws.addEventListener('message', (evt) => { let msg; try { msg = JSON.parse(evt.data); } catch (err) { return; } if (msg.type === 'members' && onMembers) onMembers(msg.members); if (msg.type === 'playstate' && onPlaystate) onPlaystate(msg); if (msg.type === 'error' && onError) onError(msg.message); }); ws.addEventListener('close', () => { if (onClose) onClose(); }); return { sendPlaystate(isPaused, positionTicks) { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'playstate', isPaused, positionTicks })); } }, leave() { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'leave' })); } ws.close(); } }; }