diff --git a/sync-server/docker-compose.yml b/sync-server/docker-compose.yml index 3c159ec..cfd12b5 100644 --- a/sync-server/docker-compose.yml +++ b/sync-server/docker-compose.yml @@ -9,6 +9,8 @@ services: - VAPID_PUBLIC_KEY=BNWACJudUOeHEZKEFB-0Wz086nHYsWzj12LqQ7lsUNT38ThtNUoZTJYEH9lttQitCROE2G3Ob71ZUww47yvCDbk - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY:-x3yCse1Q4rbZ1XLgnJ1KpSuRlw2ccHDW0fMcKtQ1qcw} - VAPID_SUBJECT=mailto:push@rmaps.online + # Automatic location request interval (ms) - 0 to disable + - LOCATION_REQUEST_INTERVAL=60000 labels: - "traefik.enable=true" # HTTP router (redirects to HTTPS via Cloudflare) diff --git a/sync-server/server.js b/sync-server/server.js index bb1d6b5..d790845 100644 --- a/sync-server/server.js +++ b/sync-server/server.js @@ -6,6 +6,7 @@ import webpush from 'web-push'; const PORT = process.env.PORT || 3001; const STALE_THRESHOLD_MS = 60 * 60 * 1000; // 1 hour +const LOCATION_REQUEST_INTERVAL_MS = parseInt(process.env.LOCATION_REQUEST_INTERVAL || '60000', 10); // 60 seconds default // VAPID keys for push notifications // Generate with: npx web-push generate-vapid-keys @@ -292,6 +293,51 @@ setInterval(() => { } }, 5 * 60 * 1000); // Every 5 minutes +// Automatic location request - periodically ask all clients for location updates via silent push +async function requestLocationFromAllRooms() { + if (!VAPID_PUBLIC_KEY || !VAPID_PRIVATE_KEY) return; + + for (const [roomSlug, subs] of pushSubscriptions.entries()) { + if (subs.size === 0) continue; + + const room = rooms.get(roomSlug); + // Only request if room has participants (active room) + if (!room || Object.keys(room.participants).length === 0) continue; + + console.log(`[${roomSlug}] Requesting location from ${subs.size} subscribers`); + + const failedEndpoints = []; + for (const sub of subs) { + try { + await webpush.sendNotification(sub, JSON.stringify({ + silent: true, + data: { type: 'location_request', roomSlug } + })); + } catch (error) { + if (error.statusCode === 404 || error.statusCode === 410) { + failedEndpoints.push(sub.endpoint); + } + } + } + + // Clean up failed subscriptions + for (const endpoint of failedEndpoints) { + for (const sub of subs) { + if (sub.endpoint === endpoint) { + subs.delete(sub); + console.log(`[${roomSlug}] Removed stale push subscription`); + } + } + } + } +} + +// Start automatic location request interval +if (VAPID_PUBLIC_KEY && VAPID_PRIVATE_KEY && LOCATION_REQUEST_INTERVAL_MS > 0) { + setInterval(requestLocationFromAllRooms, LOCATION_REQUEST_INTERVAL_MS); + console.log(`Automatic location requests enabled every ${LOCATION_REQUEST_INTERVAL_MS / 1000}s`); +} + // Parse JSON body from request async function parseJsonBody(req) { return new Promise((resolve, reject) => {