initial work on showing notifications through serviceworkers

This commit is contained in:
Henry Jameson 2023-10-25 18:58:33 +03:00
parent 4c11ac9a27
commit 73fbe89a4b
5 changed files with 58 additions and 21 deletions

View File

@ -16,6 +16,7 @@ import backendInteractorService from '../services/backend_interactor_service/bac
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import { applyTheme, applyConfig } from '../services/style_setter/style_setter.js' import { applyTheme, applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js' import FaviconService from '../services/favicon_service/favicon_service.js'
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
let staticInitialResults = null let staticInitialResults = null
@ -344,6 +345,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
store.dispatch('setLayoutHeight', windowHeight()) store.dispatch('setLayoutHeight', windowHeight())
FaviconService.initFaviconService() FaviconService.initFaviconService()
initServiceWorker()
window.addEventListener('focus', () => updateFocus())
const overrides = window.___pleromafe_dev_overrides || {} const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin

View File

@ -2,7 +2,7 @@ import backendInteractorService from '../services/backend_interactor_service/bac
import { windowWidth, windowHeight } from '../services/window_utils/window_utils' import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import oauthApi from '../services/new_api/oauth.js' import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash' import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' import { registerPushNotifications, unregisterPushNotifications } from '../services/sw/sw.js'
// TODO: Unify with mergeOrAdd in statuses.js // TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => { export const mergeOrAdd = (arr, obj, item) => {

View File

@ -1,9 +1,8 @@
import { showDesktopNotification as swDesktopNotification } from '../sw/sw.js'
export const showDesktopNotification = (rootState, desktopNotificationOpts) => { export const showDesktopNotification = (rootState, desktopNotificationOpts) => {
if (!('Notification' in window && window.Notification.permission === 'granted')) return if (!('Notification' in window && window.Notification.permission === 'granted')) return
if (rootState.statuses.notifications.desktopNotificationSilence) { return } if (rootState.statuses.notifications.desktopNotificationSilence) { return }
const desktopNotification = new window.Notification(desktopNotificationOpts.title, desktopNotificationOpts) swDesktopNotification(desktopNotificationOpts)
// Chrome is known for not closing notifications automatically
// according to MDN, anyway.
setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
} }

View File

@ -10,8 +10,12 @@ function urlBase64ToUint8Array (base64String) {
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0))) return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)))
} }
function isSWSupported () {
return 'serviceWorker' in navigator
}
function isPushSupported () { function isPushSupported () {
return 'serviceWorker' in navigator && 'PushManager' in window return 'PushManager' in window
} }
function getOrCreateServiceWorker () { function getOrCreateServiceWorker () {
@ -39,7 +43,7 @@ function unsubscribePush (registration) {
} }
function deleteSubscriptionFromBackEnd (token) { function deleteSubscriptionFromBackEnd (token) {
return window.fetch('/api/v1/push/subscription/', { return fetch('/api/v1/push/subscription/', {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -78,6 +82,20 @@ function sendSubscriptionToBackEnd (subscription, token, notificationVisibility)
return responseData return responseData
}) })
} }
export function initServiceWorker () {
if (!isSWSupported()) return
getOrCreateServiceWorker()
}
export async function showDesktopNotification (content) {
const { active: sw } = await window.navigator.serviceWorker.getRegistration()
sw.postMessage({ type: 'desktopNotification', content })
}
export async function updateFocus () {
const { active: sw } = await window.navigator.serviceWorker.getRegistration()
sw.postMessage({ type: 'updateFocus' })
}
export function registerPushNotifications (isEnabled, vapidPublicKey, token, notificationVisibility) { export function registerPushNotifications (isEnabled, vapidPublicKey, token, notificationVisibility) {
if (isPushSupported()) { if (isPushSupported()) {
@ -98,13 +116,8 @@ export function unregisterPushNotifications (token) {
}) })
.then(([registration, unsubResult]) => { .then(([registration, unsubResult]) => {
if (!unsubResult) { if (!unsubResult) {
console.warn('Push subscription cancellation wasn\'t successful, killing SW anyway...') console.warn('Push subscription cancellation wasn\'t successful')
} }
return registration.unregister().then((result) => {
if (!result) {
console.warn('Failed to kill SW')
}
})
}) })
]).catch((e) => console.warn(`Failed to disable Web Push Notifications: ${e.message}`)) ]).catch((e) => console.warn(`Failed to disable Web Push Notifications: ${e.message}`))
} }

View File

@ -13,9 +13,9 @@ const i18n = createI18n({
messages messages
}) })
function isEnabled () { const state = {
return localForage.getItem('vuex-lz') lastFocused: null,
.then(data => data.config.webPushNotifications) notificationIds: new Set()
} }
function getWindowClients () { function getWindowClients () {
@ -29,11 +29,11 @@ const setLocale = async () => {
i18n.locale = locale i18n.locale = locale
} }
const maybeShowNotification = async (event) => { const showPushNotification = async (event) => {
const enabled = await isEnabled()
const activeClients = await getWindowClients() const activeClients = await getWindowClients()
await setLocale() await setLocale()
if (enabled && (activeClients.length === 0)) { // Only show push notifications if all tabs/windows are closed
if (activeClients.length === 0) {
const data = event.data.json() const data = event.data.json()
const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}` const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}`
@ -48,8 +48,27 @@ const maybeShowNotification = async (event) => {
} }
self.addEventListener('push', async (event) => { self.addEventListener('push', async (event) => {
console.log(event)
if (event.data) { if (event.data) {
event.waitUntil(maybeShowNotification(event)) event.waitUntil(showPushNotification(event))
}
})
self.addEventListener('message', async (event) => {
const { type, content } = event.data
console.log(event)
if (type === 'desktopNotification') {
const { title, body, icon, id } = content
if (state.notificationIds.has(id)) return
state.notificationIds.add(id)
setTimeout(() => state.notificationIds.remove(id), 10000)
self.registration.showNotification('SWTEST: ' + title, { body, icon })
}
if (type === 'updateFocus') {
state.lastFocused = event.source.id
console.log(state)
} }
}) })
@ -59,7 +78,9 @@ self.addEventListener('notificationclick', (event) => {
event.waitUntil(getWindowClients().then((list) => { event.waitUntil(getWindowClients().then((list) => {
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
const client = list[i] const client = list[i]
if (client.url === '/' && 'focus' in client) { return client.focus() } if (state.lastFocused === null || client.id === state.lastFocused) {
if ('focus' in client) return client.focus()
}
} }
if (clients.openWindow) return clients.openWindow('/') if (clients.openWindow) return clients.openWindow('/')