mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 05:56:53 +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.
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
/**
|
|
* FE-IOS-001 to FE-IOS-004: the two iPhone and iPad facts shared by file
|
|
* opening and Web Push.
|
|
*/
|
|
import { describe, it, expect, afterEach } from 'vitest'
|
|
import { isIosDevice, isIosStandalone } from './iosDevice'
|
|
|
|
function define(name: 'userAgent' | 'maxTouchPoints' | 'standalone', value: unknown) {
|
|
Object.defineProperty(navigator, name, { configurable: true, get: () => value })
|
|
}
|
|
|
|
afterEach(() => {
|
|
for (const name of ['userAgent', 'maxTouchPoints', 'standalone']) {
|
|
delete (navigator as unknown as Record<string, unknown>)[name]
|
|
}
|
|
})
|
|
|
|
describe('isIosStandalone', () => {
|
|
it("FE-IOS-001: is true only for Apple's own home-screen flag", () => {
|
|
expect(isIosStandalone()).toBe(false)
|
|
define('standalone', false)
|
|
expect(isIosStandalone()).toBe(false)
|
|
define('standalone', true)
|
|
expect(isIosStandalone()).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('isIosDevice', () => {
|
|
it('FE-IOS-002: recognises iPhone, iPod and iPad by their user agent', () => {
|
|
for (const device of ['iPhone', 'iPod touch', 'iPad']) {
|
|
define('userAgent', `Mozilla/5.0 (${device}; CPU OS 18_0 like Mac OS X) AppleWebKit/605.1.15`)
|
|
expect(isIosDevice()).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('FE-IOS-003: an iPad asking for the desktop site is a Mac with a touch screen', () => {
|
|
define('userAgent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15')
|
|
define('maxTouchPoints', 5)
|
|
expect(isIosDevice()).toBe(true)
|
|
define('maxTouchPoints', 0)
|
|
expect(isIosDevice()).toBe(false)
|
|
})
|
|
|
|
it('FE-IOS-004: Android and Windows are not iOS', () => {
|
|
define('maxTouchPoints', 5)
|
|
define('userAgent', 'Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 Chrome/140.0 Mobile Safari/537.36')
|
|
expect(isIosDevice()).toBe(false)
|
|
define('userAgent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/140.0 Safari/537.36')
|
|
expect(isIosDevice()).toBe(false)
|
|
})
|
|
})
|