99 lines
2.9 KiB
JavaScript
99 lines
2.9 KiB
JavaScript
/**
|
|
* UserMetaCache
|
|
*
|
|
* Singleton in-memory cache for per-user browser metadata (timezone, language).
|
|
* Keyed by numeric user ID in multi-user mode, or the string 'primary' in
|
|
* single-user mode. Populated from the X-Timezone / X-Language request headers
|
|
* on every authenticated HTTP request via the validatedRequest middleware.
|
|
*
|
|
* Because every request repopulates the cache from headers, the data stays
|
|
* fresh within a session without any database writes.
|
|
*
|
|
* Usage:
|
|
* const { UserMetaCache } = require("../userLocale");
|
|
* UserMetaCache.set(userId, { timezone, lang });
|
|
* const { timezone, lang } = UserMetaCache.get(userId);
|
|
*/
|
|
|
|
const DEFAULT_TIMEZONE = "UTC";
|
|
const DEFAULT_LANG = "en";
|
|
|
|
class _UserMetaCache {
|
|
#cache = new Map();
|
|
|
|
/**
|
|
* Validate that a string is a recognised IANA timezone identifier.
|
|
* @param {string} tz
|
|
* @returns {boolean}
|
|
*/
|
|
#isValidTimezone(tz) {
|
|
if (!tz || typeof tz !== "string") return false;
|
|
try {
|
|
Intl.DateTimeFormat(undefined, { timeZone: tz });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Normalise a user ID to a cache key.
|
|
* @param {number|null|undefined} userId
|
|
* @returns {number|'primary'}
|
|
*/
|
|
#key(userId) {
|
|
return userId ?? "primary";
|
|
}
|
|
|
|
/**
|
|
* Store or update locale metadata for a user.
|
|
* Invalid timezone strings are silently ignored (existing/default value kept).
|
|
*
|
|
* @param {number|null} userId - Numeric user ID, or null for single-user mode.
|
|
* @param {{ timezone?: string, lang?: string }} meta
|
|
*/
|
|
set(userId, { timezone, lang } = {}) {
|
|
const key = this.#key(userId);
|
|
const current = this.#cache.get(key) || {};
|
|
this.#cache.set(key, {
|
|
timezone:
|
|
timezone && this.#isValidTimezone(timezone)
|
|
? timezone
|
|
: current.timezone || DEFAULT_TIMEZONE,
|
|
lang: lang || current.lang || DEFAULT_LANG,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Retrieve locale metadata for a user.
|
|
* Returns defaults if nothing has been cached yet.
|
|
*
|
|
* @param {number|null} userId - Numeric user ID, or null for single-user mode.
|
|
* @returns {{ timezone: string, lang: string }}
|
|
*/
|
|
get(userId) {
|
|
return (
|
|
this.#cache.get(this.#key(userId)) || {
|
|
timezone: DEFAULT_TIMEZONE,
|
|
lang: DEFAULT_LANG,
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Read X-Timezone and X-Language from an Express request and store them.
|
|
* Intended to be called from the validatedRequest middleware after auth.
|
|
*
|
|
* @param {import('express').Request} request
|
|
* @param {number|null} userId - null for single-user mode (stored under 'primary')
|
|
*/
|
|
setFromRequest(request, userId = null) {
|
|
const timezone = request.header("X-Timezone");
|
|
const lang = request.header("X-Language");
|
|
if (timezone || lang) this.set(userId, { timezone, lang });
|
|
}
|
|
}
|
|
|
|
const UserMetaCache = new _UserMetaCache();
|
|
module.exports = { UserMetaCache };
|