All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m59s
- Implemented `trigger_chore_expiry.py` script to manually trigger chore expiry notifications for users via the admin API. - Developed `chore_expiry_notification_scheduler.py` to handle the logic for sending notifications for chores expiring within the next 75 minutes. - Created utility functions in `schedule_utils.py` to determine scheduling and deadlines for chores. - Added comprehensive tests for the chore expiry notification system in `test_chore_expiry_notification_scheduler.py`, covering various scenarios including scheduled chores, confirmations, and user settings.
84 lines
2.3 KiB
JavaScript
84 lines
2.3 KiB
JavaScript
/* Service Worker for Reward App push notifications */
|
|
|
|
/* Activate immediately — no waiting for old tabs to close */
|
|
self.addEventListener('install', function (event) {
|
|
event.waitUntil(self.skipWaiting())
|
|
})
|
|
|
|
self.addEventListener('activate', function (event) {
|
|
event.waitUntil(self.clients.claim())
|
|
})
|
|
|
|
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'
|
|
// Expiry-warning notifications are informational only — no action buttons
|
|
const actions =
|
|
payload.type === 'chore_expiring_soon'
|
|
? []
|
|
: [
|
|
{ action: 'approve', title: 'Approve' },
|
|
{ action: 'deny', title: 'Deny' },
|
|
]
|
|
const options = {
|
|
body: payload.body || '',
|
|
icon: '/icons/icon-192x192.png',
|
|
data: payload,
|
|
actions,
|
|
}
|
|
|
|
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)
|
|
}),
|
|
)
|
|
})
|