From c2b58baa6e92842dfcf5ee13b67b5e58b80a6e19 Mon Sep 17 00:00:00 2001 From: elpatron Date: Tue, 2 Jun 2026 20:07:44 +0200 Subject: [PATCH] fix: implement callback-based Notification.requestPermission compatibility and manual key extraction fallback to fix mobile push subscription --- .../components/PushNotificationSettings.tsx | 3 +- client/src/components/SettingsForm.tsx | 3 +- client/src/services/pushNotifications.ts | 58 +++++++++++++++++-- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/client/src/components/PushNotificationSettings.tsx b/client/src/components/PushNotificationSettings.tsx index 06a9bbe..ab92953 100644 --- a/client/src/components/PushNotificationSettings.tsx +++ b/client/src/components/PushNotificationSettings.tsx @@ -58,7 +58,8 @@ export default function PushNotificationSettings() { trackPlausibleEvent(PlausibleEvents.PUSH_DISABLED) } } catch (err: unknown) { - const message = err instanceof Error ? err.message : t('profile.push_error') + console.error('Failed to toggle push notifications:', err) + const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err) showAlert(message) void loadPrefs() } finally { diff --git a/client/src/components/SettingsForm.tsx b/client/src/components/SettingsForm.tsx index 1317914..4eb2c64 100644 --- a/client/src/components/SettingsForm.tsx +++ b/client/src/components/SettingsForm.tsx @@ -193,7 +193,8 @@ export default function SettingsForm({ logbookId, onLogbookRestored }: SettingsF trackPlausibleEvent(PlausibleEvents.PUSH_ENABLED) } catch (err: unknown) { console.error('Failed to enable push after invite:', err) - await showAlert(err instanceof Error ? err.message : t('profile.push_error')) + const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err) + await showAlert(message) } } diff --git a/client/src/services/pushNotifications.ts b/client/src/services/pushNotifications.ts index 423c813..cc97e0c 100644 --- a/client/src/services/pushNotifications.ts +++ b/client/src/services/pushNotifications.ts @@ -96,11 +96,61 @@ export async function savePushPrefs(collaboratorChangesEnabled: boolean): Promis }) } +async function requestNotificationPermission(): Promise { + if (typeof Notification === 'undefined') return 'denied' + + // Try promise-based signature first + try { + const result = Notification.requestPermission() + if (result !== undefined) { + return await result + } + } catch { + // Ignore and fall back to callback + } + + // Callback-based fallback + return new Promise((resolve) => { + Notification.requestPermission((permission) => { + resolve(permission) + }) + }) +} + async function saveSubscriptionToServer(subscription: PushSubscription): Promise { if (!localStorage.getItem('active_userid')) throw new Error('Not authenticated') + const endpoint = subscription.endpoint const json = subscription.toJSON() - if (!json.endpoint || !json.keys?.p256dh || !json.keys?.auth) { + let p256dh = json.keys?.p256dh + let auth = json.keys?.auth + + // Fallback for browsers (like Safari) that might not serialize keys in toJSON() + if (!p256dh && typeof subscription.getKey === 'function') { + try { + const rawKey = subscription.getKey('p256dh') + if (rawKey) { + p256dh = btoa(String.fromCharCode(...new Uint8Array(rawKey))) + .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') + } + } catch (e) { + console.warn('Failed to extract p256dh key manually:', e) + } + } + + if (!auth && typeof subscription.getKey === 'function') { + try { + const rawAuth = subscription.getKey('auth') + if (rawAuth) { + auth = btoa(String.fromCharCode(...new Uint8Array(rawAuth))) + .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') + } + } catch (e) { + console.warn('Failed to extract auth key manually:', e) + } + } + + if (!endpoint || !p256dh || !auth) { throw new Error('Invalid push subscription') } @@ -109,8 +159,8 @@ async function saveSubscriptionToServer(subscription: PushSubscription): Promise await apiJson(`${API_BASE}/subscription`, { method: 'PUT', body: JSON.stringify({ - endpoint: json.endpoint, - keys: json.keys, + endpoint, + keys: { p256dh, auth }, locale, userAgent: navigator.userAgent }) @@ -131,7 +181,7 @@ export async function subscribeToPush(): Promise { throw new Error('Push notifications are not configured on this server') } - const permission = await Notification.requestPermission() + const permission = await requestNotificationPermission() if (permission !== 'granted') { throw new Error('Notification permission denied') }