Add activity push notifications and registration patches

This commit is contained in:
2026-09-15 08:31:45 +02:00
parent 7c801dfddd
commit 55174684af
38 changed files with 1096 additions and 42 deletions
+6
View File
@@ -609,3 +609,9 @@ h1, .page-title h1 {
.bug-success { color:#bbf7d0; }
.bug-report-form button:disabled { opacity:.6; cursor:wait; }
@media(max-width:600px) { .bug-report-options { grid-template-columns:1fr; } }
.notification-preferences { display: grid; gap: 12px; margin: 16px 0; }
.notification-preferences label { display: flex; gap: 10px; align-items: center; }
.notification-preferences input[type="checkbox"] { width: auto; }
.patch.earned.patch-alpha_tester { border: 2px solid #e7bc58; box-shadow: 0 0 12px #e7bc5855; background: #211b0f; }
.patch.earned.patch-beta_tester { border: 1px solid #a7b4c5; }
.patch.earned.patch-early_bird { border: 1px solid #b4835e; }
+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 210" role="img" aria-label="Alpha Tester">
<path fill="#151313" stroke="#e8bf63" stroke-width="5" d="M90 6 170 36v99q-12 45-80 69-68-24-80-69V36Z"/>
<path fill="none" stroke="#a88236" stroke-width="1.5" stroke-dasharray="3 3" d="M90 15 161 43v89q-10 39-71 62-61-23-71-62V43Z"/>
<path fill="#e8bf63" d="m57 37 16 10 17-20 17 20 16-10-7 27H64Z"/>
<path fill="#e8bf63" d="m90 73 31 65h-17l-5-13H81l-5 13H59Zm0 26-5 14h10Z"/>
<text x="90" y="157" fill="#f5dfab" text-anchor="middle" font-family="sans-serif" font-weight="700" font-size="18" letter-spacing="2">ALPHA</text>
<text x="90" y="177" fill="#d4b97a" text-anchor="middle" font-family="sans-serif" font-size="11" letter-spacing="3">TESTER</text>
</svg>

After

Width:  |  Height:  |  Size: 775 B

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 210" role="img" aria-label="Early Bird">
<path fill="#171514" stroke="#b98b66" stroke-width="4" d="M90 9 167 38v95q-11 43-77 67-66-24-77-67V38Z"/>
<path fill="none" stroke="#947153" stroke-dasharray="3 3" d="M90 18 158 45v85q-10 38-68 60-58-22-68-60V45Z"/>
<path fill="#c8996f" d="m40 65 44 32 33-29 15 11 16 3-15 9-12 25-33 18-26-14 21-4-28-14 26 1Z"/>
<circle cx="125" cy="80" r="2.5" fill="#171514"/>
<text x="90" y="155" fill="#e2be9f" text-anchor="middle" font-family="sans-serif" font-weight="700" font-size="17" letter-spacing="1">EARLY BIRD</text>
<text x="90" y="177" fill="#b98b66" text-anchor="middle" font-family="sans-serif" font-size="9" letter-spacing="2">METALCIRCLE</text>
</svg>

After

Width:  |  Height:  |  Size: 766 B

+34 -3
View File
@@ -15,6 +15,17 @@
let sendQueue = Promise.resolve();
let panel;
async function notificationTarget(notification) {
const data = notification?.data || {};
if (!/^[a-f0-9-]{36}$/.test(data.notification_id || '') || !/^[a-f0-9]{64}$/.test(data.session_tag || '')) return null;
const response = await fetch('/api/push/session', {credentials: 'same-origin', cache: 'no-store'});
if (!response.ok || stopped) return null;
const session = await response.json();
const native = await device.getInfo();
if (!session.authenticated || session.session_tag !== data.session_tag || native.binding !== data.session_tag) return null;
return '/notifications/' + data.notification_id;
}
function notice(message, offerPermission = false) {
if (!panel) {
panel = document.createElement('section');
@@ -130,9 +141,29 @@
});
}),
push.addListener('registrationError', () => { if (!stopped) notice(texts.failed); }),
push.addListener('pushNotificationActionPerformed', () => {
// Do not navigate to arbitrary URLs supplied by a notification payload.
location.assign('/');
push.addListener('pushNotificationActionPerformed', async event => {
try {
const target = await notificationTarget(event.notification);
if (target) location.assign(target);
} catch (_) { /* A tap never bypasses current-session authorization. */ }
}),
push.addListener('pushNotificationReceived', async notification => {
try {
const target = await notificationTarget(notification);
if (!target) return;
document.getElementById('push-in-app-notice')?.remove();
const banner = document.createElement('section');
banner.id = 'push-in-app-notice';
banner.className = 'native-push-panel';
banner.setAttribute('role', 'status');
const link = document.createElement('a');
link.className = 'button';
link.href = target;
// Generic localized text; never insert remote HTML or private message content.
link.textContent = texts.openNotification;
banner.append(link);
(document.querySelector('main') || document.body).prepend(banner);
} catch (_) { /* Push reception cannot interrupt use of the app. */ }
})
]).then(() => {
synchronize();