stackchain-dashboard/frontend/push-notifications.js
timmy 5e6328a8d0
All checks were successful
CI / lint (pull_request) Successful in 1m34s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped
feat: send mobile deadline reminders (Closes #709)
2026-08-13 05:00:32 +00:00

115 lines
4.7 KiB
JavaScript

(function(root, factory) {
if (typeof module === 'object' && module.exports) module.exports = factory;
else root.createPushNotifications = factory;
})(typeof self !== 'undefined' ? self : this, function createPushNotifications({
control, status, deadlineControl, deadlineStatus, notification, serviceWorker, fetchJson,
}) {
let configuration = null;
function applicationServerKey(value) {
const padded = value.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - value.length % 4) % 4);
if (typeof atob === 'function') {
return Uint8Array.from(atob(padded), character => character.charCodeAt(0));
}
return Uint8Array.from(Buffer.from(padded, 'base64'));
}
async function disable() {
const registration = await serviceWorker.ready;
const subscription = await registration.pushManager.getSubscription();
await fetchJson('api/v1/push-subscription', {method:'DELETE'});
await subscription?.unsubscribe?.();
control.checked = false;
if (deadlineControl) deadlineControl.checked = false;
status.textContent = 'New update notifications are off for this device.';
if (deadlineStatus) deadlineStatus.textContent = 'Deadline reminders are off for this device.';
}
async function enable() {
const permission = await notification.requestPermission();
if (permission !== 'granted') {
control.checked = false;
status.textContent = 'Notifications are blocked. Allow them in your browser settings to enable updates.';
return;
}
const registration = await serviceWorker.ready;
let subscription = await registration.pushManager.getSubscription();
if (!subscription) {
subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey(configuration.public_key),
});
}
await fetchJson('api/v1/push-subscription', {
method:'PUT',
headers:{'Content-Type':'application/json'},
body:JSON.stringify(subscription.toJSON()),
});
control.checked = true;
status.textContent = 'New update notifications enabled for this device.';
}
async function change() {
control.disabled = true;
try {
if (control.checked) await enable();
else await disable();
} catch (error) {
control.checked = !control.checked;
status.textContent = 'Could not change update notifications. Check your connection and try again.';
} finally {
control.disabled = false;
}
}
async function changeDeadline() {
deadlineControl.disabled = true;
try {
const registration = await serviceWorker.ready;
const subscription = await registration.pushManager.getSubscription();
if (deadlineControl.checked && !subscription) {
deadlineControl.checked = false;
deadlineStatus.textContent = 'Enable new update notifications first.';
return;
}
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
await fetchJson('api/v1/push-subscription/deadlines', {
method:'PUT',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({enabled:deadlineControl.checked, timezone, reminder_hour:9}),
});
deadlineStatus.textContent = deadlineControl.checked
? 'Deadline reminders enabled for 9:00 local time.'
: 'Deadline reminders are off for this device.';
} catch (error) {
deadlineControl.checked = !deadlineControl.checked;
deadlineStatus.textContent = 'Could not change deadline reminders. Check your connection and try again.';
} finally {
deadlineControl.disabled = false;
}
}
async function init() {
if (!control || !notification || !serviceWorker) return;
control.addEventListener('change', change);
deadlineControl?.addEventListener('change', changeDeadline);
configuration = await fetchJson('api/v1/push-subscription');
if (!configuration.available) {
control.disabled = true;
if (deadlineControl) deadlineControl.disabled = true;
status.textContent = 'New update notifications are not available on this server.';
return;
}
control.checked = Boolean(configuration.subscribed);
if (deadlineControl) deadlineControl.checked = Boolean(configuration.deadline_enabled);
status.textContent = configuration.subscribed
? 'New update notifications enabled for this device.'
: 'New update notifications are off for this device.';
if (deadlineStatus) deadlineStatus.textContent = configuration.deadline_enabled
? `Deadline reminders enabled for ${configuration.reminder_hour}:00 local time.`
: 'Deadline reminders are off for this device.';
}
return {init, change, changeDeadline};
});