Version 4.7.0

This commit is contained in:
Neo committed 2025-12-19 16:00:35 -08:00
1 parent 517a5e1f70
commit 8ba7d43898
1933 files changed
+65613 -11332

No files matched your search

+48 -171
View File
@@ -188,26 +188,18 @@ self.addEventListener("notificationclick", (e) => {
);
});
self.addEventListener("message", (e) => {
switch (e.data.type) {
case "MEMBER_ID":
idbKeyval.set("member_id", parseInt(e.data.id)).then(() => log(`Member ID is set to ${e.data.id}`));
break;
case "LOGGED_IN":
idbKeyval.set("logged_in", e.data.value).then(() => log(`Logged in status is set to ${e.data.value}`));
break;
case "IS_ACP":
idbKeyval.set("is_acp", e.data.value).then(() => log(`Is Admin status is set to ${e.data.value}`));
break;
}
});
// ------------------------------------------------
// Fetch a resource - use to detect offline state
// ------------------------------------------------
self.addEventListener("fetch", (e) => {
const { request } = e;
// We test /admin/ first and then a fallback for the deprecated custom admin directory name feature
if (request.url.startsWith(BASE_URL + "admin/") || e.currentTarget.location.href.match(/type=admin/)) {
log("In ACP, nothing to do...");
return;
}
if (!request.url.startsWith(BASE_URL) || (request.method === "GET" && request.mode !== "navigate")) {
return;
}
@@ -215,11 +207,7 @@ self.addEventListener("fetch", (e) => {
// We intercept fetch requests in the following situations:
// 1: GET requests where we're offline
// Show a cached offline page instead
// 2: GET requests where have a loggedIn cookie
// This can happen either when we're a guest but have a logged in cookie, or when we're
// logged in but see a browser-cached guest page. We cachebust the url if we're within
// the time when a cached page could exist,
// 3: POST requests when we're a guest
// 2: POST requests when we're a guest
// We need to wrap POST requests in an additional call to get the correct csrf token first
// Situation 1: offline GET requests
@@ -235,88 +223,60 @@ self.addEventListener("fetch", (e) => {
return;
}
let matches = e.currentTarget.location.href.match(/loggedIn=(true|false)/);
const loggedIn = matches[1];
// If we're logged in, we need none of this
if (loggedIn == 'true') {
log("Logged in, nothing to do...");
return;
}
e.respondWith(
new Promise((resolve, reject) => {
// Fetch stored value from indexeddb
idbKeyval
.getMany(["logged_in", "member_id", "is_acp"])
.then(([logged_in, member_id, is_acp]) => {
log(`On navigation, logged_in is ${logged_in}`);
log(`On navigation, is_acp is ${is_acp}`);
log(`ServiceWorker cache busting is ${SW_CACHE_BUST}`);
log(`On navigation, logged_in is ${loggedIn}`);
// Situation 2: GET requests where we have a logged_in cookie
if (SW_CACHE_BUST && request.mode === "navigate" && request.method === "GET" && logged_in && !is_acp) {
// Calculate whether we're outside of the window of the browser/CF having a cached version
const outsideCacheWindow = Math.floor(Date.now() / 1000) - (HTTP_CACHE_DURATION + HTTP_CACHE_BUFFER) > logged_in;
const curRequest = request.clone(); // Clone current request because we can't use it after reading it
let curUrl = curRequest.url;
// Situation 2: POST requests when we're a guest
if (loggedIn == 'false' && request.method === "POST") {
// Clone current request because we can't use it after reading headers later
const curRequest = request.clone();
// If we are, just resolve normally
if (outsideCacheWindow || curUrl.match(/[\?\&]ct=/)) {
resolve(fetch(request));
return;
}
log("Intercepting guest post request");
// If we're within the cache window, add a cache bust to the URL
log(`Within potential cache window, adding cachebust to ${request.url}`);
// Grab the path so we can check in PHP if it's a CP_DIRECTORY URL
let url = new URL(curRequest.url);
let path = url.pathname;
let ts = Math.round(new Date().getTime() / 1000);
curUrl += curUrl.match(/\?/) ? "&ct=" + ts : "?ct=" + ts;
log(`Request URL ${curUrl}`);
// First get the current csrf key from the server
fetch(`${BASE_URL}index.php?app=core&module=system&controller=ajax&do=getCsrfKey&path=${path}`)
.then((response) => response.json())
.then((response) => {
log(`Got new csrf key: ${response.key}`);
const newRequest = new Request(curUrl, {
method: curRequest.method,
headers: curRequest.headers,
mode: "same-origin",
// Create new header object starting with existing headers and adding csrf
const headers = new Headers(curRequest.headers);
headers.set("X-Csrf-Token", response.key);
const newRequest = new Request(curRequest, {
headers,
credentials: curRequest.credentials,
redirect: "manual",
cache: "reload",
referrer: curRequest.referrer,
});
// Send cloned request
resolve(fetch(newRequest));
return;
}
})
.catch((err) => {
console.log(err);
reject(err);
});
// Situation 3: POST requests when we're a guest
if (member_id === 0 && request.method === "POST" && !is_acp) {
// Clone current request because we can't use it after reading headers later
const curRequest = request.clone();
return;
}
log("Intercepting guest post request");
// First get the current csrf key from the server
fetch(`${BASE_URL}index.php?app=core&module=system&controller=ajax&do=getCsrfKey`)
.then((response) => response.json())
.then((response) => {
log(`Got new csrf key: ${response.key}`);
// Create new header object starting with existing headers and adding csrf
const headers = new Headers(curRequest.headers);
headers.set("X-Csrf-Token", response.key);
const newRequest = new Request(curRequest, {
headers,
});
log("Headers:");
for (var pair of headers.entries()) {
log(pair[0] + ": " + pair[1]);
}
// Send cloned request
resolve(fetch(newRequest));
});
return;
}
resolve(fetch(request));
})
.catch((err) => {
reject(err);
});
resolve(fetch(request));
}).catch((err) => {
console.log(err);
})
);
});
@@ -336,8 +296,6 @@ const log = (message) => {
const cacheAssets = () => {
return caches.open(CACHE_NAME).then((cache) => {
log("Updating cache:");
log(CACHED_ASSETS);
return cache.addAll(CACHED_ASSETS);
});
};
@@ -381,85 +339,4 @@ const pluralize = (word, params) => {
});
return word;
};
// https://github.com/jakearchibald/idb-keyval#readme
const idbKeyval = (function (t) {
"use strict";
function e(t) {
return new Promise((e, n) => {
(t.oncomplete = t.onsuccess = () => e(t.result)), (t.onabort = t.onerror = () => n(t.error));
});
}
function n(t, n) {
const r = indexedDB.open(t);
r.onupgradeneeded = () => r.result.createObjectStore(n);
const o = e(r);
return (t, e) => o.then((r) => e(r.transaction(n, t).objectStore(n)));
}
let r;
function o() {
return r || (r = n("keyval-store", "keyval")), r;
}
function u(t, n) {
return t(
"readonly",
(t) => (
(t.openCursor().onsuccess = function () {
this.result && (n(this.result), this.result.continue());
}),
e(t.transaction)
)
);
}
return (
(t.clear = function (t = o()) {
return t("readwrite", (t) => (t.clear(), e(t.transaction)));
}),
(t.createStore = n),
(t.del = function (t, n = o()) {
return n("readwrite", (n) => (n.delete(t), e(n.transaction)));
}),
(t.entries = function (t = o()) {
const e = [];
return u(t, (t) => e.push([t.key, t.value])).then(() => e);
}),
(t.get = function (t, n = o()) {
return n("readonly", (n) => e(n.get(t)));
}),
(t.getMany = function (t, n = o()) {
return n("readonly", (n) => Promise.all(t.map((t) => e(n.get(t)))));
}),
(t.keys = function (t = o()) {
const e = [];
return u(t, (t) => e.push(t.key)).then(() => e);
}),
(t.promisifyRequest = e),
(t.set = function (t, n, r = o()) {
return r("readwrite", (r) => (r.put(n, t), e(r.transaction)));
}),
(t.setMany = function (t, n = o()) {
return n("readwrite", (n) => (t.forEach((t) => n.put(t[1], t[0])), e(n.transaction)));
}),
(t.update = function (t, n, r = o()) {
return r(
"readwrite",
(r) =>
new Promise((o, u) => {
r.get(t).onsuccess = function () {
try {
r.put(n(this.result), t), o(e(r.transaction));
} catch (t) {
u(t);
}
};
})
);
}),
(t.values = function (t = o()) {
const e = [];
return u(t, (t) => e.push(t.value)).then(() => e);
}),
t
);
})({});
};