@justdanielndev/status-page 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.prettierrc.cjs +1 -0
  2. package/.upptimerc.yml +42 -0
  3. package/CHANGELOG.md +791 -0
  4. package/LICENSE +21 -0
  5. package/README.md +46 -0
  6. package/cypress/fixtures/example.json +5 -0
  7. package/cypress/integration/incident.spec.js +9 -0
  8. package/cypress/integration/live-status.spec.js +12 -0
  9. package/cypress/plugins/index.js +17 -0
  10. package/cypress/support/commands.js +25 -0
  11. package/cypress/support/index.js +20 -0
  12. package/cypress.json +4 -0
  13. package/i18n.yml +61 -0
  14. package/init-tests.ts +12 -0
  15. package/jest.config.js +4 -0
  16. package/package.json +78 -0
  17. package/post-process.ts +61 -0
  18. package/pre-process.ts +32 -0
  19. package/release.config.js +1 -0
  20. package/rollup.config.js +116 -0
  21. package/src/client.js +18 -0
  22. package/src/components/ActiveIncidents.svelte +79 -0
  23. package/src/components/ActiveScheduled.svelte +96 -0
  24. package/src/components/Graph.svelte +76 -0
  25. package/src/components/History.svelte +84 -0
  26. package/src/components/Incident.svelte +161 -0
  27. package/src/components/Incidents.svelte +83 -0
  28. package/src/components/LiveStatus.svelte +190 -0
  29. package/src/components/Loading.svelte +37 -0
  30. package/src/components/Nav.svelte +88 -0
  31. package/src/components/Scheduled.svelte +72 -0
  32. package/src/components/Summary.svelte +54 -0
  33. package/src/routes/_error.svelte +41 -0
  34. package/src/routes/_layout.svelte +110 -0
  35. package/src/routes/error.svelte +27 -0
  36. package/src/routes/history/[number].svelte +17 -0
  37. package/src/routes/incident/[number].svelte +13 -0
  38. package/src/routes/index.svelte +48 -0
  39. package/src/routes/rate-limit-exceeded.svelte +88 -0
  40. package/src/server.js +25 -0
  41. package/src/service-worker.js +15 -0
  42. package/src/template.html +34 -0
  43. package/src/utils/createOctokit.js +67 -0
  44. package/static/global.css +203 -0
  45. package/static/logo-192.png +0 -0
  46. package/static/logo-512.png +0 -0
  47. package/static/manifest.json +20 -0
  48. package/static/themes/dark.css +26 -0
  49. package/static/themes/light.css +26 -0
  50. package/static/themes/night.css +26 -0
  51. package/static/themes/ocean.css +26 -0
  52. package/tsconfig.json +20 -0
@@ -0,0 +1,37 @@
1
+ <script>
2
+ import config from "../data/config.json";
3
+ </script>
4
+
5
+ <style>
6
+ .loading {
7
+ text-align: center;
8
+ margin: 2.5rem 0;
9
+ }
10
+ svg {
11
+ zoom: 1.5;
12
+ }
13
+ </style>
14
+
15
+ <div class="loading">
16
+ <svg
17
+ width="38"
18
+ height="38"
19
+ xmlns="http://www.w3.org/2000/svg"
20
+ stroke="#aaa"
21
+ title={config.loading}>
22
+ <g fill="none" fill-rule="evenodd">
23
+ <g transform="translate(1 1)" stroke-width="2">
24
+ <circle stroke-opacity=".5" cx="18" cy="18" r="18" />
25
+ <path d="M36 18c0-9.94-8.06-18-18-18">
26
+ <animateTransform
27
+ attributeName="transform"
28
+ type="rotate"
29
+ from="0 18 18"
30
+ to="360 18 18"
31
+ dur="1s"
32
+ repeatCount="indefinite" />
33
+ </path>
34
+ </g>
35
+ </g>
36
+ </svg>
37
+ </div>
@@ -0,0 +1,88 @@
1
+ <script>
2
+ import config from "../data/config.json";
3
+ export let segment;
4
+ </script>
5
+
6
+ <nav>
7
+ <div class="container">
8
+ {#if config["status-website"] && config["status-website"].logoUrl}
9
+ <div>
10
+ <a href={config["status-website"].logoHref || config.path} class="logo">
11
+ {#if config["status-website"] && !config["status-website"].hideNavLogo}
12
+ <img alt="" src={config["status-website"].logoUrl} />
13
+ {/if}
14
+ {#if config["status-website"] && !config["status-website"].hideNavTitle}
15
+ <div>{config["status-website"].name}</div>
16
+ {/if}
17
+ </a>
18
+ </div>
19
+ {/if}
20
+ <ul>
21
+ {#if config["status-website"] && config["status-website"].navbar}
22
+ {#each config["status-website"].navbar as item}
23
+ <li>
24
+ <a
25
+ aria-current={segment === (item.href === "/" ? undefined : item.href)
26
+ ? "page"
27
+ : undefined}
28
+ href={item.href.replace("$OWNER", config.owner).replace("$REPO", config.repo)}
29
+ target={item.target || "_self"}>
30
+ {item.title}
31
+ </a>
32
+ </li>
33
+ {/each}
34
+ {/if}
35
+ {#if config["status-website"] && config["status-website"].navbarGitHub && !config["status-website"].navbar}
36
+ <li>
37
+ <a href={`https://github.com/${config.owner}/${config.repo}`}>
38
+ {config.i18n.navGitHub}
39
+ </a>
40
+ </li>
41
+ {/if}
42
+ </ul>
43
+ </div>
44
+ </nav>
45
+
46
+ <style>
47
+ nav {
48
+ font-weight: 300;
49
+ padding: 0 1em;
50
+ margin-bottom: 2rem;
51
+ white-space: nowrap;
52
+ overflow-x: auto;
53
+ }
54
+
55
+ ul {
56
+ margin: 0;
57
+ padding: 0;
58
+ display: flex;
59
+ list-style: none;
60
+ align-items: center;
61
+ justify-content: center;
62
+ }
63
+
64
+ a {
65
+ text-decoration: none;
66
+ padding: 1.5rem 2rem;
67
+ display: block;
68
+ }
69
+
70
+ .logo {
71
+ float: left;
72
+ display: flex;
73
+ align-items: center;
74
+ padding: 0.5rem 0;
75
+ font-weight: bold;
76
+ margin-right: 2rem;
77
+ }
78
+ .logo img {
79
+ margin-right: 1rem;
80
+ height: 3rem;
81
+ border-radius: 0.2rem;
82
+ }
83
+ .container {
84
+ display: flex;
85
+ justify-content: space-between;
86
+ align-items: center;
87
+ }
88
+ </style>
@@ -0,0 +1,72 @@
1
+ <script>
2
+ import Loading from "../components/Loading.svelte";
3
+ import { onMount } from "svelte";
4
+ import config from "../data/config.json";
5
+ import { cachedResponse, createOctokit, handleError } from "../utils/createOctokit";
6
+
7
+ let loading = true;
8
+ const octokit = createOctokit();
9
+ const owner = config.owner;
10
+ const repo = config.repo;
11
+ let incidents = [];
12
+
13
+ onMount(async () => {
14
+ try {
15
+ incidents = (
16
+ await cachedResponse(`maintenance-issues-${owner}-${repo}`, () =>
17
+ octokit.issues.listForRepo({
18
+ owner,
19
+ repo,
20
+ state: "closed",
21
+ filter: "all",
22
+ sort: "created",
23
+ direction: "desc",
24
+ labels: "maintenance",
25
+ })
26
+ )
27
+ ).data;
28
+ } catch (error) {
29
+ handleError(error);
30
+ }
31
+ incidents = incidents.map((incident, index) => {
32
+ incident.showHeading =
33
+ index === 0 ||
34
+ new Date(incidents[index - 1].created_at).toLocaleDateString() !==
35
+ new Date(incident.created_at).toLocaleDateString();
36
+ return incident;
37
+ });
38
+ loading = false;
39
+ });
40
+ </script>
41
+
42
+ <section>
43
+ {#if loading}
44
+ <Loading />
45
+ {:else if incidents.length}
46
+ <h2>{config.i18n.pastScheduledMaintenance}</h2>
47
+ {#each incidents as incident}
48
+ {#if incident.showHeading}
49
+ <h3>{new Date(incident.created_at).toLocaleDateString(config.i18n.locale)}</h3>
50
+ {/if}
51
+ <article class="link degraded">
52
+ <div class="f">
53
+ <div>
54
+ <h4>{incident.title.replace("🛑", "").replace("⚠️", "").trim()}</h4>
55
+ <div>Completed</div>
56
+ </div>
57
+ <div class="f r">
58
+ <a href={`${config.path}/incident/${incident.number}`}>
59
+ {config.i18n.incidentReport.replace(/\$NUMBER/g, incident.number)}
60
+ </a>
61
+ </div>
62
+ </div>
63
+ </article>
64
+ {/each}
65
+ {/if}
66
+ </section>
67
+
68
+ <style>
69
+ h2 {
70
+ margin-top: 2rem;
71
+ }
72
+ </style>
@@ -0,0 +1,54 @@
1
+ <script>
2
+ import Loading from "../components/Loading.svelte";
3
+ import { onMount } from "svelte";
4
+ import config from "../data/config.json";
5
+ import { handleError } from "../utils/createOctokit";
6
+
7
+ export let slug;
8
+ let loading = true;
9
+
10
+ let { apiBaseUrl,userContentBaseUrl } = config["status-website"] || {};
11
+ if (!apiBaseUrl) apiBaseUrl = "https://api.github.com";
12
+ if (!userContentBaseUrl) userContentBaseUrl = "https://raw.githubusercontent.com";
13
+
14
+ const owner = config.owner;
15
+ const repo = config.repo;
16
+ let summary = null;
17
+
18
+ onMount(async () => {
19
+ try {
20
+ const res = await fetch(`${userContentBaseUrl}/${owner}/${repo}/master/history/summary.json`);
21
+ summary = (await res.json()).find((item) => item.slug === slug);
22
+ } catch (error) {
23
+ handleError(error);
24
+ }
25
+ loading = false;
26
+ });
27
+ </script>
28
+
29
+ <style>
30
+ .no-underline {
31
+ text-decoration: none;
32
+ }
33
+ </style>
34
+
35
+ <section>
36
+ {#if loading}
37
+ <Loading />
38
+ {:else if summary}
39
+ <h1>
40
+ <a class="no-underline" href={summary.url.startsWith('$') ? '#' : summary.url}>{summary.name}</a>
41
+ <span class={`tag ${summary.status}`}>
42
+ {summary.status === 'up' ? config.i18n.up : config.i18n.down}
43
+ </span>
44
+ </h1>
45
+ <dl>
46
+ <dt>{config.i18n.overallUptimeTitle}</dt>
47
+ <dd>{summary.uptime}</dd>
48
+ {#if summary.showAverageResponseTime === undefined || summary.showAverageResponseTime}
49
+ <dt>{config.i18n.averageResponseTimeTitle}</dt>
50
+ <dd>{summary.time}{config.i18n.ms}</dd>
51
+ {/if}
52
+ </dl>
53
+ {/if}
54
+ </section>
@@ -0,0 +1,41 @@
1
+ <script>
2
+ export let status;
3
+ export let error;
4
+
5
+ const dev = process.env.NODE_ENV === "development";
6
+ </script>
7
+
8
+ <style>
9
+ h1,
10
+ p {
11
+ margin: 0 auto;
12
+ }
13
+
14
+ h1 {
15
+ font-size: 2.8em;
16
+ font-weight: 700;
17
+ margin: 0 0 0.5em 0;
18
+ }
19
+
20
+ p {
21
+ margin: 1em auto;
22
+ }
23
+
24
+ @media (min-width: 480px) {
25
+ h1 {
26
+ font-size: 4em;
27
+ }
28
+ }
29
+ </style>
30
+
31
+ <svelte:head>
32
+ <title>{status}</title>
33
+ </svelte:head>
34
+
35
+ <h1>{status}</h1>
36
+
37
+ <p>{error.message}</p>
38
+
39
+ {#if dev && error.stack}
40
+ <pre>{error.stack}</pre>
41
+ {/if}
@@ -0,0 +1,110 @@
1
+ <script>
2
+ import Nav from "../components/Nav.svelte";
3
+ import config from "../data/config.json";
4
+ import snarkdown from "snarkdown";
5
+ export let segment;
6
+ </script>
7
+
8
+ <svelte:head>
9
+ {#if (config["status-website"] || {}).customHeadHtml}
10
+ {@html (config["status-website"] || {}).customHeadHtml}
11
+ {/if}
12
+ {#if (config["status-website"] || {}).themeUrl}
13
+ <link rel="stylesheet" href={(config["status-website"] || {}).themeUrl} />
14
+ {:else if (config["status-website"] || {}).theme}
15
+ <link
16
+ rel="stylesheet"
17
+ href={`${config.path}/themes/${config["status-website"].theme}.css`}
18
+ />
19
+ {:else}
20
+ <!-- https://caniuse.com/prefers-color-scheme -->
21
+ <!-- https://web.dev/prefers-color-scheme/ -->
22
+ <script>
23
+ // If `prefers-color-scheme` is not supported, fall back to light mode.
24
+ // In this case, light.css will be downloaded with `highest` priority.
25
+ if (typeof window !== "undefined" && typeof document !== "undefined" && "matchMedia" in window && window.matchMedia('(prefers-color-scheme: dark)').media === 'not all') {
26
+ document.documentElement.style.display = 'none';
27
+ document.head.insertAdjacentHTML(
28
+ 'beforeend',
29
+ '<link rel="stylesheet" href={`${config.path}/themes/light.css`} onload="document.documentElement.style.display = \'\'">',
30
+ );
31
+ }
32
+ </script>
33
+ <link
34
+ rel="stylesheet"
35
+ href={`${config.path}/themes/light.css`}
36
+ media="(prefers-color-scheme: light)"
37
+ />
38
+ <link
39
+ rel="stylesheet"
40
+ href={`${config.path}/themes/dark.css`}
41
+ media="(prefers-color-scheme: dark)"
42
+ />
43
+ {/if}
44
+ <link rel="stylesheet" href={`${config.path}/global.css`} />
45
+ <link
46
+ rel="icon"
47
+ type="image/svg"
48
+ href={(config["status-website"] || {}).faviconSvg ||
49
+ (config["status-website"] || {}).favicon ||
50
+ `https://raw.githubusercontent.com/upptime/upptime/master/assets/upptime-icon.svg`}
51
+ />
52
+ <link
53
+ rel="icon"
54
+ type="image/png"
55
+ href={(config["status-website"] || {}).favicon || `/logo-192.png`}
56
+ />
57
+ {#if (config["status-website"] || {}).scripts}
58
+ {#each (config["status-website"] || {}).scripts as script}<script
59
+ src={script.src}
60
+ async={!!script.async}
61
+ defer={!!script.async}>
62
+ </script>{/each}
63
+ {/if}
64
+ {#if (config["status-website"] || {}).links}
65
+ {#each (config["status-website"] || {}).links as link}
66
+ <link rel={link.rel} href={link.href} media={link.media} />
67
+ {/each}
68
+ {/if}
69
+ {#if (config["status-website"] || {}).metaTags}
70
+ {#each (config["status-website"] || {}).metaTags as link}
71
+ <meta name={link.name} content={link.content} />
72
+ {/each}
73
+ {/if}
74
+ {#if config['status-website'].css}
75
+ {@html `<style>${config['status-website'].css}</style>`}
76
+ {/if}
77
+ {#if config['status-website'].js}
78
+ {@html `<script>${config['status-website'].js}</script>`}
79
+ {/if}
80
+ </svelte:head>
81
+
82
+ {#if (config["status-website"] || {}).customBodyHtml}
83
+ {@html (config["status-website"] || {}).customBodyHtml}
84
+ {/if}
85
+
86
+ <Nav {segment} />
87
+
88
+ <main class="container">
89
+ <slot />
90
+ </main>
91
+
92
+ <footer>
93
+ <p>
94
+ {@html snarkdown(
95
+ config.i18n.footer.replace(/\$REPO/, `https://github.com/${config.owner}/${config.repo}`)
96
+ )}
97
+ </p>
98
+ </footer>
99
+
100
+ <style>
101
+ footer {
102
+ text-align: center;
103
+ opacity: 0.75;
104
+ margin-top: 3rem;
105
+ }
106
+ </style>
107
+
108
+ {#if (config["status-website"] || {}).customFootHtml}
109
+ {@html (config["status-website"] || {}).customFootHtml}
110
+ {/if}
@@ -0,0 +1,27 @@
1
+ <script>
2
+ import config from "../data/config.json";
3
+ </script>
4
+
5
+ <svelte:head>
6
+ <title>{config.i18n.errorTitle}</title>
7
+ </svelte:head>
8
+
9
+ <h1>{config.i18n.errorTitle}</h1>
10
+
11
+ <p class="lead">{config.i18n.errorIntro}</p>
12
+
13
+ <p>{config.i18n.errorText}</p>
14
+
15
+ <a href={config.path} class="error-button">{config.i18n.errorHome}</a>
16
+
17
+ <style>
18
+ p.lead {
19
+ font-size: 110%;
20
+ }
21
+ a.error-button {
22
+ font: inherit;
23
+ padding: 0.5rem 1rem;
24
+ border-radius: 0.2rem;
25
+ text-decoration: none;
26
+ }
27
+ </style>
@@ -0,0 +1,17 @@
1
+ <script context="module">
2
+ export async function preload(page) {
3
+ const { number } = page.params;
4
+ return { slug: number };
5
+ }
6
+ </script>
7
+
8
+ <script>
9
+ import Summary from "../../components/Summary.svelte";
10
+ import History from "../../components/History.svelte";
11
+ import Graph from "../../components/Graph.svelte";
12
+ export let slug;
13
+ </script>
14
+
15
+ <Summary {slug} />
16
+ <Graph {slug} />
17
+ <History {slug} />
@@ -0,0 +1,13 @@
1
+ <script context="module">
2
+ export async function preload(page) {
3
+ const { number } = page.params;
4
+ return { number };
5
+ }
6
+ </script>
7
+
8
+ <script>
9
+ import Incident from "../../components/Incident.svelte";
10
+ export let number;
11
+ </script>
12
+
13
+ <Incident {number} />
@@ -0,0 +1,48 @@
1
+ <script>
2
+ import snarkdown from "snarkdown";
3
+ import ActiveIncidents from "../components/ActiveIncidents.svelte";
4
+ import ActiveScheduled from "../components/ActiveScheduled.svelte";
5
+ import Incidents from "../components/Incidents.svelte";
6
+ import LiveStatus from "../components/LiveStatus.svelte";
7
+ import Scheduled from "../components/Scheduled.svelte";
8
+ import config from "../data/config.json";
9
+
10
+ let title = "Status";
11
+ try {
12
+ title = config["status-website"].name;
13
+ } catch (error) {}
14
+ </script>
15
+
16
+ <svelte:head>
17
+ <title>{title}</title>
18
+ </svelte:head>
19
+
20
+ <header>
21
+ {#if config["status-website"]}
22
+ {#if config["status-website"].introTitle}
23
+ <h1>
24
+ {@html snarkdown(config["status-website"].introTitle)}
25
+ </h1>
26
+ {/if}
27
+ {#if config["status-website"].introMessage}
28
+ <p class="lead">
29
+ {@html snarkdown(config["status-website"].introMessage)}
30
+ </p>
31
+ {/if}
32
+ {/if}
33
+ </header>
34
+
35
+ <ActiveIncidents />
36
+ <ActiveScheduled />
37
+ <LiveStatus />
38
+ <Scheduled />
39
+ <Incidents />
40
+
41
+ <style>
42
+ p.lead {
43
+ font-size: 110%;
44
+ }
45
+ header {
46
+ margin-bottom: 2rem;
47
+ }
48
+ </style>
@@ -0,0 +1,88 @@
1
+ <script>
2
+ import { onMount } from "svelte";
3
+ import { goto } from "@sapper/app";
4
+ import config from "../data/config.json";
5
+
6
+ let token = "";
7
+ let localStorageToken = false;
8
+ const save = () => {
9
+ if (typeof window !== "undefined" && "localStorage" in window)
10
+ window.localStorage.setItem("personal-access-token", token);
11
+ goto(config.path);
12
+ };
13
+ const remove = () => {
14
+ token = "";
15
+ localStorageToken = false;
16
+ if (typeof window !== "undefined" && "localStorage" in window)
17
+ window.localStorage.removeItem("personal-access-token");
18
+ };
19
+ onMount(() => {
20
+ if (
21
+ typeof window !== "undefined" &&
22
+ "localStorage" in window &&
23
+ localStorage.getItem("personal-access-token")
24
+ )
25
+ localStorageToken = true;
26
+ });
27
+ </script>
28
+
29
+ <svelte:head>
30
+ <title>{config.i18n.rateLimitExceededTitle}</title>
31
+ </svelte:head>
32
+
33
+ <h1>{config.i18n.rateLimitExceededTitle}</h1>
34
+
35
+ <p class="lead">{config.i18n.rateLimitExceededIntro}</p>
36
+
37
+ <h2>{config.i18n.rateLimitExceededWhatDoesErrorMean}</h2>
38
+
39
+ <p>{config.i18n.rateLimitExceededErrorMeaning}</p>
40
+
41
+ <h2>{config.i18n.rateLimitExceededErrorHowCanFix}</h2>
42
+
43
+ <p>
44
+ {config.i18n.rateLimitExceededErrorFix}
45
+ <a
46
+ href="https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token"
47
+ target="_blank">{config.i18n.rateLimitExceededGeneratePAT}</a
48
+ >
49
+ </p>
50
+
51
+ {#if localStorageToken}
52
+ <p>{config.i18n.rateLimitExceededHasSet}</p>
53
+ <button on:click={remove}>{config.i18n.rateLimitExceededRemoveToken}</button>
54
+ {:else}
55
+ <form on:submit|preventDefault={save}>
56
+ <label>
57
+ <span>{config.i18n.rateLimitExceededGitHubPAT}</span>
58
+ <input
59
+ type="text"
60
+ bind:value={token}
61
+ placeholder={config.i18n.rateLimitExceededCopyPastePAT}
62
+ />
63
+ </label>
64
+ <button class="submit-button" type="submit">{config.i18n.rateLimitExceededSaveToken}</button>
65
+ </form>
66
+ {/if}
67
+
68
+ <style>
69
+ p.lead {
70
+ font-size: 110%;
71
+ }
72
+ label span {
73
+ display: block;
74
+ font-weight: bold;
75
+ margin-bottom: 0.5rem;
76
+ }
77
+ input,
78
+ button {
79
+ font: inherit;
80
+ padding: 0.5rem 1rem;
81
+ border: 0.1rem solid rgba(0, 0, 0, 0.25);
82
+ border-radius: 0.2rem;
83
+ }
84
+ input {
85
+ width: 15rem;
86
+ max-width: 100%;
87
+ }
88
+ </style>
package/src/server.js ADDED
@@ -0,0 +1,25 @@
1
+ import * as sapper from "@sapper/server";
2
+ import compression from "compression";
3
+ import fs from "fs-extra";
4
+ import polka from "polka";
5
+ import sirv from "sirv";
6
+ import { load } from "js-yaml";
7
+ import { join } from "path";
8
+
9
+ const { PORT, NODE_ENV } = process.env;
10
+ const dev = NODE_ENV === "development";
11
+
12
+ let config
13
+ if(fs.existsSync(join("..", ".uclirc.yml"))){
14
+ config = load(fs.readFileSync(join("..", ".uclirc.yml"), "utf8"));
15
+ }else{
16
+ config = load(fs.readFileSync(join("..", ".upptimerc.yml"), "utf8"));
17
+ }
18
+
19
+ const baseUrl = (config["status-website"] || {}).baseUrl || "/";
20
+
21
+ polka()
22
+ .use(baseUrl, compression({ threshold: 0 }), sirv("static", { dev }), sapper.middleware())
23
+ .listen(PORT, (err) => {
24
+ if (err) console.log("error", err);
25
+ });
@@ -0,0 +1,15 @@
1
+ self.addEventListener("install", function () {
2
+ self.skipWaiting();
3
+ });
4
+
5
+ self.addEventListener("activate", function () {
6
+ self.registration
7
+ .unregister()
8
+ .then(function () {
9
+ return self.clients.matchAll();
10
+ })
11
+ .then(function (clients) {
12
+ clients.forEach((client) => client.navigate(client.url));
13
+ })
14
+ .catch(function () {});
15
+ });
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
6
+ <meta name="theme-color" content="#333333" />
7
+ %sapper.base%
8
+ <link rel="manifest" href="manifest.json" crossorigin="use-credentials" />
9
+ %sapper.scripts% %sapper.styles% %sapper.head%
10
+ </head>
11
+ <body>
12
+ <div id="sapper">%sapper.html%</div>
13
+ <script>
14
+ try {
15
+ if (typeof window !== "undefined" && window.navigator && navigator.serviceWorker) {
16
+ navigator.serviceWorker.getRegistrations().then(function (registrations) {
17
+ for (let registration of registrations) {
18
+ registration.unregister();
19
+ }
20
+ });
21
+ }
22
+ if (typeof window !== "undefined" && "caches" in window) {
23
+ caches.keys().then(function (keyList) {
24
+ return Promise.all(
25
+ keyList.map(function (key) {
26
+ return caches.delete(key);
27
+ })
28
+ );
29
+ });
30
+ }
31
+ } catch (error) {}
32
+ </script>
33
+ </body>
34
+ </html>