feat: Add automatic periodic location requests via silent push
- Server sends silent push notifications every 60s (configurable) - Only requests from rooms with active participants - Cleans up stale push subscriptions automatically - Interval configurable via LOCATION_REQUEST_INTERVAL env var 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
27e8344e7a
commit
ca1cd4877d
|
|
@ -9,6 +9,8 @@ services:
|
||||||
- VAPID_PUBLIC_KEY=BNWACJudUOeHEZKEFB-0Wz086nHYsWzj12LqQ7lsUNT38ThtNUoZTJYEH9lttQitCROE2G3Ob71ZUww47yvCDbk
|
- VAPID_PUBLIC_KEY=BNWACJudUOeHEZKEFB-0Wz086nHYsWzj12LqQ7lsUNT38ThtNUoZTJYEH9lttQitCROE2G3Ob71ZUww47yvCDbk
|
||||||
- VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY:-x3yCse1Q4rbZ1XLgnJ1KpSuRlw2ccHDW0fMcKtQ1qcw}
|
- VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY:-x3yCse1Q4rbZ1XLgnJ1KpSuRlw2ccHDW0fMcKtQ1qcw}
|
||||||
- VAPID_SUBJECT=mailto:push@rmaps.online
|
- VAPID_SUBJECT=mailto:push@rmaps.online
|
||||||
|
# Automatic location request interval (ms) - 0 to disable
|
||||||
|
- LOCATION_REQUEST_INTERVAL=60000
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
# HTTP router (redirects to HTTPS via Cloudflare)
|
# HTTP router (redirects to HTTPS via Cloudflare)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import webpush from 'web-push';
|
||||||
|
|
||||||
const PORT = process.env.PORT || 3001;
|
const PORT = process.env.PORT || 3001;
|
||||||
const STALE_THRESHOLD_MS = 60 * 60 * 1000; // 1 hour
|
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
|
// VAPID keys for push notifications
|
||||||
// Generate with: npx web-push generate-vapid-keys
|
// Generate with: npx web-push generate-vapid-keys
|
||||||
|
|
@ -292,6 +293,51 @@ setInterval(() => {
|
||||||
}
|
}
|
||||||
}, 5 * 60 * 1000); // Every 5 minutes
|
}, 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
|
// Parse JSON body from request
|
||||||
async function parseJsonBody(req) {
|
async function parseJsonBody(req) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue