├── .editorconfig
├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── assets
├── cover.jpg
├── demo.gif
└── separate.jpg
├── changelog.md
├── code-of-conduct.md
├── components
├── Layout.js
├── SEO.js
├── common
│ ├── Button.js
│ └── Input.js
└── context
│ └── AuthContext.js
├── config
└── firebase.js
├── contributing.md
├── jsconfig.json
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── _app.js
├── _document.js
├── api
│ └── hello.js
├── index.js
├── login.js
└── signup.js
├── public
├── favicon.ico
├── logo-128x128.png
├── logo-512x512.png
├── manifest.json
├── sw.js
├── sw.js.map
├── vercel.svg
├── workbox-1ffba242.js
└── workbox-1ffba242.js.map
└── styles
├── Auth.module.css
├── Button.module.css
├── Home.module.css
├── Input.module.css
└── globals.css
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # environment file
37 | .env.local
38 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "singleQuote": true,
4 | "printWidth": 80,
5 | "useTabs": true,
6 | "tabWidth": 4,
7 | "semi": true
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License Copyright (c) 2021 msaaddev
2 |
3 | Permission is hereby granted, free of
4 | charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use, copy, modify, merge,
7 | publish, distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to the
9 | following conditions:
10 |
11 | The above copyright notice and this permission notice
12 | (including the next paragraph) shall be included in all copies or substantial
13 | portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 | A Next.js template with firebase user authentication implemented to quickly get you started with your project
5 |
6 |
7 |
8 |
9 |
15 |
16 | 
17 |
18 | - **PWA**: A progressive web app so you can also install and use it as a desktop app
19 | - **Firebase Auth**: Contains all the code you may need to setup user sign up and login features in your project
20 | - **State Management**: State Management with ~~Redux~~ React's Context API
21 | - **Prettier**: Integrated Prettier to easily format the code with `npm run format`
22 | - **MIT Licensed**: Free to use for personal and commercial use
23 |
24 | ## 🚀 Usage
25 |
26 | #### 🏗 Firebase Setup
27 |
28 | 1. Create a firebase project first then create a web app in that project.
29 | 2. Once you have done this, you will get some config keys like API key, auth domain, etc. Make sure you save it somewhere.
30 | 3. Click on Authentication from the left sidebar, go to `Sign-in method` and enable `Email/Password`.
31 |
32 | #### 💥 Template Setup
33 |
34 | 1. Either fork the repo or click on the `Use this template` button to create a new repository with this template.
35 | 2. Now clone the repo, open it in your preferred code editor, and install all the dependencies using `npm install`.
36 | 3. Create a `.env.local` file in the root directory and paste the following in there.
37 |
38 | ```env
39 | NEXT_PUBLIC_Firebase_API_Key=YOUR_API_KEY_GOES_HERE
40 | NEXT_PUBLIC_Auth_Domain=YOUR_AUTH_DOMAIN_GOES_HERE
41 | NEXT_PUBLIC_Project_Id=YOUR_PROJECT_ID_GOES_HERE
42 | NEXT_PUBLIC_Storage_Bucket=YOUR_STORAGE_BUCKET_GOES_HERE
43 | NEXT_PUBLIC_Message_Sender_Id=YOUR_MESSAGE_SENDER_ID_GOES_HERE
44 | NEXT_PUBLIC_App_Id=YOUR_APP_ID_GOES_HERE
45 | ```
46 |
47 | 4. Replace the values above with the keys you got when you set up the web app. (Firebase Setup #2)
48 | 5. Now open your terminal and type in `npm run dev` to start the server.
49 |
50 | ## 🎩 Preview
51 |
52 | 
53 | ## 👨🏻💻 Contributing
54 |
55 | Make sure you read the [contributing guidelines](https://github.com/msaaddev/next-firebase-auth-template/blob/main/contributing.md) before opening a PR.
56 |
57 | ## ⚡️ Other Projects
58 |
59 | I have curated a [detailed list](https://github.com/msaaddev/open-source) of all the open-source projects I have authored. Do take out a moment and take a look.
60 |
61 | ## 🔑 License & Conduct
62 |
63 | - MIT © [Saad Irfan](https://github.com/msaaddev)
64 | - [Code of Conduct](https://github.com/msaaddev/next-firebase-auth-template/blob/main/code-of-conduct.md)
65 |
--------------------------------------------------------------------------------
/assets/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msaaddev/next-firebase-auth-template/23a917fef3e02efdb82519c187b664be35bd268d/assets/cover.jpg
--------------------------------------------------------------------------------
/assets/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msaaddev/next-firebase-auth-template/23a917fef3e02efdb82519c187b664be35bd268d/assets/demo.gif
--------------------------------------------------------------------------------
/assets/separate.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msaaddev/next-firebase-auth-template/23a917fef3e02efdb82519c187b664be35bd268d/assets/separate.jpg
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | ## Change across different version
2 |
3 | ### v0.1.0
4 |
5 | - PWA implemented
6 | - Firebase Auth Implemented
7 |
--------------------------------------------------------------------------------
/code-of-conduct.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | mrsaadirfan@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/components/Layout.js:
--------------------------------------------------------------------------------
1 | import SEO from './SEO';
2 |
3 | const Layout = ({ children }) => {
4 | return (
5 | <>
6 |
7 | {children}
8 | >
9 | );
10 | };
11 |
12 | export default Layout;
13 |
--------------------------------------------------------------------------------
/components/SEO.js:
--------------------------------------------------------------------------------
1 | // packages
2 | import Head from 'next/head';
3 |
4 | const SEO = () => {
5 | return (
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default SEO;
20 |
--------------------------------------------------------------------------------
/components/common/Button.js:
--------------------------------------------------------------------------------
1 | import css from 'styles/Button.module.css';
2 |
3 | const Button = ({ label, onClick }) => {
4 | return (
5 |
8 | );
9 | };
10 |
11 | export default Button;
12 |
--------------------------------------------------------------------------------
/components/common/Input.js:
--------------------------------------------------------------------------------
1 | import css from 'styles/Input.module.css';
2 |
3 | const Input = ({ htmlFor, label, type, autoFocus, value, onChange, err }) => {
4 | return (
5 |
6 |
7 |
8 |
onChange(e.target.value)}
14 | />
15 | {err !== '' &&
{err}
}
16 |
17 | );
18 | };
19 |
20 | export default Input;
21 |
--------------------------------------------------------------------------------
/components/context/AuthContext.js:
--------------------------------------------------------------------------------
1 | import { useState, createContext } from 'react';
2 |
3 | const AuthContext = createContext();
4 |
5 | const AuthProvider = ({ children }) => {
6 | const [user, setUser] = useState('');
7 | const [email, setEmail] = useState('');
8 | const [password, setPassword] = useState('');
9 | const [emailErr, setEmailErr] = useState('');
10 | const [passwordErr, setPasswordErr] = useState('');
11 |
12 | return (
13 |
27 | {children}
28 |
29 | );
30 | };
31 |
32 | export { AuthContext, AuthProvider };
33 |
--------------------------------------------------------------------------------
/config/firebase.js:
--------------------------------------------------------------------------------
1 | import { initializeApp } from 'firebase/app';
2 | import {
3 | getAuth,
4 | createUserWithEmailAndPassword,
5 | signInWithEmailAndPassword,
6 | onAuthStateChanged,
7 | signOut
8 | } from 'firebase/auth';
9 |
10 | const config = {
11 | apiKey: `${process.env.NEXT_PUBLIC_Firebase_API_Key}`,
12 | authDomain: `${process.env.NEXT_PUBLIC_Auth_Domain}`,
13 | projectId: `${process.env.NEXT_PUBLIC_Project_Id}`,
14 | storageBucket: `${process.env.NEXT_PUBLIC_Storage_Bucket}`,
15 | messagingSenderId: `${process.env.NEXT_PUBLIC_Message_Sender_Id}`,
16 | appId: `${process.env.NEXT_PUBLIC_App_Id}`
17 | };
18 |
19 | initializeApp(config);
20 |
21 | export {
22 | getAuth,
23 | createUserWithEmailAndPassword,
24 | signInWithEmailAndPassword,
25 | onAuthStateChanged,
26 | signOut
27 | };
28 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | 1. Open the issue first.
4 | 2. Fork the repository.
5 | 3. Create and switch to the new branch. New branch name convention must be like this yourUsername/newFeature, for instance, `msaaddev/pr-review`
6 | 4. Commit the changes in your forked repository.
7 | 1. Make sure you use [Emoji-log](https://github.com/ahmadawais/Emoji-Log) in your commit messages.
8 | 5. Open a pull request & mention the issue number in the pull request for reference.
9 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "."
4 | },
5 | "exclude": ["node_modules"]
6 | }
7 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withPWA = require('next-pwa');
2 |
3 | module.exports = withPWA({
4 | pwa: {
5 | dest: 'public',
6 | register: true,
7 | skipWaiting: true
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-firebase-auth-template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "format": "prettier --write \"./**/*.{js,json}\""
10 | },
11 | "dependencies": {
12 | "firebase": "^9.0.2",
13 | "next": "11.1.2",
14 | "next-pwa": "^5.3.1",
15 | "react": "17.0.2",
16 | "react-dom": "17.0.2"
17 | },
18 | "devDependencies": {
19 | "eslint": "7.32.0",
20 | "eslint-config-next": "11.1.2",
21 | "prettier": "^2.4.0"
22 | },
23 | "license": "MIT"
24 | }
25 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import { AuthProvider } from 'components/context/AuthContext';
2 | import Layout from 'components/Layout';
3 | import 'styles/globals.css';
4 |
5 | function MyApp({ Component, pageProps }) {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
15 | export default MyApp;
16 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document';
2 |
3 | class MyDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default MyDocument;
22 |
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' });
5 | }
6 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import Head from 'next/head';
3 | import Link from 'next/link';
4 | import css from 'styles/Home.module.css';
5 |
6 | // config
7 | import { getAuth, signOut } from 'config/firebase';
8 |
9 | // context
10 | import { AuthContext } from 'components/context/AuthContext';
11 |
12 | export default function Home() {
13 | const { email, setEmail } = useContext(AuthContext);
14 |
15 | /**
16 | *
17 | *
18 | * logout user
19 | */
20 | const handleLogout = () => {
21 | const auth = getAuth();
22 | signOut(auth)
23 | .then(() => {
24 | setEmail('');
25 | })
26 | .catch((err) => {
27 | console.log(err);
28 | });
29 | };
30 |
31 | return (
32 |
33 |
34 |
Next.js Firebase Auth Template
35 |
36 |
37 |
38 | {email !== '' ? (
39 |
40 | {' '}
41 |
User email: {email}
42 |
45 |
46 | ) : (
47 | <>
48 |
49 |
52 |
53 |
54 |
57 |
58 | >
59 | )}
60 |
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/pages/login.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useContext } from 'react';
2 | import { useRouter } from 'next/router';
3 | import {
4 | getAuth,
5 | signInWithEmailAndPassword,
6 | onAuthStateChanged
7 | } from 'config/firebase';
8 |
9 | // components
10 | import Input from 'components/common/Input';
11 | import Button from 'components/common/Button';
12 |
13 | // context
14 | import { AuthContext } from 'components/context/AuthContext';
15 |
16 | // stylesheet
17 | import css from 'styles/Auth.module.css';
18 |
19 | const Login = () => {
20 | const { user, setUser } = useContext(AuthContext);
21 | const { email, setEmail } = useContext(AuthContext);
22 | const { password, setPassword } = useContext(AuthContext);
23 | const { emailErr, setEmailErr } = useContext(AuthContext);
24 | const { passwordErr, setPasswordErr } = useContext(AuthContext);
25 | const router = useRouter();
26 | const auth = getAuth();
27 |
28 | /**
29 | *
30 | *
31 | * reset values of errors to empty string
32 | */
33 | const clearErrs = () => {
34 | setEmailErr('');
35 | setPasswordErr('');
36 | };
37 |
38 | /**
39 | *
40 | *
41 | * login to the existing account
42 | */
43 | const handleLogin = () => {
44 | clearErrs();
45 |
46 | signInWithEmailAndPassword(auth, email, password)
47 | .then(() => {
48 | setPassword('');
49 | router.push('/');
50 | })
51 | .catch((err) => {
52 | const { code, message } = err;
53 |
54 | if (
55 | code === 'auth/invalid-email' ||
56 | code === 'auth/user-disabled' ||
57 | code === 'auth/user-not-found'
58 | ) {
59 | setEmailErr(message);
60 | }
61 |
62 | if (code === 'auth/wrong-password') {
63 | setPasswordErr(message);
64 | }
65 | });
66 | };
67 |
68 | /**
69 | *
70 | *
71 | * check if user exists
72 | */
73 | const authListener = () => {
74 | onAuthStateChanged(auth, (user) => {
75 | if (user) {
76 | setPassword('');
77 | setUser(user);
78 | } else {
79 | setUser('');
80 | }
81 | });
82 | };
83 |
84 | useEffect(() => {
85 | authListener();
86 | }, []);
87 |
88 | return (
89 |
90 |
99 |
108 |
109 |
110 | );
111 | };
112 |
113 | export default Login;
114 |
--------------------------------------------------------------------------------
/pages/signup.js:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { useRouter } from 'next/router';
3 | import { getAuth, createUserWithEmailAndPassword } from 'config/firebase';
4 |
5 | // components
6 | import Input from 'components/common/Input';
7 | import Button from 'components/common/Button';
8 |
9 | // context
10 | import { AuthContext } from 'components/context/AuthContext';
11 |
12 | // stylesheet
13 | import css from 'styles/Auth.module.css';
14 |
15 | const SignUp = () => {
16 | const { email, setEmail } = useContext(AuthContext);
17 | const { password, setPassword } = useContext(AuthContext);
18 | const { emailErr, setEmailErr } = useContext(AuthContext);
19 | const { passwordErr, setPasswordErr } = useContext(AuthContext);
20 | const router = useRouter();
21 |
22 | /**
23 | *
24 | *
25 | * reset values ofo inputs to empty string
26 | */
27 | const clearInput = () => {
28 | setEmail('');
29 | setPassword('');
30 | };
31 |
32 | /**
33 | *
34 | *
35 | * reset values of errors to empty string
36 | */
37 | const clearErrs = () => {
38 | setEmailErr('');
39 | setPasswordErr('');
40 | };
41 |
42 | /**
43 | *
44 | *
45 | * sign up user if everything checks out
46 | */
47 | const handleSignUp = () => {
48 | clearErrs();
49 |
50 | const auth = getAuth();
51 | createUserWithEmailAndPassword(auth, email, password)
52 | .then(() => {
53 | clearInput();
54 | router.push('/login');
55 | })
56 | .catch((err) => {
57 | const { code, message } = err;
58 |
59 | if (
60 | code === 'auth/email-already-in-use' ||
61 | code === 'auth/invalid-email'
62 | ) {
63 | setEmailErr(message);
64 | }
65 |
66 | if (code === 'auth/weak-password') {
67 | setPasswordErr(message);
68 | }
69 | });
70 | };
71 |
72 | return (
73 |
74 |
83 |
92 |
93 |
94 | );
95 | };
96 |
97 | export default SignUp;
98 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msaaddev/next-firebase-auth-template/23a917fef3e02efdb82519c187b664be35bd268d/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msaaddev/next-firebase-auth-template/23a917fef3e02efdb82519c187b664be35bd268d/public/logo-128x128.png
--------------------------------------------------------------------------------
/public/logo-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msaaddev/next-firebase-auth-template/23a917fef3e02efdb82519c187b664be35bd268d/public/logo-512x512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-firebase-auth-template",
3 | "short_name": "nextjs-firebase-auth-template",
4 | "icons": [
5 | {
6 | "src": "/logo-128x128.png",
7 | "sizes": "128x128",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/logo-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "start_url": "/",
19 | "display": "standalone",
20 | "orientation": "portrait"
21 | }
22 |
--------------------------------------------------------------------------------
/public/sw.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2018 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | // If the loader is already loaded, just stop.
15 | if (!self.define) {
16 | const singleRequire = name => {
17 | if (name !== 'require') {
18 | name = name + '.js';
19 | }
20 | let promise = Promise.resolve();
21 | if (!registry[name]) {
22 |
23 | promise = new Promise(async resolve => {
24 | if ("document" in self) {
25 | const script = document.createElement("script");
26 | script.src = name;
27 | document.head.appendChild(script);
28 | script.onload = resolve;
29 | } else {
30 | importScripts(name);
31 | resolve();
32 | }
33 | });
34 |
35 | }
36 | return promise.then(() => {
37 | if (!registry[name]) {
38 | throw new Error(`Module ${name} didn’t register its module`);
39 | }
40 | return registry[name];
41 | });
42 | };
43 |
44 | const require = (names, resolve) => {
45 | Promise.all(names.map(singleRequire))
46 | .then(modules => resolve(modules.length === 1 ? modules[0] : modules));
47 | };
48 |
49 | const registry = {
50 | require: Promise.resolve(require)
51 | };
52 |
53 | self.define = (moduleName, depsNames, factory) => {
54 | if (registry[moduleName]) {
55 | // Module is already loading or loaded.
56 | return;
57 | }
58 | registry[moduleName] = Promise.resolve().then(() => {
59 | let exports = {};
60 | const module = {
61 | uri: location.origin + moduleName.slice(1)
62 | };
63 | return Promise.all(
64 | depsNames.map(depName => {
65 | switch(depName) {
66 | case "exports":
67 | return exports;
68 | case "module":
69 | return module;
70 | default:
71 | return singleRequire(depName);
72 | }
73 | })
74 | ).then(deps => {
75 | const facValue = factory(...deps);
76 | if(!exports.default) {
77 | exports.default = facValue;
78 | }
79 | return exports;
80 | });
81 | });
82 | };
83 | }
84 | define("./sw.js",['./workbox-1ffba242'], function (workbox) { 'use strict';
85 |
86 | /**
87 | * Welcome to your Workbox-powered service worker!
88 | *
89 | * You'll need to register this file in your web app.
90 | * See https://goo.gl/nhQhGp
91 | *
92 | * The rest of the code is auto-generated. Please don't update this file
93 | * directly; instead, make changes to your Workbox build configuration
94 | * and re-run your build process.
95 | * See https://goo.gl/2aRDsh
96 | */
97 |
98 | importScripts();
99 | self.skipWaiting();
100 | workbox.clientsClaim();
101 | workbox.registerRoute("/", new workbox.NetworkFirst({
102 | "cacheName": "start-url",
103 | plugins: [{
104 | cacheWillUpdate: async ({
105 | request,
106 | response,
107 | event,
108 | state
109 | }) => {
110 | if (response && response.type === 'opaqueredirect') {
111 | return new Response(response.body, {
112 | status: 200,
113 | statusText: 'OK',
114 | headers: response.headers
115 | });
116 | }
117 |
118 | return response;
119 | }
120 | }]
121 | }), 'GET');
122 | workbox.registerRoute(/.*/i, new workbox.NetworkOnly({
123 | "cacheName": "dev",
124 | plugins: []
125 | }), 'GET');
126 |
127 | });
128 | //# sourceMappingURL=sw.js.map
129 |
--------------------------------------------------------------------------------
/public/sw.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"sw.js","sources":["../../../../private/var/folders/lc/5_4_g4_556g4mg475xdbr45c0000gn/T/25e4a4510e8b3cac4db5d07b8067d219/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/saadirfan/GitHub/nextjs-firebase-auth-template/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/saadirfan/GitHub/nextjs-firebase-auth-template/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/saadirfan/GitHub/nextjs-firebase-auth-template/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/saadirfan/GitHub/nextjs-firebase-auth-template/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 \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; } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), '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","workbox_strategies_NetworkOnly"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG2J;EAC3J;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAGAA,aAAa;EAUbC,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;EAApC,CAApC,CAAN,EAAmU,KAAnU,CAA7B;AACAL,uBAA6B,CAAC,KAAD,EAAQ,IAAIc,mBAAJ,CAAmC;EAAE,eAAY,KAAd;EAAqBZ,EAAAA,OAAO,EAAE;EAA9B,CAAnC,CAAR,EAAgF,KAAhF,CAA7B;;"}
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/workbox-1ffba242.js:
--------------------------------------------------------------------------------
1 | define("./workbox-1ffba242.js",['exports'], function (exports) { 'use strict';
2 |
3 | try {
4 | self['workbox:core:6.2.4'] && _();
5 | } catch (e) {}
6 |
7 | /*
8 | Copyright 2019 Google LLC
9 | Use of this source code is governed by an MIT-style
10 | license that can be found in the LICENSE file or at
11 | https://opensource.org/licenses/MIT.
12 | */
13 | const logger = (() => {
14 | // Don't overwrite this value if it's already set.
15 | // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923
16 | if (!('__WB_DISABLE_DEV_LOGS' in self)) {
17 | self.__WB_DISABLE_DEV_LOGS = false;
18 | }
19 |
20 | let inGroup = false;
21 | const methodToColorMap = {
22 | debug: `#7f8c8d`,
23 | log: `#2ecc71`,
24 | warn: `#f39c12`,
25 | error: `#c0392b`,
26 | groupCollapsed: `#3498db`,
27 | groupEnd: null
28 | };
29 |
30 | const print = function (method, args) {
31 | if (self.__WB_DISABLE_DEV_LOGS) {
32 | return;
33 | }
34 |
35 | if (method === 'groupCollapsed') {
36 | // Safari doesn't print all console.groupCollapsed() arguments:
37 | // https://bugs.webkit.org/show_bug.cgi?id=182754
38 | if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
39 | console[method](...args);
40 | return;
41 | }
42 | }
43 |
44 | 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.
45 |
46 | const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];
47 | console[method](...logPrefix, ...args);
48 |
49 | if (method === 'groupCollapsed') {
50 | inGroup = true;
51 | }
52 |
53 | if (method === 'groupEnd') {
54 | inGroup = false;
55 | }
56 | }; // eslint-disable-next-line @typescript-eslint/ban-types
57 |
58 |
59 | const api = {};
60 | const loggerMethods = Object.keys(methodToColorMap);
61 |
62 | for (const key of loggerMethods) {
63 | const method = key;
64 |
65 | api[method] = (...args) => {
66 | print(method, args);
67 | };
68 | }
69 |
70 | return api;
71 | })();
72 |
73 | /*
74 | Copyright 2018 Google LLC
75 |
76 | Use of this source code is governed by an MIT-style
77 | license that can be found in the LICENSE file or at
78 | https://opensource.org/licenses/MIT.
79 | */
80 | const messages$1 = {
81 | 'invalid-value': ({
82 | paramName,
83 | validValueDescription,
84 | value
85 | }) => {
86 | if (!paramName || !validValueDescription) {
87 | throw new Error(`Unexpected input to 'invalid-value' error.`);
88 | }
89 |
90 | return `The '${paramName}' parameter was given a value with an ` + `unexpected value. ${validValueDescription} Received a value of ` + `${JSON.stringify(value)}.`;
91 | },
92 | 'not-an-array': ({
93 | moduleName,
94 | className,
95 | funcName,
96 | paramName
97 | }) => {
98 | if (!moduleName || !className || !funcName || !paramName) {
99 | throw new Error(`Unexpected input to 'not-an-array' error.`);
100 | }
101 |
102 | return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className}.${funcName}()' must be an array.`;
103 | },
104 | 'incorrect-type': ({
105 | expectedType,
106 | paramName,
107 | moduleName,
108 | className,
109 | funcName
110 | }) => {
111 | if (!expectedType || !paramName || !moduleName || !funcName) {
112 | throw new Error(`Unexpected input to 'incorrect-type' error.`);
113 | }
114 |
115 | const classNameStr = className ? `${className}.` : '';
116 | return `The parameter '${paramName}' passed into ` + `'${moduleName}.${classNameStr}` + `${funcName}()' must be of type ${expectedType}.`;
117 | },
118 | 'incorrect-class': ({
119 | expectedClassName,
120 | paramName,
121 | moduleName,
122 | className,
123 | funcName,
124 | isReturnValueProblem
125 | }) => {
126 | if (!expectedClassName || !moduleName || !funcName) {
127 | throw new Error(`Unexpected input to 'incorrect-class' error.`);
128 | }
129 |
130 | const classNameStr = className ? `${className}.` : '';
131 |
132 | if (isReturnValueProblem) {
133 | return `The return value from ` + `'${moduleName}.${classNameStr}${funcName}()' ` + `must be an instance of class ${expectedClassName}.`;
134 | }
135 |
136 | return `The parameter '${paramName}' passed into ` + `'${moduleName}.${classNameStr}${funcName}()' ` + `must be an instance of class ${expectedClassName}.`;
137 | },
138 | 'missing-a-method': ({
139 | expectedMethod,
140 | paramName,
141 | moduleName,
142 | className,
143 | funcName
144 | }) => {
145 | if (!expectedMethod || !paramName || !moduleName || !className || !funcName) {
146 | throw new Error(`Unexpected input to 'missing-a-method' error.`);
147 | }
148 |
149 | return `${moduleName}.${className}.${funcName}() expected the ` + `'${paramName}' parameter to expose a '${expectedMethod}' method.`;
150 | },
151 | 'add-to-cache-list-unexpected-type': ({
152 | entry
153 | }) => {
154 | 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.`;
155 | },
156 | 'add-to-cache-list-conflicting-entries': ({
157 | firstEntry,
158 | secondEntry
159 | }) => {
160 | if (!firstEntry || !secondEntry) {
161 | throw new Error(`Unexpected input to ` + `'add-to-cache-list-duplicate-entries' error.`);
162 | }
163 |
164 | 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.`;
165 | },
166 | 'plugin-error-request-will-fetch': ({
167 | thrownErrorMessage
168 | }) => {
169 | if (!thrownErrorMessage) {
170 | throw new Error(`Unexpected input to ` + `'plugin-error-request-will-fetch', error.`);
171 | }
172 |
173 | return `An error was thrown by a plugins 'requestWillFetch()' method. ` + `The thrown error message was: '${thrownErrorMessage}'.`;
174 | },
175 | 'invalid-cache-name': ({
176 | cacheNameId,
177 | value
178 | }) => {
179 | if (!cacheNameId) {
180 | throw new Error(`Expected a 'cacheNameId' for error 'invalid-cache-name'`);
181 | }
182 |
183 | return `You must provide a name containing at least one character for ` + `setCacheDetails({${cacheNameId}: '...'}). Received a value of ` + `'${JSON.stringify(value)}'`;
184 | },
185 | 'unregister-route-but-not-found-with-method': ({
186 | method
187 | }) => {
188 | if (!method) {
189 | throw new Error(`Unexpected input to ` + `'unregister-route-but-not-found-with-method' error.`);
190 | }
191 |
192 | return `The route you're trying to unregister was not previously ` + `registered for the method type '${method}'.`;
193 | },
194 | 'unregister-route-route-not-registered': () => {
195 | return `The route you're trying to unregister was not previously ` + `registered.`;
196 | },
197 | 'queue-replay-failed': ({
198 | name
199 | }) => {
200 | return `Replaying the background sync queue '${name}' failed.`;
201 | },
202 | 'duplicate-queue-name': ({
203 | name
204 | }) => {
205 | return `The Queue name '${name}' is already being used. ` + `All instances of backgroundSync.Queue must be given unique names.`;
206 | },
207 | 'expired-test-without-max-age': ({
208 | methodName,
209 | paramName
210 | }) => {
211 | return `The '${methodName}()' method can only be used when the ` + `'${paramName}' is used in the constructor.`;
212 | },
213 | 'unsupported-route-type': ({
214 | moduleName,
215 | className,
216 | funcName,
217 | paramName
218 | }) => {
219 | return `The supplied '${paramName}' parameter was an unsupported type. ` + `Please check the docs for ${moduleName}.${className}.${funcName} for ` + `valid input types.`;
220 | },
221 | 'not-array-of-class': ({
222 | value,
223 | expectedClass,
224 | moduleName,
225 | className,
226 | funcName,
227 | paramName
228 | }) => {
229 | 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.`;
230 | },
231 | 'max-entries-or-age-required': ({
232 | moduleName,
233 | className,
234 | funcName
235 | }) => {
236 | return `You must define either config.maxEntries or config.maxAgeSeconds` + `in ${moduleName}.${className}.${funcName}`;
237 | },
238 | 'statuses-or-headers-required': ({
239 | moduleName,
240 | className,
241 | funcName
242 | }) => {
243 | return `You must define either config.statuses or config.headers` + `in ${moduleName}.${className}.${funcName}`;
244 | },
245 | 'invalid-string': ({
246 | moduleName,
247 | funcName,
248 | paramName
249 | }) => {
250 | if (!paramName || !moduleName || !funcName) {
251 | throw new Error(`Unexpected input to 'invalid-string' error.`);
252 | }
253 |
254 | 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.`;
255 | },
256 | 'channel-name-required': () => {
257 | return `You must provide a channelName to construct a ` + `BroadcastCacheUpdate instance.`;
258 | },
259 | 'invalid-responses-are-same-args': () => {
260 | return `The arguments passed into responsesAreSame() appear to be ` + `invalid. Please ensure valid Responses are used.`;
261 | },
262 | 'expire-custom-caches-only': () => {
263 | return `You must provide a 'cacheName' property when using the ` + `expiration plugin with a runtime caching strategy.`;
264 | },
265 | 'unit-must-be-bytes': ({
266 | normalizedRangeHeader
267 | }) => {
268 | if (!normalizedRangeHeader) {
269 | throw new Error(`Unexpected input to 'unit-must-be-bytes' error.`);
270 | }
271 |
272 | return `The 'unit' portion of the Range header must be set to 'bytes'. ` + `The Range header provided was "${normalizedRangeHeader}"`;
273 | },
274 | 'single-range-only': ({
275 | normalizedRangeHeader
276 | }) => {
277 | if (!normalizedRangeHeader) {
278 | throw new Error(`Unexpected input to 'single-range-only' error.`);
279 | }
280 |
281 | return `Multiple ranges are not supported. Please use a single start ` + `value, and optional end value. The Range header provided was ` + `"${normalizedRangeHeader}"`;
282 | },
283 | 'invalid-range-values': ({
284 | normalizedRangeHeader
285 | }) => {
286 | if (!normalizedRangeHeader) {
287 | throw new Error(`Unexpected input to 'invalid-range-values' error.`);
288 | }
289 |
290 | 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}"`;
291 | },
292 | 'no-range-header': () => {
293 | return `No Range header was found in the Request provided.`;
294 | },
295 | 'range-not-satisfiable': ({
296 | size,
297 | start,
298 | end
299 | }) => {
300 | return `The start (${start}) and end (${end}) values in the Range are ` + `not satisfiable by the cached response, which is ${size} bytes.`;
301 | },
302 | 'attempt-to-cache-non-get-request': ({
303 | url,
304 | method
305 | }) => {
306 | return `Unable to cache '${url}' because it is a '${method}' request and ` + `only 'GET' requests can be cached.`;
307 | },
308 | 'cache-put-with-no-response': ({
309 | url
310 | }) => {
311 | return `There was an attempt to cache '${url}' but the response was not ` + `defined.`;
312 | },
313 | 'no-response': ({
314 | url,
315 | error
316 | }) => {
317 | let message = `The strategy could not generate a response for '${url}'.`;
318 |
319 | if (error) {
320 | message += ` The underlying error is ${error}.`;
321 | }
322 |
323 | return message;
324 | },
325 | 'bad-precaching-response': ({
326 | url,
327 | status
328 | }) => {
329 | return `The precaching request for '${url}' failed` + (status ? ` with an HTTP status of ${status}.` : `.`);
330 | },
331 | 'non-precached-url': ({
332 | url
333 | }) => {
334 | return `createHandlerBoundToURL('${url}') was called, but that URL is ` + `not precached. Please pass in a URL that is precached instead.`;
335 | },
336 | 'add-to-cache-list-conflicting-integrities': ({
337 | url
338 | }) => {
339 | return `Two of the entries passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + `${url} with different integrity values. Please remove one of them.`;
340 | },
341 | 'missing-precache-entry': ({
342 | cacheName,
343 | url
344 | }) => {
345 | return `Unable to find a precached response in ${cacheName} for ${url}.`;
346 | },
347 | 'cross-origin-copy-response': ({
348 | origin
349 | }) => {
350 | return `workbox-core.copyResponse() can only be used with same-origin ` + `responses. It was passed a response with origin ${origin}.`;
351 | }
352 | };
353 |
354 | /*
355 | Copyright 2018 Google LLC
356 |
357 | Use of this source code is governed by an MIT-style
358 | license that can be found in the LICENSE file or at
359 | https://opensource.org/licenses/MIT.
360 | */
361 |
362 | const generatorFunction = (code, details = {}) => {
363 | const message = messages$1[code];
364 |
365 | if (!message) {
366 | throw new Error(`Unable to find message for code '${code}'.`);
367 | }
368 |
369 | return message(details);
370 | };
371 |
372 | const messageGenerator = generatorFunction;
373 |
374 | /*
375 | Copyright 2018 Google LLC
376 |
377 | Use of this source code is governed by an MIT-style
378 | license that can be found in the LICENSE file or at
379 | https://opensource.org/licenses/MIT.
380 | */
381 | /**
382 | * Workbox errors should be thrown with this class.
383 | * This allows use to ensure the type easily in tests,
384 | * helps developers identify errors from workbox
385 | * easily and allows use to optimise error
386 | * messages correctly.
387 | *
388 | * @private
389 | */
390 |
391 | class WorkboxError extends Error {
392 | /**
393 | *
394 | * @param {string} errorCode The error code that
395 | * identifies this particular error.
396 | * @param {Object=} details Any relevant arguments
397 | * that will help developers identify issues should
398 | * be added as a key on the context object.
399 | */
400 | constructor(errorCode, details) {
401 | const message = messageGenerator(errorCode, details);
402 | super(message);
403 | this.name = errorCode;
404 | this.details = details;
405 | }
406 |
407 | }
408 |
409 | /*
410 | Copyright 2018 Google LLC
411 |
412 | Use of this source code is governed by an MIT-style
413 | license that can be found in the LICENSE file or at
414 | https://opensource.org/licenses/MIT.
415 | */
416 | /*
417 | * This method throws if the supplied value is not an array.
418 | * The destructed values are required to produce a meaningful error for users.
419 | * The destructed and restructured object is so it's clear what is
420 | * needed.
421 | */
422 |
423 | const isArray = (value, details) => {
424 | if (!Array.isArray(value)) {
425 | throw new WorkboxError('not-an-array', details);
426 | }
427 | };
428 |
429 | const hasMethod = (object, expectedMethod, details) => {
430 | const type = typeof object[expectedMethod];
431 |
432 | if (type !== 'function') {
433 | details['expectedMethod'] = expectedMethod;
434 | throw new WorkboxError('missing-a-method', details);
435 | }
436 | };
437 |
438 | const isType = (object, expectedType, details) => {
439 | if (typeof object !== expectedType) {
440 | details['expectedType'] = expectedType;
441 | throw new WorkboxError('incorrect-type', details);
442 | }
443 | };
444 |
445 | const isInstance = (object, // Need the general type to do the check later.
446 | // eslint-disable-next-line @typescript-eslint/ban-types
447 | expectedClass, details) => {
448 | if (!(object instanceof expectedClass)) {
449 | details['expectedClassName'] = expectedClass.name;
450 | throw new WorkboxError('incorrect-class', details);
451 | }
452 | };
453 |
454 | const isOneOf = (value, validValues, details) => {
455 | if (!validValues.includes(value)) {
456 | details['validValueDescription'] = `Valid values are ${JSON.stringify(validValues)}.`;
457 | throw new WorkboxError('invalid-value', details);
458 | }
459 | };
460 |
461 | const isArrayOfClass = (value, // Need general type to do check later.
462 | expectedClass, // eslint-disable-line
463 | details) => {
464 | const error = new WorkboxError('not-array-of-class', details);
465 |
466 | if (!Array.isArray(value)) {
467 | throw error;
468 | }
469 |
470 | for (const item of value) {
471 | if (!(item instanceof expectedClass)) {
472 | throw error;
473 | }
474 | }
475 | };
476 |
477 | const finalAssertExports = {
478 | hasMethod,
479 | isArray,
480 | isInstance,
481 | isOneOf,
482 | isType,
483 | isArrayOfClass
484 | };
485 |
486 | try {
487 | self['workbox:routing:6.2.4'] && _();
488 | } catch (e) {}
489 |
490 | /*
491 | Copyright 2018 Google LLC
492 |
493 | Use of this source code is governed by an MIT-style
494 | license that can be found in the LICENSE file or at
495 | https://opensource.org/licenses/MIT.
496 | */
497 | /**
498 | * The default HTTP method, 'GET', used when there's no specific method
499 | * configured for a route.
500 | *
501 | * @type {string}
502 | *
503 | * @private
504 | */
505 |
506 | const defaultMethod = 'GET';
507 | /**
508 | * The list of valid HTTP methods associated with requests that could be routed.
509 | *
510 | * @type {Array}
511 | *
512 | * @private
513 | */
514 |
515 | const validMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'];
516 |
517 | /*
518 | Copyright 2018 Google LLC
519 |
520 | Use of this source code is governed by an MIT-style
521 | license that can be found in the LICENSE file or at
522 | https://opensource.org/licenses/MIT.
523 | */
524 | /**
525 | * @param {function()|Object} handler Either a function, or an object with a
526 | * 'handle' method.
527 | * @return {Object} An object with a handle method.
528 | *
529 | * @private
530 | */
531 |
532 | const normalizeHandler = handler => {
533 | if (handler && typeof handler === 'object') {
534 | {
535 | finalAssertExports.hasMethod(handler, 'handle', {
536 | moduleName: 'workbox-routing',
537 | className: 'Route',
538 | funcName: 'constructor',
539 | paramName: 'handler'
540 | });
541 | }
542 |
543 | return handler;
544 | } else {
545 | {
546 | finalAssertExports.isType(handler, 'function', {
547 | moduleName: 'workbox-routing',
548 | className: 'Route',
549 | funcName: 'constructor',
550 | paramName: 'handler'
551 | });
552 | }
553 |
554 | return {
555 | handle: handler
556 | };
557 | }
558 | };
559 |
560 | /*
561 | Copyright 2018 Google LLC
562 |
563 | Use of this source code is governed by an MIT-style
564 | license that can be found in the LICENSE file or at
565 | https://opensource.org/licenses/MIT.
566 | */
567 | /**
568 | * A `Route` consists of a pair of callback functions, "match" and "handler".
569 | * The "match" callback determine if a route should be used to "handle" a
570 | * request by returning a non-falsy value if it can. The "handler" callback
571 | * is called when there is a match and should return a Promise that resolves
572 | * to a `Response`.
573 | *
574 | * @memberof module:workbox-routing
575 | */
576 |
577 | class Route {
578 | /**
579 | * Constructor for Route class.
580 | *
581 | * @param {module:workbox-routing~matchCallback} match
582 | * A callback function that determines whether the route matches a given
583 | * `fetch` event by returning a non-falsy value.
584 | * @param {module:workbox-routing~handlerCallback} handler A callback
585 | * function that returns a Promise resolving to a Response.
586 | * @param {string} [method='GET'] The HTTP method to match the Route
587 | * against.
588 | */
589 | constructor(match, handler, method = defaultMethod) {
590 | {
591 | finalAssertExports.isType(match, 'function', {
592 | moduleName: 'workbox-routing',
593 | className: 'Route',
594 | funcName: 'constructor',
595 | paramName: 'match'
596 | });
597 |
598 | if (method) {
599 | finalAssertExports.isOneOf(method, validMethods, {
600 | paramName: 'method'
601 | });
602 | }
603 | } // These values are referenced directly by Router so cannot be
604 | // altered by minificaton.
605 |
606 |
607 | this.handler = normalizeHandler(handler);
608 | this.match = match;
609 | this.method = method;
610 | }
611 | /**
612 | *
613 | * @param {module:workbox-routing-handlerCallback} handler A callback
614 | * function that returns a Promise resolving to a Response
615 | */
616 |
617 |
618 | setCatchHandler(handler) {
619 | this.catchHandler = normalizeHandler(handler);
620 | }
621 |
622 | }
623 |
624 | /*
625 | Copyright 2018 Google LLC
626 |
627 | Use of this source code is governed by an MIT-style
628 | license that can be found in the LICENSE file or at
629 | https://opensource.org/licenses/MIT.
630 | */
631 | /**
632 | * RegExpRoute makes it easy to create a regular expression based
633 | * [Route]{@link module:workbox-routing.Route}.
634 | *
635 | * For same-origin requests the RegExp only needs to match part of the URL. For
636 | * requests against third-party servers, you must define a RegExp that matches
637 | * the start of the URL.
638 | *
639 | * [See the module docs for info.]{@link https://developers.google.com/web/tools/workbox/modules/workbox-routing}
640 | *
641 | * @memberof module:workbox-routing
642 | * @extends module:workbox-routing.Route
643 | */
644 |
645 | class RegExpRoute extends Route {
646 | /**
647 | * If the regular expression contains
648 | * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references},
649 | * the captured values will be passed to the
650 | * [handler's]{@link module:workbox-routing~handlerCallback} `params`
651 | * argument.
652 | *
653 | * @param {RegExp} regExp The regular expression to match against URLs.
654 | * @param {module:workbox-routing~handlerCallback} handler A callback
655 | * function that returns a Promise resulting in a Response.
656 | * @param {string} [method='GET'] The HTTP method to match the Route
657 | * against.
658 | */
659 | constructor(regExp, handler, method) {
660 | {
661 | finalAssertExports.isInstance(regExp, RegExp, {
662 | moduleName: 'workbox-routing',
663 | className: 'RegExpRoute',
664 | funcName: 'constructor',
665 | paramName: 'pattern'
666 | });
667 | }
668 |
669 | const match = ({
670 | url
671 | }) => {
672 | const result = regExp.exec(url.href); // Return immediately if there's no match.
673 |
674 | if (!result) {
675 | return;
676 | } // Require that the match start at the first character in the URL string
677 | // if it's a cross-origin request.
678 | // See https://github.com/GoogleChrome/workbox/issues/281 for the context
679 | // behind this behavior.
680 |
681 |
682 | if (url.origin !== location.origin && result.index !== 0) {
683 | {
684 | 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.`);
685 | }
686 |
687 | return;
688 | } // If the route matches, but there aren't any capture groups defined, then
689 | // this will return [], which is truthy and therefore sufficient to
690 | // indicate a match.
691 | // If there are capture groups, then it will return their values.
692 |
693 |
694 | return result.slice(1);
695 | };
696 |
697 | super(match, handler, method);
698 | }
699 |
700 | }
701 |
702 | /*
703 | Copyright 2018 Google LLC
704 |
705 | Use of this source code is governed by an MIT-style
706 | license that can be found in the LICENSE file or at
707 | https://opensource.org/licenses/MIT.
708 | */
709 |
710 | const getFriendlyURL = url => {
711 | const urlObj = new URL(String(url), location.href); // See https://github.com/GoogleChrome/workbox/issues/2323
712 | // We want to include everything, except for the origin if it's same-origin.
713 |
714 | return urlObj.href.replace(new RegExp(`^${location.origin}`), '');
715 | };
716 |
717 | /*
718 | Copyright 2018 Google LLC
719 |
720 | Use of this source code is governed by an MIT-style
721 | license that can be found in the LICENSE file or at
722 | https://opensource.org/licenses/MIT.
723 | */
724 | /**
725 | * The Router can be used to process a FetchEvent through one or more
726 | * [Routes]{@link module:workbox-routing.Route} responding with a Request if
727 | * a matching route exists.
728 | *
729 | * If no route matches a given a request, the Router will use a "default"
730 | * handler if one is defined.
731 | *
732 | * Should the matching Route throw an error, the Router will use a "catch"
733 | * handler if one is defined to gracefully deal with issues and respond with a
734 | * Request.
735 | *
736 | * If a request matches multiple routes, the **earliest** registered route will
737 | * be used to respond to the request.
738 | *
739 | * @memberof module:workbox-routing
740 | */
741 |
742 | class Router {
743 | /**
744 | * Initializes a new Router.
745 | */
746 | constructor() {
747 | this._routes = new Map();
748 | this._defaultHandlerMap = new Map();
749 | }
750 | /**
751 | * @return {Map>} routes A `Map` of HTTP
752 | * method name ('GET', etc.) to an array of all the corresponding `Route`
753 | * instances that are registered.
754 | */
755 |
756 |
757 | get routes() {
758 | return this._routes;
759 | }
760 | /**
761 | * Adds a fetch event listener to respond to events when a route matches
762 | * the event's request.
763 | */
764 |
765 |
766 | addFetchListener() {
767 | // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
768 | self.addEventListener('fetch', event => {
769 | const {
770 | request
771 | } = event;
772 | const responsePromise = this.handleRequest({
773 | request,
774 | event
775 | });
776 |
777 | if (responsePromise) {
778 | event.respondWith(responsePromise);
779 | }
780 | });
781 | }
782 | /**
783 | * Adds a message event listener for URLs to cache from the window.
784 | * This is useful to cache resources loaded on the page prior to when the
785 | * service worker started controlling it.
786 | *
787 | * The format of the message data sent from the window should be as follows.
788 | * Where the `urlsToCache` array may consist of URL strings or an array of
789 | * URL string + `requestInit` object (the same as you'd pass to `fetch()`).
790 | *
791 | * ```
792 | * {
793 | * type: 'CACHE_URLS',
794 | * payload: {
795 | * urlsToCache: [
796 | * './script1.js',
797 | * './script2.js',
798 | * ['./script3.js', {mode: 'no-cors'}],
799 | * ],
800 | * },
801 | * }
802 | * ```
803 | */
804 |
805 |
806 | addCacheListener() {
807 | // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
808 | self.addEventListener('message', event => {
809 | // event.data is type 'any'
810 | if (event.data && event.data.type === 'CACHE_URLS') {
811 | // eslint-disable-line
812 | const {
813 | payload
814 | } = event.data; // eslint-disable-line
815 |
816 | {
817 | logger.debug(`Caching URLs from the window`, payload.urlsToCache);
818 | }
819 |
820 | const requestPromises = Promise.all(payload.urlsToCache.map(entry => {
821 | if (typeof entry === 'string') {
822 | entry = [entry];
823 | }
824 |
825 | const request = new Request(...entry);
826 | return this.handleRequest({
827 | request,
828 | event
829 | }); // TODO(philipwalton): TypeScript errors without this typecast for
830 | // some reason (probably a bug). The real type here should work but
831 | // doesn't: `Array | undefined>`.
832 | })); // TypeScript
833 |
834 | event.waitUntil(requestPromises); // If a MessageChannel was used, reply to the message on success.
835 |
836 | if (event.ports && event.ports[0]) {
837 | void requestPromises.then(() => event.ports[0].postMessage(true));
838 | }
839 | }
840 | });
841 | }
842 | /**
843 | * Apply the routing rules to a FetchEvent object to get a Response from an
844 | * appropriate Route's handler.
845 | *
846 | * @param {Object} options
847 | * @param {Request} options.request The request to handle.
848 | * @param {ExtendableEvent} options.event The event that triggered the
849 | * request.
850 | * @return {Promise|undefined} A promise is returned if a
851 | * registered route can handle the request. If there is no matching
852 | * route and there's no `defaultHandler`, `undefined` is returned.
853 | */
854 |
855 |
856 | handleRequest({
857 | request,
858 | event
859 | }) {
860 | {
861 | finalAssertExports.isInstance(request, Request, {
862 | moduleName: 'workbox-routing',
863 | className: 'Router',
864 | funcName: 'handleRequest',
865 | paramName: 'options.request'
866 | });
867 | }
868 |
869 | const url = new URL(request.url, location.href);
870 |
871 | if (!url.protocol.startsWith('http')) {
872 | {
873 | logger.debug(`Workbox Router only supports URLs that start with 'http'.`);
874 | }
875 |
876 | return;
877 | }
878 |
879 | const sameOrigin = url.origin === location.origin;
880 | const {
881 | params,
882 | route
883 | } = this.findMatchingRoute({
884 | event,
885 | request,
886 | sameOrigin,
887 | url
888 | });
889 | let handler = route && route.handler;
890 | const debugMessages = [];
891 |
892 | {
893 | if (handler) {
894 | debugMessages.push([`Found a route to handle this request:`, route]);
895 |
896 | if (params) {
897 | debugMessages.push([`Passing the following params to the route's handler:`, params]);
898 | }
899 | }
900 | } // If we don't have a handler because there was no matching route, then
901 | // fall back to defaultHandler if that's defined.
902 |
903 |
904 | const method = request.method;
905 |
906 | if (!handler && this._defaultHandlerMap.has(method)) {
907 | {
908 | debugMessages.push(`Failed to find a matching route. Falling ` + `back to the default handler for ${method}.`);
909 | }
910 |
911 | handler = this._defaultHandlerMap.get(method);
912 | }
913 |
914 | if (!handler) {
915 | {
916 | // No handler so Workbox will do nothing. If logs is set of debug
917 | // i.e. verbose, we should print out this information.
918 | logger.debug(`No route found for: ${getFriendlyURL(url)}`);
919 | }
920 |
921 | return;
922 | }
923 |
924 | {
925 | // We have a handler, meaning Workbox is going to handle the route.
926 | // print the routing details to the console.
927 | logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);
928 | debugMessages.forEach(msg => {
929 | if (Array.isArray(msg)) {
930 | logger.log(...msg);
931 | } else {
932 | logger.log(msg);
933 | }
934 | });
935 | logger.groupEnd();
936 | } // Wrap in try and catch in case the handle method throws a synchronous
937 | // error. It should still callback to the catch handler.
938 |
939 |
940 | let responsePromise;
941 |
942 | try {
943 | responsePromise = handler.handle({
944 | url,
945 | request,
946 | event,
947 | params
948 | });
949 | } catch (err) {
950 | responsePromise = Promise.reject(err);
951 | } // Get route's catch handler, if it exists
952 |
953 |
954 | const catchHandler = route && route.catchHandler;
955 |
956 | if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) {
957 | responsePromise = responsePromise.catch(async err => {
958 | // If there's a route catch handler, process that first
959 | if (catchHandler) {
960 | {
961 | // Still include URL here as it will be async from the console group
962 | // and may not make sense without the URL
963 | logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);
964 | logger.error(`Error thrown by:`, route);
965 | logger.error(err);
966 | logger.groupEnd();
967 | }
968 |
969 | try {
970 | return await catchHandler.handle({
971 | url,
972 | request,
973 | event,
974 | params
975 | });
976 | } catch (catchErr) {
977 | if (catchErr instanceof Error) {
978 | err = catchErr;
979 | }
980 | }
981 | }
982 |
983 | if (this._catchHandler) {
984 | {
985 | // Still include URL here as it will be async from the console group
986 | // and may not make sense without the URL
987 | logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);
988 | logger.error(`Error thrown by:`, route);
989 | logger.error(err);
990 | logger.groupEnd();
991 | }
992 |
993 | return this._catchHandler.handle({
994 | url,
995 | request,
996 | event
997 | });
998 | }
999 |
1000 | throw err;
1001 | });
1002 | }
1003 |
1004 | return responsePromise;
1005 | }
1006 | /**
1007 | * Checks a request and URL (and optionally an event) against the list of
1008 | * registered routes, and if there's a match, returns the corresponding
1009 | * route along with any params generated by the match.
1010 | *
1011 | * @param {Object} options
1012 | * @param {URL} options.url
1013 | * @param {boolean} options.sameOrigin The result of comparing `url.origin`
1014 | * against the current origin.
1015 | * @param {Request} options.request The request to match.
1016 | * @param {Event} options.event The corresponding event.
1017 | * @return {Object} An object with `route` and `params` properties.
1018 | * They are populated if a matching route was found or `undefined`
1019 | * otherwise.
1020 | */
1021 |
1022 |
1023 | findMatchingRoute({
1024 | url,
1025 | sameOrigin,
1026 | request,
1027 | event
1028 | }) {
1029 | const routes = this._routes.get(request.method) || [];
1030 |
1031 | for (const route of routes) {
1032 | let params; // route.match returns type any, not possible to change right now.
1033 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1034 |
1035 | const matchResult = route.match({
1036 | url,
1037 | sameOrigin,
1038 | request,
1039 | event
1040 | });
1041 |
1042 | if (matchResult) {
1043 | {
1044 | // Warn developers that using an async matchCallback is almost always
1045 | // not the right thing to do.
1046 | if (matchResult instanceof Promise) {
1047 | logger.warn(`While routing ${getFriendlyURL(url)}, an async ` + `matchCallback function was used. Please convert the ` + `following route to use a synchronous matchCallback function:`, route);
1048 | }
1049 | } // See https://github.com/GoogleChrome/workbox/issues/2079
1050 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1051 |
1052 |
1053 | params = matchResult;
1054 |
1055 | if (Array.isArray(params) && params.length === 0) {
1056 | // Instead of passing an empty array in as params, use undefined.
1057 | params = undefined;
1058 | } else if (matchResult.constructor === Object && // eslint-disable-line
1059 | Object.keys(matchResult).length === 0) {
1060 | // Instead of passing an empty object in as params, use undefined.
1061 | params = undefined;
1062 | } else if (typeof matchResult === 'boolean') {
1063 | // For the boolean value true (rather than just something truth-y),
1064 | // don't set params.
1065 | // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353
1066 | params = undefined;
1067 | } // Return early if have a match.
1068 |
1069 |
1070 | return {
1071 | route,
1072 | params
1073 | };
1074 | }
1075 | } // If no match was found above, return and empty object.
1076 |
1077 |
1078 | return {};
1079 | }
1080 | /**
1081 | * Define a default `handler` that's called when no routes explicitly
1082 | * match the incoming request.
1083 | *
1084 | * Each HTTP method ('GET', 'POST', etc.) gets its own default handler.
1085 | *
1086 | * Without a default handler, unmatched requests will go against the
1087 | * network as if there were no service worker present.
1088 | *
1089 | * @param {module:workbox-routing~handlerCallback} handler A callback
1090 | * function that returns a Promise resulting in a Response.
1091 | * @param {string} [method='GET'] The HTTP method to associate with this
1092 | * default handler. Each method has its own default.
1093 | */
1094 |
1095 |
1096 | setDefaultHandler(handler, method = defaultMethod) {
1097 | this._defaultHandlerMap.set(method, normalizeHandler(handler));
1098 | }
1099 | /**
1100 | * If a Route throws an error while handling a request, this `handler`
1101 | * will be called and given a chance to provide a response.
1102 | *
1103 | * @param {module:workbox-routing~handlerCallback} handler A callback
1104 | * function that returns a Promise resulting in a Response.
1105 | */
1106 |
1107 |
1108 | setCatchHandler(handler) {
1109 | this._catchHandler = normalizeHandler(handler);
1110 | }
1111 | /**
1112 | * Registers a route with the router.
1113 | *
1114 | * @param {module:workbox-routing.Route} route The route to register.
1115 | */
1116 |
1117 |
1118 | registerRoute(route) {
1119 | {
1120 | finalAssertExports.isType(route, 'object', {
1121 | moduleName: 'workbox-routing',
1122 | className: 'Router',
1123 | funcName: 'registerRoute',
1124 | paramName: 'route'
1125 | });
1126 | finalAssertExports.hasMethod(route, 'match', {
1127 | moduleName: 'workbox-routing',
1128 | className: 'Router',
1129 | funcName: 'registerRoute',
1130 | paramName: 'route'
1131 | });
1132 | finalAssertExports.isType(route.handler, 'object', {
1133 | moduleName: 'workbox-routing',
1134 | className: 'Router',
1135 | funcName: 'registerRoute',
1136 | paramName: 'route'
1137 | });
1138 | finalAssertExports.hasMethod(route.handler, 'handle', {
1139 | moduleName: 'workbox-routing',
1140 | className: 'Router',
1141 | funcName: 'registerRoute',
1142 | paramName: 'route.handler'
1143 | });
1144 | finalAssertExports.isType(route.method, 'string', {
1145 | moduleName: 'workbox-routing',
1146 | className: 'Router',
1147 | funcName: 'registerRoute',
1148 | paramName: 'route.method'
1149 | });
1150 | }
1151 |
1152 | if (!this._routes.has(route.method)) {
1153 | this._routes.set(route.method, []);
1154 | } // Give precedence to all of the earlier routes by adding this additional
1155 | // route to the end of the array.
1156 |
1157 |
1158 | this._routes.get(route.method).push(route);
1159 | }
1160 | /**
1161 | * Unregisters a route with the router.
1162 | *
1163 | * @param {module:workbox-routing.Route} route The route to unregister.
1164 | */
1165 |
1166 |
1167 | unregisterRoute(route) {
1168 | if (!this._routes.has(route.method)) {
1169 | throw new WorkboxError('unregister-route-but-not-found-with-method', {
1170 | method: route.method
1171 | });
1172 | }
1173 |
1174 | const routeIndex = this._routes.get(route.method).indexOf(route);
1175 |
1176 | if (routeIndex > -1) {
1177 | this._routes.get(route.method).splice(routeIndex, 1);
1178 | } else {
1179 | throw new WorkboxError('unregister-route-route-not-registered');
1180 | }
1181 | }
1182 |
1183 | }
1184 |
1185 | /*
1186 | Copyright 2019 Google LLC
1187 |
1188 | Use of this source code is governed by an MIT-style
1189 | license that can be found in the LICENSE file or at
1190 | https://opensource.org/licenses/MIT.
1191 | */
1192 | let defaultRouter;
1193 | /**
1194 | * Creates a new, singleton Router instance if one does not exist. If one
1195 | * does already exist, that instance is returned.
1196 | *
1197 | * @private
1198 | * @return {Router}
1199 | */
1200 |
1201 | const getOrCreateDefaultRouter = () => {
1202 | if (!defaultRouter) {
1203 | defaultRouter = new Router(); // The helpers that use the default Router assume these listeners exist.
1204 |
1205 | defaultRouter.addFetchListener();
1206 | defaultRouter.addCacheListener();
1207 | }
1208 |
1209 | return defaultRouter;
1210 | };
1211 |
1212 | /*
1213 | Copyright 2019 Google LLC
1214 |
1215 | Use of this source code is governed by an MIT-style
1216 | license that can be found in the LICENSE file or at
1217 | https://opensource.org/licenses/MIT.
1218 | */
1219 | /**
1220 | * Easily register a RegExp, string, or function with a caching
1221 | * strategy to a singleton Router instance.
1222 | *
1223 | * This method will generate a Route for you if needed and
1224 | * call [registerRoute()]{@link module:workbox-routing.Router#registerRoute}.
1225 | *
1226 | * @param {RegExp|string|module:workbox-routing.Route~matchCallback|module:workbox-routing.Route} capture
1227 | * If the capture param is a `Route`, all other arguments will be ignored.
1228 | * @param {module:workbox-routing~handlerCallback} [handler] A callback
1229 | * function that returns a Promise resulting in a Response. This parameter
1230 | * is required if `capture` is not a `Route` object.
1231 | * @param {string} [method='GET'] The HTTP method to match the Route
1232 | * against.
1233 | * @return {module:workbox-routing.Route} The generated `Route`(Useful for
1234 | * unregistering).
1235 | *
1236 | * @memberof module:workbox-routing
1237 | */
1238 |
1239 | function registerRoute(capture, handler, method) {
1240 | let route;
1241 |
1242 | if (typeof capture === 'string') {
1243 | const captureUrl = new URL(capture, location.href);
1244 |
1245 | {
1246 | if (!(capture.startsWith('/') || capture.startsWith('http'))) {
1247 | throw new WorkboxError('invalid-string', {
1248 | moduleName: 'workbox-routing',
1249 | funcName: 'registerRoute',
1250 | paramName: 'capture'
1251 | });
1252 | } // We want to check if Express-style wildcards are in the pathname only.
1253 | // TODO: Remove this log message in v4.
1254 |
1255 |
1256 | const valueToCheck = capture.startsWith('http') ? captureUrl.pathname : capture; // See https://github.com/pillarjs/path-to-regexp#parameters
1257 |
1258 | const wildcards = '[*:?+]';
1259 |
1260 | if (new RegExp(`${wildcards}`).exec(valueToCheck)) {
1261 | 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.`);
1262 | }
1263 | }
1264 |
1265 | const matchCallback = ({
1266 | url
1267 | }) => {
1268 | {
1269 | if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) {
1270 | 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.`);
1271 | }
1272 | }
1273 |
1274 | return url.href === captureUrl.href;
1275 | }; // If `capture` is a string then `handler` and `method` must be present.
1276 |
1277 |
1278 | route = new Route(matchCallback, handler, method);
1279 | } else if (capture instanceof RegExp) {
1280 | // If `capture` is a `RegExp` then `handler` and `method` must be present.
1281 | route = new RegExpRoute(capture, handler, method);
1282 | } else if (typeof capture === 'function') {
1283 | // If `capture` is a function then `handler` and `method` must be present.
1284 | route = new Route(capture, handler, method);
1285 | } else if (capture instanceof Route) {
1286 | route = capture;
1287 | } else {
1288 | throw new WorkboxError('unsupported-route-type', {
1289 | moduleName: 'workbox-routing',
1290 | funcName: 'registerRoute',
1291 | paramName: 'capture'
1292 | });
1293 | }
1294 |
1295 | const defaultRouter = getOrCreateDefaultRouter();
1296 | defaultRouter.registerRoute(route);
1297 | return route;
1298 | }
1299 |
1300 | try {
1301 | self['workbox:strategies:6.2.4'] && _();
1302 | } catch (e) {}
1303 |
1304 | /*
1305 | Copyright 2018 Google LLC
1306 |
1307 | Use of this source code is governed by an MIT-style
1308 | license that can be found in the LICENSE file or at
1309 | https://opensource.org/licenses/MIT.
1310 | */
1311 | const cacheOkAndOpaquePlugin = {
1312 | /**
1313 | * Returns a valid response (to allow caching) if the status is 200 (OK) or
1314 | * 0 (opaque).
1315 | *
1316 | * @param {Object} options
1317 | * @param {Response} options.response
1318 | * @return {Response|null}
1319 | *
1320 | * @private
1321 | */
1322 | cacheWillUpdate: async ({
1323 | response
1324 | }) => {
1325 | if (response.status === 200 || response.status === 0) {
1326 | return response;
1327 | }
1328 |
1329 | return null;
1330 | }
1331 | };
1332 |
1333 | /*
1334 | Copyright 2018 Google LLC
1335 |
1336 | Use of this source code is governed by an MIT-style
1337 | license that can be found in the LICENSE file or at
1338 | https://opensource.org/licenses/MIT.
1339 | */
1340 | const _cacheNameDetails = {
1341 | googleAnalytics: 'googleAnalytics',
1342 | precache: 'precache-v2',
1343 | prefix: 'workbox',
1344 | runtime: 'runtime',
1345 | suffix: typeof registration !== 'undefined' ? registration.scope : ''
1346 | };
1347 |
1348 | const _createCacheName = cacheName => {
1349 | return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix].filter(value => value && value.length > 0).join('-');
1350 | };
1351 |
1352 | const eachCacheNameDetail = fn => {
1353 | for (const key of Object.keys(_cacheNameDetails)) {
1354 | fn(key);
1355 | }
1356 | };
1357 |
1358 | const cacheNames = {
1359 | updateDetails: details => {
1360 | eachCacheNameDetail(key => {
1361 | if (typeof details[key] === 'string') {
1362 | _cacheNameDetails[key] = details[key];
1363 | }
1364 | });
1365 | },
1366 | getGoogleAnalyticsName: userCacheName => {
1367 | return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics);
1368 | },
1369 | getPrecacheName: userCacheName => {
1370 | return userCacheName || _createCacheName(_cacheNameDetails.precache);
1371 | },
1372 | getPrefix: () => {
1373 | return _cacheNameDetails.prefix;
1374 | },
1375 | getRuntimeName: userCacheName => {
1376 | return userCacheName || _createCacheName(_cacheNameDetails.runtime);
1377 | },
1378 | getSuffix: () => {
1379 | return _cacheNameDetails.suffix;
1380 | }
1381 | };
1382 |
1383 | /*
1384 | Copyright 2020 Google LLC
1385 | Use of this source code is governed by an MIT-style
1386 | license that can be found in the LICENSE file or at
1387 | https://opensource.org/licenses/MIT.
1388 | */
1389 |
1390 | function stripParams(fullURL, ignoreParams) {
1391 | const strippedURL = new URL(fullURL);
1392 |
1393 | for (const param of ignoreParams) {
1394 | strippedURL.searchParams.delete(param);
1395 | }
1396 |
1397 | return strippedURL.href;
1398 | }
1399 | /**
1400 | * Matches an item in the cache, ignoring specific URL params. This is similar
1401 | * to the `ignoreSearch` option, but it allows you to ignore just specific
1402 | * params (while continuing to match on the others).
1403 | *
1404 | * @private
1405 | * @param {Cache} cache
1406 | * @param {Request} request
1407 | * @param {Object} matchOptions
1408 | * @param {Array} ignoreParams
1409 | * @return {Promise}
1410 | */
1411 |
1412 |
1413 | async function cacheMatchIgnoreParams(cache, request, ignoreParams, matchOptions) {
1414 | const strippedRequestURL = stripParams(request.url, ignoreParams); // If the request doesn't include any ignored params, match as normal.
1415 |
1416 | if (request.url === strippedRequestURL) {
1417 | return cache.match(request, matchOptions);
1418 | } // Otherwise, match by comparing keys
1419 |
1420 |
1421 | const keysOptions = Object.assign(Object.assign({}, matchOptions), {
1422 | ignoreSearch: true
1423 | });
1424 | const cacheKeys = await cache.keys(request, keysOptions);
1425 |
1426 | for (const cacheKey of cacheKeys) {
1427 | const strippedCacheKeyURL = stripParams(cacheKey.url, ignoreParams);
1428 |
1429 | if (strippedRequestURL === strippedCacheKeyURL) {
1430 | return cache.match(cacheKey, matchOptions);
1431 | }
1432 | }
1433 |
1434 | return;
1435 | }
1436 |
1437 | /*
1438 | Copyright 2018 Google LLC
1439 |
1440 | Use of this source code is governed by an MIT-style
1441 | license that can be found in the LICENSE file or at
1442 | https://opensource.org/licenses/MIT.
1443 | */
1444 | /**
1445 | * The Deferred class composes Promises in a way that allows for them to be
1446 | * resolved or rejected from outside the constructor. In most cases promises
1447 | * should be used directly, but Deferreds can be necessary when the logic to
1448 | * resolve a promise must be separate.
1449 | *
1450 | * @private
1451 | */
1452 |
1453 | class Deferred {
1454 | /**
1455 | * Creates a promise and exposes its resolve and reject functions as methods.
1456 | */
1457 | constructor() {
1458 | this.promise = new Promise((resolve, reject) => {
1459 | this.resolve = resolve;
1460 | this.reject = reject;
1461 | });
1462 | }
1463 |
1464 | }
1465 |
1466 | /*
1467 | Copyright 2018 Google LLC
1468 |
1469 | Use of this source code is governed by an MIT-style
1470 | license that can be found in the LICENSE file or at
1471 | https://opensource.org/licenses/MIT.
1472 | */
1473 | // Can't change Function type right now.
1474 | // eslint-disable-next-line @typescript-eslint/ban-types
1475 |
1476 | const quotaErrorCallbacks = new Set();
1477 |
1478 | /*
1479 | Copyright 2018 Google LLC
1480 |
1481 | Use of this source code is governed by an MIT-style
1482 | license that can be found in the LICENSE file or at
1483 | https://opensource.org/licenses/MIT.
1484 | */
1485 | /**
1486 | * Runs all of the callback functions, one at a time sequentially, in the order
1487 | * in which they were registered.
1488 | *
1489 | * @memberof module:workbox-core
1490 | * @private
1491 | */
1492 |
1493 | async function executeQuotaErrorCallbacks() {
1494 | {
1495 | logger.log(`About to run ${quotaErrorCallbacks.size} ` + `callbacks to clean up caches.`);
1496 | }
1497 |
1498 | for (const callback of quotaErrorCallbacks) {
1499 | await callback();
1500 |
1501 | {
1502 | logger.log(callback, 'is complete.');
1503 | }
1504 | }
1505 |
1506 | {
1507 | logger.log('Finished running callbacks.');
1508 | }
1509 | }
1510 |
1511 | /*
1512 | Copyright 2019 Google LLC
1513 | Use of this source code is governed by an MIT-style
1514 | license that can be found in the LICENSE file or at
1515 | https://opensource.org/licenses/MIT.
1516 | */
1517 | /**
1518 | * Returns a promise that resolves and the passed number of milliseconds.
1519 | * This utility is an async/await-friendly version of `setTimeout`.
1520 | *
1521 | * @param {number} ms
1522 | * @return {Promise}
1523 | * @private
1524 | */
1525 |
1526 | function timeout(ms) {
1527 | return new Promise(resolve => setTimeout(resolve, ms));
1528 | }
1529 |
1530 | /*
1531 | Copyright 2020 Google LLC
1532 |
1533 | Use of this source code is governed by an MIT-style
1534 | license that can be found in the LICENSE file or at
1535 | https://opensource.org/licenses/MIT.
1536 | */
1537 |
1538 | function toRequest(input) {
1539 | return typeof input === 'string' ? new Request(input) : input;
1540 | }
1541 | /**
1542 | * A class created every time a Strategy instance instance calls
1543 | * [handle()]{@link module:workbox-strategies.Strategy~handle} or
1544 | * [handleAll()]{@link module:workbox-strategies.Strategy~handleAll} that wraps all fetch and
1545 | * cache actions around plugin callbacks and keeps track of when the strategy
1546 | * is "done" (i.e. all added `event.waitUntil()` promises have resolved).
1547 | *
1548 | * @memberof module:workbox-strategies
1549 | */
1550 |
1551 |
1552 | class StrategyHandler {
1553 | /**
1554 | * Creates a new instance associated with the passed strategy and event
1555 | * that's handling the request.
1556 | *
1557 | * The constructor also initializes the state that will be passed to each of
1558 | * the plugins handling this request.
1559 | *
1560 | * @param {module:workbox-strategies.Strategy} strategy
1561 | * @param {Object} options
1562 | * @param {Request|string} options.request A request to run this strategy for.
1563 | * @param {ExtendableEvent} options.event The event associated with the
1564 | * request.
1565 | * @param {URL} [options.url]
1566 | * @param {*} [options.params]
1567 | * [match callback]{@link module:workbox-routing~matchCallback},
1568 | * (if applicable).
1569 | */
1570 | constructor(strategy, options) {
1571 | this._cacheKeys = {};
1572 | /**
1573 | * The request the strategy is performing (passed to the strategy's
1574 | * `handle()` or `handleAll()` method).
1575 | * @name request
1576 | * @instance
1577 | * @type {Request}
1578 | * @memberof module:workbox-strategies.StrategyHandler
1579 | */
1580 |
1581 | /**
1582 | * The event associated with this request.
1583 | * @name event
1584 | * @instance
1585 | * @type {ExtendableEvent}
1586 | * @memberof module:workbox-strategies.StrategyHandler
1587 | */
1588 |
1589 | /**
1590 | * A `URL` instance of `request.url` (if passed to the strategy's
1591 | * `handle()` or `handleAll()` method).
1592 | * Note: the `url` param will be present if the strategy was invoked
1593 | * from a workbox `Route` object.
1594 | * @name url
1595 | * @instance
1596 | * @type {URL|undefined}
1597 | * @memberof module:workbox-strategies.StrategyHandler
1598 | */
1599 |
1600 | /**
1601 | * A `param` value (if passed to the strategy's
1602 | * `handle()` or `handleAll()` method).
1603 | * Note: the `param` param will be present if the strategy was invoked
1604 | * from a workbox `Route` object and the
1605 | * [match callback]{@link module:workbox-routing~matchCallback} returned
1606 | * a truthy value (it will be that value).
1607 | * @name params
1608 | * @instance
1609 | * @type {*|undefined}
1610 | * @memberof module:workbox-strategies.StrategyHandler
1611 | */
1612 |
1613 | {
1614 | finalAssertExports.isInstance(options.event, ExtendableEvent, {
1615 | moduleName: 'workbox-strategies',
1616 | className: 'StrategyHandler',
1617 | funcName: 'constructor',
1618 | paramName: 'options.event'
1619 | });
1620 | }
1621 |
1622 | Object.assign(this, options);
1623 | this.event = options.event;
1624 | this._strategy = strategy;
1625 | this._handlerDeferred = new Deferred();
1626 | this._extendLifetimePromises = []; // Copy the plugins list (since it's mutable on the strategy),
1627 | // so any mutations don't affect this handler instance.
1628 |
1629 | this._plugins = [...strategy.plugins];
1630 | this._pluginStateMap = new Map();
1631 |
1632 | for (const plugin of this._plugins) {
1633 | this._pluginStateMap.set(plugin, {});
1634 | }
1635 |
1636 | this.event.waitUntil(this._handlerDeferred.promise);
1637 | }
1638 | /**
1639 | * Fetches a given request (and invokes any applicable plugin callback
1640 | * methods) using the `fetchOptions` (for non-navigation requests) and
1641 | * `plugins` defined on the `Strategy` object.
1642 | *
1643 | * The following plugin lifecycle methods are invoked when using this method:
1644 | * - `requestWillFetch()`
1645 | * - `fetchDidSucceed()`
1646 | * - `fetchDidFail()`
1647 | *
1648 | * @param {Request|string} input The URL or request to fetch.
1649 | * @return {Promise}
1650 | */
1651 |
1652 |
1653 | async fetch(input) {
1654 | const {
1655 | event
1656 | } = this;
1657 | let request = toRequest(input);
1658 |
1659 | if (request.mode === 'navigate' && event instanceof FetchEvent && event.preloadResponse) {
1660 | const possiblePreloadResponse = await event.preloadResponse;
1661 |
1662 | if (possiblePreloadResponse) {
1663 | {
1664 | logger.log(`Using a preloaded navigation response for ` + `'${getFriendlyURL(request.url)}'`);
1665 | }
1666 |
1667 | return possiblePreloadResponse;
1668 | }
1669 | } // If there is a fetchDidFail plugin, we need to save a clone of the
1670 | // original request before it's either modified by a requestWillFetch
1671 | // plugin or before the original request's body is consumed via fetch().
1672 |
1673 |
1674 | const originalRequest = this.hasCallback('fetchDidFail') ? request.clone() : null;
1675 |
1676 | try {
1677 | for (const cb of this.iterateCallbacks('requestWillFetch')) {
1678 | request = await cb({
1679 | request: request.clone(),
1680 | event
1681 | });
1682 | }
1683 | } catch (err) {
1684 | if (err instanceof Error) {
1685 | throw new WorkboxError('plugin-error-request-will-fetch', {
1686 | thrownErrorMessage: err.message
1687 | });
1688 | }
1689 | } // The request can be altered by plugins with `requestWillFetch` making
1690 | // the original request (most likely from a `fetch` event) different
1691 | // from the Request we make. Pass both to `fetchDidFail` to aid debugging.
1692 |
1693 |
1694 | const pluginFilteredRequest = request.clone();
1695 |
1696 | try {
1697 | let fetchResponse; // See https://github.com/GoogleChrome/workbox/issues/1796
1698 |
1699 | fetchResponse = await fetch(request, request.mode === 'navigate' ? undefined : this._strategy.fetchOptions);
1700 |
1701 | if ("development" !== 'production') {
1702 | logger.debug(`Network request for ` + `'${getFriendlyURL(request.url)}' returned a response with ` + `status '${fetchResponse.status}'.`);
1703 | }
1704 |
1705 | for (const callback of this.iterateCallbacks('fetchDidSucceed')) {
1706 | fetchResponse = await callback({
1707 | event,
1708 | request: pluginFilteredRequest,
1709 | response: fetchResponse
1710 | });
1711 | }
1712 |
1713 | return fetchResponse;
1714 | } catch (error) {
1715 | {
1716 | logger.log(`Network request for ` + `'${getFriendlyURL(request.url)}' threw an error.`, error);
1717 | } // `originalRequest` will only exist if a `fetchDidFail` callback
1718 | // is being used (see above).
1719 |
1720 |
1721 | if (originalRequest) {
1722 | await this.runCallbacks('fetchDidFail', {
1723 | error: error,
1724 | event,
1725 | originalRequest: originalRequest.clone(),
1726 | request: pluginFilteredRequest.clone()
1727 | });
1728 | }
1729 |
1730 | throw error;
1731 | }
1732 | }
1733 | /**
1734 | * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on
1735 | * the response generated by `this.fetch()`.
1736 | *
1737 | * The call to `this.cachePut()` automatically invokes `this.waitUntil()`,
1738 | * so you do not have to manually call `waitUntil()` on the event.
1739 | *
1740 | * @param {Request|string} input The request or URL to fetch and cache.
1741 | * @return {Promise}
1742 | */
1743 |
1744 |
1745 | async fetchAndCachePut(input) {
1746 | const response = await this.fetch(input);
1747 | const responseClone = response.clone();
1748 | void this.waitUntil(this.cachePut(input, responseClone));
1749 | return response;
1750 | }
1751 | /**
1752 | * Matches a request from the cache (and invokes any applicable plugin
1753 | * callback methods) using the `cacheName`, `matchOptions`, and `plugins`
1754 | * defined on the strategy object.
1755 | *
1756 | * The following plugin lifecycle methods are invoked when using this method:
1757 | * - cacheKeyWillByUsed()
1758 | * - cachedResponseWillByUsed()
1759 | *
1760 | * @param {Request|string} key The Request or URL to use as the cache key.
1761 | * @return {Promise} A matching response, if found.
1762 | */
1763 |
1764 |
1765 | async cacheMatch(key) {
1766 | const request = toRequest(key);
1767 | let cachedResponse;
1768 | const {
1769 | cacheName,
1770 | matchOptions
1771 | } = this._strategy;
1772 | const effectiveRequest = await this.getCacheKey(request, 'read');
1773 | const multiMatchOptions = Object.assign(Object.assign({}, matchOptions), {
1774 | cacheName
1775 | });
1776 | cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);
1777 |
1778 | {
1779 | if (cachedResponse) {
1780 | logger.debug(`Found a cached response in '${cacheName}'.`);
1781 | } else {
1782 | logger.debug(`No cached response found in '${cacheName}'.`);
1783 | }
1784 | }
1785 |
1786 | for (const callback of this.iterateCallbacks('cachedResponseWillBeUsed')) {
1787 | cachedResponse = (await callback({
1788 | cacheName,
1789 | matchOptions,
1790 | cachedResponse,
1791 | request: effectiveRequest,
1792 | event: this.event
1793 | })) || undefined;
1794 | }
1795 |
1796 | return cachedResponse;
1797 | }
1798 | /**
1799 | * Puts a request/response pair in the cache (and invokes any applicable
1800 | * plugin callback methods) using the `cacheName` and `plugins` defined on
1801 | * the strategy object.
1802 | *
1803 | * The following plugin lifecycle methods are invoked when using this method:
1804 | * - cacheKeyWillByUsed()
1805 | * - cacheWillUpdate()
1806 | * - cacheDidUpdate()
1807 | *
1808 | * @param {Request|string} key The request or URL to use as the cache key.
1809 | * @param {Response} response The response to cache.
1810 | * @return {Promise} `false` if a cacheWillUpdate caused the response
1811 | * not be cached, and `true` otherwise.
1812 | */
1813 |
1814 |
1815 | async cachePut(key, response) {
1816 | const request = toRequest(key); // Run in the next task to avoid blocking other cache reads.
1817 | // https://github.com/w3c/ServiceWorker/issues/1397
1818 |
1819 | await timeout(0);
1820 | const effectiveRequest = await this.getCacheKey(request, 'write');
1821 |
1822 | {
1823 | if (effectiveRequest.method && effectiveRequest.method !== 'GET') {
1824 | throw new WorkboxError('attempt-to-cache-non-get-request', {
1825 | url: getFriendlyURL(effectiveRequest.url),
1826 | method: effectiveRequest.method
1827 | });
1828 | } // See https://github.com/GoogleChrome/workbox/issues/2818
1829 |
1830 |
1831 | const vary = response.headers.get('Vary');
1832 |
1833 | if (vary) {
1834 | 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.`);
1835 | }
1836 | }
1837 |
1838 | if (!response) {
1839 | {
1840 | logger.error(`Cannot cache non-existent response for ` + `'${getFriendlyURL(effectiveRequest.url)}'.`);
1841 | }
1842 |
1843 | throw new WorkboxError('cache-put-with-no-response', {
1844 | url: getFriendlyURL(effectiveRequest.url)
1845 | });
1846 | }
1847 |
1848 | const responseToCache = await this._ensureResponseSafeToCache(response);
1849 |
1850 | if (!responseToCache) {
1851 | {
1852 | logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' ` + `will not be cached.`, responseToCache);
1853 | }
1854 |
1855 | return false;
1856 | }
1857 |
1858 | const {
1859 | cacheName,
1860 | matchOptions
1861 | } = this._strategy;
1862 | const cache = await self.caches.open(cacheName);
1863 | const hasCacheUpdateCallback = this.hasCallback('cacheDidUpdate');
1864 | const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams( // TODO(philipwalton): the `__WB_REVISION__` param is a precaching
1865 | // feature. Consider into ways to only add this behavior if using
1866 | // precaching.
1867 | cache, effectiveRequest.clone(), ['__WB_REVISION__'], matchOptions) : null;
1868 |
1869 | {
1870 | logger.debug(`Updating the '${cacheName}' cache with a new Response ` + `for ${getFriendlyURL(effectiveRequest.url)}.`);
1871 | }
1872 |
1873 | try {
1874 | await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
1875 | } catch (error) {
1876 | if (error instanceof Error) {
1877 | // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError
1878 | if (error.name === 'QuotaExceededError') {
1879 | await executeQuotaErrorCallbacks();
1880 | }
1881 |
1882 | throw error;
1883 | }
1884 | }
1885 |
1886 | for (const callback of this.iterateCallbacks('cacheDidUpdate')) {
1887 | await callback({
1888 | cacheName,
1889 | oldResponse,
1890 | newResponse: responseToCache.clone(),
1891 | request: effectiveRequest,
1892 | event: this.event
1893 | });
1894 | }
1895 |
1896 | return true;
1897 | }
1898 | /**
1899 | * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and
1900 | * executes any of those callbacks found in sequence. The final `Request`
1901 | * object returned by the last plugin is treated as the cache key for cache
1902 | * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have
1903 | * been registered, the passed request is returned unmodified
1904 | *
1905 | * @param {Request} request
1906 | * @param {string} mode
1907 | * @return {Promise}
1908 | */
1909 |
1910 |
1911 | async getCacheKey(request, mode) {
1912 | if (!this._cacheKeys[mode]) {
1913 | let effectiveRequest = request;
1914 |
1915 | for (const callback of this.iterateCallbacks('cacheKeyWillBeUsed')) {
1916 | effectiveRequest = toRequest(await callback({
1917 | mode,
1918 | request: effectiveRequest,
1919 | event: this.event,
1920 | // params has a type any can't change right now.
1921 | params: this.params
1922 | }));
1923 | }
1924 |
1925 | this._cacheKeys[mode] = effectiveRequest;
1926 | }
1927 |
1928 | return this._cacheKeys[mode];
1929 | }
1930 | /**
1931 | * Returns true if the strategy has at least one plugin with the given
1932 | * callback.
1933 | *
1934 | * @param {string} name The name of the callback to check for.
1935 | * @return {boolean}
1936 | */
1937 |
1938 |
1939 | hasCallback(name) {
1940 | for (const plugin of this._strategy.plugins) {
1941 | if (name in plugin) {
1942 | return true;
1943 | }
1944 | }
1945 |
1946 | return false;
1947 | }
1948 | /**
1949 | * Runs all plugin callbacks matching the given name, in order, passing the
1950 | * given param object (merged ith the current plugin state) as the only
1951 | * argument.
1952 | *
1953 | * Note: since this method runs all plugins, it's not suitable for cases
1954 | * where the return value of a callback needs to be applied prior to calling
1955 | * the next callback. See
1956 | * [`iterateCallbacks()`]{@link module:workbox-strategies.StrategyHandler#iterateCallbacks}
1957 | * below for how to handle that case.
1958 | *
1959 | * @param {string} name The name of the callback to run within each plugin.
1960 | * @param {Object} param The object to pass as the first (and only) param
1961 | * when executing each callback. This object will be merged with the
1962 | * current plugin state prior to callback execution.
1963 | */
1964 |
1965 |
1966 | async runCallbacks(name, param) {
1967 | for (const callback of this.iterateCallbacks(name)) {
1968 | // TODO(philipwalton): not sure why `any` is needed. It seems like
1969 | // this should work with `as WorkboxPluginCallbackParam[C]`.
1970 | await callback(param);
1971 | }
1972 | }
1973 | /**
1974 | * Accepts a callback and returns an iterable of matching plugin callbacks,
1975 | * where each callback is wrapped with the current handler state (i.e. when
1976 | * you call each callback, whatever object parameter you pass it will
1977 | * be merged with the plugin's current state).
1978 | *
1979 | * @param {string} name The name fo the callback to run
1980 | * @return {Array}
1981 | */
1982 |
1983 |
1984 | *iterateCallbacks(name) {
1985 | for (const plugin of this._strategy.plugins) {
1986 | if (typeof plugin[name] === 'function') {
1987 | const state = this._pluginStateMap.get(plugin);
1988 |
1989 | const statefulCallback = param => {
1990 | const statefulParam = Object.assign(Object.assign({}, param), {
1991 | state
1992 | }); // TODO(philipwalton): not sure why `any` is needed. It seems like
1993 | // this should work with `as WorkboxPluginCallbackParam[C]`.
1994 |
1995 | return plugin[name](statefulParam);
1996 | };
1997 |
1998 | yield statefulCallback;
1999 | }
2000 | }
2001 | }
2002 | /**
2003 | * Adds a promise to the
2004 | * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises}
2005 | * of the event event associated with the request being handled (usually a
2006 | * `FetchEvent`).
2007 | *
2008 | * Note: you can await
2009 | * [`doneWaiting()`]{@link module:workbox-strategies.StrategyHandler~doneWaiting}
2010 | * to know when all added promises have settled.
2011 | *
2012 | * @param {Promise} promise A promise to add to the extend lifetime promises
2013 | * of the event that triggered the request.
2014 | */
2015 |
2016 |
2017 | waitUntil(promise) {
2018 | this._extendLifetimePromises.push(promise);
2019 |
2020 | return promise;
2021 | }
2022 | /**
2023 | * Returns a promise that resolves once all promises passed to
2024 | * [`waitUntil()`]{@link module:workbox-strategies.StrategyHandler~waitUntil}
2025 | * have settled.
2026 | *
2027 | * Note: any work done after `doneWaiting()` settles should be manually
2028 | * passed to an event's `waitUntil()` method (not this handler's
2029 | * `waitUntil()` method), otherwise the service worker thread my be killed
2030 | * prior to your work completing.
2031 | */
2032 |
2033 |
2034 | async doneWaiting() {
2035 | let promise;
2036 |
2037 | while (promise = this._extendLifetimePromises.shift()) {
2038 | await promise;
2039 | }
2040 | }
2041 | /**
2042 | * Stops running the strategy and immediately resolves any pending
2043 | * `waitUntil()` promises.
2044 | */
2045 |
2046 |
2047 | destroy() {
2048 | this._handlerDeferred.resolve(null);
2049 | }
2050 | /**
2051 | * This method will call cacheWillUpdate on the available plugins (or use
2052 | * status === 200) to determine if the Response is safe and valid to cache.
2053 | *
2054 | * @param {Request} options.request
2055 | * @param {Response} options.response
2056 | * @return {Promise}
2057 | *
2058 | * @private
2059 | */
2060 |
2061 |
2062 | async _ensureResponseSafeToCache(response) {
2063 | let responseToCache = response;
2064 | let pluginsUsed = false;
2065 |
2066 | for (const callback of this.iterateCallbacks('cacheWillUpdate')) {
2067 | responseToCache = (await callback({
2068 | request: this.request,
2069 | response: responseToCache,
2070 | event: this.event
2071 | })) || undefined;
2072 | pluginsUsed = true;
2073 |
2074 | if (!responseToCache) {
2075 | break;
2076 | }
2077 | }
2078 |
2079 | if (!pluginsUsed) {
2080 | if (responseToCache && responseToCache.status !== 200) {
2081 | responseToCache = undefined;
2082 | }
2083 |
2084 | {
2085 | if (responseToCache) {
2086 | if (responseToCache.status !== 200) {
2087 | if (responseToCache.status === 0) {
2088 | 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.`);
2089 | } else {
2090 | logger.debug(`The response for '${this.request.url}' ` + `returned a status code of '${response.status}' and won't ` + `be cached as a result.`);
2091 | }
2092 | }
2093 | }
2094 | }
2095 | }
2096 |
2097 | return responseToCache;
2098 | }
2099 |
2100 | }
2101 |
2102 | /*
2103 | Copyright 2020 Google LLC
2104 |
2105 | Use of this source code is governed by an MIT-style
2106 | license that can be found in the LICENSE file or at
2107 | https://opensource.org/licenses/MIT.
2108 | */
2109 | /**
2110 | * An abstract base class that all other strategy classes must extend from:
2111 | *
2112 | * @memberof module:workbox-strategies
2113 | */
2114 |
2115 | class Strategy {
2116 | /**
2117 | * Creates a new instance of the strategy and sets all documented option
2118 | * properties as public instance properties.
2119 | *
2120 | * Note: if a custom strategy class extends the base Strategy class and does
2121 | * not need more than these properties, it does not need to define its own
2122 | * constructor.
2123 | *
2124 | * @param {Object} [options]
2125 | * @param {string} [options.cacheName] Cache name to store and retrieve
2126 | * requests. Defaults to the cache names provided by
2127 | * [workbox-core]{@link module:workbox-core.cacheNames}.
2128 | * @param {Array