Always revalidate the app's files, tell open pages when a newer version exists, and log why a session is refused
build-container / build (push) Successful in 1m58s

An open page kept running the old version after a deploy (the single page never reloads), so the new
People screens did not appear. Static files are now sent with no-cache + ETag (304 when unchanged), icons
are cached for a week, /api/config reports a build stamp, and the app shows "A new version is available -
Reload" when it comes back into view or every 10 minutes. The service worker cache name is bumped.

The server also logs, at most once a minute per reason, why it refused a session cookie (unknown account,
turned off, signed out everywhere, bad signature, expired) without printing the cookie.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-20 16:55:44 -04:00
1 parent 2d17ee9939
commit 12a1e50a0e
6 files changed
+57 -9

No files matched your search

+11 -3
View File
@@ -2,6 +2,7 @@
// (Seafile sends no CORS headers, so the browser talks to /sf/* instead).
const http = require('http');
const fs = require('fs');
const crypto = require('crypto');
const path = require('path');
const { Readable } = require('stream');
@@ -75,14 +76,21 @@ function serveStatic(req, res) {
fs.readFile(file, (err, data) => {
if (err) { res.writeHead(404); return res.end('Not found'); }
const headers = { 'content-type': TYPES[path.extname(file)] || 'application/octet-stream' };
if (p === '/sw.js') headers['cache-control'] = 'no-cache';
if (p.startsWith('/icons/')) headers['cache-control'] = 'public, max-age=604800'; // icons and splash screens rarely change
else { // everything else: the browser may keep a copy but must check with us first, so a new version is never missed
headers['cache-control'] = 'no-cache';
headers.etag = `W/"${data.length}-${crypto.createHash('sha1').update(data).digest('hex').slice(0, 16)}"`;
if (req.headers['if-none-match'] === headers.etag) { res.writeHead(304, { etag: headers.etag, 'cache-control': 'no-cache' }); return res.end(); }
}
res.writeHead(200, headers);
res.end(data);
});
}
// Changes whenever the app's files change, so an open page can tell that a newer version is available.
const BUILD = (() => { try { return crypto.createHash('sha1').update(['app.js', 'index.html', 'style.css'].map(f => fs.readFileSync(path.join(PUBLIC, f))).join('')).digest('hex').slice(0, 12); } catch { return String(Date.now()); } })();
// ---- OnlyOffice in-app editing (JWT-signed editor config + save-back callback to Seafile)
const crypto = require('crypto');
const JWT_SECRET = process.env.ONLYOFFICE_JWT_SECRET || '';
const PUBLIC_URL = (process.env.PUBLIC_URL || '').replace(/\/$/, '');
const b64u = b => Buffer.from(b).toString('base64url');
@@ -404,7 +412,7 @@ async function handleRequest(req, res) {
const c = t => users.can(user, t);
res.writeHead(200, { 'content-type': 'application/json' });
return res.end(JSON.stringify({ seafile: c('files') || c('office') ? SEAFILE_URL : '', onlyoffice: c('office') ? ONLYOFFICE_URL : '', audiobooks: c('audio') ? AUDIOBOOKS_URL : '', verse: c('verse') ? VERSE_URL : '',
podcast: c('podcast') ? PODCAST_URL : '', editor: c('office') || c('files'), jwt: !!JWT_SECRET }));
podcast: c('podcast') ? PODCAST_URL : '', editor: c('office') || c('files'), jwt: !!JWT_SECRET, build: BUILD }));
}
if (req.method === 'POST' && req.url === '/api/oo/config') return ooConfig(req, res);
if (pathname === '/api/ddg') return ddgAnswer(req, res);