-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathWebPushManager.tsx
More file actions
120 lines (98 loc) · 3.74 KB
/
Copy pathWebPushManager.tsx
File metadata and controls
120 lines (98 loc) · 3.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"use client"
import { useEffect, useState } from 'react'
import { PushNotificationDialog } from './PushNotificationDialog'
import { useUser } from '@clerk/nextjs'
// Lightweight manager to register service worker and manage push subscription dialog.
// Assumptions:
// - Service worker file at /sw.js
// - API endpoint: POST /api/protected/web-push/subscribe (now publicly accessible)
// - VAPID public key exposed via NEXT_PUBLIC_VAPID_PUBLIC_KEY
async function subscribeUser(): Promise<boolean> {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) return false
const reg = await navigator.serviceWorker.ready
const vapidKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY
if (!vapidKey) return false
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidKey)
})
// Send subscription to server
await fetch('/api/protected/web-push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ subscription: sub })
})
return true
}
async function migratePushSubscription(): Promise<void> {
// Check if user has an existing push subscription and try to migrate it
if (!('serviceWorker' in navigator) || !('PushManager' in window)) return
try {
const reg = await navigator.serviceWorker.ready
const existing = await reg.pushManager.getSubscription()
if (existing) {
// Re-send the subscription to the server with the current user's auth
// The server will update the subscription with the userId if not already set
await fetch('/api/protected/web-push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ subscription: existing })
})
}
} catch (e) {
console.error('Failed to migrate push subscription:', e)
}
}
function urlBase64ToUint8Array(base64String: string) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
const rawData = atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) outputArray[i] = rawData.charCodeAt(i)
return outputArray
}
export function WebPushManager() {
const [ready, setReady] = useState(false)
const [error, setError] = useState<string | null>(null)
const { isSignedIn } = useUser()
useEffect(() => {
let cancelled = false
async function init() {
try {
if (!('serviceWorker' in navigator)) return
// Register service worker
await navigator.serviceWorker.register('/sw.js')
if (!cancelled) setReady(true)
} catch (e: any) {
if (!cancelled) setError(e?.message || 'Push init failed')
}
}
init()
return () => { cancelled = true }
}, [])
// When user logs in, migrate their existing subscription if any
useEffect(() => {
if (isSignedIn && ready) {
migratePushSubscription()
}
}, [isSignedIn, ready])
const handleEnableNotifications = async () => {
// Request native browser permission
const permission = await Notification.requestPermission()
if (permission === 'granted') {
// Subscribe user to push notifications
await subscribeUser()
} else if (permission === 'denied') {
throw new Error('Notification permission denied')
}
}
// Non-visual manager, but we render the dialog
if (error) return <span style={{ display: 'none' }} data-push-error={error} />
return (
<>
<span style={{ display: 'none' }} data-push-ready={ready ? 'true' : 'false'} />
{ready && <PushNotificationDialog onEnableClick={handleEnableNotifications} />}
</>
)
}
export default WebPushManager