feat: Add push notifications for background friend pings

- 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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-12-29 02:33:08 +01:00
parent 7410246a01
commit 4f110a7604
4 changed files with 28 additions and 7 deletions

View File

@ -103,9 +103,14 @@ self.addEventListener('push', (event) => {
badge: data.badge || '/icon-192.png', badge: data.badge || '/icon-192.png',
tag: data.tag || 'rmaps-notification', tag: data.tag || 'rmaps-notification',
data: data.data || {}, 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 || [], actions: data.actions || [],
requireInteraction: data.requireInteraction || false, 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( event.waitUntil(

View File

@ -6,6 +6,7 @@ import dynamic from 'next/dynamic';
import { useRoom } from '@/hooks/useRoom'; import { useRoom } from '@/hooks/useRoom';
import { useLocationSharing } from '@/hooks/useLocationSharing'; import { useLocationSharing } from '@/hooks/useLocationSharing';
import { useServiceWorkerMessages } from '@/hooks/useServiceWorkerMessages'; import { useServiceWorkerMessages } from '@/hooks/useServiceWorkerMessages';
import { usePushNotifications } from '@/hooks/usePushNotifications';
import ParticipantList from '@/components/room/ParticipantList'; import ParticipantList from '@/components/room/ParticipantList';
import RoomHeader from '@/components/room/RoomHeader'; import RoomHeader from '@/components/room/RoomHeader';
import ShareModal from '@/components/room/ShareModal'; 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 // Restore last known location immediately when connected
const hasRestoredLocationRef = useRef(false); const hasRestoredLocationRef = useRef(false);
useEffect(() => { useEffect(() => {

View File

@ -128,7 +128,7 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
return NextResponse.json(data, { return NextResponse.json(data, {
status: 200, status: 200,
headers: { 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': '*', 'Access-Control-Allow-Origin': '*',
}, },
}); });

View File

@ -13,7 +13,7 @@ const MapView = dynamic(() => import('./MapView'), {
loading: () => <MapLoading />, loading: () => <MapLoading />,
}); });
const IndoorMapView = dynamic(() => import('./IndoorMapView'), { const C3NavEmbed = dynamic(() => import('./C3NavEmbed'), {
ssr: false, ssr: false,
loading: () => <MapLoading />, loading: () => <MapLoading />,
}); });
@ -139,13 +139,11 @@ export default function DualMapView({
onClearRoute={clearRoute} onClearRoute={clearRoute}
/> />
) : ( ) : (
<IndoorMapView <C3NavEmbed
eventId={eventId} eventId={eventId}
participants={participants} participants={participants}
currentUserId={currentUserId} currentUserId={currentUserId}
onParticipantClick={handleParticipantClick} onToggleOutdoor={goOutdoor}
onSwitchToOutdoor={goOutdoor}
onPositionSet={onIndoorPositionSet}
/> />
)} )}