2023-01-08 09:21:09 +03:00
import type { mastodon } from 'masto'
2022-12-18 02:29:16 +03:00
import type {
CreatePushNotification ,
PushManagerSubscriptionInfo ,
RequiredUserLogin ,
} from '~/composables/push-notifications/types'
2023-01-13 15:54:30 +03:00
import { PushSubscriptionError } from '~/composables/push-notifications/types'
2022-12-18 02:29:16 +03:00
export const createPushSubscription = async (
user : RequiredUserLogin ,
notificationData : CreatePushNotification ,
2023-01-08 09:21:09 +03:00
policy : mastodon.v1.SubscriptionPolicy = 'all' ,
2022-12-22 16:48:20 +03:00
force = false ,
2023-01-08 09:21:09 +03:00
) : Promise < mastodon.v1.WebPushSubscription | undefined > = > {
2022-12-18 02:29:16 +03:00
const { server : serverEndpoint , vapidKey } = user
return await getRegistration ( )
. then ( getPushSubscription )
2023-01-08 09:21:09 +03:00
. then ( ( { registration , subscription } ) : Promise < mastodon.v1.WebPushSubscription | undefined > = > {
2022-12-18 02:29:16 +03:00
if ( subscription ) {
const currentServerKey = ( new Uint8Array ( subscription . options . applicationServerKey ! ) ) . toString ( )
const subscriptionServerKey = urlBase64ToUint8Array ( vapidKey ) . toString ( )
// If the VAPID public key did not change and the endpoint corresponds
// to the endpoint saved in the backend, the subscription is valid
// If push subscription is not there, we need to create it: it is fetched on login
2022-12-22 16:48:20 +03:00
if ( subscriptionServerKey === currentServerKey && subscription . endpoint === serverEndpoint && ( ! force && user . pushSubscription ) ) {
2022-12-18 02:29:16 +03:00
return Promise . resolve ( user . pushSubscription )
}
else if ( user . pushSubscription ) {
2022-12-22 16:48:20 +03:00
// if we have a subscription, but it is not valid or forcing renew, we need to remove it
// we need to prevent removing push notification data
return unsubscribeFromBackend ( false , false )
. catch ( removePushNotificationDataOnError )
2022-12-18 02:29:16 +03:00
. then ( ( ) = > subscribe ( registration , vapidKey ) )
2022-12-22 16:48:20 +03:00
. then ( subscription = > sendSubscriptionToBackend ( subscription , notificationData , policy ) )
2022-12-18 02:29:16 +03:00
}
}
return subscribe ( registration , vapidKey ) . then (
2022-12-22 16:48:20 +03:00
subscription = > sendSubscriptionToBackend ( subscription , notificationData , policy ) ,
2022-12-18 02:29:16 +03:00
)
} )
. catch ( ( error ) = > {
2023-01-13 15:54:30 +03:00
let useError : PushSubscriptionError | Error = error
if ( error . code === 11 && error . name === 'InvalidStateError' )
useError = new PushSubscriptionError ( 'too_many_registrations' , 'Too many registrations' )
else if ( error . code === 20 && error . name === 'AbortError' )
2023-02-04 21:43:24 +03:00
useError = new PushSubscriptionError ( 'vapid_not_supported' , 'Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.' )
2022-12-18 02:29:16 +03:00
else if ( error . code === 5 && error . name === 'InvalidCharacterError' )
2023-02-04 21:43:24 +03:00
useError = new PushSubscriptionError ( 'invalid_vapid_key' , ` The VAPID public key seems to be invalid: ${ vapidKey } ` )
2022-12-18 02:29:16 +03:00
return getRegistration ( )
. then ( getPushSubscription )
. then ( ( ) = > unsubscribeFromBackend ( true ) )
. then ( ( ) = > Promise . resolve ( undefined ) )
. catch ( ( e ) = > {
console . error ( e )
return Promise . resolve ( undefined )
} )
2023-01-13 15:54:30 +03:00
. finally ( ( ) = > {
return Promise . reject ( useError )
} )
2022-12-18 02:29:16 +03:00
} )
}
// Taken from https://www.npmjs.com/package/web-push
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
}
function getRegistration() {
return navigator . serviceWorker . ready
}
async function getPushSubscription ( registration : ServiceWorkerRegistration ) : Promise < PushManagerSubscriptionInfo > {
const subscription = await registration . pushManager . getSubscription ( )
return { registration , subscription }
}
async function subscribe (
registration : ServiceWorkerRegistration ,
applicationServerKey : string ,
) : Promise < PushSubscription > {
return await registration . pushManager . subscribe ( {
userVisibleOnly : true ,
applicationServerKey : urlBase64ToUint8Array ( applicationServerKey ) ,
} )
}
2022-12-22 16:48:20 +03:00
async function unsubscribeFromBackend ( fromSWPushManager : boolean , removePushNotification = true ) {
const cu = currentUser . value
if ( cu ) {
await removePushNotifications ( cu )
removePushNotification && await removePushNotificationData ( cu , fromSWPushManager )
}
}
async function removePushNotificationDataOnError ( e : Error ) {
2022-12-18 02:29:16 +03:00
const cu = currentUser . value
if ( cu )
2022-12-22 16:48:20 +03:00
await removePushNotificationData ( cu , true )
throw e
2022-12-18 02:29:16 +03:00
}
async function sendSubscriptionToBackend (
subscription : PushSubscription ,
data : CreatePushNotification ,
2023-01-08 09:21:09 +03:00
policy : mastodon.v1.SubscriptionPolicy ,
) : Promise < mastodon.v1.WebPushSubscription > {
2022-12-18 02:29:16 +03:00
const { endpoint , keys } = subscription . toJSON ( )
2023-01-08 09:21:09 +03:00
const params : mastodon.v1.CreateWebPushSubscriptionParams = {
2022-12-22 16:48:20 +03:00
policy ,
2022-12-18 02:29:16 +03:00
subscription : {
endpoint : endpoint ! ,
keys : {
p256dh : keys ! . p256dh ! ,
auth : keys ! . auth ! ,
} ,
} ,
data ,
}
2023-01-15 11:38:02 +03:00
return await useMastoClient ( ) . v1 . webPushSubscriptions . create ( params )
2022-12-18 02:29:16 +03:00
}