diff --git a/cache.js b/cache.js
index 9a80f12..423faea 100644
--- a/cache.js
+++ b/cache.js
@@ -130,8 +130,7 @@ module.exports = [
const isSameOrigin = self.origin === url.origin;
if (!isSameOrigin) return false;
const pathname = url.pathname;
- console.log("SW", pathname, isSameOrigin);
- if (pathname.startsWith("/api/blobbz")) return true;
+ if (pathname.startsWith("/api/blobbz/")) return true;
return false;
},
handler: "CacheFirst",
@@ -143,6 +142,24 @@ module.exports = [
},
},
},
+ {
+ urlPattern: ({ url }) => {
+ const isSameOrigin = self.origin === url.origin;
+ if (!isSameOrigin) return false;
+ const pathname = url.pathname;
+ if (pathname.startsWith("/api/blobbz/")) return false;
+ if (pathname.startsWith("/api/blobbz?page=")) return true;
+ return false;
+ },
+ handler: "CacheFirst",
+ options: {
+ cacheName: "blobz-collection-cache",
+ expiration: {
+ maxEntries: 52,
+ maxAgeSeconds: 5, // 5 min
+ },
+ },
+ },
{
urlPattern: ({ url }) => {
const isSameOrigin = self.origin === url.origin;
diff --git a/package.json b/package.json
index 5e1a43c..fe72076 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
{
"name": "w3bbz",
"private": true,
+ "version": "0.1.0",
"scripts": {
"dev": "next dev",
"build": "next build",
@@ -9,19 +10,16 @@
"typecheck": "tsc --p . --noEmit"
},
"dependencies": {
- "@types/ioredis": "^4.28.7",
- "@types/styled-system": "^5.1.15",
"ethers": "^5.5.3",
"ioredis": "^4.28.3",
- "lodash": "^4.17.21",
"modern-normalize": "^1.1.0",
"next": "12.0.8",
"next-i18next": "^10.2.0",
"next-pwa": "^5.4.4",
+ "qrcode-generator": "^1.4.4",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-use": "^17.3.2",
- "rxjs": "^7.5.2",
"styled-components": "^5.2.3",
"styled-system": "^5.1.5",
"swr": "^1.2.0",
@@ -31,6 +29,8 @@
"@types/react-dom": "^17.0.11",
"@types/styled-components": "^5.1.20",
"@typescript-eslint/eslint-plugin": "^5.10.0",
+ "@types/ioredis": "^4.28.7",
+ "@types/styled-system": "^5.1.15",
"eslint": "^8.7.0",
"eslint-config-next": "^12.0.8",
"eslint-config-prettier": "^8.3.0",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index de3c06f..61c5ecf 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -11,6 +11,7 @@ import {
SmileyContainer,
} from "../src/components";
import "modern-normalize";
+import "../styles/global.css";
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
return (
@@ -20,63 +21,6 @@ function MyApp({ Component, pageProps }: AppProps): JSX.Element {
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
W3bbz
diff --git a/pages/_document.tsx b/pages/_document.tsx
index b0add8e..e475fcb 100644
--- a/pages/_document.tsx
+++ b/pages/_document.tsx
@@ -1,5 +1,11 @@
import React from "react";
-import Document, { DocumentContext } from "next/document";
+import Document, {
+ DocumentContext,
+ Html,
+ Head,
+ Main,
+ NextScript,
+} from "next/document";
import { ServerStyleSheet } from "styled-components";
import { AppType } from "next/dist/shared/lib/utils";
@@ -29,4 +35,77 @@ export default class MyDocument extends Document {
sheet.seal();
}
}
+
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
}
diff --git a/pages/about/index.tsx b/pages/about/index.tsx
index f63b454..b0b33c7 100644
--- a/pages/about/index.tsx
+++ b/pages/about/index.tsx
@@ -15,6 +15,7 @@ export default function About() {
(pad32 + value).substr(value.length);
@@ -57,7 +60,7 @@ export default async function blobbzHandler(
query: { id },
} = req;
const nId = parseInt(id);
- const cacheID = "tokenURI:" + CONTRACT_ADDRESS;
+ const cacheID = "tokenURI:" + VERSION + ":" + CONTRACT_ADDRESS;
const cache = await redis.hget(cacheID, id);
@@ -78,7 +81,19 @@ export default async function blobbzHandler(
const tokenURI = hexResult
.substr(hexResult.indexOf(JSON_PREFIX))
.replace(/\x00+/g, "");
- const parsedResult = { tokenURI };
+ const metadata = JSON.parse(
+ Buffer.from(tokenURI.replace(JSON_PREFIX, ""), "base64").toString(
+ "binary"
+ )
+ );
+
+ metadata.image = Buffer.from(
+ metadata.image.replace(SVG_PREFIX, ""),
+ "base64"
+ ).toString("binary");
+
+ const parsedResult = { metadata, id };
+
redis.hset(cacheID, id, JSON.stringify(parsedResult));
return res.status(200).json(parsedResult);
});
diff --git a/pages/api/blobbz/index.ts b/pages/api/blobbz/index.ts
new file mode 100644
index 0000000..f3dd384
--- /dev/null
+++ b/pages/api/blobbz/index.ts
@@ -0,0 +1,171 @@
+/* eslint-disable no-control-regex */
+import Redis from "ioredis";
+
+const redis = new Redis(
+ process.env.REDIS_URL || process.env.NEXT_PUBLIC_REDIS_URL
+);
+
+const VERSION = process.env.VERSION_ID || process.env.NEXT_PUBLIC_VERSION_ID;
+
+const CONTRACT_ADDRESS =
+ process.env.BLOBZ_CONTRACT_ADDRESS ||
+ process.env.NEXT_PUBLIC_BLOBZ_CONTRACT_ADDRESS;
+
+const INFURA_ID = process.env.INFURA_ID || process.env.NEXT_PUBLIC_INFURA_ID;
+
+const headers = {
+ "Content-Type": "application/json",
+};
+
+const MIN_ID = 0;
+const MAX_ID = 511;
+const requestID = 42;
+const totalSupplyHex = "0x18160ddd";
+const tokenURIHex = "0xc87b56dd";
+const pad32 = Array(32).fill("00").join("");
+const JSON_PREFIX = "data:application/json;base64,";
+const SVG_PREFIX = "data:image/svg+xml;base64,";
+
+const padValue = (value: string) => (pad32 + value).substr(value.length);
+
+const getInfuraTotalSupplyPayload = () => ({
+ method: "eth_call",
+ params: [
+ {
+ to: CONTRACT_ADDRESS,
+
+ data: totalSupplyHex,
+ },
+ "latest",
+ ],
+ id: requestID,
+ jsonrpc: "2.0",
+});
+
+const getInfuraTokenURIPayload = (id: number) => ({
+ method: "eth_call",
+ params: [
+ {
+ to: CONTRACT_ADDRESS,
+
+ data:
+ tokenURIHex +
+ padValue(Math.max(MIN_ID, Math.min(MAX_ID, id)).toString(16)),
+ },
+ "latest",
+ ],
+ id: requestID,
+ jsonrpc: "2.0",
+});
+
+function hex_to_ascii(str1: string) {
+ if (!str1) return "";
+ const hex = str1.toString();
+ let str = "";
+ for (let n = 0; n < hex.length; n += 2) {
+ str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
+ }
+ return str;
+}
+
+async function fetchTokenURI(id: number) {
+ const cacheID = "tokenURI:" + VERSION + ":" + CONTRACT_ADDRESS;
+ const cache = await redis.hget(cacheID, `${id}`);
+
+ if (cache) {
+ return JSON.parse(cache);
+ } else {
+ const options = {
+ method: "POST",
+ headers: headers,
+ body: JSON.stringify(getInfuraTokenURIPayload(id)),
+ };
+
+ return fetch(`https://polygon-mainnet.infura.io/v3/${INFURA_ID}`, options)
+ .then((r) => r.json())
+ .then((data) => {
+ const { result } = data;
+ const hexResult = hex_to_ascii(result);
+ const tokenURI = hexResult
+ .substr(hexResult.indexOf(JSON_PREFIX))
+ .replace(/\x00+/g, "");
+ const metadata = JSON.parse(
+ Buffer.from(tokenURI.replace(JSON_PREFIX, ""), "base64").toString(
+ "binary"
+ )
+ );
+
+ metadata.image = Buffer.from(
+ metadata.image.replace(SVG_PREFIX, ""),
+ "base64"
+ ).toString("binary");
+
+ const parsedResult = { metadata, id };
+ redis.hset(cacheID, id, JSON.stringify(parsedResult));
+ return parsedResult;
+ });
+ }
+}
+
+const PER_PAGE = 5;
+
+async function fetchTokenURIS(
+ { page, totalSupply }: { page: number; totalSupply: number },
+ res: any
+) {
+ const offset = page * PER_PAGE;
+
+ if (offset > totalSupply) {
+ return res.status(200).json({ data: [] });
+ }
+
+ const list = [];
+
+ const limit = Math.max(0, totalSupply - offset - PER_PAGE);
+
+ for (let i = totalSupply - offset - 1; i >= limit; i--) {
+ list.push(await fetchTokenURI(i));
+ }
+
+ return res.status(200).json({
+ data: list,
+ page,
+ limit,
+ totalSupply,
+ remaining: MAX_ID - totalSupply + 1,
+ });
+}
+
+export default async function blobbzListHandler(
+ req: { query: { page: string } },
+ res: any
+) {
+ const {
+ query: { page },
+ } = req;
+ const totalSupplyCacheID = "totalSupply:" + VERSION + ":" + CONTRACT_ADDRESS;
+
+ const totalSupplyCache = await redis.get(totalSupplyCacheID);
+
+ if (totalSupplyCache) {
+ return fetchTokenURIS(
+ { page: parseInt(page), totalSupply: parseInt(totalSupplyCache) },
+ res
+ );
+ } else {
+ const totalSupplyOptions = {
+ method: "POST",
+ headers: headers,
+ body: JSON.stringify(getInfuraTotalSupplyPayload()),
+ };
+ const totalSupply = await fetch(
+ `https://polygon-mainnet.infura.io/v3/${INFURA_ID}`,
+ totalSupplyOptions
+ );
+ const json = await totalSupply.json();
+ const result = parseInt(json.result);
+ redis.set(totalSupplyCacheID, `${result}`, "EX", 60);
+
+ return fetchTokenURIS({ page: parseInt(page), totalSupply: result }, res);
+ }
+}
diff --git a/pages/nft/blobbz/[id].tsx b/pages/nft/blobbz/[id].tsx
index f627805..c0b38fa 100644
--- a/pages/nft/blobbz/[id].tsx
+++ b/pages/nft/blobbz/[id].tsx
@@ -2,9 +2,17 @@ import React, { useState, useEffect } from "react";
import useSWR from "swr";
import Head from "next/head";
import { useRouter } from "next/router";
-import { NFTViewer, Banner, BannerContainer } from "../../../src/components";
-
-const extraIframeHTML = ``;
+import {
+ NFTViewer,
+ Banner,
+ BannerContainer,
+ QRCode,
+ Flex,
+ Link,
+ Text,
+} from "../../../src/components";
+
+const extraIframeHTML = ``;
const insertionCallback = (ref: any) => {
if (!ref || !ref.current || !ref.current.contentDocument) return null;
@@ -34,12 +42,19 @@ const insertionCallback = (ref: any) => {
const { width, height } = b.getBoundingClientRect();
let ratioX = 600 / width;
let ratioY = 600 / height;
+ let isHeightBigger = height > width;
+ let minmax = isHeightBigger ? height : width;
+
+ let offsetMax = (minmax - 600) / 2;
+ let demiOffset = offsetMax / 2;
+ const getRatio = (t: number, isX: boolean) =>
+ isX !== isHeightBigger ? -demiOffset + (t * (600 + offsetMax)) / 600 : t;
b.addEventListener(
"mousedown",
(e: any) => {
- x = e.offsetX * ratioX;
- y = e.offsetY * ratioY;
+ x = getRatio(e.offsetX * ratioX, true);
+ y = getRatio(e.offsetY * ratioY, false);
isDrawing = true;
},
false
@@ -53,8 +68,8 @@ const insertionCallback = (ref: any) => {
"style",
`transform: translate(${x}px, ${y}px); animation:none;`
);
- x = e.offsetX * ratioX;
- y = e.offsetY * ratioY;
+ x = getRatio(e.offsetX * ratioX, true);
+ y = getRatio(e.offsetY * ratioY, false);
}
},
false
@@ -70,8 +85,8 @@ const insertionCallback = (ref: any) => {
"style",
`transform: translate(${x}px, ${y}px); animation:none;`
);
- x = clientX * ratioX;
- y = clientY * ratioY;
+ x = getRatio(clientX * ratioX, true);
+ y = getRatio(clientY * ratioY, false);
}
},
false
@@ -101,6 +116,10 @@ const insertionCallback = (ref: any) => {
const { width, height } = b.getBoundingClientRect();
ratioX = 600 / width;
ratioY = 600 / height;
+ isHeightBigger = height > width;
+ minmax = isHeightBigger ? height : width;
+ offsetMax = (minmax - 600) / 2;
+ demiOffset = offsetMax / 2;
})
.catch((err: { message: string; name: string }) => {
console.error(
@@ -112,6 +131,10 @@ const insertionCallback = (ref: any) => {
const { width, height } = b.getBoundingClientRect();
ratioX = 600 / width;
ratioY = 600 / height;
+ isHeightBigger = height > width;
+ minmax = isHeightBigger ? height : width;
+ offsetMax = (minmax - 600) / 2;
+ demiOffset = offsetMax / 2;
});
}
},
@@ -140,19 +163,55 @@ function Blobz({ id }: { id: string }) {
/>
+
- {data?.tokenURI ? (
+ {data?.metadata ? (
<>
+
+
+
+
+ {data.metadata.name}
+
+
+ view on opensea
+
+
+
+
+
+
+
-
- CLICK {"&"} MOVE {">>>"} DOUBLE CLICK FULLSCREEN {">>>"}
-
+ {`CLICK & MOVE >>> DOUBLE CLICK FULLSCREEN >>>`}
-
>
) : error ? (
diff --git a/pages/nft/blobbz/collection.tsx b/pages/nft/blobbz/collection.tsx
new file mode 100644
index 0000000..5ff6e17
--- /dev/null
+++ b/pages/nft/blobbz/collection.tsx
@@ -0,0 +1,258 @@
+import React, { useState, useEffect, useMemo, useCallback, memo } from "react";
+import styled from "styled-components";
+import useSWR from "swr";
+import { useRouter } from "next/router";
+import Head from "next/head";
+
+import {
+ NFTViewer,
+ Banner,
+ BannerContainer,
+ Text,
+ Button,
+ QRCode,
+} from "../../../src/components";
+import Flex, { FlexBoxProps } from "../../../src/components/Layout/Flex";
+
+const IdText = styled(Text).attrs({
+ whiteSpace: "nowrap",
+ height: "min-content",
+ variant: "small",
+ color: "white",
+ flex: "1",
+})``;
+
+const IdLabel = styled(Flex).attrs({
+ height: "82px",
+ alignItems: "stretch",
+ bottom: 4,
+ position: "absolute",
+})``;
+
+const RowContent = styled(Flex).attrs({
+ p: 4,
+ overflow: "hidden",
+ position: "relative",
+})`
+ flex: 0 0 50%;
+ height: ${(p) => (p.height ? p.height : "200%")};
+ @media (max-width: 720px) {
+ flex: 1;
+ height: 100%;
+ }
+`;
+
+const ButtonsContainer = styled(Flex).attrs({
+ width: "100%",
+ height: "50px",
+ justifyContent: "space-between",
+ flexWrap: "wrap",
+ px: "0.5rem",
+ bg: "background.main",
+ alignItems: "flex-end",
+})``;
+
+type RowContainerProps = FlexBoxProps & { index: number };
+
+const RowContainer = styled(Flex).attrs(
+ ({ index }: { index: number }) => ({
+ flexDirection: index % 2 ? "row" : "row-reverse",
+ position: "relative",
+ overflowX: "visible",
+ zIndex: 999 - index,
+ height: 360,
+ index,
+ })
+)`
+ ${IdLabel} {
+ right: ${(p) => (p.index % 2 ? "unset" : p.theme.space[4])}px;
+ }
+`;
+
+const LabelContainer = styled(Flex).attrs({
+ p: 2,
+ flexDirection: "column",
+})`
+ background-image: var(--filter-noise);
+ background-repeat: repeat;
+ mix-blend-mode: difference;
+`;
+
+type TokenData = {
+ id: number;
+ metadata: {
+ image: string;
+ name: string;
+ attributes: any[];
+ };
+};
+
+const BlobzRow = memo(({ blobz, id }: { blobz: TokenData; id: number }) => {
+ const router = useRouter();
+
+ const prefix = id % 2 ? ">>>" : "";
+ const suffix = id % 2 ? "" : "<<<";
+
+ return (
+
+
+
+
+
+ {`${prefix} ${blobz.metadata.name} ${suffix}`}
+ {`${prefix} Main color #${blobz.metadata.attributes[0].value} ${suffix}`}
+ {`${prefix} Accent color #${blobz.metadata.attributes[1].value} ${suffix}`}
+
+
+
+ router.push(`/nft/blobbz/${blobz.id}`)}
+ />
+
+
+ );
+});
+
+BlobzRow.displayName = "BlobzRow";
+
+const fetcher = (url: string) => fetch(url).then((r) => r.json());
+
+function BlobzCollection() {
+ const [fetchedPages, setFetchedPages] = useState([0]);
+ const [page, setPage] = useState(0);
+ const [offset, setOffset] = useState(0);
+ const [loading, setLoading] = useState(true);
+ const [collectionMetadata, setCollectionMetadata] = useState({
+ totalSupply: 0,
+ remaining: 0,
+ limit: 0,
+ });
+ const [indexedList, setIndexedList] = useState>({});
+ const { data, error } = useSWR("/api/blobbz?page=" + page, fetcher);
+
+ const collection: TokenData[] = useMemo(() => {
+ const iL: TokenData[] = Object.values(indexedList);
+ iL.reverse();
+ return iL.slice(offset * 5, offset * 5 + 5);
+ }, [indexedList, offset]);
+
+ useEffect(() => {
+ if (data) {
+ const indexedData = data.data.reduce(
+ (acc: Record, d: TokenData) => {
+ acc[d.id] = d;
+ return acc;
+ },
+ {}
+ );
+
+ setIndexedList((state) => ({ ...state, ...indexedData }));
+ setCollectionMetadata({
+ totalSupply: data.totalSupply,
+ remaining: data.remaining,
+ limit: data.limit,
+ });
+ setFetchedPages((s) => [...s, data.page]);
+ setLoading(false);
+ }
+ }, [data]);
+
+ const nextPage = useCallback(() => {
+ if (!fetchedPages.includes(offset + 1)) {
+ setPage(offset + 1);
+ }
+ setOffset(offset + 1);
+ window.scrollTo(0, 0);
+ }, [fetchedPages, offset]);
+ const previousPage = useCallback(() => {
+ setOffset(offset - 1);
+ window.scrollTo(0, 0);
+ }, [offset]);
+
+ return (
+ <>
+
+ WEBBZ - BLOBZBLOCKZ
+
+
+
+
+
+
+
+ {collection.length <= 0 ? null : (
+ <>
+ {" "}
+ {collection.map((blobz, i) => (
+
+ ))}
+
+
+
+ >
+ )}
+
+
+
+ {offset > 0 ? (
+
+ ) : (
+
+ )}
+ {collectionMetadata.limit <= 0 ? (
+
+ ) : (
+
+ )}
+
+
+ {loading ? (
+ LOADING {">>>"}
+ ) : error ? (
+ ERROR {">>>"}
+ ) : (
+
+ {`BLOBZ-BLOCKZ >>> TOTAL SUPPLY: ${collectionMetadata.totalSupply} >>> BLOBZ-BLOCKZ >>> REMAINING: ${collectionMetadata.remaining} >>> `}
+
+ )}
+
+
+ >
+ );
+}
+
+export default memo(BlobzCollection);
diff --git a/pages/nft/blobbz/index.tsx b/pages/nft/blobbz/index.tsx
index 981c5a6..b2c81ae 100644
--- a/pages/nft/blobbz/index.tsx
+++ b/pages/nft/blobbz/index.tsx
@@ -1,5 +1,5 @@
import Head from "next/head";
-import { ConnectButton, TopBanner, Articles } from "../../../src/components";
+import { ConnectButton, Articles } from "../../../src/components";
import abi from "./abi-blobz.json";
@@ -24,14 +24,7 @@ export default function BlobzHome() {
}
-
-
+
>> checkout the code",
+ label: "checkout the code",
},
+ link: [
+ {
+ href: "/nft/blobbz/collection",
+ label: ">>> view collection",
+ },
+ ],
},
{
id: "blockz-blockz-2",
@@ -67,7 +66,7 @@ export default function BlobzHome() {
The mix between the random shapes and the random positions makes it so that the animations that arent randomized apear to be.
However animation timings are overlapped and are set during the minting process making so that the total animation loop has an average duration of arroung 30mins.
- This work is heavily inspired by lava lamps and abtract artists such as yaoi kusama
+ This work is heavily inspired by lava lamps and abtract artists such as yayoi kusama
(theres even a subtheme thats a direct reference in colors and shape to her pumpkin series).
Feel free to check out the whole collection on opensea or mint one here!
@@ -75,12 +74,19 @@ export default function BlobzHome() {
image: "/images/yaoi-kusama.jpg",
href: {
href: "https://opensea.io/collection/blobz-blockz",
- label: ">>> View on opensea",
+ label: "View on opensea",
},
},
],
}}
/>
+
>
);
}
diff --git a/pages/nft/index.tsx b/pages/nft/index.tsx
index eea8240..a30e04d 100644
--- a/pages/nft/index.tsx
+++ b/pages/nft/index.tsx
@@ -34,7 +34,7 @@ export default function NftHome() {
title: "Blobz-Blockz",
subTitle: "Generative blobz on the blockchain",
content: `A series of 512 blobz fully generated on chain.
- Inspired by mathmos©️ lava lamps space-age designs and yaoi kusama ❤️
+ Inspired by mathmos©️ lava lamps space-age designs and yayoi kusama ❤️
This is the most technically advanced project i did so far, mixing
pseudo random svg path generation using trigonometry on solidity,
@@ -47,12 +47,18 @@ export default function NftHome() {
Just check the collection and mint some!
`,
- image: "/images/blobz-banner.svg",
+ image: "/images/blobzzz.svg",
noFilterImage: true,
- link: {
- href: "/nft/blobbz",
- label: ">>> view collection",
- },
+ link: [
+ {
+ href: "/nft/blobbz",
+ label: ">>> mint it",
+ },
+ {
+ href: "/nft/blobbz/collection",
+ label: ">>> view collection",
+ },
+ ],
hashtags: ["artz", "solidity", "svg"],
},
{
diff --git a/public/fallback-development.js b/public/fallback-development.js
new file mode 100644
index 0000000..9ffd22d
--- /dev/null
+++ b/public/fallback-development.js
@@ -0,0 +1,36 @@
+/******/ (() => { // webpackBootstrap
+/******/ "use strict";
+var __webpack_exports__ = {};
+
+
+self.fallback = async request => {
+ // https://developer.mozilla.org/en-US/docs/Web/API/RequestDestination
+ switch (request.destination) {
+ case 'document':
+ if (true) return caches.match("/_offline", {
+ ignoreSearch: true
+ });
+
+ case 'image':
+ if (false) {}
+
+ case 'audio':
+ if (false) {}
+
+ case 'video':
+ if (false) {}
+
+ case 'font':
+ if (false) {}
+
+ case '':
+ if (false) {}
+
+ default:
+ return Response.error();
+ }
+
+ ;
+};
+/******/ })()
+;
\ No newline at end of file
diff --git a/public/fallback-ws8Jg6LZdXq_DsMmpobLI.js b/public/fallback-ws8Jg6LZdXq_DsMmpobLI.js
deleted file mode 100644
index 541a726..0000000
--- a/public/fallback-ws8Jg6LZdXq_DsMmpobLI.js
+++ /dev/null
@@ -1 +0,0 @@
-(()=>{"use strict";self.fallback=async e=>"document"===e.destination?caches.match("/_offline",{ignoreSearch:!0}):Response.error()})();
\ No newline at end of file
diff --git a/public/images/blobzzz.svg b/public/images/blobzzz.svg
index 39b0fa7..0a0cf92 100644
--- a/public/images/blobzzz.svg
+++ b/public/images/blobzzz.svg
@@ -22,11 +22,11 @@
--cc5: hsl(262, 79%, 65%);
--cc6: hsl(280, 78%, 69%);
--bg: hsl(280, 50%, 0%);
- --t1: 26.8s;
- --t2: 29.2s;
- --t3: 23s;
- --t4: 67s;
- --t5: 123.456s;
+ --t1: 5s;
+ --t2: 10s;
+ --t3: 15s;
+ --t4: 20s;
+ --t5: 30s;
--b0: 0;
--b1: 1;
--b2: 1;
@@ -49,6 +49,7 @@
* {
transform-origin: 50% 50%;
+ will-change: transform;
}
svg {
diff --git a/public/sw.js b/public/sw.js
index e997551..30362ba 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -1 +1,122 @@
-if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise((s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()})).then((()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e})));self.define=(n,i)=>{const r=e||("document"in self?document.currentScript.src:"")||location.href;if(s[r])return;let c={};const o=e=>a(e,r),t={module:{uri:r},exports:c,require:o};s[r]=Promise.all(n.map((e=>t[e]||o(e)))).then((e=>(i(...e),c)))}}define(["./workbox-1846d813"],(function(e){"use strict";importScripts("fallback-ws8Jg6LZdXq_DsMmpobLI.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/chunks/framework-91d7f78b5b4003c8.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/main-91ce97a9a575995d.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/pages/404-b28f44eb49fc8845.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/pages/_app-4af5f75e5829aedf.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/pages/_error-2280fa386d040b66.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/pages/_offline-04a3fbfd683367e1.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/pages/about-7dfc9927b5104366.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/pages/index-f72dc8cad0f73e36.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/pages/nft-adcc4e593ff6bc4b.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/pages/nft/blobbz-863dbf58e61a3b0b.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/pages/nft/blobbz/%5Bid%5D-04deea994ceb3b6c.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/polyfills-5cd94c89d3acac5f.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/chunks/webpack-c66a1cc0a3689c22.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/css/d59d4d36926057db.css",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/ws8Jg6LZdXq_DsMmpobLI/_buildManifest.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/ws8Jg6LZdXq_DsMmpobLI/_middlewareManifest.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_next/static/ws8Jg6LZdXq_DsMmpobLI/_ssgManifest.js",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/_offline",revision:"ws8Jg6LZdXq_DsMmpobLI"},{url:"/cursor.ico",revision:"f6288794c2d952fbb913d1b4b155c5e8"},{url:"/cursor.png",revision:"c2f3f7bbe612698a89a4e7677d549ea9"},{url:"/favicon.ico",revision:"21023a60f24226aeb7785ce0773b95e0"},{url:"/fonts/5by7.bold.woff2",revision:"2207d579e16631d3e37e8c1d84725066"},{url:"/fonts/5by7.regular.ttf",revision:"2929632ab13c1cd96bc5f3e2ae948e8e"},{url:"/fonts/5by7.regular.woff2",revision:"f5528a4a03edac5e8a62107d2ef9ce31"},{url:"/fonts/Excelorate-Font.otf",revision:"35517a7a26ad9ea16acdb743d438f1f4"},{url:"/fonts/FuturaLT-Bold.woff2",revision:"b5daf4a5022793ea90a83eca29906983"},{url:"/fonts/FuturaLT-Condensed.woff2",revision:"be37a4d7d13b818f53aa47ae924dec1d"},{url:"/fonts/FuturaLT-ExtraBoldOblique.woff2",revision:"eab35159b40c806bc25fcfc4ebdf1ad6"},{url:"/fonts/FuturaLT-Light.woff2",revision:"cfa129e21925665e9cf3dd5977f6b043"},{url:"/fonts/golden-girdle.regular.ttf",revision:"f364d1eb7bd84c7adac33a6a72d5ff1e"},{url:"/fonts/tinier.regular.ttf",revision:"559f8413b60fa2aa9122b569eaeb10f2"},{url:"/fonts/tinier.regular.woff2",revision:"b10687ced7e8e20f9cffa43740490b63"},{url:"/fonts/tiny.regular.ttf",revision:"1887733ccba8e939f084405045abc719"},{url:"/fonts/tiny.regular.woff2",revision:"318ad8e84eefb74bf38473c6339e9915"},{url:"/iconx128.png",revision:"82de758018d136a13370d54e80536256"},{url:"/iconx144.png",revision:"0b7c83ec3a784f68e0f6476be9493d0d"},{url:"/iconx152.png",revision:"c658638af5207d2c97d91120af2be196"},{url:"/iconx16.png",revision:"8117b181cdc7e29882f408d8a9bd1e28"},{url:"/iconx167.png",revision:"08e4fa910d2ecfdd347444fac7b9b1e1"},{url:"/iconx180.png",revision:"346d1bc47e0c97a1a043b833fe4f532d"},{url:"/iconx192.png",revision:"5f0051f2c711bec568f6baf1ffdb08b8"},{url:"/iconx32.png",revision:"ec9b5e124c798af8ad84f2cd50ce9649"},{url:"/iconx384.png",revision:"cd4c62b5d52a5365297a234c62f71d0c"},{url:"/iconx512.png",revision:"52b05f3778300e31bea7c99c883de95b"},{url:"/iconx72.png",revision:"cf3da56270cd700a6fe59faa7414340b"},{url:"/iconx96.png",revision:"5df34a93f84fe8354974db773620ae46"},{url:"/images/404.gif",revision:"b8716eceb0b601e750e0e66d6730ca7b"},{url:"/images/about-me.jpeg",revision:"e70f482ea698804d5b74153a0186ada8"},{url:"/images/bg-noise-dark.png",revision:"b486c931814e35bfbd9b1b59d1b2a102"},{url:"/images/bg-noise-light.png",revision:"c9a0b2270e9db7c99226ecb3cc817c6d"},{url:"/images/bg-noise.png",revision:"49adf3b8743db95da5cefd80daaef92d"},{url:"/images/blobz-banner.png",revision:"594afb642d4ac18c52b2051eee5bd9f2"},{url:"/images/blobz-banner.svg",revision:"31ad0967101c7ed95664908f94defe52"},{url:"/images/blobz.gif",revision:"eedbc8ffbb4d40df781f6e76b38e2494"},{url:"/images/blobzzz.png",revision:"c39e10862a47f9d38f093fe954493c27"},{url:"/images/blobzzz.svg",revision:"6c7fba913c9c9f9045e9bb961bcd442a"},{url:"/images/blobzzzz.png",revision:"e8b98c435c8b3e30dd77a0a5f2f826f7"},{url:"/images/lava-lamps.jpg",revision:"5a441e3a58432728ca8068704dc6cc09"},{url:"/images/space-invader.gif",revision:"e3b130839b6366133896bcf90b987ef7"},{url:"/images/svgt.png",revision:"76236942aa1de570aeae2e89428d391e"},{url:"/images/tetris-99.gif",revision:"2e17e4453adc11246cef9184d8fa5c0f"},{url:"/images/tetris-99.png",revision:"46a0203f5778e1b5c556e6e73f9dc8cb"},{url:"/images/tetris-99.svg",revision:"24c2ec3885ee1930bac55b7f3ca80dee"},{url:"/images/weeb.gif",revision:"36b1c7883f71cd751e4a0052fc361ab3"},{url:"/images/yaoi-kusama.jpg",revision:"571a48a343a0664548de8cc69a037903"},{url:"/locales/en/common.json",revision:"76b38ec518e642da264ea75dc38c5f13"},{url:"/locales/fr/common.json",revision:"76b38ec518e642da264ea75dc38c5f13"},{url:"/manifest.json",revision:"a6f3164baaeb3d608810770ade2e1e30"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:a,state:n})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s},{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute((({url:e})=>{const s=self.origin===e.origin;if(!s)return!1;const a=e.pathname;return console.log("SW",a,s),!!a.startsWith("/api/blobbz")}),new e.CacheFirst({cacheName:"blobz-cache",plugins:[new e.ExpirationPlugin({maxEntries:512,maxAgeSeconds:604800}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute((({url:e})=>!(self.origin===e.origin)),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET")}));
+/**
+ * Copyright 2018 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// If the loader is already loaded, just stop.
+if (!self.define) {
+ let registry = {};
+
+ // Used for `eval` and `importScripts` where we can't get script URL by other means.
+ // In both cases, it's safe to use a global var because those functions are synchronous.
+ let nextDefineUri;
+
+ const singleRequire = (uri, parentUri) => {
+ uri = new URL(uri + ".js", parentUri).href;
+ return registry[uri] || (
+
+ new Promise(resolve => {
+ if ("document" in self) {
+ const script = document.createElement("script");
+ script.src = uri;
+ script.onload = resolve;
+ document.head.appendChild(script);
+ } else {
+ nextDefineUri = uri;
+ importScripts(uri);
+ resolve();
+ }
+ })
+
+ .then(() => {
+ let promise = registry[uri];
+ if (!promise) {
+ throw new Error(`Module ${uri} didn’t register its module`);
+ }
+ return promise;
+ })
+ );
+ };
+
+ self.define = (depsNames, factory) => {
+ const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
+ if (registry[uri]) {
+ // Module is already loading or loaded.
+ return;
+ }
+ let exports = {};
+ const require = depUri => singleRequire(depUri, uri);
+ const specialDeps = {
+ module: { uri },
+ exports,
+ require
+ };
+ registry[uri] = Promise.all(depsNames.map(
+ depName => specialDeps[depName] || require(depName)
+ )).then(deps => {
+ factory(...deps);
+ return exports;
+ });
+ };
+}
+define(['./workbox-9a8b0a38'], (function (workbox) { 'use strict';
+
+ /**
+ * Welcome to your Workbox-powered service worker!
+ *
+ * You'll need to register this file in your web app.
+ * See https://goo.gl/nhQhGp
+ *
+ * The rest of the code is auto-generated. Please don't update this file
+ * directly; instead, make changes to your Workbox build configuration
+ * and re-run your build process.
+ * See https://goo.gl/2aRDsh
+ */
+
+ importScripts("fallback-development.js");
+ self.skipWaiting();
+ workbox.clientsClaim();
+ workbox.registerRoute("/", new workbox.NetworkFirst({
+ "cacheName": "start-url",
+ plugins: [{
+ cacheWillUpdate: async ({
+ request,
+ response,
+ event,
+ state
+ }) => {
+ if (response && response.type === 'opaqueredirect') {
+ return new Response(response.body, {
+ status: 200,
+ statusText: 'OK',
+ headers: response.headers
+ });
+ }
+
+ return response;
+ }
+ }, {
+ handlerDidError: async ({
+ request
+ }) => self.fallback(request)
+ }]
+ }), 'GET');
+ workbox.registerRoute(/.*/i, new workbox.NetworkOnly({
+ "cacheName": "dev",
+ plugins: [{
+ handlerDidError: async ({
+ request
+ }) => self.fallback(request)
+ }]
+ }), 'GET');
+
+}));
+//# sourceMappingURL=sw.js.map
diff --git a/public/sw.js.map b/public/sw.js.map
new file mode 100644
index 0000000..3fe61ce
--- /dev/null
+++ b/public/sw.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"sw.js","sources":["../../../../tmp/b56e4c45a85c98e3bdcb99743de192a1/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/home/lbarreto/Apps/w3bz/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/home/lbarreto/Apps/w3bz/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/home/lbarreto/Apps/w3bz/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/home/lbarreto/Apps/w3bz/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \"fallback-development.js\"\n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({request, response, event, state}) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, {status: 200, statusText: 'OK', headers: response.headers}); } return response; } }, { handlerDidError: async ({request}) => self.fallback(request) }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [{ handlerDidError: async ({request}) => self.fallback(request) }] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","handlerDidError","fallback","workbox_strategies_NetworkOnly"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG8H;EAC9H;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAGAA,aAAa,CACX,yBADW,CAAb;EAUAC,IAAI,CAACC,WAAL;AAEAC,sBAAyB;AAIzBC,uBAA6B,CAAC,GAAD,EAAM,IAAIC,oBAAJ,CAAoC;EAAE,eAAY,WAAd;EAA2BC,EAAAA,OAAO,EAAE,CAAC;EAAEC,IAAAA,eAAe,EAAE,OAAO;EAACC,MAAAA,OAAD;EAAUC,MAAAA,QAAV;EAAoBC,MAAAA,KAApB;EAA2BC,MAAAA;EAA3B,KAAP,KAA6C;EAAE,UAAIF,QAAQ,IAAIA,QAAQ,CAACG,IAAT,KAAkB,gBAAlC,EAAoD;EAAE,eAAO,IAAIC,QAAJ,CAAaJ,QAAQ,CAACK,IAAtB,EAA4B;EAACC,UAAAA,MAAM,EAAE,GAAT;EAAcC,UAAAA,UAAU,EAAE,IAA1B;EAAgCC,UAAAA,OAAO,EAAER,QAAQ,CAACQ;EAAlD,SAA5B,CAAP;EAAiG;;EAAC,aAAOR,QAAP;EAAkB;EAA5O,GAAD,EAAiP;EAAES,IAAAA,eAAe,EAAE,OAAO;EAACV,MAAAA;EAAD,KAAP,KAAqBP,IAAI,CAACkB,QAAL,CAAcX,OAAd;EAAxC,GAAjP;EAApC,CAApC,CAAN,EAAqY,KAArY,CAA7B;AACAJ,uBAA6B,CAAC,KAAD,EAAQ,IAAIgB,mBAAJ,CAAmC;EAAE,eAAY,KAAd;EAAqBd,EAAAA,OAAO,EAAE,CAAC;EAAEY,IAAAA,eAAe,EAAE,OAAO;EAACV,MAAAA;EAAD,KAAP,KAAqBP,IAAI,CAACkB,QAAL,CAAcX,OAAd;EAAxC,GAAD;EAA9B,CAAnC,CAAR,EAAgJ,KAAhJ,CAA7B;;"}
\ No newline at end of file
diff --git a/public/workbox-1846d813.js b/public/workbox-1846d813.js
deleted file mode 100644
index 6a6a2d1..0000000
--- a/public/workbox-1846d813.js
+++ /dev/null
@@ -1 +0,0 @@
-define(["exports"],(function(t){"use strict";try{self["workbox:core:6.4.1"]&&_()}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:6.4.1"]&&_()}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class r{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s}setCatchHandler(t){this.catchHandler=n(t)}}class i extends r{constructor(t,e,s){super((({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)}),e,s)}}class a{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",(t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map((e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t})})));t.waitUntil(s),t.ports&&t.ports[0]&&s.then((()=>t.ports[0].postMessage(!0)))}}))}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:i}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let a=i&&i.handler;const o=t.method;if(!a&&this.i.has(o)&&(a=this.i.get(o)),!a)return;let c;try{c=a.handle({url:s,request:t,event:e,params:r})}catch(t){c=Promise.reject(t)}const h=i&&i.catchHandler;return c instanceof Promise&&(this.o||h)&&(c=c.catch((async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:r})}catch(t){t instanceof Error&&(n=t)}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n}))),c}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const r=this.t.get(s.method)||[];for(const i of r){let r;const a=i.match({url:t,sameOrigin:e,request:s,event:n});if(a)return r=a,(Array.isArray(r)&&0===r.length||a.constructor===Object&&0===Object.keys(a).length||"boolean"==typeof a)&&(r=void 0),{route:i,params:r}}return{}}setDefaultHandler(t,e="GET"){this.i.set(e,n(t))}setCatchHandler(t){this.o=n(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1)}}let o;const c=()=>(o||(o=new a,o.addFetchListener(),o.addCacheListener()),o);function h(t,e,n){let a;if("string"==typeof t){const s=new URL(t,location.href);a=new r((({url:t})=>t.href===s.href),e,n)}else if(t instanceof RegExp)a=new i(t,e,n);else if("function"==typeof t)a=new r(t,e,n);else{if(!(t instanceof r))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});a=t}return c().registerRoute(a),a}try{self["workbox:strategies:6.4.1"]&&_()}catch(t){}const u={cacheWillUpdate:async({response:t})=>200===t.status||0===t.status?t:null},l={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},f=t=>[l.prefix,t,l.suffix].filter((t=>t&&t.length>0)).join("-"),w=t=>t||f(l.precache),d=t=>t||f(l.runtime);function p(t,e){const s=new URL(t);for(const t of e)s.searchParams.delete(t);return s.href}class y{constructor(){this.promise=new Promise(((t,e)=>{this.resolve=t,this.reject=e}))}}const g=new Set;function m(t){return"string"==typeof t?new Request(t):t}class R{constructor(t,e){this.h={},Object.assign(this,e),this.event=e.event,this.u=t,this.l=new y,this.p=[],this.g=[...t.plugins],this.m=new Map;for(const t of this.g)this.m.set(t,{});this.event.waitUntil(this.l.promise)}async fetch(t){const{event:e}=this;let n=m(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t}const r=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e})}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message})}const i=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.u.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:i,response:t});return t}catch(t){throw r&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:r.clone(),request:i.clone()}),t}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e}async cacheMatch(t){const e=m(t);let s;const{cacheName:n,matchOptions:r}=this.u,i=await this.getCacheKey(e,"read"),a=Object.assign(Object.assign({},r),{cacheName:n});s=await caches.match(i,a);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:r,cachedResponse:s,request:i,event:this.event})||void 0;return s}async cachePut(t,e){const n=m(t);var r;await(r=0,new Promise((t=>setTimeout(t,r))));const i=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(a=i.url,new URL(String(a),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var a;const o=await this.R(e);if(!o)return!1;const{cacheName:c,matchOptions:h}=this.u,u=await self.caches.open(c),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const r=p(e.url,s);if(e.url===r)return t.match(e,n);const i=Object.assign(Object.assign({},n),{ignoreSearch:!0}),a=await t.keys(e,i);for(const e of a)if(r===p(e.url,s))return t.match(e,n)}(u,i.clone(),["__WB_REVISION__"],h):null;try{await u.put(i,l?o.clone():o)}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of g)await t()}(),t}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:c,oldResponse:f,newResponse:o.clone(),request:i,event:this.event});return!0}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.h[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=m(await t({mode:e,request:n,event:this.event,params:this.params}));this.h[s]=n}return this.h[s]}hasCallback(t){for(const e of this.u.plugins)if(t in e)return!0;return!1}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e)}*iterateCallbacks(t){for(const e of this.u.plugins)if("function"==typeof e[t]){const s=this.m.get(e),n=n=>{const r=Object.assign(Object.assign({},n),{state:s});return e[t](r)};yield n}}waitUntil(t){return this.p.push(t),t}async doneWaiting(){let t;for(;t=this.p.shift();)await t}destroy(){this.l.resolve(null)}async R(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e}}class v{constructor(t={}){this.cacheName=d(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions}handle(t){const[e]=this.handleAll(t);return e}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,r=new R(this,{event:e,request:s,params:n}),i=this.v(r,s,e);return[i,this.q(i,r,s,e)]}async v(t,e,n){let r;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(r=await this.D(e,t),!r||"error"===r.type)throw new s("no-response",{url:e.url})}catch(s){if(s instanceof Error)for(const i of t.iterateCallbacks("handlerDidError"))if(r=await i({error:s,event:n,request:e}),r)break;if(!r)throw s}for(const s of t.iterateCallbacks("handlerWillRespond"))r=await s({event:n,request:e,response:r});return r}async q(t,e,s,n){let r,i;try{r=await t}catch(i){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:r}),await e.doneWaiting()}catch(t){t instanceof Error&&(i=t)}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:r,error:i}),e.destroy(),i)throw i}}function b(t){t.then((()=>{}))}function q(){return q=Object.assign||function(t){for(var e=1;e(t[e]=s,!0),has:(t,e)=>t instanceof IDBTransaction&&("done"===e||"store"===e)||e in t};function O(t){return t!==IDBDatabase.prototype.transaction||"objectStoreNames"in IDBTransaction.prototype?(U||(U=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(t)?function(...e){return t.apply(B(this),e),k(x.get(this))}:function(...e){return k(t.apply(B(this),e))}:function(e,...s){const n=t.call(B(this),e,...s);return I.set(n,e.sort?e.sort():[e]),k(n)}}function T(t){return"function"==typeof t?O(t):(t instanceof IDBTransaction&&function(t){if(L.has(t))return;const e=new Promise(((e,s)=>{const n=()=>{t.removeEventListener("complete",r),t.removeEventListener("error",i),t.removeEventListener("abort",i)},r=()=>{e(),n()},i=()=>{s(t.error||new DOMException("AbortError","AbortError")),n()};t.addEventListener("complete",r),t.addEventListener("error",i),t.addEventListener("abort",i)}));L.set(t,e)}(t),e=t,(D||(D=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])).some((t=>e instanceof t))?new Proxy(t,N):t);var e}function k(t){if(t instanceof IDBRequest)return function(t){const e=new Promise(((e,s)=>{const n=()=>{t.removeEventListener("success",r),t.removeEventListener("error",i)},r=()=>{e(k(t.result)),n()},i=()=>{s(t.error),n()};t.addEventListener("success",r),t.addEventListener("error",i)}));return e.then((e=>{e instanceof IDBCursor&&x.set(e,t)})).catch((()=>{})),E.set(e,t),e}(t);if(C.has(t))return C.get(t);const e=T(t);return e!==t&&(C.set(t,e),E.set(e,t)),e}const B=t=>E.get(t);const P=["get","getKey","getAll","getAllKeys","count"],M=["put","add","delete","clear"],W=new Map;function j(t,e){if(!(t instanceof IDBDatabase)||e in t||"string"!=typeof e)return;if(W.get(e))return W.get(e);const s=e.replace(/FromIndex$/,""),n=e!==s,r=M.includes(s);if(!(s in(n?IDBIndex:IDBObjectStore).prototype)||!r&&!P.includes(s))return;const i=async function(t,...e){const i=this.transaction(t,r?"readwrite":"readonly");let a=i.store;return n&&(a=a.index(e.shift())),(await Promise.all([a[s](...e),r&&i.done]))[0]};return W.set(e,i),i}N=(t=>q({},t,{get:(e,s,n)=>j(e,s)||t.get(e,s,n),has:(e,s)=>!!j(e,s)||t.has(e,s)}))(N);try{self["workbox:expiration:6.4.1"]&&_()}catch(t){}const S="cache-entries",K=t=>{const e=new URL(t,location.href);return e.hash="",e.href};class A{constructor(t){this.U=null,this._=t}L(t){const e=t.createObjectStore(S,{keyPath:"id"});e.createIndex("cacheName","cacheName",{unique:!1}),e.createIndex("timestamp","timestamp",{unique:!1})}I(t){this.L(t),this._&&function(t,{blocked:e}={}){const s=indexedDB.deleteDatabase(t);e&&s.addEventListener("blocked",(()=>e())),k(s).then((()=>{}))}(this._)}async setTimestamp(t,e){const s={url:t=K(t),timestamp:e,cacheName:this._,id:this.C(t)},n=(await this.getDb()).transaction(S,"readwrite",{durability:"relaxed"});await n.store.put(s),await n.done}async getTimestamp(t){const e=await this.getDb(),s=await e.get(S,this.C(t));return null==s?void 0:s.timestamp}async expireEntries(t,e){const s=await this.getDb();let n=await s.transaction(S).store.index("timestamp").openCursor(null,"prev");const r=[];let i=0;for(;n;){const s=n.value;s.cacheName===this._&&(t&&s.timestamp=e?r.push(n.value):i++),n=await n.continue()}const a=[];for(const t of r)await s.delete(S,t.id),a.push(t.url);return a}C(t){return this._+"|"+K(t)}async getDb(){return this.U||(this.U=await function(t,e,{blocked:s,upgrade:n,blocking:r,terminated:i}={}){const a=indexedDB.open(t,e),o=k(a);return n&&a.addEventListener("upgradeneeded",(t=>{n(k(a.result),t.oldVersion,t.newVersion,k(a.transaction))})),s&&a.addEventListener("blocked",(()=>s())),o.then((t=>{i&&t.addEventListener("close",(()=>i())),r&&t.addEventListener("versionchange",(()=>r()))})).catch((()=>{})),o}("workbox-expiration",1,{upgrade:this.I.bind(this)})),this.U}}class F{constructor(t,e={}){this.N=!1,this.O=!1,this.T=e.maxEntries,this.k=e.maxAgeSeconds,this.B=e.matchOptions,this._=t,this.P=new A(t)}async expireEntries(){if(this.N)return void(this.O=!0);this.N=!0;const t=this.k?Date.now()-1e3*this.k:0,e=await this.P.expireEntries(t,this.T),s=await self.caches.open(this._);for(const t of e)await s.delete(t,this.B);this.N=!1,this.O&&(this.O=!1,b(this.expireEntries()))}async updateTimestamp(t){await this.P.setTimestamp(t,Date.now())}async isURLExpired(t){if(this.k){const e=await this.P.getTimestamp(t),s=Date.now()-1e3*this.k;return void 0===e||er||e&&e<0)throw new s("range-not-satisfiable",{size:r,end:n,start:e});let i,a;return void 0!==e&&void 0!==n?(i=e,a=n+1):void 0!==e&&void 0===n?(i=e,a=r):void 0!==n&&void 0===e&&(i=r-n,a=r),{start:i,end:a}}(i,r.start,r.end),o=i.slice(a.start,a.end),c=o.size,h=new Response(o,{status:206,statusText:"Partial Content",headers:e.headers});return h.headers.set("Content-Length",String(c)),h.headers.set("Content-Range",`bytes ${a.start}-${a.end-1}/${i.size}`),h}catch(t){return new Response("",{status:416,statusText:"Range Not Satisfiable"})}}function $(t,e){const s=e();return t.waitUntil(s),s}try{self["workbox:precaching:6.4.1"]&&_()}catch(t){}function z(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const r=new URL(n,location.href),i=new URL(n,location.href);return r.searchParams.set("__WB_REVISION__",e),{cacheKey:r.href,url:i.href}}class G{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t)},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t)}return s}}}class V{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.M.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t},this.M=t}}let J,Q;async function X(t,e){let n=null;if(t.url){n=new URL(t.url).origin}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const r=t.clone(),i={headers:new Headers(r.headers),status:r.status,statusText:r.statusText},a=e?e(i):i,o=function(){if(void 0===J){const t=new Response("");if("body"in t)try{new Response(t.body),J=!0}catch(t){J=!1}J=!1}return J}()?r.body:await r.blob();return new Response(o,a)}class Y extends v{constructor(t={}){t.cacheName=w(t.cacheName),super(t),this.W=!1!==t.fallbackToNetwork,this.plugins.push(Y.copyRedirectedCacheableResponsesPlugin)}async D(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.j(t,e):await this.S(t,e))}async S(t,e){let n;const r=e.params||{};if(!this.W)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=r.integrity,i=t.integrity,a=!i||i===s;n=await e.fetch(new Request(t,{integrity:i||s})),s&&a&&(this.K(),await e.cachePut(t,n.clone()))}return n}async j(t,e){this.K();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n}K(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==Y.copyRedirectedCacheableResponsesPlugin&&(n===Y.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(Y.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1)}}Y.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},Y.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await X(t):t};class Z{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.A=new Map,this.F=new Map,this.H=new Map,this.u=new Y({cacheName:w(t),plugins:[...e,new V({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.u}precache(t){this.addToCacheList(t),this.$||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.$=!0)}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:r}=z(n),i="string"!=typeof n&&n.revision?"reload":"default";if(this.A.has(r)&&this.A.get(r)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.A.get(r),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.H.has(t)&&this.H.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:r});this.H.set(t,n.integrity)}if(this.A.set(r,t),this.F.set(r,i),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}install(t){return $(t,(async()=>{const e=new G;this.strategy.plugins.push(e);for(const[e,s]of this.A){const n=this.H.get(s),r=this.F.get(e),i=new Request(e,{integrity:n,cache:r,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:i,event:t}))}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n}}))}activate(t){return $(t,(async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.A.values()),n=[];for(const r of e)s.has(r.url)||(await t.delete(r),n.push(r.url));return{deletedURLs:n}}))}getURLsToCacheKeys(){return this.A}getCachedURLs(){return[...this.A.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.A.get(e.href)}getIntegrityForCacheKey(t){return this.H.get(t)}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s)}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s))}}const tt=()=>(Q||(Q=new Z),Q);class et extends r{constructor(t,e){super((({request:s})=>{const n=t.getURLsToCacheKeys();for(const r of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:r}={}){const i=new URL(t,location.href);i.hash="",yield i.href;const a=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some((t=>t.test(s)))&&t.searchParams.delete(s);return t}(i,e);if(yield a.href,s&&a.pathname.endsWith("/")){const t=new URL(a.href);t.pathname+=s,yield t.href}if(n){const t=new URL(a.href);t.pathname+=".html",yield t.href}if(r){const t=r({url:i});for(const e of t)yield e.href}}(s.url,e)){const e=n.get(r);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)}}}}),t.strategy)}}t.CacheFirst=class extends v{async D(t,e){let n,r=await e.cacheMatch(t);if(!r)try{r=await e.fetchAndCachePut(t)}catch(t){t instanceof Error&&(n=t)}if(!r)throw new s("no-response",{url:t.url,error:n});return r}},t.ExpirationPlugin=class{constructor(t={}){this.cachedResponseWillBeUsed=async({event:t,request:e,cacheName:s,cachedResponse:n})=>{if(!n)return null;const r=this.G(n),i=this.V(s);b(i.expireEntries());const a=i.updateTimestamp(e.url);if(t)try{t.waitUntil(a)}catch(t){}return r?n:null},this.cacheDidUpdate=async({cacheName:t,request:e})=>{const s=this.V(t);await s.updateTimestamp(e.url),await s.expireEntries()},this.J=t,this.k=t.maxAgeSeconds,this.X=new Map,t.purgeOnQuotaError&&function(t){g.add(t)}((()=>this.deleteCacheAndMetadata()))}V(t){if(t===d())throw new s("expire-custom-caches-only");let e=this.X.get(t);return e||(e=new F(t,this.J),this.X.set(t,e)),e}G(t){if(!this.k)return!0;const e=this.Y(t);if(null===e)return!0;return e>=Date.now()-1e3*this.k}Y(t){if(!t.headers.has("date"))return null;const e=t.headers.get("date"),s=new Date(e).getTime();return isNaN(s)?null:s}async deleteCacheAndMetadata(){for(const[t,e]of this.X)await self.caches.delete(t),await e.delete();this.X=new Map}},t.NetworkFirst=class extends v{constructor(t={}){super(t),this.plugins.some((t=>"cacheWillUpdate"in t))||this.plugins.unshift(u),this.Z=t.networkTimeoutSeconds||0}async D(t,e){const n=[],r=[];let i;if(this.Z){const{id:s,promise:a}=this.tt({request:t,logs:n,handler:e});i=s,r.push(a)}const a=this.et({timeoutId:i,request:t,logs:n,handler:e});r.push(a);const o=await e.waitUntil((async()=>await e.waitUntil(Promise.race(r))||await a)());if(!o)throw new s("no-response",{url:t.url});return o}tt({request:t,logs:e,handler:s}){let n;return{promise:new Promise((e=>{n=setTimeout((async()=>{e(await s.cacheMatch(t))}),1e3*this.Z)})),id:n}}async et({timeoutId:t,request:e,logs:s,handler:n}){let r,i;try{i=await n.fetchAndCachePut(e)}catch(t){t instanceof Error&&(r=t)}return t&&clearTimeout(t),!r&&i||(i=await n.cacheMatch(e)),i}},t.RangeRequestsPlugin=class{constructor(){this.cachedResponseWillBeUsed=async({request:t,cachedResponse:e})=>e&&t.headers.has("range")?await H(t,e):e}},t.StaleWhileRevalidate=class extends v{constructor(t={}){super(t),this.plugins.some((t=>"cacheWillUpdate"in t))||this.plugins.unshift(u)}async D(t,e){const n=e.fetchAndCachePut(t).catch((()=>{}));let r,i=await e.cacheMatch(t);if(i);else try{i=await n}catch(t){t instanceof Error&&(r=t)}if(!i)throw new s("no-response",{url:t.url,error:r});return i}},t.cleanupOutdatedCaches=function(){self.addEventListener("activate",(t=>{const e=w();t.waitUntil((async(t,e="-precache-")=>{const s=(await self.caches.keys()).filter((s=>s.includes(e)&&s.includes(self.registration.scope)&&s!==t));return await Promise.all(s.map((t=>self.caches.delete(t)))),s})(e).then((t=>{})))}))},t.clientsClaim=function(){self.addEventListener("activate",(()=>self.clients.claim()))},t.precacheAndRoute=function(t,e){!function(t){tt().precache(t)}(t),function(t){const e=tt();h(new et(e,t))}(e)},t.registerRoute=h}));
diff --git a/public/workbox-9a8b0a38.js b/public/workbox-9a8b0a38.js
new file mode 100644
index 0000000..76cdb54
--- /dev/null
+++ b/public/workbox-9a8b0a38.js
@@ -0,0 +1,2737 @@
+define(['exports'], (function (exports) { 'use strict';
+
+ try {
+ self['workbox:core:6.4.1'] && _();
+ } catch (e) {}
+
+ /*
+ Copyright 2019 Google LLC
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ const logger = (() => {
+ // Don't overwrite this value if it's already set.
+ // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923
+ if (!('__WB_DISABLE_DEV_LOGS' in self)) {
+ self.__WB_DISABLE_DEV_LOGS = false;
+ }
+
+ let inGroup = false;
+ const methodToColorMap = {
+ debug: `#7f8c8d`,
+ log: `#2ecc71`,
+ warn: `#f39c12`,
+ error: `#c0392b`,
+ groupCollapsed: `#3498db`,
+ groupEnd: null // No colored prefix on groupEnd
+
+ };
+
+ const print = function (method, args) {
+ if (self.__WB_DISABLE_DEV_LOGS) {
+ return;
+ }
+
+ if (method === 'groupCollapsed') {
+ // Safari doesn't print all console.groupCollapsed() arguments:
+ // https://bugs.webkit.org/show_bug.cgi?id=182754
+ if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
+ console[method](...args);
+ return;
+ }
+ }
+
+ const styles = [`background: ${methodToColorMap[method]}`, `border-radius: 0.5em`, `color: white`, `font-weight: bold`, `padding: 2px 0.5em`]; // When in a group, the workbox prefix is not displayed.
+
+ const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];
+ console[method](...logPrefix, ...args);
+
+ if (method === 'groupCollapsed') {
+ inGroup = true;
+ }
+
+ if (method === 'groupEnd') {
+ inGroup = false;
+ }
+ }; // eslint-disable-next-line @typescript-eslint/ban-types
+
+
+ const api = {};
+ const loggerMethods = Object.keys(methodToColorMap);
+
+ for (const key of loggerMethods) {
+ const method = key;
+
+ api[method] = (...args) => {
+ print(method, args);
+ };
+ }
+
+ return api;
+ })();
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ const messages$1 = {
+ 'invalid-value': ({
+ paramName,
+ validValueDescription,
+ value
+ }) => {
+ if (!paramName || !validValueDescription) {
+ throw new Error(`Unexpected input to 'invalid-value' error.`);
+ }
+
+ return `The '${paramName}' parameter was given a value with an ` + `unexpected value. ${validValueDescription} Received a value of ` + `${JSON.stringify(value)}.`;
+ },
+ 'not-an-array': ({
+ moduleName,
+ className,
+ funcName,
+ paramName
+ }) => {
+ if (!moduleName || !className || !funcName || !paramName) {
+ throw new Error(`Unexpected input to 'not-an-array' error.`);
+ }
+
+ return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className}.${funcName}()' must be an array.`;
+ },
+ 'incorrect-type': ({
+ expectedType,
+ paramName,
+ moduleName,
+ className,
+ funcName
+ }) => {
+ if (!expectedType || !paramName || !moduleName || !funcName) {
+ throw new Error(`Unexpected input to 'incorrect-type' error.`);
+ }
+
+ const classNameStr = className ? `${className}.` : '';
+ return `The parameter '${paramName}' passed into ` + `'${moduleName}.${classNameStr}` + `${funcName}()' must be of type ${expectedType}.`;
+ },
+ 'incorrect-class': ({
+ expectedClassName,
+ paramName,
+ moduleName,
+ className,
+ funcName,
+ isReturnValueProblem
+ }) => {
+ if (!expectedClassName || !moduleName || !funcName) {
+ throw new Error(`Unexpected input to 'incorrect-class' error.`);
+ }
+
+ const classNameStr = className ? `${className}.` : '';
+
+ if (isReturnValueProblem) {
+ return `The return value from ` + `'${moduleName}.${classNameStr}${funcName}()' ` + `must be an instance of class ${expectedClassName}.`;
+ }
+
+ return `The parameter '${paramName}' passed into ` + `'${moduleName}.${classNameStr}${funcName}()' ` + `must be an instance of class ${expectedClassName}.`;
+ },
+ 'missing-a-method': ({
+ expectedMethod,
+ paramName,
+ moduleName,
+ className,
+ funcName
+ }) => {
+ if (!expectedMethod || !paramName || !moduleName || !className || !funcName) {
+ throw new Error(`Unexpected input to 'missing-a-method' error.`);
+ }
+
+ return `${moduleName}.${className}.${funcName}() expected the ` + `'${paramName}' parameter to expose a '${expectedMethod}' method.`;
+ },
+ 'add-to-cache-list-unexpected-type': ({
+ entry
+ }) => {
+ return `An unexpected entry was passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' The entry ` + `'${JSON.stringify(entry)}' isn't supported. You must supply an array of ` + `strings with one or more characters, objects with a url property or ` + `Request objects.`;
+ },
+ 'add-to-cache-list-conflicting-entries': ({
+ firstEntry,
+ secondEntry
+ }) => {
+ if (!firstEntry || !secondEntry) {
+ throw new Error(`Unexpected input to ` + `'add-to-cache-list-duplicate-entries' error.`);
+ }
+
+ return `Two of the entries passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + `${firstEntry} but different revision details. Workbox is ` + `unable to cache and version the asset correctly. Please remove one ` + `of the entries.`;
+ },
+ 'plugin-error-request-will-fetch': ({
+ thrownErrorMessage
+ }) => {
+ if (!thrownErrorMessage) {
+ throw new Error(`Unexpected input to ` + `'plugin-error-request-will-fetch', error.`);
+ }
+
+ return `An error was thrown by a plugins 'requestWillFetch()' method. ` + `The thrown error message was: '${thrownErrorMessage}'.`;
+ },
+ 'invalid-cache-name': ({
+ cacheNameId,
+ value
+ }) => {
+ if (!cacheNameId) {
+ throw new Error(`Expected a 'cacheNameId' for error 'invalid-cache-name'`);
+ }
+
+ return `You must provide a name containing at least one character for ` + `setCacheDetails({${cacheNameId}: '...'}). Received a value of ` + `'${JSON.stringify(value)}'`;
+ },
+ 'unregister-route-but-not-found-with-method': ({
+ method
+ }) => {
+ if (!method) {
+ throw new Error(`Unexpected input to ` + `'unregister-route-but-not-found-with-method' error.`);
+ }
+
+ return `The route you're trying to unregister was not previously ` + `registered for the method type '${method}'.`;
+ },
+ 'unregister-route-route-not-registered': () => {
+ return `The route you're trying to unregister was not previously ` + `registered.`;
+ },
+ 'queue-replay-failed': ({
+ name
+ }) => {
+ return `Replaying the background sync queue '${name}' failed.`;
+ },
+ 'duplicate-queue-name': ({
+ name
+ }) => {
+ return `The Queue name '${name}' is already being used. ` + `All instances of backgroundSync.Queue must be given unique names.`;
+ },
+ 'expired-test-without-max-age': ({
+ methodName,
+ paramName
+ }) => {
+ return `The '${methodName}()' method can only be used when the ` + `'${paramName}' is used in the constructor.`;
+ },
+ 'unsupported-route-type': ({
+ moduleName,
+ className,
+ funcName,
+ paramName
+ }) => {
+ return `The supplied '${paramName}' parameter was an unsupported type. ` + `Please check the docs for ${moduleName}.${className}.${funcName} for ` + `valid input types.`;
+ },
+ 'not-array-of-class': ({
+ value,
+ expectedClass,
+ moduleName,
+ className,
+ funcName,
+ paramName
+ }) => {
+ return `The supplied '${paramName}' parameter must be an array of ` + `'${expectedClass}' objects. Received '${JSON.stringify(value)},'. ` + `Please check the call to ${moduleName}.${className}.${funcName}() ` + `to fix the issue.`;
+ },
+ 'max-entries-or-age-required': ({
+ moduleName,
+ className,
+ funcName
+ }) => {
+ return `You must define either config.maxEntries or config.maxAgeSeconds` + `in ${moduleName}.${className}.${funcName}`;
+ },
+ 'statuses-or-headers-required': ({
+ moduleName,
+ className,
+ funcName
+ }) => {
+ return `You must define either config.statuses or config.headers` + `in ${moduleName}.${className}.${funcName}`;
+ },
+ 'invalid-string': ({
+ moduleName,
+ funcName,
+ paramName
+ }) => {
+ if (!paramName || !moduleName || !funcName) {
+ throw new Error(`Unexpected input to 'invalid-string' error.`);
+ }
+
+ return `When using strings, the '${paramName}' parameter must start with ` + `'http' (for cross-origin matches) or '/' (for same-origin matches). ` + `Please see the docs for ${moduleName}.${funcName}() for ` + `more info.`;
+ },
+ 'channel-name-required': () => {
+ return `You must provide a channelName to construct a ` + `BroadcastCacheUpdate instance.`;
+ },
+ 'invalid-responses-are-same-args': () => {
+ return `The arguments passed into responsesAreSame() appear to be ` + `invalid. Please ensure valid Responses are used.`;
+ },
+ 'expire-custom-caches-only': () => {
+ return `You must provide a 'cacheName' property when using the ` + `expiration plugin with a runtime caching strategy.`;
+ },
+ 'unit-must-be-bytes': ({
+ normalizedRangeHeader
+ }) => {
+ if (!normalizedRangeHeader) {
+ throw new Error(`Unexpected input to 'unit-must-be-bytes' error.`);
+ }
+
+ return `The 'unit' portion of the Range header must be set to 'bytes'. ` + `The Range header provided was "${normalizedRangeHeader}"`;
+ },
+ 'single-range-only': ({
+ normalizedRangeHeader
+ }) => {
+ if (!normalizedRangeHeader) {
+ throw new Error(`Unexpected input to 'single-range-only' error.`);
+ }
+
+ return `Multiple ranges are not supported. Please use a single start ` + `value, and optional end value. The Range header provided was ` + `"${normalizedRangeHeader}"`;
+ },
+ 'invalid-range-values': ({
+ normalizedRangeHeader
+ }) => {
+ if (!normalizedRangeHeader) {
+ throw new Error(`Unexpected input to 'invalid-range-values' error.`);
+ }
+
+ return `The Range header is missing both start and end values. At least ` + `one of those values is needed. The Range header provided was ` + `"${normalizedRangeHeader}"`;
+ },
+ 'no-range-header': () => {
+ return `No Range header was found in the Request provided.`;
+ },
+ 'range-not-satisfiable': ({
+ size,
+ start,
+ end
+ }) => {
+ return `The start (${start}) and end (${end}) values in the Range are ` + `not satisfiable by the cached response, which is ${size} bytes.`;
+ },
+ 'attempt-to-cache-non-get-request': ({
+ url,
+ method
+ }) => {
+ return `Unable to cache '${url}' because it is a '${method}' request and ` + `only 'GET' requests can be cached.`;
+ },
+ 'cache-put-with-no-response': ({
+ url
+ }) => {
+ return `There was an attempt to cache '${url}' but the response was not ` + `defined.`;
+ },
+ 'no-response': ({
+ url,
+ error
+ }) => {
+ let message = `The strategy could not generate a response for '${url}'.`;
+
+ if (error) {
+ message += ` The underlying error is ${error}.`;
+ }
+
+ return message;
+ },
+ 'bad-precaching-response': ({
+ url,
+ status
+ }) => {
+ return `The precaching request for '${url}' failed` + (status ? ` with an HTTP status of ${status}.` : `.`);
+ },
+ 'non-precached-url': ({
+ url
+ }) => {
+ return `createHandlerBoundToURL('${url}') was called, but that URL is ` + `not precached. Please pass in a URL that is precached instead.`;
+ },
+ 'add-to-cache-list-conflicting-integrities': ({
+ url
+ }) => {
+ return `Two of the entries passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + `${url} with different integrity values. Please remove one of them.`;
+ },
+ 'missing-precache-entry': ({
+ cacheName,
+ url
+ }) => {
+ return `Unable to find a precached response in ${cacheName} for ${url}.`;
+ },
+ 'cross-origin-copy-response': ({
+ origin
+ }) => {
+ return `workbox-core.copyResponse() can only be used with same-origin ` + `responses. It was passed a response with origin ${origin}.`;
+ }
+ };
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+
+ const generatorFunction = (code, details = {}) => {
+ const message = messages$1[code];
+
+ if (!message) {
+ throw new Error(`Unable to find message for code '${code}'.`);
+ }
+
+ return message(details);
+ };
+
+ const messageGenerator = generatorFunction;
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * Workbox errors should be thrown with this class.
+ * This allows use to ensure the type easily in tests,
+ * helps developers identify errors from workbox
+ * easily and allows use to optimise error
+ * messages correctly.
+ *
+ * @private
+ */
+
+ class WorkboxError extends Error {
+ /**
+ *
+ * @param {string} errorCode The error code that
+ * identifies this particular error.
+ * @param {Object=} details Any relevant arguments
+ * that will help developers identify issues should
+ * be added as a key on the context object.
+ */
+ constructor(errorCode, details) {
+ const message = messageGenerator(errorCode, details);
+ super(message);
+ this.name = errorCode;
+ this.details = details;
+ }
+
+ }
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /*
+ * This method throws if the supplied value is not an array.
+ * The destructed values are required to produce a meaningful error for users.
+ * The destructed and restructured object is so it's clear what is
+ * needed.
+ */
+
+ const isArray = (value, details) => {
+ if (!Array.isArray(value)) {
+ throw new WorkboxError('not-an-array', details);
+ }
+ };
+
+ const hasMethod = (object, expectedMethod, details) => {
+ const type = typeof object[expectedMethod];
+
+ if (type !== 'function') {
+ details['expectedMethod'] = expectedMethod;
+ throw new WorkboxError('missing-a-method', details);
+ }
+ };
+
+ const isType = (object, expectedType, details) => {
+ if (typeof object !== expectedType) {
+ details['expectedType'] = expectedType;
+ throw new WorkboxError('incorrect-type', details);
+ }
+ };
+
+ const isInstance = (object, // Need the general type to do the check later.
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ expectedClass, details) => {
+ if (!(object instanceof expectedClass)) {
+ details['expectedClassName'] = expectedClass.name;
+ throw new WorkboxError('incorrect-class', details);
+ }
+ };
+
+ const isOneOf = (value, validValues, details) => {
+ if (!validValues.includes(value)) {
+ details['validValueDescription'] = `Valid values are ${JSON.stringify(validValues)}.`;
+ throw new WorkboxError('invalid-value', details);
+ }
+ };
+
+ const isArrayOfClass = (value, // Need general type to do check later.
+ expectedClass, // eslint-disable-line
+ details) => {
+ const error = new WorkboxError('not-array-of-class', details);
+
+ if (!Array.isArray(value)) {
+ throw error;
+ }
+
+ for (const item of value) {
+ if (!(item instanceof expectedClass)) {
+ throw error;
+ }
+ }
+ };
+
+ const finalAssertExports = {
+ hasMethod,
+ isArray,
+ isInstance,
+ isOneOf,
+ isType,
+ isArrayOfClass
+ };
+
+ try {
+ self['workbox:routing:6.4.1'] && _();
+ } catch (e) {}
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * The default HTTP method, 'GET', used when there's no specific method
+ * configured for a route.
+ *
+ * @type {string}
+ *
+ * @private
+ */
+
+ const defaultMethod = 'GET';
+ /**
+ * The list of valid HTTP methods associated with requests that could be routed.
+ *
+ * @type {Array}
+ *
+ * @private
+ */
+
+ const validMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'];
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * @param {function()|Object} handler Either a function, or an object with a
+ * 'handle' method.
+ * @return {Object} An object with a handle method.
+ *
+ * @private
+ */
+
+ const normalizeHandler = handler => {
+ if (handler && typeof handler === 'object') {
+ {
+ finalAssertExports.hasMethod(handler, 'handle', {
+ moduleName: 'workbox-routing',
+ className: 'Route',
+ funcName: 'constructor',
+ paramName: 'handler'
+ });
+ }
+
+ return handler;
+ } else {
+ {
+ finalAssertExports.isType(handler, 'function', {
+ moduleName: 'workbox-routing',
+ className: 'Route',
+ funcName: 'constructor',
+ paramName: 'handler'
+ });
+ }
+
+ return {
+ handle: handler
+ };
+ }
+ };
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * A `Route` consists of a pair of callback functions, "match" and "handler".
+ * The "match" callback determine if a route should be used to "handle" a
+ * request by returning a non-falsy value if it can. The "handler" callback
+ * is called when there is a match and should return a Promise that resolves
+ * to a `Response`.
+ *
+ * @memberof module:workbox-routing
+ */
+
+ class Route {
+ /**
+ * Constructor for Route class.
+ *
+ * @param {module:workbox-routing~matchCallback} match
+ * A callback function that determines whether the route matches a given
+ * `fetch` event by returning a non-falsy value.
+ * @param {module:workbox-routing~handlerCallback} handler A callback
+ * function that returns a Promise resolving to a Response.
+ * @param {string} [method='GET'] The HTTP method to match the Route
+ * against.
+ */
+ constructor(match, handler, method = defaultMethod) {
+ {
+ finalAssertExports.isType(match, 'function', {
+ moduleName: 'workbox-routing',
+ className: 'Route',
+ funcName: 'constructor',
+ paramName: 'match'
+ });
+
+ if (method) {
+ finalAssertExports.isOneOf(method, validMethods, {
+ paramName: 'method'
+ });
+ }
+ } // These values are referenced directly by Router so cannot be
+ // altered by minificaton.
+
+
+ this.handler = normalizeHandler(handler);
+ this.match = match;
+ this.method = method;
+ }
+ /**
+ *
+ * @param {module:workbox-routing-handlerCallback} handler A callback
+ * function that returns a Promise resolving to a Response
+ */
+
+
+ setCatchHandler(handler) {
+ this.catchHandler = normalizeHandler(handler);
+ }
+
+ }
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * RegExpRoute makes it easy to create a regular expression based
+ * [Route]{@link module:workbox-routing.Route}.
+ *
+ * For same-origin requests the RegExp only needs to match part of the URL. For
+ * requests against third-party servers, you must define a RegExp that matches
+ * the start of the URL.
+ *
+ * [See the module docs for info.]{@link https://developers.google.com/web/tools/workbox/modules/workbox-routing}
+ *
+ * @memberof module:workbox-routing
+ * @extends module:workbox-routing.Route
+ */
+
+ class RegExpRoute extends Route {
+ /**
+ * If the regular expression contains
+ * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references},
+ * the captured values will be passed to the
+ * [handler's]{@link module:workbox-routing~handlerCallback} `params`
+ * argument.
+ *
+ * @param {RegExp} regExp The regular expression to match against URLs.
+ * @param {module:workbox-routing~handlerCallback} handler A callback
+ * function that returns a Promise resulting in a Response.
+ * @param {string} [method='GET'] The HTTP method to match the Route
+ * against.
+ */
+ constructor(regExp, handler, method) {
+ {
+ finalAssertExports.isInstance(regExp, RegExp, {
+ moduleName: 'workbox-routing',
+ className: 'RegExpRoute',
+ funcName: 'constructor',
+ paramName: 'pattern'
+ });
+ }
+
+ const match = ({
+ url
+ }) => {
+ const result = regExp.exec(url.href); // Return immediately if there's no match.
+
+ if (!result) {
+ return;
+ } // Require that the match start at the first character in the URL string
+ // if it's a cross-origin request.
+ // See https://github.com/GoogleChrome/workbox/issues/281 for the context
+ // behind this behavior.
+
+
+ if (url.origin !== location.origin && result.index !== 0) {
+ {
+ logger.debug(`The regular expression '${regExp.toString()}' only partially matched ` + `against the cross-origin URL '${url.toString()}'. RegExpRoute's will only ` + `handle cross-origin requests if they match the entire URL.`);
+ }
+
+ return;
+ } // If the route matches, but there aren't any capture groups defined, then
+ // this will return [], which is truthy and therefore sufficient to
+ // indicate a match.
+ // If there are capture groups, then it will return their values.
+
+
+ return result.slice(1);
+ };
+
+ super(match, handler, method);
+ }
+
+ }
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+
+ const getFriendlyURL = url => {
+ const urlObj = new URL(String(url), location.href); // See https://github.com/GoogleChrome/workbox/issues/2323
+ // We want to include everything, except for the origin if it's same-origin.
+
+ return urlObj.href.replace(new RegExp(`^${location.origin}`), '');
+ };
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * The Router can be used to process a FetchEvent through one or more
+ * [Routes]{@link module:workbox-routing.Route} responding with a Request if
+ * a matching route exists.
+ *
+ * If no route matches a given a request, the Router will use a "default"
+ * handler if one is defined.
+ *
+ * Should the matching Route throw an error, the Router will use a "catch"
+ * handler if one is defined to gracefully deal with issues and respond with a
+ * Request.
+ *
+ * If a request matches multiple routes, the **earliest** registered route will
+ * be used to respond to the request.
+ *
+ * @memberof module:workbox-routing
+ */
+
+ class Router {
+ /**
+ * Initializes a new Router.
+ */
+ constructor() {
+ this._routes = new Map();
+ this._defaultHandlerMap = new Map();
+ }
+ /**
+ * @return {Map>} routes A `Map` of HTTP
+ * method name ('GET', etc.) to an array of all the corresponding `Route`
+ * instances that are registered.
+ */
+
+
+ get routes() {
+ return this._routes;
+ }
+ /**
+ * Adds a fetch event listener to respond to events when a route matches
+ * the event's request.
+ */
+
+
+ addFetchListener() {
+ // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
+ self.addEventListener('fetch', event => {
+ const {
+ request
+ } = event;
+ const responsePromise = this.handleRequest({
+ request,
+ event
+ });
+
+ if (responsePromise) {
+ event.respondWith(responsePromise);
+ }
+ });
+ }
+ /**
+ * Adds a message event listener for URLs to cache from the window.
+ * This is useful to cache resources loaded on the page prior to when the
+ * service worker started controlling it.
+ *
+ * The format of the message data sent from the window should be as follows.
+ * Where the `urlsToCache` array may consist of URL strings or an array of
+ * URL string + `requestInit` object (the same as you'd pass to `fetch()`).
+ *
+ * ```
+ * {
+ * type: 'CACHE_URLS',
+ * payload: {
+ * urlsToCache: [
+ * './script1.js',
+ * './script2.js',
+ * ['./script3.js', {mode: 'no-cors'}],
+ * ],
+ * },
+ * }
+ * ```
+ */
+
+
+ addCacheListener() {
+ // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
+ self.addEventListener('message', event => {
+ // event.data is type 'any'
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ if (event.data && event.data.type === 'CACHE_URLS') {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ const {
+ payload
+ } = event.data;
+
+ {
+ logger.debug(`Caching URLs from the window`, payload.urlsToCache);
+ }
+
+ const requestPromises = Promise.all(payload.urlsToCache.map(entry => {
+ if (typeof entry === 'string') {
+ entry = [entry];
+ }
+
+ const request = new Request(...entry);
+ return this.handleRequest({
+ request,
+ event
+ }); // TODO(philipwalton): TypeScript errors without this typecast for
+ // some reason (probably a bug). The real type here should work but
+ // doesn't: `Array | undefined>`.
+ })); // TypeScript
+
+ event.waitUntil(requestPromises); // If a MessageChannel was used, reply to the message on success.
+
+ if (event.ports && event.ports[0]) {
+ void requestPromises.then(() => event.ports[0].postMessage(true));
+ }
+ }
+ });
+ }
+ /**
+ * Apply the routing rules to a FetchEvent object to get a Response from an
+ * appropriate Route's handler.
+ *
+ * @param {Object} options
+ * @param {Request} options.request The request to handle.
+ * @param {ExtendableEvent} options.event The event that triggered the
+ * request.
+ * @return {Promise|undefined} A promise is returned if a
+ * registered route can handle the request. If there is no matching
+ * route and there's no `defaultHandler`, `undefined` is returned.
+ */
+
+
+ handleRequest({
+ request,
+ event
+ }) {
+ {
+ finalAssertExports.isInstance(request, Request, {
+ moduleName: 'workbox-routing',
+ className: 'Router',
+ funcName: 'handleRequest',
+ paramName: 'options.request'
+ });
+ }
+
+ const url = new URL(request.url, location.href);
+
+ if (!url.protocol.startsWith('http')) {
+ {
+ logger.debug(`Workbox Router only supports URLs that start with 'http'.`);
+ }
+
+ return;
+ }
+
+ const sameOrigin = url.origin === location.origin;
+ const {
+ params,
+ route
+ } = this.findMatchingRoute({
+ event,
+ request,
+ sameOrigin,
+ url
+ });
+ let handler = route && route.handler;
+ const debugMessages = [];
+
+ {
+ if (handler) {
+ debugMessages.push([`Found a route to handle this request:`, route]);
+
+ if (params) {
+ debugMessages.push([`Passing the following params to the route's handler:`, params]);
+ }
+ }
+ } // If we don't have a handler because there was no matching route, then
+ // fall back to defaultHandler if that's defined.
+
+
+ const method = request.method;
+
+ if (!handler && this._defaultHandlerMap.has(method)) {
+ {
+ debugMessages.push(`Failed to find a matching route. Falling ` + `back to the default handler for ${method}.`);
+ }
+
+ handler = this._defaultHandlerMap.get(method);
+ }
+
+ if (!handler) {
+ {
+ // No handler so Workbox will do nothing. If logs is set of debug
+ // i.e. verbose, we should print out this information.
+ logger.debug(`No route found for: ${getFriendlyURL(url)}`);
+ }
+
+ return;
+ }
+
+ {
+ // We have a handler, meaning Workbox is going to handle the route.
+ // print the routing details to the console.
+ logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);
+ debugMessages.forEach(msg => {
+ if (Array.isArray(msg)) {
+ logger.log(...msg);
+ } else {
+ logger.log(msg);
+ }
+ });
+ logger.groupEnd();
+ } // Wrap in try and catch in case the handle method throws a synchronous
+ // error. It should still callback to the catch handler.
+
+
+ let responsePromise;
+
+ try {
+ responsePromise = handler.handle({
+ url,
+ request,
+ event,
+ params
+ });
+ } catch (err) {
+ responsePromise = Promise.reject(err);
+ } // Get route's catch handler, if it exists
+
+
+ const catchHandler = route && route.catchHandler;
+
+ if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) {
+ responsePromise = responsePromise.catch(async err => {
+ // If there's a route catch handler, process that first
+ if (catchHandler) {
+ {
+ // Still include URL here as it will be async from the console group
+ // and may not make sense without the URL
+ logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);
+ logger.error(`Error thrown by:`, route);
+ logger.error(err);
+ logger.groupEnd();
+ }
+
+ try {
+ return await catchHandler.handle({
+ url,
+ request,
+ event,
+ params
+ });
+ } catch (catchErr) {
+ if (catchErr instanceof Error) {
+ err = catchErr;
+ }
+ }
+ }
+
+ if (this._catchHandler) {
+ {
+ // Still include URL here as it will be async from the console group
+ // and may not make sense without the URL
+ logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);
+ logger.error(`Error thrown by:`, route);
+ logger.error(err);
+ logger.groupEnd();
+ }
+
+ return this._catchHandler.handle({
+ url,
+ request,
+ event
+ });
+ }
+
+ throw err;
+ });
+ }
+
+ return responsePromise;
+ }
+ /**
+ * Checks a request and URL (and optionally an event) against the list of
+ * registered routes, and if there's a match, returns the corresponding
+ * route along with any params generated by the match.
+ *
+ * @param {Object} options
+ * @param {URL} options.url
+ * @param {boolean} options.sameOrigin The result of comparing `url.origin`
+ * against the current origin.
+ * @param {Request} options.request The request to match.
+ * @param {Event} options.event The corresponding event.
+ * @return {Object} An object with `route` and `params` properties.
+ * They are populated if a matching route was found or `undefined`
+ * otherwise.
+ */
+
+
+ findMatchingRoute({
+ url,
+ sameOrigin,
+ request,
+ event
+ }) {
+ const routes = this._routes.get(request.method) || [];
+
+ for (const route of routes) {
+ let params; // route.match returns type any, not possible to change right now.
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+
+ const matchResult = route.match({
+ url,
+ sameOrigin,
+ request,
+ event
+ });
+
+ if (matchResult) {
+ {
+ // Warn developers that using an async matchCallback is almost always
+ // not the right thing to do.
+ if (matchResult instanceof Promise) {
+ logger.warn(`While routing ${getFriendlyURL(url)}, an async ` + `matchCallback function was used. Please convert the ` + `following route to use a synchronous matchCallback function:`, route);
+ }
+ } // See https://github.com/GoogleChrome/workbox/issues/2079
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+
+
+ params = matchResult;
+
+ if (Array.isArray(params) && params.length === 0) {
+ // Instead of passing an empty array in as params, use undefined.
+ params = undefined;
+ } else if (matchResult.constructor === Object && // eslint-disable-line
+ Object.keys(matchResult).length === 0) {
+ // Instead of passing an empty object in as params, use undefined.
+ params = undefined;
+ } else if (typeof matchResult === 'boolean') {
+ // For the boolean value true (rather than just something truth-y),
+ // don't set params.
+ // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353
+ params = undefined;
+ } // Return early if have a match.
+
+
+ return {
+ route,
+ params
+ };
+ }
+ } // If no match was found above, return and empty object.
+
+
+ return {};
+ }
+ /**
+ * Define a default `handler` that's called when no routes explicitly
+ * match the incoming request.
+ *
+ * Each HTTP method ('GET', 'POST', etc.) gets its own default handler.
+ *
+ * Without a default handler, unmatched requests will go against the
+ * network as if there were no service worker present.
+ *
+ * @param {module:workbox-routing~handlerCallback} handler A callback
+ * function that returns a Promise resulting in a Response.
+ * @param {string} [method='GET'] The HTTP method to associate with this
+ * default handler. Each method has its own default.
+ */
+
+
+ setDefaultHandler(handler, method = defaultMethod) {
+ this._defaultHandlerMap.set(method, normalizeHandler(handler));
+ }
+ /**
+ * If a Route throws an error while handling a request, this `handler`
+ * will be called and given a chance to provide a response.
+ *
+ * @param {module:workbox-routing~handlerCallback} handler A callback
+ * function that returns a Promise resulting in a Response.
+ */
+
+
+ setCatchHandler(handler) {
+ this._catchHandler = normalizeHandler(handler);
+ }
+ /**
+ * Registers a route with the router.
+ *
+ * @param {module:workbox-routing.Route} route The route to register.
+ */
+
+
+ registerRoute(route) {
+ {
+ finalAssertExports.isType(route, 'object', {
+ moduleName: 'workbox-routing',
+ className: 'Router',
+ funcName: 'registerRoute',
+ paramName: 'route'
+ });
+ finalAssertExports.hasMethod(route, 'match', {
+ moduleName: 'workbox-routing',
+ className: 'Router',
+ funcName: 'registerRoute',
+ paramName: 'route'
+ });
+ finalAssertExports.isType(route.handler, 'object', {
+ moduleName: 'workbox-routing',
+ className: 'Router',
+ funcName: 'registerRoute',
+ paramName: 'route'
+ });
+ finalAssertExports.hasMethod(route.handler, 'handle', {
+ moduleName: 'workbox-routing',
+ className: 'Router',
+ funcName: 'registerRoute',
+ paramName: 'route.handler'
+ });
+ finalAssertExports.isType(route.method, 'string', {
+ moduleName: 'workbox-routing',
+ className: 'Router',
+ funcName: 'registerRoute',
+ paramName: 'route.method'
+ });
+ }
+
+ if (!this._routes.has(route.method)) {
+ this._routes.set(route.method, []);
+ } // Give precedence to all of the earlier routes by adding this additional
+ // route to the end of the array.
+
+
+ this._routes.get(route.method).push(route);
+ }
+ /**
+ * Unregisters a route with the router.
+ *
+ * @param {module:workbox-routing.Route} route The route to unregister.
+ */
+
+
+ unregisterRoute(route) {
+ if (!this._routes.has(route.method)) {
+ throw new WorkboxError('unregister-route-but-not-found-with-method', {
+ method: route.method
+ });
+ }
+
+ const routeIndex = this._routes.get(route.method).indexOf(route);
+
+ if (routeIndex > -1) {
+ this._routes.get(route.method).splice(routeIndex, 1);
+ } else {
+ throw new WorkboxError('unregister-route-route-not-registered');
+ }
+ }
+
+ }
+
+ /*
+ Copyright 2019 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ let defaultRouter;
+ /**
+ * Creates a new, singleton Router instance if one does not exist. If one
+ * does already exist, that instance is returned.
+ *
+ * @private
+ * @return {Router}
+ */
+
+ const getOrCreateDefaultRouter = () => {
+ if (!defaultRouter) {
+ defaultRouter = new Router(); // The helpers that use the default Router assume these listeners exist.
+
+ defaultRouter.addFetchListener();
+ defaultRouter.addCacheListener();
+ }
+
+ return defaultRouter;
+ };
+
+ /*
+ Copyright 2019 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * Easily register a RegExp, string, or function with a caching
+ * strategy to a singleton Router instance.
+ *
+ * This method will generate a Route for you if needed and
+ * call [registerRoute()]{@link module:workbox-routing.Router#registerRoute}.
+ *
+ * @param {RegExp|string|module:workbox-routing.Route~matchCallback|module:workbox-routing.Route} capture
+ * If the capture param is a `Route`, all other arguments will be ignored.
+ * @param {module:workbox-routing~handlerCallback} [handler] A callback
+ * function that returns a Promise resulting in a Response. This parameter
+ * is required if `capture` is not a `Route` object.
+ * @param {string} [method='GET'] The HTTP method to match the Route
+ * against.
+ * @return {module:workbox-routing.Route} The generated `Route`(Useful for
+ * unregistering).
+ *
+ * @memberof module:workbox-routing
+ */
+
+ function registerRoute(capture, handler, method) {
+ let route;
+
+ if (typeof capture === 'string') {
+ const captureUrl = new URL(capture, location.href);
+
+ {
+ if (!(capture.startsWith('/') || capture.startsWith('http'))) {
+ throw new WorkboxError('invalid-string', {
+ moduleName: 'workbox-routing',
+ funcName: 'registerRoute',
+ paramName: 'capture'
+ });
+ } // We want to check if Express-style wildcards are in the pathname only.
+ // TODO: Remove this log message in v4.
+
+
+ const valueToCheck = capture.startsWith('http') ? captureUrl.pathname : capture; // See https://github.com/pillarjs/path-to-regexp#parameters
+
+ const wildcards = '[*:?+]';
+
+ if (new RegExp(`${wildcards}`).exec(valueToCheck)) {
+ logger.debug(`The '$capture' parameter contains an Express-style wildcard ` + `character (${wildcards}). Strings are now always interpreted as ` + `exact matches; use a RegExp for partial or wildcard matches.`);
+ }
+ }
+
+ const matchCallback = ({
+ url
+ }) => {
+ {
+ if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) {
+ logger.debug(`${capture} only partially matches the cross-origin URL ` + `${url.toString()}. This route will only handle cross-origin requests ` + `if they match the entire URL.`);
+ }
+ }
+
+ return url.href === captureUrl.href;
+ }; // If `capture` is a string then `handler` and `method` must be present.
+
+
+ route = new Route(matchCallback, handler, method);
+ } else if (capture instanceof RegExp) {
+ // If `capture` is a `RegExp` then `handler` and `method` must be present.
+ route = new RegExpRoute(capture, handler, method);
+ } else if (typeof capture === 'function') {
+ // If `capture` is a function then `handler` and `method` must be present.
+ route = new Route(capture, handler, method);
+ } else if (capture instanceof Route) {
+ route = capture;
+ } else {
+ throw new WorkboxError('unsupported-route-type', {
+ moduleName: 'workbox-routing',
+ funcName: 'registerRoute',
+ paramName: 'capture'
+ });
+ }
+
+ const defaultRouter = getOrCreateDefaultRouter();
+ defaultRouter.registerRoute(route);
+ return route;
+ }
+
+ try {
+ self['workbox:strategies:6.4.1'] && _();
+ } catch (e) {}
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ const cacheOkAndOpaquePlugin = {
+ /**
+ * Returns a valid response (to allow caching) if the status is 200 (OK) or
+ * 0 (opaque).
+ *
+ * @param {Object} options
+ * @param {Response} options.response
+ * @return {Response|null}
+ *
+ * @private
+ */
+ cacheWillUpdate: async ({
+ response
+ }) => {
+ if (response.status === 200 || response.status === 0) {
+ return response;
+ }
+
+ return null;
+ }
+ };
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ const _cacheNameDetails = {
+ googleAnalytics: 'googleAnalytics',
+ precache: 'precache-v2',
+ prefix: 'workbox',
+ runtime: 'runtime',
+ suffix: typeof registration !== 'undefined' ? registration.scope : ''
+ };
+
+ const _createCacheName = cacheName => {
+ return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix].filter(value => value && value.length > 0).join('-');
+ };
+
+ const eachCacheNameDetail = fn => {
+ for (const key of Object.keys(_cacheNameDetails)) {
+ fn(key);
+ }
+ };
+
+ const cacheNames = {
+ updateDetails: details => {
+ eachCacheNameDetail(key => {
+ if (typeof details[key] === 'string') {
+ _cacheNameDetails[key] = details[key];
+ }
+ });
+ },
+ getGoogleAnalyticsName: userCacheName => {
+ return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics);
+ },
+ getPrecacheName: userCacheName => {
+ return userCacheName || _createCacheName(_cacheNameDetails.precache);
+ },
+ getPrefix: () => {
+ return _cacheNameDetails.prefix;
+ },
+ getRuntimeName: userCacheName => {
+ return userCacheName || _createCacheName(_cacheNameDetails.runtime);
+ },
+ getSuffix: () => {
+ return _cacheNameDetails.suffix;
+ }
+ };
+
+ /*
+ Copyright 2020 Google LLC
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+
+ function stripParams(fullURL, ignoreParams) {
+ const strippedURL = new URL(fullURL);
+
+ for (const param of ignoreParams) {
+ strippedURL.searchParams.delete(param);
+ }
+
+ return strippedURL.href;
+ }
+ /**
+ * Matches an item in the cache, ignoring specific URL params. This is similar
+ * to the `ignoreSearch` option, but it allows you to ignore just specific
+ * params (while continuing to match on the others).
+ *
+ * @private
+ * @param {Cache} cache
+ * @param {Request} request
+ * @param {Object} matchOptions
+ * @param {Array} ignoreParams
+ * @return {Promise}
+ */
+
+
+ async function cacheMatchIgnoreParams(cache, request, ignoreParams, matchOptions) {
+ const strippedRequestURL = stripParams(request.url, ignoreParams); // If the request doesn't include any ignored params, match as normal.
+
+ if (request.url === strippedRequestURL) {
+ return cache.match(request, matchOptions);
+ } // Otherwise, match by comparing keys
+
+
+ const keysOptions = Object.assign(Object.assign({}, matchOptions), {
+ ignoreSearch: true
+ });
+ const cacheKeys = await cache.keys(request, keysOptions);
+
+ for (const cacheKey of cacheKeys) {
+ const strippedCacheKeyURL = stripParams(cacheKey.url, ignoreParams);
+
+ if (strippedRequestURL === strippedCacheKeyURL) {
+ return cache.match(cacheKey, matchOptions);
+ }
+ }
+
+ return;
+ }
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * The Deferred class composes Promises in a way that allows for them to be
+ * resolved or rejected from outside the constructor. In most cases promises
+ * should be used directly, but Deferreds can be necessary when the logic to
+ * resolve a promise must be separate.
+ *
+ * @private
+ */
+
+ class Deferred {
+ /**
+ * Creates a promise and exposes its resolve and reject functions as methods.
+ */
+ constructor() {
+ this.promise = new Promise((resolve, reject) => {
+ this.resolve = resolve;
+ this.reject = reject;
+ });
+ }
+
+ }
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ // Can't change Function type right now.
+ // eslint-disable-next-line @typescript-eslint/ban-types
+
+ const quotaErrorCallbacks = new Set();
+
+ /*
+ Copyright 2018 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * Runs all of the callback functions, one at a time sequentially, in the order
+ * in which they were registered.
+ *
+ * @memberof module:workbox-core
+ * @private
+ */
+
+ async function executeQuotaErrorCallbacks() {
+ {
+ logger.log(`About to run ${quotaErrorCallbacks.size} ` + `callbacks to clean up caches.`);
+ }
+
+ for (const callback of quotaErrorCallbacks) {
+ await callback();
+
+ {
+ logger.log(callback, 'is complete.');
+ }
+ }
+
+ {
+ logger.log('Finished running callbacks.');
+ }
+ }
+
+ /*
+ Copyright 2019 Google LLC
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * Returns a promise that resolves and the passed number of milliseconds.
+ * This utility is an async/await-friendly version of `setTimeout`.
+ *
+ * @param {number} ms
+ * @return {Promise}
+ * @private
+ */
+
+ function timeout(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ /*
+ Copyright 2020 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+
+ function toRequest(input) {
+ return typeof input === 'string' ? new Request(input) : input;
+ }
+ /**
+ * A class created every time a Strategy instance instance calls
+ * [handle()]{@link module:workbox-strategies.Strategy~handle} or
+ * [handleAll()]{@link module:workbox-strategies.Strategy~handleAll} that wraps all fetch and
+ * cache actions around plugin callbacks and keeps track of when the strategy
+ * is "done" (i.e. all added `event.waitUntil()` promises have resolved).
+ *
+ * @memberof module:workbox-strategies
+ */
+
+
+ class StrategyHandler {
+ /**
+ * Creates a new instance associated with the passed strategy and event
+ * that's handling the request.
+ *
+ * The constructor also initializes the state that will be passed to each of
+ * the plugins handling this request.
+ *
+ * @param {module:workbox-strategies.Strategy} strategy
+ * @param {Object} options
+ * @param {Request|string} options.request A request to run this strategy for.
+ * @param {ExtendableEvent} options.event The event associated with the
+ * request.
+ * @param {URL} [options.url]
+ * @param {*} [options.params]
+ * [match callback]{@link module:workbox-routing~matchCallback},
+ * (if applicable).
+ */
+ constructor(strategy, options) {
+ this._cacheKeys = {};
+ /**
+ * The request the strategy is performing (passed to the strategy's
+ * `handle()` or `handleAll()` method).
+ * @name request
+ * @instance
+ * @type {Request}
+ * @memberof module:workbox-strategies.StrategyHandler
+ */
+
+ /**
+ * The event associated with this request.
+ * @name event
+ * @instance
+ * @type {ExtendableEvent}
+ * @memberof module:workbox-strategies.StrategyHandler
+ */
+
+ /**
+ * A `URL` instance of `request.url` (if passed to the strategy's
+ * `handle()` or `handleAll()` method).
+ * Note: the `url` param will be present if the strategy was invoked
+ * from a workbox `Route` object.
+ * @name url
+ * @instance
+ * @type {URL|undefined}
+ * @memberof module:workbox-strategies.StrategyHandler
+ */
+
+ /**
+ * A `param` value (if passed to the strategy's
+ * `handle()` or `handleAll()` method).
+ * Note: the `param` param will be present if the strategy was invoked
+ * from a workbox `Route` object and the
+ * [match callback]{@link module:workbox-routing~matchCallback} returned
+ * a truthy value (it will be that value).
+ * @name params
+ * @instance
+ * @type {*|undefined}
+ * @memberof module:workbox-strategies.StrategyHandler
+ */
+
+ {
+ finalAssertExports.isInstance(options.event, ExtendableEvent, {
+ moduleName: 'workbox-strategies',
+ className: 'StrategyHandler',
+ funcName: 'constructor',
+ paramName: 'options.event'
+ });
+ }
+
+ Object.assign(this, options);
+ this.event = options.event;
+ this._strategy = strategy;
+ this._handlerDeferred = new Deferred();
+ this._extendLifetimePromises = []; // Copy the plugins list (since it's mutable on the strategy),
+ // so any mutations don't affect this handler instance.
+
+ this._plugins = [...strategy.plugins];
+ this._pluginStateMap = new Map();
+
+ for (const plugin of this._plugins) {
+ this._pluginStateMap.set(plugin, {});
+ }
+
+ this.event.waitUntil(this._handlerDeferred.promise);
+ }
+ /**
+ * Fetches a given request (and invokes any applicable plugin callback
+ * methods) using the `fetchOptions` (for non-navigation requests) and
+ * `plugins` defined on the `Strategy` object.
+ *
+ * The following plugin lifecycle methods are invoked when using this method:
+ * - `requestWillFetch()`
+ * - `fetchDidSucceed()`
+ * - `fetchDidFail()`
+ *
+ * @param {Request|string} input The URL or request to fetch.
+ * @return {Promise}
+ */
+
+
+ async fetch(input) {
+ const {
+ event
+ } = this;
+ let request = toRequest(input);
+
+ if (request.mode === 'navigate' && event instanceof FetchEvent && event.preloadResponse) {
+ const possiblePreloadResponse = await event.preloadResponse;
+
+ if (possiblePreloadResponse) {
+ {
+ logger.log(`Using a preloaded navigation response for ` + `'${getFriendlyURL(request.url)}'`);
+ }
+
+ return possiblePreloadResponse;
+ }
+ } // If there is a fetchDidFail plugin, we need to save a clone of the
+ // original request before it's either modified by a requestWillFetch
+ // plugin or before the original request's body is consumed via fetch().
+
+
+ const originalRequest = this.hasCallback('fetchDidFail') ? request.clone() : null;
+
+ try {
+ for (const cb of this.iterateCallbacks('requestWillFetch')) {
+ request = await cb({
+ request: request.clone(),
+ event
+ });
+ }
+ } catch (err) {
+ if (err instanceof Error) {
+ throw new WorkboxError('plugin-error-request-will-fetch', {
+ thrownErrorMessage: err.message
+ });
+ }
+ } // The request can be altered by plugins with `requestWillFetch` making
+ // the original request (most likely from a `fetch` event) different
+ // from the Request we make. Pass both to `fetchDidFail` to aid debugging.
+
+
+ const pluginFilteredRequest = request.clone();
+
+ try {
+ let fetchResponse; // See https://github.com/GoogleChrome/workbox/issues/1796
+
+ fetchResponse = await fetch(request, request.mode === 'navigate' ? undefined : this._strategy.fetchOptions);
+
+ if ("development" !== 'production') {
+ logger.debug(`Network request for ` + `'${getFriendlyURL(request.url)}' returned a response with ` + `status '${fetchResponse.status}'.`);
+ }
+
+ for (const callback of this.iterateCallbacks('fetchDidSucceed')) {
+ fetchResponse = await callback({
+ event,
+ request: pluginFilteredRequest,
+ response: fetchResponse
+ });
+ }
+
+ return fetchResponse;
+ } catch (error) {
+ {
+ logger.log(`Network request for ` + `'${getFriendlyURL(request.url)}' threw an error.`, error);
+ } // `originalRequest` will only exist if a `fetchDidFail` callback
+ // is being used (see above).
+
+
+ if (originalRequest) {
+ await this.runCallbacks('fetchDidFail', {
+ error: error,
+ event,
+ originalRequest: originalRequest.clone(),
+ request: pluginFilteredRequest.clone()
+ });
+ }
+
+ throw error;
+ }
+ }
+ /**
+ * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on
+ * the response generated by `this.fetch()`.
+ *
+ * The call to `this.cachePut()` automatically invokes `this.waitUntil()`,
+ * so you do not have to manually call `waitUntil()` on the event.
+ *
+ * @param {Request|string} input The request or URL to fetch and cache.
+ * @return {Promise}
+ */
+
+
+ async fetchAndCachePut(input) {
+ const response = await this.fetch(input);
+ const responseClone = response.clone();
+ void this.waitUntil(this.cachePut(input, responseClone));
+ return response;
+ }
+ /**
+ * Matches a request from the cache (and invokes any applicable plugin
+ * callback methods) using the `cacheName`, `matchOptions`, and `plugins`
+ * defined on the strategy object.
+ *
+ * The following plugin lifecycle methods are invoked when using this method:
+ * - cacheKeyWillByUsed()
+ * - cachedResponseWillByUsed()
+ *
+ * @param {Request|string} key The Request or URL to use as the cache key.
+ * @return {Promise} A matching response, if found.
+ */
+
+
+ async cacheMatch(key) {
+ const request = toRequest(key);
+ let cachedResponse;
+ const {
+ cacheName,
+ matchOptions
+ } = this._strategy;
+ const effectiveRequest = await this.getCacheKey(request, 'read');
+ const multiMatchOptions = Object.assign(Object.assign({}, matchOptions), {
+ cacheName
+ });
+ cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);
+
+ {
+ if (cachedResponse) {
+ logger.debug(`Found a cached response in '${cacheName}'.`);
+ } else {
+ logger.debug(`No cached response found in '${cacheName}'.`);
+ }
+ }
+
+ for (const callback of this.iterateCallbacks('cachedResponseWillBeUsed')) {
+ cachedResponse = (await callback({
+ cacheName,
+ matchOptions,
+ cachedResponse,
+ request: effectiveRequest,
+ event: this.event
+ })) || undefined;
+ }
+
+ return cachedResponse;
+ }
+ /**
+ * Puts a request/response pair in the cache (and invokes any applicable
+ * plugin callback methods) using the `cacheName` and `plugins` defined on
+ * the strategy object.
+ *
+ * The following plugin lifecycle methods are invoked when using this method:
+ * - cacheKeyWillByUsed()
+ * - cacheWillUpdate()
+ * - cacheDidUpdate()
+ *
+ * @param {Request|string} key The request or URL to use as the cache key.
+ * @param {Response} response The response to cache.
+ * @return {Promise} `false` if a cacheWillUpdate caused the response
+ * not be cached, and `true` otherwise.
+ */
+
+
+ async cachePut(key, response) {
+ const request = toRequest(key); // Run in the next task to avoid blocking other cache reads.
+ // https://github.com/w3c/ServiceWorker/issues/1397
+
+ await timeout(0);
+ const effectiveRequest = await this.getCacheKey(request, 'write');
+
+ {
+ if (effectiveRequest.method && effectiveRequest.method !== 'GET') {
+ throw new WorkboxError('attempt-to-cache-non-get-request', {
+ url: getFriendlyURL(effectiveRequest.url),
+ method: effectiveRequest.method
+ });
+ } // See https://github.com/GoogleChrome/workbox/issues/2818
+
+
+ const vary = response.headers.get('Vary');
+
+ if (vary) {
+ logger.debug(`The response for ${getFriendlyURL(effectiveRequest.url)} ` + `has a 'Vary: ${vary}' header. ` + `Consider setting the {ignoreVary: true} option on your strategy ` + `to ensure cache matching and deletion works as expected.`);
+ }
+ }
+
+ if (!response) {
+ {
+ logger.error(`Cannot cache non-existent response for ` + `'${getFriendlyURL(effectiveRequest.url)}'.`);
+ }
+
+ throw new WorkboxError('cache-put-with-no-response', {
+ url: getFriendlyURL(effectiveRequest.url)
+ });
+ }
+
+ const responseToCache = await this._ensureResponseSafeToCache(response);
+
+ if (!responseToCache) {
+ {
+ logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' ` + `will not be cached.`, responseToCache);
+ }
+
+ return false;
+ }
+
+ const {
+ cacheName,
+ matchOptions
+ } = this._strategy;
+ const cache = await self.caches.open(cacheName);
+ const hasCacheUpdateCallback = this.hasCallback('cacheDidUpdate');
+ const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams( // TODO(philipwalton): the `__WB_REVISION__` param is a precaching
+ // feature. Consider into ways to only add this behavior if using
+ // precaching.
+ cache, effectiveRequest.clone(), ['__WB_REVISION__'], matchOptions) : null;
+
+ {
+ logger.debug(`Updating the '${cacheName}' cache with a new Response ` + `for ${getFriendlyURL(effectiveRequest.url)}.`);
+ }
+
+ try {
+ await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
+ } catch (error) {
+ if (error instanceof Error) {
+ // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError
+ if (error.name === 'QuotaExceededError') {
+ await executeQuotaErrorCallbacks();
+ }
+
+ throw error;
+ }
+ }
+
+ for (const callback of this.iterateCallbacks('cacheDidUpdate')) {
+ await callback({
+ cacheName,
+ oldResponse,
+ newResponse: responseToCache.clone(),
+ request: effectiveRequest,
+ event: this.event
+ });
+ }
+
+ return true;
+ }
+ /**
+ * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and
+ * executes any of those callbacks found in sequence. The final `Request`
+ * object returned by the last plugin is treated as the cache key for cache
+ * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have
+ * been registered, the passed request is returned unmodified
+ *
+ * @param {Request} request
+ * @param {string} mode
+ * @return {Promise}
+ */
+
+
+ async getCacheKey(request, mode) {
+ const key = `${request.url} | ${mode}`;
+
+ if (!this._cacheKeys[key]) {
+ let effectiveRequest = request;
+
+ for (const callback of this.iterateCallbacks('cacheKeyWillBeUsed')) {
+ effectiveRequest = toRequest(await callback({
+ mode,
+ request: effectiveRequest,
+ event: this.event,
+ // params has a type any can't change right now.
+ params: this.params // eslint-disable-line
+
+ }));
+ }
+
+ this._cacheKeys[key] = effectiveRequest;
+ }
+
+ return this._cacheKeys[key];
+ }
+ /**
+ * Returns true if the strategy has at least one plugin with the given
+ * callback.
+ *
+ * @param {string} name The name of the callback to check for.
+ * @return {boolean}
+ */
+
+
+ hasCallback(name) {
+ for (const plugin of this._strategy.plugins) {
+ if (name in plugin) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ /**
+ * Runs all plugin callbacks matching the given name, in order, passing the
+ * given param object (merged ith the current plugin state) as the only
+ * argument.
+ *
+ * Note: since this method runs all plugins, it's not suitable for cases
+ * where the return value of a callback needs to be applied prior to calling
+ * the next callback. See
+ * [`iterateCallbacks()`]{@link module:workbox-strategies.StrategyHandler#iterateCallbacks}
+ * below for how to handle that case.
+ *
+ * @param {string} name The name of the callback to run within each plugin.
+ * @param {Object} param The object to pass as the first (and only) param
+ * when executing each callback. This object will be merged with the
+ * current plugin state prior to callback execution.
+ */
+
+
+ async runCallbacks(name, param) {
+ for (const callback of this.iterateCallbacks(name)) {
+ // TODO(philipwalton): not sure why `any` is needed. It seems like
+ // this should work with `as WorkboxPluginCallbackParam[C]`.
+ await callback(param);
+ }
+ }
+ /**
+ * Accepts a callback and returns an iterable of matching plugin callbacks,
+ * where each callback is wrapped with the current handler state (i.e. when
+ * you call each callback, whatever object parameter you pass it will
+ * be merged with the plugin's current state).
+ *
+ * @param {string} name The name fo the callback to run
+ * @return {Array}
+ */
+
+
+ *iterateCallbacks(name) {
+ for (const plugin of this._strategy.plugins) {
+ if (typeof plugin[name] === 'function') {
+ const state = this._pluginStateMap.get(plugin);
+
+ const statefulCallback = param => {
+ const statefulParam = Object.assign(Object.assign({}, param), {
+ state
+ }); // TODO(philipwalton): not sure why `any` is needed. It seems like
+ // this should work with `as WorkboxPluginCallbackParam[C]`.
+
+ return plugin[name](statefulParam);
+ };
+
+ yield statefulCallback;
+ }
+ }
+ }
+ /**
+ * Adds a promise to the
+ * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises}
+ * of the event event associated with the request being handled (usually a
+ * `FetchEvent`).
+ *
+ * Note: you can await
+ * [`doneWaiting()`]{@link module:workbox-strategies.StrategyHandler~doneWaiting}
+ * to know when all added promises have settled.
+ *
+ * @param {Promise} promise A promise to add to the extend lifetime promises
+ * of the event that triggered the request.
+ */
+
+
+ waitUntil(promise) {
+ this._extendLifetimePromises.push(promise);
+
+ return promise;
+ }
+ /**
+ * Returns a promise that resolves once all promises passed to
+ * [`waitUntil()`]{@link module:workbox-strategies.StrategyHandler~waitUntil}
+ * have settled.
+ *
+ * Note: any work done after `doneWaiting()` settles should be manually
+ * passed to an event's `waitUntil()` method (not this handler's
+ * `waitUntil()` method), otherwise the service worker thread my be killed
+ * prior to your work completing.
+ */
+
+
+ async doneWaiting() {
+ let promise;
+
+ while (promise = this._extendLifetimePromises.shift()) {
+ await promise;
+ }
+ }
+ /**
+ * Stops running the strategy and immediately resolves any pending
+ * `waitUntil()` promises.
+ */
+
+
+ destroy() {
+ this._handlerDeferred.resolve(null);
+ }
+ /**
+ * This method will call cacheWillUpdate on the available plugins (or use
+ * status === 200) to determine if the Response is safe and valid to cache.
+ *
+ * @param {Request} options.request
+ * @param {Response} options.response
+ * @return {Promise}
+ *
+ * @private
+ */
+
+
+ async _ensureResponseSafeToCache(response) {
+ let responseToCache = response;
+ let pluginsUsed = false;
+
+ for (const callback of this.iterateCallbacks('cacheWillUpdate')) {
+ responseToCache = (await callback({
+ request: this.request,
+ response: responseToCache,
+ event: this.event
+ })) || undefined;
+ pluginsUsed = true;
+
+ if (!responseToCache) {
+ break;
+ }
+ }
+
+ if (!pluginsUsed) {
+ if (responseToCache && responseToCache.status !== 200) {
+ responseToCache = undefined;
+ }
+
+ {
+ if (responseToCache) {
+ if (responseToCache.status !== 200) {
+ if (responseToCache.status === 0) {
+ logger.warn(`The response for '${this.request.url}' ` + `is an opaque response. The caching strategy that you're ` + `using will not cache opaque responses by default.`);
+ } else {
+ logger.debug(`The response for '${this.request.url}' ` + `returned a status code of '${response.status}' and won't ` + `be cached as a result.`);
+ }
+ }
+ }
+ }
+ }
+
+ return responseToCache;
+ }
+
+ }
+
+ /*
+ Copyright 2020 Google LLC
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ */
+ /**
+ * An abstract base class that all other strategy classes must extend from:
+ *
+ * @memberof module:workbox-strategies
+ */
+
+ class Strategy {
+ /**
+ * Creates a new instance of the strategy and sets all documented option
+ * properties as public instance properties.
+ *
+ * Note: if a custom strategy class extends the base Strategy class and does
+ * not need more than these properties, it does not need to define its own
+ * constructor.
+ *
+ * @param {Object} [options]
+ * @param {string} [options.cacheName] Cache name to store and retrieve
+ * requests. Defaults to the cache names provided by
+ * [workbox-core]{@link module:workbox-core.cacheNames}.
+ * @param {Array