From 23dca554cacfb9878bfafafea73bc2234fa25122 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 18 Apr 2023 00:39:42 -0400 Subject: [PATCH] fix: added api routes for push registration --- overseerr-api.yml | 41 ++++++++++ server/routes/user/index.ts | 45 +++++++++++ src/components/Layout/UserDropdown/index.tsx | 29 ++++++- .../UserNotificationsWebPush.tsx | 79 +++++++++++-------- 4 files changed, 158 insertions(+), 36 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index c8b52885..aa6eba13 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -3548,6 +3548,47 @@ paths: responses: '204': description: Successfully registered push subscription + /user/{key}/pushSubscription: + get: + summary: Get web push notification settings for a user + description: | + Returns web push notification settings for a user in a JSON object. + tags: + - users + parameters: + - in: path + name: key + required: true + schema: + type: string + responses: + '200': + description: User web push notification settings in JSON + content: + application/json: + schema: + type: object + properties: + endpoint: + type: string + p256dh: + type: string + auth: + type: string + delete: + summary: Delete user push subscription by key + description: Deletes the user push subscription with the provided key. + tags: + - users + parameters: + - in: path + name: key + required: true + schema: + type: string + responses: + '204': + description: Successfully removed user push subscription /user/{userId}: get: summary: Get user by ID diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index 94784df5..968cd9c0 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -138,6 +138,25 @@ router.post( } ); +router.get<{ key: string }>( + '/:key/pushSubscription', + async (req, res, next) => { + try { + const userPushSubRepository = getRepository(UserPushSubscription); + + const userPushSub = await userPushSubRepository.findOneOrFail({ + where: { + p256dh: req.params.key, + }, + }); + + return res.status(200).json(userPushSub); + } catch (e) { + next({ status: 404, message: 'User subscription not found.' }); + } + } +); + router.post< never, unknown, @@ -180,6 +199,32 @@ router.post< } }); +router.delete<{ key: string }>( + '/:key/pushSubscription', + async (req, res, next) => { + try { + const userPushSubRepository = getRepository(UserPushSubscription); + + const userPushSub = await userPushSubRepository.findOneOrFail({ + where: { p256dh: req.params.key }, + }); + + await userPushSubRepository.remove(userPushSub); + return res.status(204).send(); + } catch (e) { + logger.error('Something went wrong deleting the user push subcription', { + label: 'API', + key: req.params.key, + errorMessage: e.message, + }); + return next({ + status: 500, + message: 'User push subcription not found', + }); + } + } +); + router.get<{ id: string }>('/:id', async (req, res, next) => { try { const userRepository = getRepository(User); diff --git a/src/components/Layout/UserDropdown/index.tsx b/src/components/Layout/UserDropdown/index.tsx index 6d3fe7b9..1a417dc0 100644 --- a/src/components/Layout/UserDropdown/index.tsx +++ b/src/components/Layout/UserDropdown/index.tsx @@ -39,10 +39,33 @@ const UserDropdown = () => { const { user, revalidate } = useUser(); const logout = async () => { - const response = await axios.post('/api/v1/auth/logout'); + if ('serviceWorker' in navigator && user?.id) { + navigator.serviceWorker.getRegistration('/sw.js').then((registration) => { + registration?.pushManager + .getSubscription() + .then(async (subscription) => { + subscription + ?.unsubscribe() + .then(async () => { + const parsedSub = JSON.parse(JSON.stringify(subscription)); + await axios.delete( + `/api/v1/user/${parsedSub.keys.p256dh}/pushSubscription` + ); + const response = await axios.post('/api/v1/auth/logout'); - if (response.data?.status === 'ok') { - revalidate(); + if (response.data?.status === 'ok') { + revalidate(); + } + }) + .catch(function (error) { + // eslint-disable-next-line no-console + console.log( + '[SW] Failure unsubscribing to push manager, error:', + error + ); + }); + }); + }); } }; diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx index 9ae59a01..b6999f32 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx @@ -11,6 +11,7 @@ import { CloudArrowDownIcon, CloudArrowUpIcon, } from '@heroicons/react/24/solid'; +import type { UserPushSubscription } from '@server/entity/UserPushSubscription'; import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces'; import axios from 'axios'; import { Form, Formik } from 'formik'; @@ -49,11 +50,6 @@ const UserWebPushSettings = () => { navigator.serviceWorker .getRegistration('/sw.js') .then(async (registration) => { - const permissionState = - await registration?.pushManager.permissionState({ - userVisibleOnly: true, - }); - if (currentSettings.enablePushRegistration) { const sub = await registration?.pushManager.subscribe({ userVisibleOnly: true, @@ -63,13 +59,11 @@ const UserWebPushSettings = () => { const parsedSub = JSON.parse(JSON.stringify(sub)); if (parsedSub.keys.p256dh && parsedSub.keys.auth) { - if (permissionState === 'prompt') { - await axios.post('/api/v1/user/registerPushSubscription', { - endpoint: parsedSub.endpoint, - p256dh: parsedSub.keys.p256dh, - auth: parsedSub.keys.auth, - }); - } + await axios.post('/api/v1/user/registerPushSubscription', { + endpoint: parsedSub.endpoint, + p256dh: parsedSub.keys.p256dh, + auth: parsedSub.keys.auth, + }); setWebPushEnabled(true); } } @@ -93,7 +87,13 @@ const UserWebPushSettings = () => { .then(async (subscription) => { subscription ?.unsubscribe() - .then(() => setWebPushEnabled(false)) + .then(async () => { + const parsedSub = JSON.parse(JSON.stringify(subscription)); + await axios.delete( + `/api/v1/user/${parsedSub.keys.p256dh}/pushSubscription` + ); + setWebPushEnabled(false); + }) .catch(function (error) { // eslint-disable-next-line no-console console.log( @@ -113,11 +113,22 @@ const UserWebPushSettings = () => { navigator.serviceWorker .getRegistration('/sw.js') .then(async (registration) => { - await registration?.pushManager.getSubscription().then((status) => { - if (status) { - setWebPushEnabled(true); - } - }); + await registration?.pushManager + .getSubscription() + .then(async (subscription) => { + if (subscription) { + const parsedKey = JSON.parse(JSON.stringify(subscription)); + const currentUserPushSub = + await axios.get( + `/api/v1/user/${parsedKey.keys.p256dh}/pushSubscription` + ); + + if (currentUserPushSub.data.p256dh !== parsedKey.keys.p256dh) { + return; + } + setWebPushEnabled(true); + } + }); }) .catch(function (error) { // eslint-disable-next-line no-console @@ -198,7 +209,7 @@ const UserWebPushSettings = () => {
- - - + {webPushEnabled && ( + + + + )}