//! This is a slightly modified version of coi-serviceworker, commit 7b1d2a092d0d2dd2b7270b6f12f13605de26f214
//! https://github.com/gzuidhof/coi-serviceworker
/*!
 * MIT License
 *
 * Copyright (c) 2021 Guido Zuidhof
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
*/

const CACHE_NAME = "astrabit.luminol";

let coepCredentialless = true;
if (typeof window === 'undefined') {
    self.addEventListener("install", () => self.skipWaiting());
    self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));

    self.addEventListener("message", (ev) => {
        if (!ev.data) {
            return;
        } else if (ev.data.type === "deregister") {
            self.registration
                .unregister()
                .then(() => {
                    return self.clients.matchAll();
                })
                .then(clients => {
                    clients.forEach((client) => client.navigate(client.url));
                });
        } else if (ev.data.type === "coepCredentialless") {
            coepCredentialless = ev.data.value;
        }
    });

    self.addEventListener("fetch", function (event) {
        const r = event.request;
        if (r.cache === "only-if-cached" && r.mode !== "same-origin") {
            return;
        }

        const request = (coepCredentialless && r.mode === "no-cors")
            ? new Request(r, {
                credentials: "omit",
            })
            : new URL(r.url).searchParams.has("luminol-invalidator")
            ? (() => {
                // Remove 'luminol-invalidator' from the request's query string if it exists in the query string
                const url = new URL(r.url);
                url.searchParams.delete("luminol-invalidator");
                return new Request(url, r);
            })()
            : r;

        const url = new URL(request.url);
        url.hash = "";
        url.pathname = url.pathname.trim();
        // Unescape escape codes like "%2f"
        url.pathname = decodeURIComponent(url.pathname);
        // Replace backslashes with forward slashes
        url.pathname = url.pathname.replace(/\\/g, "/");
        // Collapse repeated slashes
        url.pathname = url.pathname.replace(/\/+/g, "/");
        // Remove trailing slashes
        if (url.pathname.endsWith("/")) url.pathname = url.pathname.slice(0, -1);
        // Strip "index.html" from the end
        if (url.pathname.endsWith("/index.html")) url.pathname = url.pathname.slice(0, -11);

        event.respondWith(
            self.caches
                .match(url)
                .then((cached) => cached || fetch(request, { cache: "no-store" })) // Respond with cached response if one exists for this request
                .then((response) => {
                    if (response.status === 0) {
                        return new Response();
                    }

                    const newHeaders = new Headers(response.headers);
                    newHeaders.set("Cross-Origin-Embedder-Policy",
                        coepCredentialless ? "credentialless" : "require-corp"
                    );
                    if (!coepCredentialless) {
                        newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin");
                    }
                    newHeaders.set("Cross-Origin-Opener-Policy", "same-origin");

                    const newResponse = new Response(response.body, {
                        status: response.status,
                        statusText: response.statusText,
                        headers: newHeaders,
                    });

                    // Auto-cache non-error, non-opaque responses for all same-origin requests other than buildinfo.json
                    if (response.type === "error" || url.origin !== self.origin || url.pathname.endsWith("/buildinfo.json")) {
                        return newResponse;
                    } else {
                        return self.caches
                            .open(CACHE_NAME)
                            .then((cache) => cache.put(url, newResponse.clone()))
                            .then(() => newResponse);
                    }
                })
                .catch((e) => {
                    if (!url.pathname.endsWith("/buildinfo.json")) {
                        console.error(e);
                    }
                })
        );
    });

} else {
    (() => {
        // Check for the current Luminol build info, and then clear the cache storage if
        // it doesn't match the build info we previously stored in local storage
        if (!window.sessionStorage.getItem("luminolCheckedForUpdate") && window.sessionStorage.getItem("coiReloadedAfterSuccess")) {
            (
                window.location.hash === "#dev"
                    ? Promise.resolve(null)
                    : fetch("./buildinfo.json")
                        .then((response) => {
                            if (response.status === 200) {
                                return response.json();
                            } else {
                                console.warn("Error checking for Luminol updates: request returned status code", response.status);
                            }
                        })
            )
                .then((info) => {
                    if (info === undefined) {
                        return;
                    }
                    const oldInfo = JSON.parse(window.localStorage.getItem("luminolBuildInfo"));
                    if (
                        info === null
                            || oldInfo === null
                            || info.epoch !== oldInfo.epoch
                            || info.rev !== oldInfo.rev
                            || info.profile !== oldInfo.profile
                            || info.profile !== "release"
                            || info.rev.endsWith("-modified")
                    ) {
                        !coi.quiet && console.log("New Luminol update detected - clearing cache.");
                        return window.caches.delete(CACHE_NAME).then(() => info);
                    }
                })
                .then((info) => {
                    if (info === undefined) {
                        return false;
                    }
                    window.sessionStorage.clear();
                    window.localStorage.setItem("luminolBuildInfo", JSON.stringify(info));
                    window.sessionStorage.setItem("luminolCheckedForUpdate", "true");
                    return window.navigator?.serviceWorker.getRegistration()
                        .then((registration) => registration?.unregister())
                        .then(() => true)
                            ?? true;
                })
                .then((shouldRefresh) => {
                    if (!shouldRefresh) {
                        return;
                    }
                    !coi.quiet && console.log("Reloading page to finish clearing cache.");
                    coi.doReload();
                })
                .catch((e) => console.warn("Error checking for Luminol updates:", e));
        }

        const reloadedBySelf = window.sessionStorage.getItem("coiReloadedBySelf");
        window.sessionStorage.removeItem("coiReloadedBySelf");
        const coepDegrading = (reloadedBySelf == "coepdegrade");

        // You can customize the behavior of this script through a global `coi` variable.
        const coi = {
            shouldRegister: () => !reloadedBySelf,
            shouldDeregister: () => false,
            coepCredentialless: () => true,
            coepDegrade: () => true,
            doReload: () => window.location.reload(),
            quiet: false,
            ...window.coi
        };

        const n = navigator;
        const controlling = n.serviceWorker && n.serviceWorker.controller;

        // Record the failure if the page is served by serviceWorker.
        if (controlling && !window.crossOriginIsolated) {
            window.sessionStorage.setItem("coiCoepHasFailed", "true");
        }
        const coepHasFailed = window.sessionStorage.getItem("coiCoepHasFailed");

        if (controlling) {
            // Reload only on the first failure.
            const reloadToDegrade = coi.coepDegrade() && !(
                coepDegrading || window.crossOriginIsolated
            );
            n.serviceWorker.controller.postMessage({
                type: "coepCredentialless",
                value: (reloadToDegrade || coepHasFailed && coi.coepDegrade())
                    ? false
                    : coi.coepCredentialless(),
            });
            if (reloadToDegrade) {
                !coi.quiet && console.log("Reloading page to degrade COEP.");
                window.sessionStorage.setItem("coiReloadedBySelf", "coepdegrade");
                coi.doReload("coepdegrade");
            }

            if (coi.shouldDeregister()) {
                n.serviceWorker.controller.postMessage({ type: "deregister" });
            }
        }

        // If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are
        // already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here.
        if (window.crossOriginIsolated !== false || !coi.shouldRegister()) {
            // Reload once to set the COEP for this service worker as well
            if (!window.sessionStorage.getItem("coiReloadedAfterSuccess")) {
                !coi.quiet && console.log("Reloading page to set COEP for this service worker.");
                window.sessionStorage.setItem("coiReloadedAfterSuccess", "true");
                coi.doReload("coepaftersuccess");
            } else {
                window.sessionStorage.removeItem("luminolCheckedForUpdate");
                window.sessionStorage.removeItem("coiReloadedAfterSuccess");
            }
            return;
        }
        window.sessionStorage.removeItem("coiReloadedAfterSuccess");

        if (!window.isSecureContext) {
            !coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required.");
            window.sessionStorage.removeItem("luminolCheckedForUpdate");
            return;
        }

        // In some environments (e.g. Firefox private mode) this won't be available
        if (!n.serviceWorker) {
            !coi.quiet && console.error("COOP/COEP Service Worker not registered, perhaps due to private mode.");
            window.sessionStorage.removeItem("luminolCheckedForUpdate");
            return;
        }

        n.serviceWorker.register(window.document.currentScript.src).then(
            (registration) => {
                !coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope);

                registration.addEventListener("updatefound", () => {
                    !coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker.");
                    window.sessionStorage.setItem("coiReloadedBySelf", "updatefound");
                    coi.doReload();
                });

                // If the registration is active, but it's not controlling the page
                if (registration.active && !n.serviceWorker.controller) {
                    !coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker.");
                    window.sessionStorage.setItem("coiReloadedBySelf", "notcontrolling");
                    coi.doReload();
                }
            },
            (err) => {
                window.sessionStorage.removeItem("luminolCheckedForUpdate");
                !coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err);
            }
        );
    })();
}
