From c3f884d2c482289bd15d20c6a686e0165eb63f82 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 19 Feb 2026 00:28:18 +0000 Subject: [PATCH] Ensure offline users vibrate and auto-respond to manual pings Three fixes for offline ping gaps: 1. SW silent push fallback: when a manual ping arrives as a silent push but no app window is open, show a visible notification with vibration instead of failing silently. 2. SW notificationclick: when opening a fresh window (app was closed), append ?ping=manual to the URL so the app can detect it was pinged. 3. page.tsx: on mount, detect ?ping=manual param, clean it from the URL, and auto-fire GPS once the WebSocket connection is established. Co-Authored-By: Claude Opus 4.6 --- public/sw.js | 22 ++++++++++++++++++++-- src/app/[slug]/page.tsx | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/public/sw.js b/public/sw.js index ad3167c..5cf022f 100644 --- a/public/sw.js +++ b/public/sw.js @@ -346,8 +346,24 @@ self.addEventListener('push', (event) => { const isManual = !!(data.data?.manual); event.waitUntil( - requestLocationFromClient(isManual).then((sent) => { + requestLocationFromClient(isManual).then(async (sent) => { console.log('[SW] Push location request sent:', sent, 'manual:', isManual); + // If no app window is open and this is a manual ping, show a visible notification + // so the user can tap to open the app and auto-respond with location + if (!sent && isManual) { + await self.registration.showNotification('📍 Someone is looking for you!', { + body: 'Tap to share your location', + icon: '/icon-192.png', + badge: '/icon-192.png', + tag: `callout-${data.data?.roomSlug || 'unknown'}`, + data: { type: 'callout', roomSlug: data.data?.roomSlug, manual: true }, + vibrate: [200, 100, 200, 100, 400], + requireInteraction: true, + actions: [{ action: 'view', title: 'Open Map' }], + renotify: true, + silent: false, + }); + } }) ); return; @@ -418,7 +434,9 @@ self.addEventListener('notificationclick', (event) => { } } if (clients.openWindow) { - return clients.openWindow(targetUrl); + // If manual ping, append ?ping=manual so the app auto-responds with GPS on load + const openUrl = data.manual ? `${targetUrl}?ping=manual` : targetUrl; + return clients.openWindow(openUrl); } }) ); diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index eca4a69..a1ecf9f 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -47,6 +47,23 @@ export default function RoomPage() { const [shouldAutoStartSharing, setShouldAutoStartSharing] = useState(false); const [zoomToLocation, setZoomToLocation] = useState<{ latitude: number; longitude: number } | null>(null); const [needsJoin, setNeedsJoin] = useState(false); + const [pendingManualPing, setPendingManualPing] = useState(false); + + // Check if opened from a manual ping notification (e.g. ?ping=manual) + useEffect(() => { + if (typeof window !== 'undefined') { + const params = new URLSearchParams(window.location.search); + if (params.get('ping') === 'manual') { + // Clean the param from URL + params.delete('ping'); + const newUrl = params.toString() + ? `${window.location.pathname}?${params.toString()}` + : window.location.pathname; + window.history.replaceState({}, '', newUrl); + setPendingManualPing(true); + } + } + }, []); // Load user and sharing preference from localStorage useEffect(() => { @@ -207,6 +224,15 @@ export default function RoomPage() { } }, [isConnected, isPushSubscribed, subscribePush, syncUrl]); + // Execute pending manual ping once connected (from notification tap while app was closed) + useEffect(() => { + if (pendingManualPing && isConnected) { + console.log('Executing pending manual ping from notification tap'); + setPendingManualPing(false); + handleManualPing(); + } + }, [pendingManualPing, isConnected, handleManualPing]); + // Restore last known location immediately when connected const hasRestoredLocationRef = useRef(false); useEffect(() => {