diff --git a/.prettierignore b/.prettierignore index 1001e6b1..6e942806 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,4 +5,4 @@ config/ # assets src/assets/ -# public/ +public/ diff --git a/public/sw.js b/public/sw.js index cf9593f7..85a0b29b 100644 --- a/public/sw.js +++ b/public/sw.js @@ -4,30 +4,30 @@ // This variable is intentionally declared and unused. // eslint-disable-next-line @typescript-eslint/no-unused-vars const OFFLINE_VERSION = 3; -const CACHE_NAME = 'offline'; +const CACHE_NAME = "offline"; // Customize this with a different URL if needed. -const OFFLINE_URL = '/offline.html'; +const OFFLINE_URL = "/offline.html"; -self.addEventListener('install', (event) => { +self.addEventListener("install", (event) => { event.waitUntil( (async () => { const cache = await caches.open(CACHE_NAME); // Setting {cache: 'reload'} in the new request will ensure that the // response isn't fulfilled from the HTTP cache; i.e., it will be from // the network. - await cache.add(new Request(OFFLINE_URL, { cache: 'reload' })); + await cache.add(new Request(OFFLINE_URL, { cache: "reload" })); })() ); // Force the waiting service worker to become the active service worker. self.skipWaiting(); }); -self.addEventListener('activate', (event) => { +self.addEventListener("activate", (event) => { event.waitUntil( (async () => { // Enable navigation preload if it's supported. // See https://developers.google.com/web/updates/2017/02/navigation-preload - if ('navigationPreload' in self.registration) { + if ("navigationPreload" in self.registration) { await self.registration.navigationPreload.enable(); } })() @@ -37,10 +37,10 @@ self.addEventListener('activate', (event) => { clients.claim(); }); -self.addEventListener('fetch', (event) => { +self.addEventListener("fetch", (event) => { // We only want to call event.respondWith() if this is a navigation request // for an HTML page. - if (event.request.mode === 'navigate') { + if (event.request.mode === "navigate") { event.respondWith( (async () => { try { @@ -59,7 +59,7 @@ self.addEventListener('fetch', (event) => { // If fetch() returns a valid HTTP response with a response code in // the 4xx or 5xx range, the catch() will NOT be called. // eslint-disable-next-line no-console - console.log('Fetch failed; returning offline page instead.', error); + console.log("Fetch failed; returning offline page instead.", error); const cache = await caches.open(CACHE_NAME); const cachedResponse = await cache.match(OFFLINE_URL); @@ -70,7 +70,7 @@ self.addEventListener('fetch', (event) => { } }); -self.addEventListener('push', async (event) => { +self.addEventListener('push', (event) => { const payload = event.data ? event.data.json() : {}; const options = { @@ -85,13 +85,15 @@ self.addEventListener('push', async (event) => { requestId: payload.requestId, }, actions: [], - }; + } - if (payload.actionUrl) { - options.actions.push({ - action: 'view', - title: payload.actionUrlTitle ?? 'View', - }); + if (payload.actionUrl){ + options.actions.push( + { + action: 'view', + title: payload.actionUrlTitle ?? 'View', + } + ); } if (payload.notificationType === 'MEDIA_PENDING') { @@ -107,10 +109,12 @@ self.addEventListener('push', async (event) => { ); } + //Set the badge with the amount of pending requests + //Only update the badge if the payload confirms they are the admin if ( (payload.notificationType === 'MEDIA_APPROVED' || payload.notificationType === 'MEDIA_DECLINED') && - payload.isAdmin === true + payload.isAdmin ) { if ('setAppBadge' in navigator) { navigator.setAppBadge(payload.pendingRequestsCount); @@ -124,29 +128,27 @@ self.addEventListener('push', async (event) => { } } - event.waitUntil(self.registration.showNotification(payload.subject, options)); + event.waitUntil( + self.registration.showNotification(payload.subject, options) + ); }); -self.addEventListener( - 'notificationclick', - async (event) => { - const notificationData = event.notification.data; +self.addEventListener('notificationclick', (event) => { + const notificationData = event.notification.data; - event.notification.close(); + event.notification.close(); - if (event.action === 'approve') { - fetch(`/api/v1/request/${notificationData.requestId}/approve`, { - method: 'POST', - }); - } else if (event.action === 'decline') { - fetch(`/api/v1/request/${notificationData.requestId}/decline`, { - method: 'POST', - }); - } + if (event.action === 'approve') { + fetch(`/api/v1/request/${notificationData.requestId}/approve`, { + method: 'POST', + }); + } else if (event.action === 'decline') { + fetch(`/api/v1/request/${notificationData.requestId}/decline`, { + method: 'POST', + }); + } - if (notificationData.actionUrl) { - clients.openWindow(notificationData.actionUrl); - } - }, - false -); + if (notificationData.actionUrl) { + clients.openWindow(notificationData.actionUrl); + } +}, false); diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index 38bb09a2..9ad4caa4 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -1178,11 +1178,9 @@ export class MediaRequest { switch (type) { case Notification.MEDIA_APPROVED: event = `${this.is4k ? '4K ' : ''}${mediaType} Request Approved`; - notifyAdmin = false; break; case Notification.MEDIA_DECLINED: event = `${this.is4k ? '4K ' : ''}${mediaType} Request Declined`; - notifyAdmin = false; break; case Notification.MEDIA_PENDING: event = `New ${this.is4k ? '4K ' : ''}${mediaType} Request`; @@ -1211,7 +1209,9 @@ export class MediaRequest { request: this, notifyAdmin, notifySystem, - notifyUser: notifyAdmin ? undefined : this.requestedBy, + notifyUser: Notification.MEDIA_AUTO_REQUESTED + ? this.requestedBy + : undefined, event, subject: `${movie.title}${ movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : '' diff --git a/server/lib/notifications/agents/webpush.ts b/server/lib/notifications/agents/webpush.ts index b9117ebd..3095e52e 100644 --- a/server/lib/notifications/agents/webpush.ts +++ b/server/lib/notifications/agents/webpush.ts @@ -156,6 +156,45 @@ class WebPushAgent const mainUser = await userRepository.findOne({ where: { id: 1 } }); + const webPushNotification = async ( + pushSub: UserPushSubscription, + notificationPayload: Buffer + ) => { + logger.debug('Sending web push notification', { + label: 'Notifications', + recipient: pushSub.user.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await webpush.sendNotification( + { + endpoint: pushSub.endpoint, + keys: { + auth: pushSub.auth, + p256dh: pushSub.p256dh, + }, + }, + notificationPayload + ); + } catch (e) { + logger.error( + 'Error sending web push notification; removing subscription', + { + label: 'Notifications', + recipient: pushSub.user.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + } + ); + + // Failed to send notification so we need to remove the subscription + userPushSubRepository.remove(pushSub); + } + }; + if ( payload.notifyUser && // Check if user has webpush notifications enabled and fallback to true if undefined @@ -196,97 +235,38 @@ class WebPushAgent }) .getMany(); - pushSubs.push(...allSubs); - } + if ( + type === Notification.MEDIA_APPROVED || + type === Notification.MEDIA_DECLINED + ) { + if (mainUser && allSubs.length > 0) { + webpush.setVapidDetails( + `mailto:${mainUser.email}`, + settings.vapidPublic, + settings.vapidPrivate + ); - if ( - type === Notification.MEDIA_APPROVED || - type === Notification.MEDIA_DECLINED - ) { - const users = await userRepository.find(); - const pushSubsForBadge: UserPushSubscription[] = []; + const notificationBadgePayload = Buffer.from( + JSON.stringify( + this.getNotificationPayload(type, { + subject: payload.subject, + notifySystem: false, + notifyAdmin: true, + isAdmin: true, + pendingRequestsCount: payload.pendingRequestsCount, + }) + ), + 'utf-8' + ); - const manageUsers = users.filter( - (user) => - // Check if user has webpush notifications enabled and fallback to true if undefined - // since web push should default to true - (user.settings?.hasNotificationType( - NotificationAgentKey.WEBPUSH, - type - ) ?? - true) && - shouldSendAdminNotification(type, user, payload) - ); - - const allSubs = await userPushSubRepository - .createQueryBuilder('pushSub') - .leftJoinAndSelect('pushSub.user', 'user') - .where('pushSub.userId IN (:users)', { - users: manageUsers - .filter((user) => user.id === 1) - .map((user) => user.id), - }) - .getMany(); - - pushSubsForBadge.push(...allSubs); - - if (mainUser && pushSubsForBadge.length > 0) { - webpush.setVapidDetails( - `mailto:${mainUser.email}`, - settings.vapidPublic, - settings.vapidPrivate - ); - - const notificationPayload = Buffer.from( - JSON.stringify( - this.getNotificationPayload(type, { - subject: 'Set PWA badge', - notifySystem: false, - notifyAdmin: true, - isAdmin: true, - pendingRequestsCount: payload.pendingRequestsCount, + await Promise.all( + allSubs.map(async (sub) => { + webPushNotification(sub, notificationBadgePayload); }) - ), - 'utf-8' - ); - - await Promise.all( - pushSubsForBadge.map(async (sub) => { - logger.debug('Sending web push notification for badge update', { - label: 'Notifications', - recipient: sub.user.displayName, - type: Notification[type], - subject: payload.subject, - }); - - try { - await webpush.sendNotification( - { - endpoint: sub.endpoint, - keys: { - auth: sub.auth, - p256dh: sub.p256dh, - }, - }, - notificationPayload - ); - } catch (e) { - logger.error( - 'Error sending web push notification to update badge; removing subscription', - { - label: 'Notifications', - recipient: sub.user.displayName, - type: Notification[type], - subject: payload.subject, - errorMessage: e.message, - } - ); - - // Failed to send notification so we need to remove the subscription - userPushSubRepository.remove(sub); - } - }) - ); + ); + } + } else { + pushSubs.push(...allSubs); } } @@ -304,39 +284,7 @@ class WebPushAgent await Promise.all( pushSubs.map(async (sub) => { - logger.debug('Sending web push notification', { - label: 'Notifications', - recipient: sub.user.displayName, - type: Notification[type], - subject: payload.subject, - }); - - try { - await webpush.sendNotification( - { - endpoint: sub.endpoint, - keys: { - auth: sub.auth, - p256dh: sub.p256dh, - }, - }, - notificationPayload - ); - } catch (e) { - logger.error( - 'Error sending web push notification; removing subscription', - { - label: 'Notifications', - recipient: sub.user.displayName, - type: Notification[type], - subject: payload.subject, - errorMessage: e.message, - } - ); - - // Failed to send notification so we need to remove the subscription - userPushSubRepository.remove(sub); - } + webPushNotification(sub, notificationPayload); }) ); } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 74453396..d2f8c544 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -114,30 +114,27 @@ const CoreApp: Omit = ({ loadLocaleData(currentLocale).then(setMessages); }, [currentLocale]); - const requestsCount = async () => { - const response = await axios.get('/api/v1/request/count'); - - return response.data; - }; - const { hasPermission } = useUser(); useEffect(() => { - //Set navigator to new variable with any type to prevent setAppBadge unknown error - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let newNavigator: any; + const requestsCount = async () => { + const response = await axios.get('/api/v1/request/count'); + return response.data; + }; + + //Set navigator to new variable with any type to prevent setAppBadge unknown error since it does actually exist on navigator + //eslint-disable-next-line @typescript-eslint/no-explicit-any + const newNavigator: any = navigator; if ('setAppBadge' in navigator) { - newNavigator = navigator; - } - - if ( - !router.pathname.match(/(login|setup|resetpassword)/) && - hasPermission(Permission.ADMIN) - ) { - requestsCount().then((data) => newNavigator.setAppBadge(data.pending)); - } else { - newNavigator.clearAppBadge(); + if ( + !router.pathname.match(/(login|setup|resetpassword)/) && + hasPermission(Permission.ADMIN) + ) { + requestsCount().then((data) => newNavigator.setAppBadge(data.pending)); + } else { + newNavigator.clearAppBadge(); + } } }, [hasPermission, router.pathname]);