152 lines
5.0 KiB
TypeScript
152 lines
5.0 KiB
TypeScript
const VAPID_PUBLIC_KEY = import.meta.env.VITE_PUBLIC_VAPID_KEY as string;
|
|
const VITE_BASE_URL = import.meta.env.VITE_BASE_URL as string;
|
|
|
|
function urlBase64ToUint8Array(base64String: string) {
|
|
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;
|
|
}
|
|
|
|
export async function registerServiceWorker() {
|
|
if (!('serviceWorker' in navigator)) {
|
|
throw new Error('Service workers are not supported in this browser');
|
|
}
|
|
|
|
let registration: ServiceWorkerRegistration;
|
|
|
|
try {
|
|
// Check if service worker is already registered
|
|
const existingRegistration = await navigator.serviceWorker.getRegistration('/service-worker.js');
|
|
|
|
if (existingRegistration) {
|
|
// Service worker already registered, wait for it to be ready
|
|
registration = await navigator.serviceWorker.ready;
|
|
} else {
|
|
// Register new service worker
|
|
registration = await navigator.serviceWorker.register('/service-worker.js');
|
|
|
|
// Wait for the service worker to be ready (may take a moment)
|
|
registration = await navigator.serviceWorker.ready;
|
|
}
|
|
} catch (error: any) {
|
|
throw new Error(`Failed to register service worker: ${error?.message || 'Unknown error'}`);
|
|
}
|
|
|
|
return registration;
|
|
}
|
|
|
|
export async function subscribeUserToPush(register: ServiceWorkerRegistration) {
|
|
if (!VAPID_PUBLIC_KEY) {
|
|
throw new Error('Missing VAPID public key configuration');
|
|
}
|
|
|
|
// Check if already subscribed
|
|
let subscription: PushSubscription;
|
|
try {
|
|
const existingSubscription = await register.pushManager.getSubscription();
|
|
|
|
if (existingSubscription) {
|
|
// Already subscribed, check if it's still valid
|
|
subscription = existingSubscription;
|
|
} else {
|
|
// Subscribe to push
|
|
subscription = await register.pushManager.subscribe({
|
|
userVisibleOnly: true,
|
|
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
|
|
});
|
|
}
|
|
} catch (error: any) {
|
|
throw new Error(`Failed to subscribe to push notifications: ${error?.message || 'Unknown error'}`);
|
|
}
|
|
|
|
// Convert subscription to JSON format for backend
|
|
const subscriptionJson = subscription.toJSON();
|
|
|
|
// Attach auth token if available
|
|
const token = (window as any)?.localStorage?.getItem?.('accessToken') ||
|
|
(document?.cookie || '').match(/accessToken=([^;]+)/)?.[1] || '';
|
|
|
|
if (!token) {
|
|
throw new Error('Authentication token not found. Please log in again.');
|
|
}
|
|
|
|
// Send subscription to backend
|
|
try {
|
|
const response = await fetch(`${VITE_BASE_URL}/api/v1/workflows/notifications/subscribe`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(subscriptionJson)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
throw new Error(errorData?.error || errorData?.message || `Server error: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (!result.success) {
|
|
throw new Error(result.error || 'Failed to save subscription');
|
|
}
|
|
} catch (error: any) {
|
|
// If it's already our error, rethrow it
|
|
if (error instanceof Error && error.message.includes('Failed')) {
|
|
throw error;
|
|
}
|
|
throw new Error(`Failed to save subscription to server: ${error?.message || 'Network error'}`);
|
|
}
|
|
|
|
return subscription;
|
|
}
|
|
|
|
export async function setupPushNotifications() {
|
|
// Check if notifications are supported
|
|
if (!('Notification' in window)) {
|
|
throw new Error('Notifications are not supported in this browser');
|
|
}
|
|
|
|
// Check permission status
|
|
let permission = Notification.permission;
|
|
|
|
if (permission === 'denied') {
|
|
throw new Error('Notification permission was denied. Please enable notifications in your browser settings and try again.');
|
|
}
|
|
|
|
if (permission === 'default') {
|
|
// Request permission if not already requested
|
|
permission = await Notification.requestPermission();
|
|
if (permission !== 'granted') {
|
|
throw new Error('Notification permission was denied. Please enable notifications in your browser settings and try again.');
|
|
}
|
|
}
|
|
|
|
// Final check - permission should be 'granted' at this point
|
|
if (permission !== 'granted') {
|
|
throw new Error('Notification permission is required. Please grant permission and try again.');
|
|
}
|
|
|
|
// Register service worker (or get existing)
|
|
let reg: ServiceWorkerRegistration;
|
|
try {
|
|
reg = await registerServiceWorker();
|
|
} catch (error: any) {
|
|
throw new Error(`Service worker registration failed: ${error?.message || 'Unknown error'}`);
|
|
}
|
|
|
|
// Subscribe to push
|
|
try {
|
|
await subscribeUserToPush(reg);
|
|
} catch (error: any) {
|
|
throw error; // Re-throw with detailed message
|
|
}
|
|
}
|
|
|
|
|