PWA for iPhone and iPad: PNG icons, splash screens, manifest; stay signed in for 180 days
build-container / build (push) Successful in 1m56s
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:
1 parent
916882e55d
commit
044589686c
47 files changed
+293
-16
No files matched your search
@@ -29,7 +29,9 @@ const save = () => writeJson('passkeys.json', store);
|
||||
const SETUP_CODE = process.env.HUB_SETUP_CODE || crypto.randomBytes(4).toString('hex');
|
||||
if (!store.creds.length) console.log(`[auth] No passkeys yet. Setup code: ${SETUP_CODE}`);
|
||||
|
||||
const SESSION_DAYS = 30;
|
||||
// Sessions last SESSION_DAYS and slide forward: using the app renews them, so you only sign in again after that long idle.
|
||||
const SESSION_DAYS = Math.max(1, parseInt(process.env.SESSION_DAYS, 10) || 180);
|
||||
const LIFETIME = SESSION_DAYS * 864e5;
|
||||
const mac = v => crypto.createHmac('sha256', secret).update(v).digest('base64url');
|
||||
const cookies = 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 isHttps = req => (req.headers['x-forwarded-proto'] || '') === 'https' || (process.env.PUBLIC_URL || '').startsWith('https://');
|
||||
@@ -40,9 +42,15 @@ function isAuthed(req) {
|
||||
const want = mac('s' + exp);
|
||||
return sig.length === want.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(want)) && Number(exp) > Date.now();
|
||||
}
|
||||
function startSession(req, res) {
|
||||
const exp = Date.now() + SESSION_DAYS * 864e5;
|
||||
res.setHeader('Set-Cookie', `hub_session=${exp}.${mac('s' + exp)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_DAYS * 86400}${isHttps(req) ? '; Secure' : ''}`);
|
||||
const sessionCookie = req => { const exp = Date.now() + LIFETIME;
|
||||
return `hub_session=${exp}.${mac('s' + exp)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_DAYS * 86400}${isHttps(req) ? '; Secure' : ''}`; };
|
||||
// Add a Set-Cookie without clobbering ones other code already set on this response.
|
||||
const addCookie = (res, c) => { const cur = res.getHeader('Set-Cookie'); res.setHeader('Set-Cookie', cur ? [].concat(cur, c) : c); };
|
||||
function startSession(req, res) { addCookie(res, sessionCookie(req)); }
|
||||
/** Renew the session cookie when it is more than a day old, so an active session never runs out. */
|
||||
function refresh(req, res) {
|
||||
const [exp] = String(cookies(req).hub_session || '').split('.');
|
||||
if (Number(exp) - Date.now() < LIFETIME - 864e5) addCookie(res, sessionCookie(req));
|
||||
}
|
||||
|
||||
const challenges = new Map(); // cid -> { challenge, kind, exp }
|
||||
@@ -100,7 +108,7 @@ async function handle(req, res) {
|
||||
try {
|
||||
if (p === '/api/auth/state') return send(res, 200, { registered: store.creds.length > 0, authed: isAuthed(req) });
|
||||
if (p === '/api/auth/logout' && req.method === 'POST') {
|
||||
res.setHeader('Set-Cookie', 'hub_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0');
|
||||
addCookie(res, 'hub_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0');
|
||||
return send(res, 200, { ok: true });
|
||||
}
|
||||
const body = req.method === 'POST' || req.method === 'DELETE' ? JSON.parse((await readBody(req)) || '{}') : {};
|
||||
@@ -177,4 +185,4 @@ async function handle(req, res) {
|
||||
} catch (e) { send(res, 400, { error: e.message }); }
|
||||
}
|
||||
|
||||
module.exports = { handle, isAuthed, secret };
|
||||
module.exports = { handle, isAuthed, refresh, addCookie, secret, SESSION_DAYS };
|
||||
Reference in new issue
Block a user