fix #3199: firefox using cached images instead of new images. (#4743) * Fixed: https://github.com/SillyTavern/SillyTavern/issues/3199#issue-2745917391 * Wrap header set in isFirefox * Only invalidate Firefox caches. Also invalidate thumbnail caches. * Skip `mime.lookup` on non-Firefox browsers. * Improve param comments --------- Co-authored-by: user <user@exmaple.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

105b436a66df8d2520a7e1e659b4fb7e7249a98b

DeclineThyself <FallenHaze@tutamail.com>

Signed
4 files changed, +36 -2Ignore whitespace
src/endpoints/thumbnails.js+7 -1
@@ -8,7 +8,7 @@ import sanitize from 'sanitize-filename';
88import { Jimp, JimpMime } from '../jimp.js';
99import { sync as writeFileAtomicSync } from 'write-file-atomic';
1010
1111import { getConfigValue, invalidateFirefoxCache } from '../util.js';
1212
1313const thumbnailsEnabled = !!getConfigValue('thumbnails.enabled', true, 'boolean');
1414const quality = Math.min(100, Math.max(1, parseInt(getConfigValue('thumbnails.quality', 95, 'number'))));
@@ -222,6 +222,9 @@ router.get('/', async function (request, response) {
222222 const contentType = mime.lookup(pathToOriginalFile) || 'image/png';
223223 const originalFile = await fsPromises.readFile(pathToOriginalFile);
224224 response.setHeader('Content-Type', contentType);
225+
226+ invalidateFirefoxCache(pathToOriginalFile, request, response);
227+
225228 return response.send(originalFile);
226229 }
227230
@@ -238,6 +241,9 @@ router.get('/', async function (request, response) {
238241 const contentType = mime.lookup(pathToCachedFile) || 'image/jpeg';
239242 const cachedFile = await fsPromises.readFile(pathToCachedFile);
240243 response.setHeader('Content-Type', contentType);
244+
245+ invalidateFirefoxCache(file, request, response);
246+
241247 return response.send(cachedFile);
242248 } catch (error) {
243249 console.error('Failed getting thumbnail', error);
src/express-common.js+10 -0
@@ -40,3 +40,13 @@ export function getRealIpFromHeader(req) {
4040
4141 return getIpFromRequest(req);
4242}
43+
44+/**
45+ * Checks if the request is coming from a Firefox browser.
46+ * @param {import('express').Request} req Request object
47+ * @returns {boolean} True if the request is from Firefox, false otherwise.
48+ */
49+export function isFirefox(req) {
50+ const userAgent = req.headers['user-agent'] || '';
51+ return /firefox/i.test(userAgent);
52+}
src/users.js+3 -1
@@ -16,7 +16,7 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic';
1616import sanitize from 'sanitize-filename';
1717
1818import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE, UPLOADS_DIRECTORY } from './constants.js';
1919import { getConfigValue, color, delay, generateTimestamp, invalidateFirefoxCache } from './util.js';
2020import { readSecret, writeSecret } from './endpoints/secrets.js';
2121import { getContentOfType } from './endpoints/content-manager.js';
2222import { serverDirectory } from './server-directory.js';
@@ -952,6 +952,8 @@ function createRouteHandler(directoryFn) {
952952 if (!exists) {
953953 return res.sendStatus(404);
954954 }
955+
956+ invalidateFirefoxCache(filePath, req, res);
955957 return res.sendFile(filePath, { root: directory });
956958 } catch (error) {
957959 return res.sendStatus(500);
src/util.js+16 -0
@@ -19,6 +19,7 @@ import chalk from 'chalk';
1919import bytes from 'bytes';
2020import { LOG_LEVELS, CHAT_COMPLETION_SOURCES } from './constants.js';
2121import { serverDirectory } from './server-directory.js';
22+import { isFirefox } from './express-common.js';
2223
2324/**
2425 * Parsed config object.
@@ -1295,3 +1296,18 @@ export function flattenSchema(schema, api) {
12951296 delete flattenedSchema.$schema;
12961297 return flattenedSchema;
12971298}
1299+
1300+/**
1301+ * If the file is an image, and the request's user agent matches Firefox, then the response's headers are set to invalidate the cache.
1302+ * Without this, Firefox ignores updated images even after a refresh.
1303+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control
1304+ * @param {string} file File path
1305+ * @param {import('express').Request} request Request object
1306+ * @param {import('express').Response} response Response object
1307+ */
1308+export function invalidateFirefoxCache(file, request, response) {
1309+ const mimeType = isFirefox(request) && mime.lookup(file);
1310+ if (mimeType && mimeType.startsWith('image/')) {
1311+ response.setHeader('Cache-Control', 'must-understand, no-store');
1312+ }
1313+}