diff options
Diffstat (limited to 'code/api/wwwroot/scripts/base.js')
| -rw-r--r-- | code/api/wwwroot/scripts/base.js | 284 |
1 files changed, 190 insertions, 94 deletions
diff --git a/code/api/wwwroot/scripts/base.js b/code/api/wwwroot/scripts/base.js index e695c04..b03a52a 100644 --- a/code/api/wwwroot/scripts/base.js +++ b/code/api/wwwroot/scripts/base.js @@ -1,108 +1,204 @@ -const session = { - _storageKey: "session_data", - get() { - const stringVal = sessionStorage.getItem(session._storageKey); - if (!stringVal) return undefined; - return JSON.parse(stringVal).value; - }, - set(data) { - sessionStorage.setItem(session._storageKey, JSON.stringify({ - set_at: Temporal.Now.instant().epochSeconds, - value: data - })) - }, - clear() { - sessionStorage.removeItem(session._storageKey) - } +const LOCALE_STORAGE_KEY = "locale"; +const AVAILABLE_LOCALES = ["en", "nb"]; + +class WebStorageBackedCache { + #key; + #staleAfter; + #dataRetriever; + #storage; + + constructor(key, staleAfter, dataRetriever, storage = window.sessionStorage) { + this.#key = key; + this.#staleAfter = staleAfter; + this.#dataRetriever = dataRetriever; + this.#storage = storage; + if (this.#shouldUpdate()) { + (async () => { + this.set(await this.#dataRetriever()); + })(); + } + } + + #shouldUpdate(set_at) { + if (!set_at) { + return true; + } + if (this.#staleAfter === -1) { + return false; + } + return (set_at + this.#staleAfter) < Temporal.Now.instant().epochSeconds; + } + + async get() { + const stringVal = this.#storage.getItem(this.#key); + if (!stringVal) { + return undefined; + } + const data = JSON.parse(stringVal); + if (this.#shouldUpdate(data.set_at)) { + const newData = await this.#dataRetriever(); + this.set(newData); + return newData; + } + return data.value; + } + + get_sync() { + const stringVal = this.#storage.getItem(this.#key); + if (!stringVal) { + return undefined; + } + return JSON.parse(stringVal).value; + } + + set(data) { + this.#storage.setItem(this.#key, JSON.stringify({ + set_at: Temporal.Now.instant().epochSeconds, + value: data + })); + } + + clear() { + this.#storage.removeItem(session._storageKey); + } } const api = { - async get_session_async() { - const res = await fetch("/session", { - credentials: "include" - }) - if (!res.ok) act_if_401(res); - return res; - }, - account: { - login_async(payload) { - return fetch("/account/login", { - method: "post", - body: JSON.stringify(payload), - headers: { - "Content-Type": "application/json;charset=utf-8" - } - }) - }, - async logout_async() { - const res = await fetch("/account/logout", { - credentials: "include" - }) - if (!res.ok) act_if_401(res); - return res; - }, - async delete_account_async() { - const res = await fetch("/account/delete", { - method: "delete", - credentials: "include" - }); - if (!res.ok) act_if_401(res); - return res; - } - } -} + async _fetch(input, init = undefined) { + const r = new Request(input); + r.credentials = "include"; + r.headers.append("x-tz", Temporal.Now.timeZoneId()); + if (init?.method) { + r.method = init.method; + } + if (init?.body) { + r.body = init.body; + } + if (init?.headers) { + for (const [key, value] of Object.entries(init.headers)) { + r.headers.set(key, value); + } + } + const response = await fetch(r); + if (response.status === 401) { + session.clear(); + if (!location.pathname.startsWith("/login")) { + location.href = "/login?reason=401"; + } + } + return response; + }, + get_session_async() { + return api._fetch("/session"); + }, + get_translation_async(locale) { + if (!locale) { + return; + } + return fetch("/translations/" + locale + ".json"); + }, + account: { + login_async(payload) { + return fetch("/account/login", { + method: "post", + body: JSON.stringify(payload), + headers: { + "Content-Type": "application/json;charset=utf-8" + } + }); + }, + logout_async() { + return api._fetch("/account/logout"); + }, + delete_account_async() { + return api._fetch("/account/delete", { + method: "delete", + }); + } + } +}; -function act_if_401(response) { - if (response.status === 401) { - session.clear(); - location.href = "/login?reason=401"; - } +async function logout_and_exit() { + session.clear(); + await api.account.logout_async(); + location.href = "/login?reason=logged_out"; } -async function logout_and_exit() { - session.clear(); - await api.account.logout_async(); - location.href = "/login?reason=logged_out"; +function set_locale_iso(val) { + if (!AVAILABLE_LOCALES.includes(val)) { + console.error(val + " is not a valid locale"); + return; + } + localStorage.setItem(LOCALE_STORAGE_KEY, val); } -function json_or_default_async(response, defaultValue = undefined) { - try { - return response.json(); - } catch { - return new Promise(resolve => resolve(defaultValue)); - } +function current_locale_iso() { + const localeStorageValue = localStorage.getItem(LOCALE_STORAGE_KEY) ?? undefined; + if (localeStorageValue && AVAILABLE_LOCALES.includes(localeStorageValue)) { + return localeStorageValue; + } + return "en"; } -const LOCALE_STORAGE_KEY = "locale"; -const AVAILABLE_LOCALES = ["en", "nb"]; +const session = new WebStorageBackedCache("session_data", 3600, async () => { + const response = await api.get_session_async(); + if (!response.ok) { + return {}; + } + return json_or_default_async(response, {}); +}); -function set_locale_iso(val) { - if (!AVAILABLE_LOCALES.includes(val)) { - console.error(val + " is not a valid locale"); - return; - } - localStorage.setItem(LOCALE_STORAGE_KEY, val); -} +const translationData = new WebStorageBackedCache("translation_" + current_locale_iso(), -1, async () => { + return json_or_default_async(await api.get_translation_async(current_locale_iso()), {}); +}); -function current_locale_iso() { - const localeStorageValue = localStorage.getItem(LOCALE_STORAGE_KEY) ?? undefined; - if (localeStorageValue && AVAILABLE_LOCALES.includes(localeStorageValue)) return localeStorageValue; - return "en"; +function deepFind(obj, path, keySeparator = ".") { + if (!obj) { + return undefined; + } + if (obj[path]) { + return obj[path]; + } + const tokens = path.split(keySeparator); + let current = obj; + for (let i = 0; i < tokens.length;) { + if (!current || typeof current !== "object") { + return undefined; + } + let next; + let nextPath = ""; + for (let j = i; j < tokens.length; ++j) { + if (j !== i) { + nextPath += keySeparator; + } + nextPath += tokens[j]; + next = current[nextPath]; + if (next !== undefined) { + if (["string", "number", "boolean"].indexOf(typeof next) > -1 && j < tokens.length - 1) { + continue; + } + i += j - i + 1; + break; + } + } + current = next; + } + return current; } -const strings = { - anErrorOccured: { - nb: "En feil oppstod", - en: "An error occured", - v() { - return strings.anErrorOccured[current_locale_iso()] - } - }, - tryAgainSoon: { - nb: "Prøv igjen snart", - en: "Try again soon", - v() { - return strings.tryAgainSoon[current_locale_iso()] - } - }, -}
\ No newline at end of file +function t(key, args = undefined) { + if (!key) { + return undefined; + } + const res = deepFind(translationData.get_sync(), key); + if (!args || typeof args !== "object") { + return res; + } + return res.replace( + /{(\w+)}/g, + (placeholderWithDelimiters, placeholderWithoutDelimiters) => + args.hasOwnProperty(placeholderWithoutDelimiters) + ? args[placeholderWithoutDelimiters] + : placeholderWithDelimiters + ); +} |
