All files / src/services push.ts

97.43% Statements 38/39
93.33% Branches 14/15
100% Functions 6/6
100% Lines 34/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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      2x 2x 2x 2x 36x 2x       2x 2x       4x 3x 3x   1x 1x     2x 2x       2x       3x 3x                 1x     7x 2x     5x 5x   5x 2x     5x   4x 4x   1x         2x 2x 2x 1x 1x      
import api from './api'
 
function urlBase64ToUint8Array(base64String: string): Uint8Array<ArrayBuffer> {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
  const raw = atob(base64)
  const arr = new Uint8Array(raw.length)
  for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i)
  return arr
}
 
async function getVapidPublicKey(): Promise<string> {
  const { data } = await api.get<{ publicKey: string }>('/push/public-key')
  return data.publicKey
}
 
async function subscribeToPush(): Promise<void> {
  const reg = await navigator.serviceWorker.ready
  const existing = await reg.pushManager.getSubscription()
  if (existing) {
    // Re-send to backend in case it was lost (e.g. new device install)
    await sendSubscriptionToServer(existing)
    return
  }
 
  const vapidKey = await getVapidPublicKey()
  const sub = await reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(vapidKey),
  })
  await sendSubscriptionToServer(sub)
}
 
async function sendSubscriptionToServer(sub: PushSubscription): Promise<void> {
  const json = sub.toJSON()
  await api.post('/push/subscribe', {
    endpoint: json.endpoint,
    keys: {
      p256dh: json.keys?.p256dh,
      auth: json.keys?.auth,
    },
  })
}
 
export const pushService = {
  /** Call once on app init. Requests permission, subscribes, registers with backend. */
  async init(): Promise<void> {
    if (!('Notification' in window) || !('serviceWorker' in navigator) || !('PushManager' in window)) {
      return // push not supported in this browser
    }
 
    let permission = Notification.permission
    Iif (permission === 'denied') return
 
    if (permission === 'default') {
      permission = await Notification.requestPermission()
    }
 
    if (permission !== 'granted') return
 
    try {
      await subscribeToPush()
    } catch (e) {
      console.warn('[Push] Failed to subscribe:', e)
    }
  },
 
  async unsubscribe(): Promise<void> {
    const reg = await navigator.serviceWorker.ready
    const sub = await reg.pushManager.getSubscription()
    if (!sub) return
    await api.delete('/push/subscribe', { data: { endpoint: sub.endpoint } })
    await sub.unsubscribe()
  },
}