Revert "fix(fitness): mirror finish overview to other devices via SSE"

This reverts commit e87b8bd864.
This commit is contained in:
2026-05-12 17:30:02 +02:00
parent 5ac56db46c
commit 8c09b0b2f4
4 changed files with 12 additions and 71 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "homepage", "name": "homepage",
"version": "1.70.0", "version": "1.70.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+5 -23
View File
@@ -37,7 +37,6 @@ export function createWorkoutSync() {
let status: SyncStatus = $state('idle'); let status: SyncStatus = $state('idle');
let serverVersion = $state(0); let serverVersion = $state(0);
let lastFinishedSession: any = $state(null);
let eventSource: EventSource | null = null; let eventSource: EventSource | null = null;
let debounceTimer: ReturnType<typeof setTimeout> | null = null; let debounceTimer: ReturnType<typeof setTimeout> | null = null;
let reconnectTimer: ReturnType<typeof setTimeout> | null = null; let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
@@ -167,15 +166,8 @@ export function createWorkoutSync() {
} catch {} } catch {}
}); });
eventSource.addEventListener('finished', (e) => { eventSource.addEventListener('finished', () => {
// Another device finished the workout. If they passed along the saved // Another device finished the workout
// session, expose it so the active page can build the completion overview.
try {
const data = JSON.parse(e.data);
lastFinishedSession = data?.session ?? null;
} catch {
lastFinishedSession = null;
}
workout.cancel(); workout.cancel();
disconnectSSE(); disconnectSSE();
}); });
@@ -225,22 +217,14 @@ export function createWorkoutSync() {
connectSSE(); connectSSE();
} }
/** Called when workout finishes or is cancelled — clean up server state. /** Called when workout finishes or is cancelled — clean up server state */
* Pass the just-saved session id so other devices receive it via SSE async function onWorkoutEnd() {
* and can render the finish overview. */
async function onWorkoutEnd(sessionId?: string | null) {
disconnectSSE(); disconnectSSE();
try { try {
const qs = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : ''; await fetch('/api/fitness/workout/active', { method: 'DELETE' });
await fetch(`/api/fitness/workout/active${qs}`, { method: 'DELETE' });
} catch {} } catch {}
} }
/** Clear the finished-session payload after the page has consumed it. */
function clearFinishedSession() {
lastFinishedSession = null;
}
/** Called on app load — reconcile local vs server state */ /** Called on app load — reconcile local vs server state */
async function init() { async function init() {
try { try {
@@ -306,11 +290,9 @@ export function createWorkoutSync() {
return { return {
get status() { return status; }, get status() { return status; },
get serverVersion() { return serverVersion; }, get serverVersion() { return serverVersion; },
get lastFinishedSession() { return lastFinishedSession; },
init, init,
onWorkoutStart, onWorkoutStart,
onWorkoutEnd, onWorkoutEnd,
clearFinishedSession,
notifyChange, notifyChange,
destroy destroy
}; };
@@ -2,7 +2,6 @@ import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { dbConnect } from '$utils/db'; import { dbConnect } from '$utils/db';
import { ActiveWorkout } from '$models/ActiveWorkout'; import { ActiveWorkout } from '$models/ActiveWorkout';
import { WorkoutSession } from '$models/WorkoutSession';
import { broadcast } from '$lib/server/sseManager'; import { broadcast } from '$lib/server/sseManager';
// GET /api/fitness/workout/active — fetch current active workout // GET /api/fitness/workout/active — fetch current active workout
@@ -92,9 +91,7 @@ export const PUT: RequestHandler = async ({ request, locals }) => {
}; };
// DELETE /api/fitness/workout/active — clear active workout (finish/cancel) // DELETE /api/fitness/workout/active — clear active workout (finish/cancel)
// Optional ?sessionId=<id> attaches the just-saved session to the broadcast so export const DELETE: RequestHandler = async ({ locals }) => {
// other devices can render the finish overview instead of a blank page.
export const DELETE: RequestHandler = async ({ locals, url }) => {
const session = locals.session ?? await locals.auth(); const session = locals.session ?? await locals.auth();
if (!session?.user?.nickname) { if (!session?.user?.nickname) {
return json({ error: 'Unauthorized' }, { status: 401 }); return json({ error: 'Unauthorized' }, { status: 401 });
@@ -105,17 +102,8 @@ export const DELETE: RequestHandler = async ({ locals, url }) => {
const userId = session.user.nickname; const userId = session.user.nickname;
await ActiveWorkout.deleteOne({ userId }); await ActiveWorkout.deleteOne({ userId });
const sessionId = url.searchParams.get('sessionId'); // Notify all devices that workout is finished
let sessionDoc: unknown = null; broadcast(userId, 'finished', { active: false });
if (sessionId) {
try {
sessionDoc = await WorkoutSession.findOne({ _id: sessionId, createdBy: userId }).lean();
} catch {
sessionDoc = null;
}
}
broadcast(userId, 'finished', { active: false, session: sessionDoc });
return json({ ok: true }); return json({ ok: true });
} catch (error) { } catch (error) {
@@ -161,36 +161,8 @@
let templateDiffs = $state([]); let templateDiffs = $state([]);
let templateUpdateStatus = $state('idle'); // 'idle' | 'updating' | 'done' let templateUpdateStatus = $state('idle'); // 'idle' | 'updating' | 'done'
// Track whether we've ever observed an active workout on this page so the // Celebratory fanfare on workout completion. Fires once when completionData
// remote-end redirect doesn't fire during the brief gap before localStorage // becomes truthy after a successful finish.
// + server hydration completes on initial mount.
let _everActive = false;
// Mirror the finish overview when another device finishes the workout.
// Sync surfaces the saved session via SSE; treat it like the local finish path.
$effect(() => {
const remoteSession = sync.lastFinishedSession;
if (!remoteSession || completionData) return;
completionData = buildCompletion(remoteSession, remoteSession);
computeTemplateDiff(completionData);
sync.clearFinishedSession();
});
// If the workout ends remotely without a session payload (cancel from another
// device, or that device couldn't post the session), exit cleanly instead of
// leaving this device on a blank active page.
$effect(() => {
if (workout.active) {
_everActive = true;
return;
}
if (!_everActive || completionData || sync.lastFinishedSession) return;
goto(`/fitness/${sl.workout}`);
});
// Celebratory fanfare on workout completion. Fires once, regardless of
// whether completionData was populated by the local finish path or by the
// remote-finish sync effect above.
let _completionSoundPlayed = false; let _completionSoundPlayed = false;
$effect(() => { $effect(() => {
if (completionData && !_completionSoundPlayed) { if (completionData && !_completionSoundPlayed) {
@@ -786,16 +758,15 @@
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sessionData) body: JSON.stringify(sessionData)
}); });
await sync.onWorkoutEnd();
if (res.ok) { if (res.ok) {
const d = await res.json(); const d = await res.json();
completionData = buildCompletion(sessionData, d.session); completionData = buildCompletion(sessionData, d.session);
computeTemplateDiff(completionData); computeTemplateDiff(completionData);
await sync.onWorkoutEnd(d.session?._id);
} else { } else {
await queueSession(sessionData); await queueSession(sessionData);
offlineQueued = true; offlineQueued = true;
completionData = buildCompletion(sessionData, { _id: null }); completionData = buildCompletion(sessionData, { _id: null });
await sync.onWorkoutEnd();
} }
} catch { } catch {
await queueSession(sessionData); await queueSession(sessionData);