Version 4.6.0
This commit is contained in:
1 parent
f79dcf067a
commit
517a5e1f70
2036 files changed
+110041
-26162
No files matched your search
@@ -84,6 +84,13 @@
|
||||
|
||||
var height = ips.utils.getObjHeight( _div );
|
||||
|
||||
if( !height )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ips.utils.log("Determined height as " + height + ' for embed ID ' + _embedId );
|
||||
|
||||
// If we HAVEN'T started posting our size
|
||||
if( !_posting ){
|
||||
if( height == currentSize ){
|
||||
@@ -158,7 +165,7 @@
|
||||
|
||||
function windowMessage (e) {
|
||||
if( e.origin !== _origin ){
|
||||
ips.utils.log( e.origin + 'does not equal' + _origin );
|
||||
ips.utils.log( e.origin + ' does not equal ' + _origin );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
define('REPORT_EXCEPTIONS', TRUE);
|
||||
require_once '../../../../init.php';
|
||||
|
||||
\IPS\Output::i()->pageCaching = FALSE;
|
||||
|
||||
$langId = \intval( \IPS\Request::i()->langId );
|
||||
$_lang = array();
|
||||
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
/**
|
||||
* Invision Community
|
||||
* (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
|
||||
*
|
||||
* Invision Community service worker
|
||||
*/
|
||||
|
||||
// ------------------------------------------------
|
||||
// Install/activate SW events
|
||||
// ------------------------------------------------
|
||||
self.addEventListener("install", (e) => {
|
||||
console.log("Service worker installed");
|
||||
e.waitUntil(
|
||||
CACHED_ASSETS.length
|
||||
? cacheAssets().then(() => {
|
||||
return self.skipWaiting();
|
||||
})
|
||||
: self.skipWaiting()
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (e) => {
|
||||
const cacheAllowList = [CACHE_NAME];
|
||||
|
||||
// Clean up any caches that don't match our current cache key
|
||||
// Ensure we don't have outdated styles/assets/etc.
|
||||
e.waitUntil(
|
||||
Promise.all([
|
||||
caches.keys().then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames.map((cacheName) => {
|
||||
if (cacheAllowList.indexOf(cacheName) === -1) {
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
}),
|
||||
self.clients.claim(),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
const returnDefaultNotification = () => {
|
||||
return self.registration.showNotification(DEFAULT_NOTIFICATION_TITLE, {
|
||||
body: DEFAULT_NOTIFICATION_BODY,
|
||||
icon: NOTIFICATION_ICON,
|
||||
data: {
|
||||
url: BASE_URL,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// ------------------------------------------------
|
||||
// Push notification event handler
|
||||
// ------------------------------------------------
|
||||
self.addEventListener("push", (e) => {
|
||||
// A couple of basic sanity checks
|
||||
if (!e.data) {
|
||||
console.log("Invalid notification data");
|
||||
return; // Invalid notification data
|
||||
}
|
||||
|
||||
const pingData = e.data.json();
|
||||
const { id } = pingData;
|
||||
|
||||
// We don't send the notification data in the push, otherwise we run into issues whereby
|
||||
// a user could have logged out but will still receive notification unintentionally.
|
||||
// Instead, we'll receive an ID in the push, then we'll ping the server to get
|
||||
// the actual content (and run our usual authorization checks)
|
||||
|
||||
const promiseChain = fetch(`${BASE_URL}index.php?app=core&module=system&controller=notifications&do=fetchNotification&id=${id}`, {
|
||||
method: "POST",
|
||||
credentials: "include", // Must send cookies so we can check auth
|
||||
})
|
||||
.then((response) => {
|
||||
// Fetch went wrong - but we must show a notification, so just send a generic message
|
||||
if (!response.ok) {
|
||||
throw new Error("Invalid response");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
// The server returned an error - but we must show a notification, so just send a generic message
|
||||
if (data.error) {
|
||||
throw new Error("Server error");
|
||||
}
|
||||
|
||||
const { body, url, grouped, groupedTitle, groupedUrl, icon, image } = data;
|
||||
let { title } = data;
|
||||
let tag;
|
||||
|
||||
if (data.tag) {
|
||||
tag = data.tag.substr(0, 30);
|
||||
}
|
||||
|
||||
let options = {
|
||||
body,
|
||||
icon: icon ? icon : NOTIFICATION_ICON,
|
||||
image: image ? image : null,
|
||||
data: {
|
||||
url,
|
||||
},
|
||||
};
|
||||
|
||||
if (!tag || !grouped) {
|
||||
// This notification has no tag or grouped lang, so just send it
|
||||
// as a one-off thing
|
||||
return self.registration.showNotification(title, options);
|
||||
} else {
|
||||
return self.registration.getNotifications({ tag }).then((notifications) => {
|
||||
// Tagged notifications require some additional data
|
||||
options = {
|
||||
...options,
|
||||
tag,
|
||||
renotify: true, // Required, otherwise browsers won't renotify for this tag
|
||||
data: {
|
||||
...options.data,
|
||||
unseenCount: 1,
|
||||
},
|
||||
};
|
||||
|
||||
if (notifications.length) {
|
||||
try {
|
||||
// Get the most recent notification and see if it has a count
|
||||
// If it does, increase the unseenCount by one and update the message
|
||||
// With this approach we'll always have a reference to the previous notification's count
|
||||
// and can simply increase and fire a new notification to tell the user
|
||||
const lastWithTag = notifications[notifications.length - 1];
|
||||
|
||||
if (lastWithTag.data && typeof lastWithTag.data.unseenCount !== "undefined") {
|
||||
const unseenCount = lastWithTag.data.unseenCount + 1;
|
||||
|
||||
options.data.unseenCount = unseenCount;
|
||||
options.body = pluralize(grouped.replace("{count}", unseenCount), unseenCount);
|
||||
|
||||
if (groupedUrl) {
|
||||
options.data.url = groupedUrl ? groupedUrl : options.data.url;
|
||||
}
|
||||
|
||||
if (groupedTitle) {
|
||||
title = pluralize(groupedTitle.replace("{count}", unseenCount), unseenCount);
|
||||
}
|
||||
|
||||
lastWithTag.close();
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
return self.registration.showNotification(title, options);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
// The server returned an error - but we must show a notification, so just send a generic message
|
||||
return returnDefaultNotification();
|
||||
});
|
||||
|
||||
e.waitUntil(promiseChain);
|
||||
});
|
||||
|
||||
// ------------------------------------------------
|
||||
// Notification click event handler
|
||||
// ------------------------------------------------
|
||||
self.addEventListener("notificationclick", (e) => {
|
||||
const { data } = e.notification;
|
||||
|
||||
e.waitUntil(
|
||||
self.clients.matchAll().then((clients) => {
|
||||
console.log(clients);
|
||||
|
||||
// If we already have the site open, use that window
|
||||
if (clients.length > 0 && "navigate" in clients[0]) {
|
||||
if (data.url) {
|
||||
clients[0].navigate(data.url);
|
||||
} else {
|
||||
clients[0].navigate(BASE_URL);
|
||||
}
|
||||
|
||||
return clients[0].focus();
|
||||
}
|
||||
|
||||
// otherwise open a new window
|
||||
return self.clients.openWindow(data.url ? data.url : BASE_URL);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
if (!request.url.startsWith(BASE_URL) || (request.method === "GET" && request.mode !== "navigate")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
// We need to wrap POST requests in an additional call to get the correct csrf token first
|
||||
|
||||
// Situation 1: offline GET requests
|
||||
if (request.mode === "navigate" && request.method === "GET" && !navigator.onLine) {
|
||||
e.respondWith(
|
||||
fetch(request).catch((err) => {
|
||||
return caches.open(CACHE_NAME).then((cache) => {
|
||||
console.log(`Browser appears to be offline: ${request.url}`);
|
||||
return cache.match(OFFLINE_URL);
|
||||
});
|
||||
})
|
||||
);
|
||||
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}`);
|
||||
|
||||
// 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;
|
||||
|
||||
// If we are, just resolve normally
|
||||
if (outsideCacheWindow || curUrl.match(/[\?\&]ct=/)) {
|
||||
resolve(fetch(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're within the cache window, add a cache bust to the URL
|
||||
log(`Within potential cache window, adding cachebust to ${request.url}`);
|
||||
|
||||
let ts = Math.round(new Date().getTime() / 1000);
|
||||
curUrl += curUrl.match(/\?/) ? "&ct=" + ts : "?ct=" + ts;
|
||||
log(`Request URL ${curUrl}`);
|
||||
|
||||
const newRequest = new Request(curUrl, {
|
||||
method: curRequest.method,
|
||||
headers: curRequest.headers,
|
||||
mode: "same-origin",
|
||||
credentials: curRequest.credentials,
|
||||
redirect: "manual",
|
||||
cache: "reload",
|
||||
referrer: curRequest.referrer,
|
||||
});
|
||||
|
||||
resolve(fetch(newRequest));
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
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);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// ------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------
|
||||
const log = (message) => {
|
||||
if (DEBUG) {
|
||||
if (typeof message === "string") {
|
||||
message = `SW: ${message}`;
|
||||
}
|
||||
|
||||
console.log(message);
|
||||
}
|
||||
};
|
||||
|
||||
const cacheAssets = () => {
|
||||
return caches.open(CACHE_NAME).then((cache) => {
|
||||
log("Updating cache:");
|
||||
log(CACHED_ASSETS);
|
||||
return cache.addAll(CACHED_ASSETS);
|
||||
});
|
||||
};
|
||||
|
||||
const pluralize = (word, params) => {
|
||||
let i = 0;
|
||||
|
||||
if (!Array.isArray(params)) {
|
||||
params = [params];
|
||||
}
|
||||
|
||||
word = word.replace(/\{(!|\d+?)?#(.*?)\}/g, (a, b, c, d) => {
|
||||
// {# [1:count][?:counts]}
|
||||
if (!b || b == "!") {
|
||||
b = i;
|
||||
i++;
|
||||
}
|
||||
|
||||
let value;
|
||||
let fallback;
|
||||
let output = "";
|
||||
let replacement = params[b] + "";
|
||||
|
||||
c.replace(/\[(.+?):(.+?)\]/g, (w, x, y, z) => {
|
||||
if (x == "?") {
|
||||
fallback = y.replace("#", replacement);
|
||||
} else if (x.charAt(0) == "%" && x.substring(1) == replacement.substring(0, x.substring(1).length)) {
|
||||
value = y.replace("#", replacement);
|
||||
} else if (x.charAt(0) == "*" && x.substring(1) == replacement.substr(-x.substring(1).length)) {
|
||||
value = y.replace("#", replacement);
|
||||
} else if (x == replacement) {
|
||||
value = y.replace("#", replacement);
|
||||
}
|
||||
});
|
||||
|
||||
output = a.replace(/^\{/, "").replace(/\}$/, "").replace("!#", "");
|
||||
output = output.replace(b + "#", replacement).replace("#", replacement);
|
||||
output = output.replace(/\[.+\]/, value == null ? fallback : value).trim();
|
||||
|
||||
return output;
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
})({});
|
||||
Reference in new issue
Block a user