feat: push notifikace pro připomínku výběru oběda
All checks were successful
ci/woodpecker/push/workflow Pipeline was successful

This commit is contained in:
2026-03-04 13:33:58 +01:00
parent b77914404b
commit d62f6c1f5a
10 changed files with 439 additions and 6 deletions

View File

@@ -0,0 +1,108 @@
import { getToken } from '../Utils';
/** Převede base64url VAPID klíč na Uint8Array pro PushManager. */
function urlBase64ToUint8Array(base64String: string): Uint8Array {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
const rawData = atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
/** Helper pro autorizované API volání na push endpointy. */
async function pushApiFetch(path: string, options: RequestInit = {}): Promise<Response> {
const token = getToken();
return fetch(`/api/notifications/push${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
});
}
/**
* Zaregistruje service worker, přihlásí se k push notifikacím
* a odešle subscription na server.
*/
export async function subscribeToPush(reminderTime: string): Promise<boolean> {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
console.warn('Push notifikace nejsou v tomto prohlížeči podporovány');
return false;
}
try {
// Registrace service workeru
const registration = await navigator.serviceWorker.register('/sw.js');
await navigator.serviceWorker.ready;
// Vyžádání oprávnění
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
console.warn('Push notifikace: oprávnění zamítnuto');
return false;
}
// Získání VAPID veřejného klíče ze serveru
const vapidResponse = await pushApiFetch('/vapidKey');
if (!vapidResponse.ok) {
console.error('Push notifikace: nepodařilo se získat VAPID klíč');
return false;
}
const { key: vapidPublicKey } = await vapidResponse.json();
// Přihlášení k push
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) as BufferSource,
});
// Odeslání subscription na server
const response = await pushApiFetch('/subscribe', {
method: 'POST',
body: JSON.stringify({
subscription: subscription.toJSON(),
reminderTime,
}),
});
if (!response.ok) {
console.error('Push notifikace: nepodařilo se odeslat subscription na server');
return false;
}
console.log('Push notifikace: úspěšně přihlášeno k připomínkám v', reminderTime);
return true;
} catch (error) {
console.error('Push notifikace: chyba při registraci', error);
return false;
}
}
/**
* Odhlásí se z push notifikací a informuje server.
*/
export async function unsubscribeFromPush(): Promise<void> {
if (!('serviceWorker' in navigator)) {
return;
}
try {
const registration = await navigator.serviceWorker.getRegistration('/sw.js');
if (registration) {
const subscription = await registration.pushManager.getSubscription();
if (subscription) {
await subscription.unsubscribe();
}
}
await pushApiFetch('/unsubscribe', { method: 'POST' });
console.log('Push notifikace: úspěšně odhlášeno z připomínek');
} catch (error) {
console.error('Push notifikace: chyba při odhlášení', error);
}
}