fix:add NotificationManager and enable in layout
This commit is contained in:
150
src/lib/components/NotificationManager.svelte
Normal file
150
src/lib/components/NotificationManager.svelte
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NotificationManager - Handles push notification subscription
|
||||||
|
* Automatically requests permission and subscribes authenticated users
|
||||||
|
*/
|
||||||
|
|
||||||
|
let permissionStatus = $state<NotificationPermission>('default');
|
||||||
|
let subscriptionStatus = $state<'idle' | 'subscribing' | 'subscribed' | 'error'>('idle');
|
||||||
|
let errorMessage = $state<string>('');
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!browser) return;
|
||||||
|
|
||||||
|
// Check if notifications are supported
|
||||||
|
if (!('Notification' in window)) {
|
||||||
|
console.log('This browser does not support notifications');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if service workers are supported
|
||||||
|
if (!('serviceWorker' in navigator)) {
|
||||||
|
console.log('This browser does not support service workers');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize notification subscription
|
||||||
|
initializeNotifications();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function initializeNotifications() {
|
||||||
|
try {
|
||||||
|
// Get current permission status
|
||||||
|
permissionStatus = Notification.permission;
|
||||||
|
|
||||||
|
// If already denied, don't do anything
|
||||||
|
if (permissionStatus === 'denied') {
|
||||||
|
console.log('Notification permission denied by user');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register service worker
|
||||||
|
const registration = await navigator.serviceWorker.register('/service-worker.js', {
|
||||||
|
type: 'module'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for service worker to be ready
|
||||||
|
await navigator.serviceWorker.ready;
|
||||||
|
|
||||||
|
// If permission is default, request it
|
||||||
|
if (permissionStatus === 'default') {
|
||||||
|
permissionStatus = await Notification.requestPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If permission granted, subscribe to push notifications
|
||||||
|
if (permissionStatus === 'granted') {
|
||||||
|
await subscribeToPushNotifications(registration);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing notifications:', error);
|
||||||
|
subscriptionStatus = 'error';
|
||||||
|
errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function subscribeToPushNotifications(registration: ServiceWorkerRegistration) {
|
||||||
|
try {
|
||||||
|
subscriptionStatus = 'subscribing';
|
||||||
|
|
||||||
|
// Get VAPID public key from server
|
||||||
|
const response = await fetch('/api/notifications/subscribe');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to get VAPID public key');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { publicKey } = await response.json();
|
||||||
|
|
||||||
|
// Check if already subscribed
|
||||||
|
let subscription = await registration.pushManager.getSubscription();
|
||||||
|
|
||||||
|
// If not subscribed, create new subscription
|
||||||
|
if (!subscription) {
|
||||||
|
subscription = await registration.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: urlBase64ToUint8Array(publicKey)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send subscription to server
|
||||||
|
const saveResponse = await fetch('/api/notifications/subscribe', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
subscription: {
|
||||||
|
endpoint: subscription.endpoint,
|
||||||
|
keys: {
|
||||||
|
p256dh: arrayBufferToBase64(subscription.getKey('p256dh')),
|
||||||
|
auth: arrayBufferToBase64(subscription.getKey('auth'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!saveResponse.ok) {
|
||||||
|
throw new Error('Failed to save subscription to server');
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionStatus = 'subscribed';
|
||||||
|
console.log('Successfully subscribed to push notifications');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error subscribing to push notifications:', error);
|
||||||
|
subscriptionStatus = 'error';
|
||||||
|
errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert VAPID public key from base64 to Uint8Array
|
||||||
|
*/
|
||||||
|
function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
||||||
|
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
||||||
|
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
|
||||||
|
const rawData = window.atob(base64);
|
||||||
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
outputArray[i] = rawData.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return outputArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert ArrayBuffer to base64 string
|
||||||
|
*/
|
||||||
|
function arrayBufferToBase64(buffer: ArrayBuffer | null): string {
|
||||||
|
if (!buffer) return '';
|
||||||
|
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
let binary = '';
|
||||||
|
for (let i = 0; i < bytes.byteLength; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
return window.btoa(binary);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
import { Toaster } from '$lib/components/sonner/index.js';
|
import { Toaster } from '$lib/components/sonner/index.js';
|
||||||
import { Skeleton } from '$lib/components/skeleton';
|
import { Skeleton } from '$lib/components/skeleton';
|
||||||
import LocationManager from '$lib/components/LocationManager.svelte';
|
import LocationManager from '$lib/components/LocationManager.svelte';
|
||||||
|
import NotificationManager from '$lib/components/NotificationManager.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
@@ -42,9 +43,10 @@
|
|||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|
||||||
<!-- Auto-start location watching for authenticated users -->
|
<!-- Auto-start location and notfication watching for authenticated users -->
|
||||||
{#if data?.user && !isLoginRoute}
|
{#if data?.user && !isLoginRoute}
|
||||||
<LocationManager autoStart={true} />
|
<LocationManager autoStart={true} />
|
||||||
|
<NotificationManager />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showHeader && data.user}
|
{#if showHeader && data.user}
|
||||||
|
|||||||
Reference in New Issue
Block a user