From 4f110a760440eb50e465097b2ff9e952db32a38e Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 29 Dec 2025 02:33:08 +0100 Subject: [PATCH] feat: Add push notifications for background friend pings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auto-subscribe to push notifications when location sharing starts - Use C3NavEmbed (iframe) for faster indoor map loading - Set event to 39c3 for current congress - Increase c3nav API cache to 1 hour with stale-while-revalidate - Add strong vibration pattern and sound for notifications 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- public/sw.js | 7 ++++++- src/app/[slug]/page.tsx | 18 ++++++++++++++++++ src/app/api/c3nav/[event]/route.ts | 2 +- src/components/map/DualMapView.tsx | 8 +++----- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/public/sw.js b/public/sw.js index 1efa617..21811d6 100644 --- a/public/sw.js +++ b/public/sw.js @@ -103,9 +103,14 @@ self.addEventListener('push', (event) => { badge: data.badge || '/icon-192.png', tag: data.tag || 'rmaps-notification', data: data.data || {}, - vibrate: [100, 50, 100], + // Strong vibration pattern: buzz-pause-buzz-pause-long buzz + vibrate: [200, 100, 200, 100, 400], actions: data.actions || [], requireInteraction: data.requireInteraction || false, + // Use default system notification sound + silent: false, + // Renotify even if same tag (so user hears it again) + renotify: true, }; event.waitUntil( diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index 28c60ba..1c761d6 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -6,6 +6,7 @@ import dynamic from 'next/dynamic'; import { useRoom } from '@/hooks/useRoom'; import { useLocationSharing } from '@/hooks/useLocationSharing'; import { useServiceWorkerMessages } from '@/hooks/useServiceWorkerMessages'; +import { usePushNotifications } from '@/hooks/usePushNotifications'; import ParticipantList from '@/components/room/ParticipantList'; import RoomHeader from '@/components/room/RoomHeader'; import ShareModal from '@/components/room/ShareModal'; @@ -140,6 +141,23 @@ export default function RoomPage() { }, }); + // Push notifications for background pings + const syncUrl = process.env.NEXT_PUBLIC_SYNC_URL; + const { subscribe: subscribePush, isSubscribed: isPushSubscribed } = usePushNotifications({ + syncUrl, + roomSlug: slug, + }); + + // Auto-subscribe to push when location sharing starts + useEffect(() => { + if (isSharing && !isPushSubscribed && syncUrl) { + console.log('Auto-subscribing to push notifications for background pings'); + subscribePush().catch((err) => { + console.warn('Push subscription failed (user may have denied):', err.message); + }); + } + }, [isSharing, isPushSubscribed, subscribePush, syncUrl]); + // Restore last known location immediately when connected const hasRestoredLocationRef = useRef(false); useEffect(() => { diff --git a/src/app/api/c3nav/[event]/route.ts b/src/app/api/c3nav/[event]/route.ts index 1fdee97..e9a80bc 100644 --- a/src/app/api/c3nav/[event]/route.ts +++ b/src/app/api/c3nav/[event]/route.ts @@ -128,7 +128,7 @@ export async function GET(request: NextRequest, { params }: RouteParams) { return NextResponse.json(data, { status: 200, headers: { - 'Cache-Control': 'public, max-age=300', // Cache for 5 minutes + 'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400', // Cache 1 hour, stale 24h 'Access-Control-Allow-Origin': '*', }, }); diff --git a/src/components/map/DualMapView.tsx b/src/components/map/DualMapView.tsx index abbb326..39baab2 100644 --- a/src/components/map/DualMapView.tsx +++ b/src/components/map/DualMapView.tsx @@ -13,7 +13,7 @@ const MapView = dynamic(() => import('./MapView'), { loading: () => , }); -const IndoorMapView = dynamic(() => import('./IndoorMapView'), { +const C3NavEmbed = dynamic(() => import('./C3NavEmbed'), { ssr: false, loading: () => , }); @@ -139,13 +139,11 @@ export default function DualMapView({ onClearRoute={clearRoute} /> ) : ( - )}