From a1132371f4be4b10619903b4f25c42de88138119 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 19 Feb 2026 00:35:54 +0000 Subject: [PATCH] Thread caller name through ping notifications Ping notifications now show who pinged: " pinged you for your location!" instead of generic "Someone is looking for you". Caller name flows through WS messages, silent push payloads, SW postMessage, URL params (?pinger=), and visible push notifications. Co-Authored-By: Claude Opus 4.6 --- public/sw.js | 20 ++++++++++-------- src/app/[slug]/page.tsx | 29 ++++++++++++++++----------- src/hooks/useRoom.ts | 4 ++-- src/hooks/useServiceWorkerMessages.ts | 4 ++-- src/lib/sync.ts | 8 ++++---- sync-server/server.js | 6 +++--- 6 files changed, 40 insertions(+), 31 deletions(-) diff --git a/public/sw.js b/public/sw.js index 5cf022f..91a1654 100644 --- a/public/sw.js +++ b/public/sw.js @@ -293,14 +293,14 @@ async function syncLocation() { } } -async function requestLocationFromClient(manual = false) { +async function requestLocationFromClient(manual = false, callerName = null) { const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); if (clients.length > 0) { - clients[0].postMessage({ type: 'REQUEST_LOCATION_UPDATE', manual }); + clients[0].postMessage({ type: 'REQUEST_LOCATION_UPDATE', manual, callerName }); return true; } return false; @@ -344,19 +344,20 @@ self.addEventListener('push', (event) => { lastLocationRequestTime = now; const isManual = !!(data.data?.manual); + const callerName = data.data?.callerName || 'Someone'; event.waitUntil( - requestLocationFromClient(isManual).then(async (sent) => { + requestLocationFromClient(isManual, callerName).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!', { + await self.registration.showNotification(`📍 ${callerName} pinged you for your location!`, { 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 }, + data: { type: 'callout', roomSlug: data.data?.roomSlug, manual: true, callerName }, vibrate: [200, 100, 200, 100, 400], requireInteraction: true, actions: [{ action: 'view', title: 'Open Map' }], @@ -434,9 +435,12 @@ self.addEventListener('notificationclick', (event) => { } } if (clients.openWindow) { - // 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); + // If manual ping, append ?ping=manual&pinger= so the app auto-responds with GPS on load + if (data.manual) { + const pingerParam = data.callerName ? `&pinger=${encodeURIComponent(data.callerName)}` : ''; + return clients.openWindow(`${targetUrl}?ping=manual${pingerParam}`); + } + return clients.openWindow(targetUrl); } }) ); diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index a1ecf9f..b8d41ad 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -49,17 +49,21 @@ export default function RoomPage() { const [needsJoin, setNeedsJoin] = useState(false); const [pendingManualPing, setPendingManualPing] = useState(false); - // Check if opened from a manual ping notification (e.g. ?ping=manual) + // Check if opened from a manual ping notification (e.g. ?ping=manual&pinger=Alice) + const [pingCallerName, setPingCallerName] = useState(undefined); useEffect(() => { if (typeof window !== 'undefined') { const params = new URLSearchParams(window.location.search); if (params.get('ping') === 'manual') { - // Clean the param from URL + const pinger = params.get('pinger') || undefined; + // Clean the params from URL params.delete('ping'); + params.delete('pinger'); const newUrl = params.toString() ? `${window.location.pathname}?${params.toString()}` : window.location.pathname; window.history.replaceState({}, '', newUrl); + setPingCallerName(pinger); setPendingManualPing(true); } } @@ -149,7 +153,8 @@ export default function RoomPage() { }, [slug]); // Handle manual ping: vibrate device + one-shot GPS regardless of sharing state - const handleManualPing = useCallback(() => { + const handleManualPing = useCallback((callerName?: string) => { + console.log(`Manual ping from ${callerName || 'unknown'} — vibrating and sending GPS`); if (typeof navigator !== 'undefined' && 'vibrate' in navigator) { navigator.vibrate([200, 100, 200, 100, 400]); } @@ -188,10 +193,10 @@ export default function RoomPage() { // Service worker messages for background location sync useServiceWorkerMessages({ - onLocationRequest: (manual?: boolean) => { - console.log('Service worker requested location update, manual:', manual); + onLocationRequest: (manual?: boolean, callerName?: string) => { + console.log('Service worker requested location update, manual:', manual, 'from:', callerName); if (manual) { - handleManualPing(); + handleManualPing(callerName); } else if (isSharing) { requestUpdate(); } @@ -227,11 +232,11 @@ export default function RoomPage() { // 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'); + console.log('Executing pending manual ping from notification tap, pinger:', pingCallerName); setPendingManualPing(false); - handleManualPing(); + handleManualPing(pingCallerName); } - }, [pendingManualPing, isConnected, handleManualPing]); + }, [pendingManualPing, isConnected, handleManualPing, pingCallerName]); // Restore last known location immediately when connected const hasRestoredLocationRef = useRef(false); @@ -273,10 +278,10 @@ export default function RoomPage() { // Set up callback for when server requests location (via ping button or auto) useEffect(() => { if (isConnected) { - setLocationRequestCallback((manual?: boolean) => { - console.log('Server requested location update, manual:', manual); + setLocationRequestCallback((manual?: boolean, callerName?: string) => { + console.log('Server requested location update, manual:', manual, 'from:', callerName); if (manual) { - handleManualPing(); + handleManualPing(callerName); } else if (isSharing) { requestUpdate(); } diff --git a/src/hooks/useRoom.ts b/src/hooks/useRoom.ts index fab7cec..2f2d1d4 100644 --- a/src/hooks/useRoom.ts +++ b/src/hooks/useRoom.ts @@ -118,7 +118,7 @@ interface UseRoomReturn { addWaypoint: (waypoint: Omit) => void; removeWaypoint: (waypointId: string) => void; leave: () => void; - setLocationRequestCallback: (callback: () => void) => void; + setLocationRequestCallback: (callback: (manual?: boolean, callerName?: string) => void) => void; } export function useRoom({ slug, userName, userEmoji, encryptIdDid, authToken }: UseRoomOptions): UseRoomReturn { @@ -296,7 +296,7 @@ export function useRoom({ slug, userName, userEmoji, encryptIdDid, authToken }: }, []); // Set location request callback (called when server requests location update) - const setLocationRequestCallback = useCallback((callback: () => void) => { + const setLocationRequestCallback = useCallback((callback: (manual?: boolean, callerName?: string) => void) => { if (syncRef.current) { syncRef.current.setLocationRequestCallback(callback); } diff --git a/src/hooks/useServiceWorkerMessages.ts b/src/hooks/useServiceWorkerMessages.ts index 6a93112..01bb291 100644 --- a/src/hooks/useServiceWorkerMessages.ts +++ b/src/hooks/useServiceWorkerMessages.ts @@ -12,7 +12,7 @@ interface ServiceWorkerRegistrationWithSync extends ServiceWorkerRegistration { } interface UseServiceWorkerMessagesOptions { - onLocationRequest?: (manual?: boolean) => void; + onLocationRequest?: (manual?: boolean, callerName?: string) => void; onLocationSync?: () => void; } @@ -35,7 +35,7 @@ export function useServiceWorkerMessages(options: UseServiceWorkerMessagesOption switch (event.data?.type) { case 'REQUEST_LOCATION_UPDATE': // Service worker is requesting a location update (from push) - optionsRef.current.onLocationRequest?.(event.data?.manual); + optionsRef.current.onLocationRequest?.(event.data?.manual, event.data?.callerName); break; case 'REQUEST_LOCATION_SYNC': diff --git a/src/lib/sync.ts b/src/lib/sync.ts index 8cd9b35..47c8570 100644 --- a/src/lib/sync.ts +++ b/src/lib/sync.ts @@ -77,11 +77,11 @@ export type SyncMessage = | { type: 'waypoint_remove'; waypointId: string } | { type: 'full_state'; state: RoomState } | { type: 'request_state' } - | { type: 'request_location'; manual?: boolean }; + | { type: 'request_location'; manual?: boolean; callerName?: string }; type SyncCallback = (state: RoomState) => void; type ConnectionCallback = (connected: boolean) => void; -type LocationRequestCallback = (manual?: boolean) => void; +type LocationRequestCallback = (manual?: boolean, callerName?: string) => void; // Validate that coordinates are reasonable (not 0,0 or out of bounds) function isValidLocation(location: LocationState | undefined): boolean { @@ -328,9 +328,9 @@ export class RoomSync { case 'request_location': // Server is requesting a location update from us - console.log('[RoomSync] Received location request, manual:', message.manual); + console.log('[RoomSync] Received location request, manual:', message.manual, 'from:', message.callerName); if (this.onLocationRequest) { - this.onLocationRequest(message.manual); + this.onLocationRequest(message.manual, message.callerName); } return; // Don't notify state change for this message type } diff --git a/sync-server/server.js b/sync-server/server.js index 6b576a2..90b4168 100644 --- a/sync-server/server.js +++ b/sync-server/server.js @@ -718,7 +718,7 @@ const server = createServer(async (req, res) => { } // Send WebSocket message to connected clients, deduplicated by name - const locationRequestMsg = JSON.stringify({ type: 'request_location', manual: true }); + const locationRequestMsg = JSON.stringify({ type: 'request_location', manual: true, callerName: pingerName }); const pingedNames = new Set(); for (const [ws, clientInfo] of clients.entries()) { @@ -770,12 +770,12 @@ const server = createServer(async (req, res) => { // Online users already got the WS message; send silent push as backup await webpush.sendNotification(subscription, JSON.stringify({ silent: true, - data: { type: 'location_request', roomSlug, manual: true } + data: { type: 'location_request', roomSlug, manual: true, callerName: pingerName } })); } else { // Offline users: send a VISIBLE callout notification await webpush.sendNotification(subscription, JSON.stringify({ - title: `📍 ${pingerName} is looking for you!`, + title: `📍 ${pingerName} pinged you for your location!`, body: `Tap to share your location in ${roomSlug}`, tag: `callout-${roomSlug}`, data: {