Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Pierwsza iteracja — trigger to nowa wiadomość prywatna. Rollout fazowany przez PUSH_USER_WHITELIST w .env: pusta = wszyscy, lista user_id = tylko wymienieni. Ta sama flaga kontroluje widoczność dzwonka w navbarze (context_processor inject_push_visibility). Co jest: - database/migrations/100 — push_subscriptions + notify_push_messages - database.py — PushSubscription model + relacja na User - blueprints/push/ — vapid-public-key, subscribe, unsubscribe, test, pending-url (iOS PWA), CSRF exempt, auto-prune martwych (410/404/403) - static/sw.js — push + notificationclick (z iOS fallback przez /push/pending-url w Redis, TTL 5 min) - static/js/push-client.js — togglePush, iOS detection, ?pushdiag=1 - base.html — dzwonek + wpięcie skryptu gated przez push_bell_visible - message_routes.py — _send_message_push_notifications po emailach - requirements.txt — pywebpush==2.0.3 Kill switch: PUSH_KILL_SWITCH=1 zatrzymuje wszystkie wysyłki. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
65 lines
2.1 KiB
JavaScript
65 lines
2.1 KiB
JavaScript
// Norda Biznes Partner — Service Worker with Web Push + iOS pending-url
|
|
|
|
self.addEventListener('install', function() {
|
|
self.skipWaiting();
|
|
});
|
|
|
|
self.addEventListener('activate', function(event) {
|
|
event.waitUntil(self.clients.claim());
|
|
});
|
|
|
|
self.addEventListener('fetch', function(event) {
|
|
event.respondWith(fetch(event.request));
|
|
});
|
|
|
|
self.addEventListener('push', function(event) {
|
|
let payload = {
|
|
title: 'Norda Biznes',
|
|
body: '',
|
|
url: '/',
|
|
icon: '/static/img/favicon-192.png',
|
|
badge: '/static/img/favicon-192.png',
|
|
};
|
|
try {
|
|
if (event.data) payload = Object.assign(payload, event.data.json());
|
|
} catch (e) {
|
|
if (event.data) payload.body = event.data.text();
|
|
}
|
|
const options = {
|
|
body: payload.body,
|
|
icon: payload.icon,
|
|
badge: payload.badge || '/static/img/favicon-192.png',
|
|
data: { url: payload.url },
|
|
tag: payload.tag || undefined,
|
|
renotify: !!payload.tag,
|
|
};
|
|
event.waitUntil(self.registration.showNotification(payload.title, options));
|
|
});
|
|
|
|
self.addEventListener('notificationclick', function(event) {
|
|
event.notification.close();
|
|
const targetUrl = (event.notification.data && event.notification.data.url) || '/';
|
|
|
|
event.waitUntil((async function() {
|
|
const clientList = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
|
|
for (const client of clientList) {
|
|
if (client.url.indexOf(targetUrl) !== -1 && 'focus' in client) {
|
|
return client.focus();
|
|
}
|
|
}
|
|
// iOS PWA fallback — gdy app zamknięta, zapisz pending URL w Redis
|
|
// żeby PWA po starcie mogła pod niego przeskoczyć.
|
|
try {
|
|
await fetch('/push/pending-url', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ url: targetUrl }),
|
|
credentials: 'include',
|
|
});
|
|
} catch (e) { /* ignore */ }
|
|
if (self.clients.openWindow) {
|
|
return self.clients.openWindow(targetUrl);
|
|
}
|
|
})());
|
|
});
|