PWA for iPhone and iPad: PNG icons, splash screens, manifest; stay signed in for 180 days
build-container / build (push) Successful in 1m56s

- iOS ignores SVG touch icons: add 120/152/167/180 apple-touch-icons, 192/512 and maskable manifest icons,
  and 25 splash screens (every iPhone size, iPad portrait+landscape incl. 12.9")
- Icons and splash screens are public so iOS can fetch them without a session
- Manifest: id, scope, categories, shortcuts; iOS standalone polish (no callout, no overscroll)
- Sessions last SESSION_DAYS (default 180) and renew daily while in use
- tools/make-icons.py regenerates the artwork

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-20 15:44:39 -04:00
1 parent 916882e55d
commit 044589686c
47 files changed
+293 -16

No files matched your search

+3 -2
View File
@@ -166,7 +166,7 @@ const unsigned = v => { const i = String(v || '').lastIndexOf('.'); return i > 0
const parseCookies = req => Object.fromEntries((req.headers.cookie || '').split(/; */).filter(Boolean).map(c => { const i = c.indexOf('='); return [c.slice(0, i), decodeURIComponent(c.slice(i + 1))]; }));
const setCookies = (req, res, obj, maxAge = 31536000) => {
const secure = (req.headers['x-forwarded-proto'] || '') === 'https' ? '; Secure' : '';
res.setHeader('Set-Cookie', Object.entries(obj).map(([k, v]) => `${k}=${encodeURIComponent(v)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${v === '' ? 0 : maxAge}${secure}`));
for (const [k, v] of Object.entries(obj)) auth.addCookie(res, `${k}=${encodeURIComponent(v)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${v === '' ? 0 : maxAge}${secure}`);
};
const plexTv = async (path, token, opts = {}) => {
const r = await fetch('https://plex.tv' + path, { ...opts, headers: { ...PLEX_HEADERS, ...(token ? { 'X-Plex-Token': token } : {}) } });
@@ -289,8 +289,9 @@ http.createServer((req, res) => {
if (pathname.startsWith('/cal/feed/')) return cal.feed(req, res); // subscribe feed: protected by its own secret token, not the session
// Everything else needs a passkey session, except the sign-in page and its assets,
// and OnlyOffice's save callback (which carries its own signed token).
if (auth.isAuthed(req)) auth.refresh(req, res);
const open = ['/login', '/login.js', '/webauthn.js', '/style.css', '/icon.svg', '/manifest.webmanifest', '/sw.js', '/api/oo/callback'];
if (!open.includes(pathname) && !auth.isAuthed(req)) {
if (!open.includes(pathname) && !pathname.startsWith('/icons/') && !auth.isAuthed(req)) {
if (/^\/(api|sf|plex)\//.test(pathname)) { res.writeHead(401, { 'content-type': 'application/json', 'x-hub-auth': 'required' }); return res.end('{"error":"Sign in required"}'); }
res.writeHead(302, { location: '/login' }); return res.end();
}