mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
650451bdf7
A fourth built-in channel next to email, webhook and ntfy. Browsers register per device, the server encrypts per RFC 8291 and signs per RFC 8292 with node:crypto and sends only to known push services through the SSRF guard. The event matrix decides per category what arrives by push; admins switch the channel on under Notifications. VAPID keys come from the environment or are generated once, only while neither stored row exists, and kept encrypted. A pair that does not decrypt or match is never overwritten and never swapped for another one: push stays off with an error in the log and every device stays registered until the key is fixed. The subject is VAPID_SUBJECT, then an https APP_URL, then the project URL. Only chat messages collapse into one notification per trip, and a replacement still alerts. A 403 removes a device only after three failures in a row; 404 and 410 remove it at once. Password change, reset and an admin setting a password drop the user's devices with the other credentials. The request log redacts subscriptions and endpoints, boot errors print *** for every secret variable, and logout forgets the device with a plain fetch so an expired session cannot redirect midway. Addresses discussion #894.
48 lines
1.8 KiB
TypeScript
48 lines
1.8 KiB
TypeScript
import React from 'react'
|
|
import { useTranslation } from '../../i18n'
|
|
import { useWebPush } from '../../hooks/useWebPush'
|
|
|
|
/**
|
|
* The "Push notifications on this device" card of the Notifications tab. Only
|
|
* markup: the logic is `useWebPush`, shared with the phone's `MWebPushCard`.
|
|
* The tab renders it while the admin has the push channel switched on.
|
|
*/
|
|
export default function WebPushCard(): React.ReactElement {
|
|
const { t } = useTranslation()
|
|
const push = useWebPush()
|
|
|
|
return (
|
|
<div className="mb-4 rounded-lg border border-edge bg-surface-secondary p-3">
|
|
<p className="mb-1 text-caption font-semibold text-content-secondary">{t('settings.webPush.title')}</p>
|
|
<p className="mb-2 text-caption text-content-faint">{t('settings.webPush.hint')}</p>
|
|
{push.stateText && (
|
|
<p role="status" className="mb-2 text-caption text-content-secondary">
|
|
{push.stateText}
|
|
</p>
|
|
)}
|
|
{push.canSwitch && (
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={push.toggle}
|
|
disabled={push.busy}
|
|
className={`rounded-md px-3 py-1.5 text-caption disabled:cursor-not-allowed disabled:opacity-60 ${
|
|
push.on ? 'border border-edge bg-transparent text-content-secondary' : 'bg-accent text-accent-text'
|
|
}`}
|
|
>
|
|
{push.switchLabel}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={push.sendTest}
|
|
disabled={!push.canTest}
|
|
className="rounded-md border border-edge bg-transparent px-3 py-1.5 text-caption text-content-secondary disabled:cursor-not-allowed disabled:opacity-50"
|
|
>
|
|
{t('settings.notificationPreferences.sendTest')}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|