Add push notification functionality with tests and digest scheduler
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m14s
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m14s
- Implemented push subscription API with tests for subscribing and unsubscribing users. - Created web push notification tests triggered by child actions. - Added digest scheduler to send email digests to users at 9 PM local time. - Developed utility functions for creating and validating digest action tokens. - Integrated web push sender to handle sending notifications to users. - Added service worker for handling push notifications in the frontend. - Created a push opt-in component for user notification preferences. - Implemented tests for the push opt-in component to ensure correct behavior. - Updated frontend services to manage push subscriptions and permissions.
This commit is contained in:
68
frontend/vue-app/public/sw.js
Normal file
68
frontend/vue-app/public/sw.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/* Service Worker for Reward App push notifications */
|
||||
|
||||
self.addEventListener('push', function (event) {
|
||||
if (!event.data) return
|
||||
|
||||
let payload
|
||||
try {
|
||||
payload = event.data.json()
|
||||
} catch (e) {
|
||||
payload = { title: 'Reward App', body: event.data.text() }
|
||||
}
|
||||
|
||||
const title = payload.title || 'Reward App'
|
||||
const options = {
|
||||
body: payload.body || '',
|
||||
data: payload,
|
||||
actions: [
|
||||
{ action: 'approve', title: 'Approve' },
|
||||
{ action: 'deny', title: 'Deny' },
|
||||
],
|
||||
}
|
||||
|
||||
event.waitUntil(self.registration.showNotification(title, options))
|
||||
})
|
||||
|
||||
self.addEventListener('notificationclick', function (event) {
|
||||
event.notification.close()
|
||||
|
||||
const payload = event.notification.data || {}
|
||||
const approveToken = payload.approve_token
|
||||
const denyToken = payload.deny_token
|
||||
const childId = payload.child_id
|
||||
const entityId = payload.entity_id
|
||||
const entityType = payload.entity_type
|
||||
|
||||
if (event.action === 'approve' && approveToken) {
|
||||
event.waitUntil(fetch('/api/digest-action/' + approveToken))
|
||||
return
|
||||
}
|
||||
|
||||
if (event.action === 'deny' && denyToken) {
|
||||
event.waitUntil(fetch('/api/digest-action/' + denyToken))
|
||||
return
|
||||
}
|
||||
|
||||
// Body tap — open or focus the app then navigate to the deep link
|
||||
const deepLinkUrl =
|
||||
childId && entityId && entityType
|
||||
? '/parent/' + childId + '?scrollTo=' + entityId + '&entityType=' + entityType
|
||||
: '/parent'
|
||||
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (clientList) {
|
||||
const sameOriginClients = clientList.filter(function (c) {
|
||||
return new URL(c.url).origin === self.location.origin
|
||||
})
|
||||
|
||||
if (sameOriginClients.length > 0) {
|
||||
const client = sameOriginClients[0]
|
||||
return client.focus().then(function () {
|
||||
return client.navigate(deepLinkUrl)
|
||||
})
|
||||
}
|
||||
|
||||
return clients.openWindow(deepLinkUrl)
|
||||
}),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user