Revert "fix(fitness): mirror finish overview to other devices via SSE"
This reverts commit e87b8bd864.
This commit is contained in:
+1
-1
@@ -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": {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user