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:
parent
7410246a01
commit
4f110a7604
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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(() => {
|
||||||
|
|
|
||||||
|
|
@ -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': '*',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue