├── .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 | ![cover](assets/cover.jpg) 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 |
10 | license MIT 11 | 12 | nominate @msaaddev for GitHub Star 13 | 14 |
15 | 16 | ![separator](assets/separate.jpg) 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 | ![demo](assets/demo.gif) 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 |
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 |
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 | 3 | 4 | -------------------------------------------------------------------------------- /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} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} 2129 | * to use in conjunction with this caching strategy. 2130 | * @param {Object} [options.fetchOptions] Values passed along to the 2131 | * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) 2132 | * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796) 2133 | * `fetch()` requests made by this strategy. 2134 | * @param {Object} [options.matchOptions] The 2135 | * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions} 2136 | * for any `cache.match()` or `cache.put()` calls made by this strategy. 2137 | */ 2138 | constructor(options = {}) { 2139 | /** 2140 | * Cache name to store and retrieve 2141 | * requests. Defaults to the cache names provided by 2142 | * [workbox-core]{@link module:workbox-core.cacheNames}. 2143 | * 2144 | * @type {string} 2145 | */ 2146 | this.cacheName = cacheNames.getRuntimeName(options.cacheName); 2147 | /** 2148 | * The list 2149 | * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} 2150 | * used by this strategy. 2151 | * 2152 | * @type {Array} 2153 | */ 2154 | 2155 | this.plugins = options.plugins || []; 2156 | /** 2157 | * Values passed along to the 2158 | * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters} 2159 | * of all fetch() requests made by this strategy. 2160 | * 2161 | * @type {Object} 2162 | */ 2163 | 2164 | this.fetchOptions = options.fetchOptions; 2165 | /** 2166 | * The 2167 | * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions} 2168 | * for any `cache.match()` or `cache.put()` calls made by this strategy. 2169 | * 2170 | * @type {Object} 2171 | */ 2172 | 2173 | this.matchOptions = options.matchOptions; 2174 | } 2175 | /** 2176 | * Perform a request strategy and returns a `Promise` that will resolve with 2177 | * a `Response`, invoking all relevant plugin callbacks. 2178 | * 2179 | * When a strategy instance is registered with a Workbox 2180 | * [route]{@link module:workbox-routing.Route}, this method is automatically 2181 | * called when the route matches. 2182 | * 2183 | * Alternatively, this method can be used in a standalone `FetchEvent` 2184 | * listener by passing it to `event.respondWith()`. 2185 | * 2186 | * @param {FetchEvent|Object} options A `FetchEvent` or an object with the 2187 | * properties listed below. 2188 | * @param {Request|string} options.request A request to run this strategy for. 2189 | * @param {ExtendableEvent} options.event The event associated with the 2190 | * request. 2191 | * @param {URL} [options.url] 2192 | * @param {*} [options.params] 2193 | */ 2194 | 2195 | 2196 | handle(options) { 2197 | const [responseDone] = this.handleAll(options); 2198 | return responseDone; 2199 | } 2200 | /** 2201 | * Similar to [`handle()`]{@link module:workbox-strategies.Strategy~handle}, but 2202 | * instead of just returning a `Promise` that resolves to a `Response` it 2203 | * it will return an tuple of [response, done] promises, where the former 2204 | * (`response`) is equivalent to what `handle()` returns, and the latter is a 2205 | * Promise that will resolve once any promises that were added to 2206 | * `event.waitUntil()` as part of performing the strategy have completed. 2207 | * 2208 | * You can await the `done` promise to ensure any extra work performed by 2209 | * the strategy (usually caching responses) completes successfully. 2210 | * 2211 | * @param {FetchEvent|Object} options A `FetchEvent` or an object with the 2212 | * properties listed below. 2213 | * @param {Request|string} options.request A request to run this strategy for. 2214 | * @param {ExtendableEvent} options.event The event associated with the 2215 | * request. 2216 | * @param {URL} [options.url] 2217 | * @param {*} [options.params] 2218 | * @return {Array} A tuple of [response, done] 2219 | * promises that can be used to determine when the response resolves as 2220 | * well as when the handler has completed all its work. 2221 | */ 2222 | 2223 | 2224 | handleAll(options) { 2225 | // Allow for flexible options to be passed. 2226 | if (options instanceof FetchEvent) { 2227 | options = { 2228 | event: options, 2229 | request: options.request 2230 | }; 2231 | } 2232 | 2233 | const event = options.event; 2234 | const request = typeof options.request === 'string' ? new Request(options.request) : options.request; 2235 | const params = 'params' in options ? options.params : undefined; 2236 | const handler = new StrategyHandler(this, { 2237 | event, 2238 | request, 2239 | params 2240 | }); 2241 | 2242 | const responseDone = this._getResponse(handler, request, event); 2243 | 2244 | const handlerDone = this._awaitComplete(responseDone, handler, request, event); // Return an array of promises, suitable for use with Promise.all(). 2245 | 2246 | 2247 | return [responseDone, handlerDone]; 2248 | } 2249 | 2250 | async _getResponse(handler, request, event) { 2251 | await handler.runCallbacks('handlerWillStart', { 2252 | event, 2253 | request 2254 | }); 2255 | let response = undefined; 2256 | 2257 | try { 2258 | response = await this._handle(request, handler); // The "official" Strategy subclasses all throw this error automatically, 2259 | // but in case a third-party Strategy doesn't, ensure that we have a 2260 | // consistent failure when there's no response or an error response. 2261 | 2262 | if (!response || response.type === 'error') { 2263 | throw new WorkboxError('no-response', { 2264 | url: request.url 2265 | }); 2266 | } 2267 | } catch (error) { 2268 | if (error instanceof Error) { 2269 | for (const callback of handler.iterateCallbacks('handlerDidError')) { 2270 | response = await callback({ 2271 | error, 2272 | event, 2273 | request 2274 | }); 2275 | 2276 | if (response) { 2277 | break; 2278 | } 2279 | } 2280 | } 2281 | 2282 | if (!response) { 2283 | throw error; 2284 | } else { 2285 | logger.log(`While responding to '${getFriendlyURL(request.url)}', ` + `an ${error instanceof Error ? error.toString() : ''} error occurred. Using a fallback response provided by ` + `a handlerDidError plugin.`); 2286 | } 2287 | } 2288 | 2289 | for (const callback of handler.iterateCallbacks('handlerWillRespond')) { 2290 | response = await callback({ 2291 | event, 2292 | request, 2293 | response 2294 | }); 2295 | } 2296 | 2297 | return response; 2298 | } 2299 | 2300 | async _awaitComplete(responseDone, handler, request, event) { 2301 | let response; 2302 | let error; 2303 | 2304 | try { 2305 | response = await responseDone; 2306 | } catch (error) {// Ignore errors, as response errors should be caught via the `response` 2307 | // promise above. The `done` promise will only throw for errors in 2308 | // promises passed to `handler.waitUntil()`. 2309 | } 2310 | 2311 | try { 2312 | await handler.runCallbacks('handlerDidRespond', { 2313 | event, 2314 | request, 2315 | response 2316 | }); 2317 | await handler.doneWaiting(); 2318 | } catch (waitUntilError) { 2319 | if (waitUntilError instanceof Error) { 2320 | error = waitUntilError; 2321 | } 2322 | } 2323 | 2324 | await handler.runCallbacks('handlerDidComplete', { 2325 | event, 2326 | request, 2327 | response, 2328 | error: error 2329 | }); 2330 | handler.destroy(); 2331 | 2332 | if (error) { 2333 | throw error; 2334 | } 2335 | } 2336 | 2337 | } 2338 | /** 2339 | * Classes extending the `Strategy` based class should implement this method, 2340 | * and leverage the [`handler`]{@link module:workbox-strategies.StrategyHandler} 2341 | * arg to perform all fetching and cache logic, which will ensure all relevant 2342 | * cache, cache options, fetch options and plugins are used (per the current 2343 | * strategy instance). 2344 | * 2345 | * @name _handle 2346 | * @instance 2347 | * @abstract 2348 | * @function 2349 | * @param {Request} request 2350 | * @param {module:workbox-strategies.StrategyHandler} handler 2351 | * @return {Promise} 2352 | * 2353 | * @memberof module:workbox-strategies.Strategy 2354 | */ 2355 | 2356 | /* 2357 | Copyright 2018 Google LLC 2358 | 2359 | Use of this source code is governed by an MIT-style 2360 | license that can be found in the LICENSE file or at 2361 | https://opensource.org/licenses/MIT. 2362 | */ 2363 | const messages = { 2364 | strategyStart: (strategyName, request) => `Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`, 2365 | printFinalResponse: response => { 2366 | if (response) { 2367 | logger.groupCollapsed(`View the final response here.`); 2368 | logger.log(response || '[No response returned]'); 2369 | logger.groupEnd(); 2370 | } 2371 | } 2372 | }; 2373 | 2374 | /* 2375 | Copyright 2018 Google LLC 2376 | 2377 | Use of this source code is governed by an MIT-style 2378 | license that can be found in the LICENSE file or at 2379 | https://opensource.org/licenses/MIT. 2380 | */ 2381 | /** 2382 | * An implementation of a 2383 | * [network first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-falling-back-to-cache} 2384 | * request strategy. 2385 | * 2386 | * By default, this strategy will cache responses with a 200 status code as 2387 | * well as [opaque responses]{@link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests}. 2388 | * Opaque responses are are cross-origin requests where the response doesn't 2389 | * support [CORS]{@link https://enable-cors.org/}. 2390 | * 2391 | * If the network request fails, and there is no cache match, this will throw 2392 | * a `WorkboxError` exception. 2393 | * 2394 | * @extends module:workbox-strategies.Strategy 2395 | * @memberof module:workbox-strategies 2396 | */ 2397 | 2398 | class NetworkFirst extends Strategy { 2399 | /** 2400 | * @param {Object} [options] 2401 | * @param {string} [options.cacheName] Cache name to store and retrieve 2402 | * requests. Defaults to cache names provided by 2403 | * [workbox-core]{@link module:workbox-core.cacheNames}. 2404 | * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} 2405 | * to use in conjunction with this caching strategy. 2406 | * @param {Object} [options.fetchOptions] Values passed along to the 2407 | * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) 2408 | * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796) 2409 | * `fetch()` requests made by this strategy. 2410 | * @param {Object} [options.matchOptions] [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions) 2411 | * @param {number} [options.networkTimeoutSeconds] If set, any network requests 2412 | * that fail to respond within the timeout will fallback to the cache. 2413 | * 2414 | * This option can be used to combat 2415 | * "[lie-fi]{@link https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi}" 2416 | * scenarios. 2417 | */ 2418 | constructor(options = {}) { 2419 | super(options); // If this instance contains no plugins with a 'cacheWillUpdate' callback, 2420 | // prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list. 2421 | 2422 | if (!this.plugins.some(p => 'cacheWillUpdate' in p)) { 2423 | this.plugins.unshift(cacheOkAndOpaquePlugin); 2424 | } 2425 | 2426 | this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0; 2427 | 2428 | { 2429 | if (this._networkTimeoutSeconds) { 2430 | finalAssertExports.isType(this._networkTimeoutSeconds, 'number', { 2431 | moduleName: 'workbox-strategies', 2432 | className: this.constructor.name, 2433 | funcName: 'constructor', 2434 | paramName: 'networkTimeoutSeconds' 2435 | }); 2436 | } 2437 | } 2438 | } 2439 | /** 2440 | * @private 2441 | * @param {Request|string} request A request to run this strategy for. 2442 | * @param {module:workbox-strategies.StrategyHandler} handler The event that 2443 | * triggered the request. 2444 | * @return {Promise} 2445 | */ 2446 | 2447 | 2448 | async _handle(request, handler) { 2449 | const logs = []; 2450 | 2451 | { 2452 | finalAssertExports.isInstance(request, Request, { 2453 | moduleName: 'workbox-strategies', 2454 | className: this.constructor.name, 2455 | funcName: 'handle', 2456 | paramName: 'makeRequest' 2457 | }); 2458 | } 2459 | 2460 | const promises = []; 2461 | let timeoutId; 2462 | 2463 | if (this._networkTimeoutSeconds) { 2464 | const { 2465 | id, 2466 | promise 2467 | } = this._getTimeoutPromise({ 2468 | request, 2469 | logs, 2470 | handler 2471 | }); 2472 | 2473 | timeoutId = id; 2474 | promises.push(promise); 2475 | } 2476 | 2477 | const networkPromise = this._getNetworkPromise({ 2478 | timeoutId, 2479 | request, 2480 | logs, 2481 | handler 2482 | }); 2483 | 2484 | promises.push(networkPromise); 2485 | const response = await handler.waitUntil((async () => { 2486 | // Promise.race() will resolve as soon as the first promise resolves. 2487 | return (await handler.waitUntil(Promise.race(promises))) || ( // If Promise.race() resolved with null, it might be due to a network 2488 | // timeout + a cache miss. If that were to happen, we'd rather wait until 2489 | // the networkPromise resolves instead of returning null. 2490 | // Note that it's fine to await an already-resolved promise, so we don't 2491 | // have to check to see if it's still "in flight". 2492 | await networkPromise); 2493 | })()); 2494 | 2495 | { 2496 | logger.groupCollapsed(messages.strategyStart(this.constructor.name, request)); 2497 | 2498 | for (const log of logs) { 2499 | logger.log(log); 2500 | } 2501 | 2502 | messages.printFinalResponse(response); 2503 | logger.groupEnd(); 2504 | } 2505 | 2506 | if (!response) { 2507 | throw new WorkboxError('no-response', { 2508 | url: request.url 2509 | }); 2510 | } 2511 | 2512 | return response; 2513 | } 2514 | /** 2515 | * @param {Object} options 2516 | * @param {Request} options.request 2517 | * @param {Array} options.logs A reference to the logs array 2518 | * @param {Event} options.event 2519 | * @return {Promise} 2520 | * 2521 | * @private 2522 | */ 2523 | 2524 | 2525 | _getTimeoutPromise({ 2526 | request, 2527 | logs, 2528 | handler 2529 | }) { 2530 | let timeoutId; 2531 | const timeoutPromise = new Promise(resolve => { 2532 | const onNetworkTimeout = async () => { 2533 | { 2534 | logs.push(`Timing out the network response at ` + `${this._networkTimeoutSeconds} seconds.`); 2535 | } 2536 | 2537 | resolve(await handler.cacheMatch(request)); 2538 | }; 2539 | 2540 | timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000); 2541 | }); 2542 | return { 2543 | promise: timeoutPromise, 2544 | id: timeoutId 2545 | }; 2546 | } 2547 | /** 2548 | * @param {Object} options 2549 | * @param {number|undefined} options.timeoutId 2550 | * @param {Request} options.request 2551 | * @param {Array} options.logs A reference to the logs Array. 2552 | * @param {Event} options.event 2553 | * @return {Promise} 2554 | * 2555 | * @private 2556 | */ 2557 | 2558 | 2559 | async _getNetworkPromise({ 2560 | timeoutId, 2561 | request, 2562 | logs, 2563 | handler 2564 | }) { 2565 | let error; 2566 | let response; 2567 | 2568 | try { 2569 | response = await handler.fetchAndCachePut(request); 2570 | } catch (fetchError) { 2571 | if (fetchError instanceof Error) { 2572 | error = fetchError; 2573 | } 2574 | } 2575 | 2576 | if (timeoutId) { 2577 | clearTimeout(timeoutId); 2578 | } 2579 | 2580 | { 2581 | if (response) { 2582 | logs.push(`Got response from network.`); 2583 | } else { 2584 | logs.push(`Unable to get a response from the network. Will respond ` + `with a cached response.`); 2585 | } 2586 | } 2587 | 2588 | if (error || !response) { 2589 | response = await handler.cacheMatch(request); 2590 | 2591 | { 2592 | if (response) { 2593 | logs.push(`Found a cached response in the '${this.cacheName}'` + ` cache.`); 2594 | } else { 2595 | logs.push(`No response found in the '${this.cacheName}' cache.`); 2596 | } 2597 | } 2598 | } 2599 | 2600 | return response; 2601 | } 2602 | 2603 | } 2604 | 2605 | /* 2606 | Copyright 2018 Google LLC 2607 | 2608 | Use of this source code is governed by an MIT-style 2609 | license that can be found in the LICENSE file or at 2610 | https://opensource.org/licenses/MIT. 2611 | */ 2612 | /** 2613 | * An implementation of a 2614 | * [network-only]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-only} 2615 | * request strategy. 2616 | * 2617 | * This class is useful if you want to take advantage of any 2618 | * [Workbox plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}. 2619 | * 2620 | * If the network request fails, this will throw a `WorkboxError` exception. 2621 | * 2622 | * @extends module:workbox-strategies.Strategy 2623 | * @memberof module:workbox-strategies 2624 | */ 2625 | 2626 | class NetworkOnly extends Strategy { 2627 | /** 2628 | * @param {Object} [options] 2629 | * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} 2630 | * to use in conjunction with this caching strategy. 2631 | * @param {Object} [options.fetchOptions] Values passed along to the 2632 | * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) 2633 | * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796) 2634 | * `fetch()` requests made by this strategy. 2635 | * @param {number} [options.networkTimeoutSeconds] If set, any network requests 2636 | * that fail to respond within the timeout will result in a network error. 2637 | */ 2638 | constructor(options = {}) { 2639 | super(options); 2640 | this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0; 2641 | } 2642 | /** 2643 | * @private 2644 | * @param {Request|string} request A request to run this strategy for. 2645 | * @param {module:workbox-strategies.StrategyHandler} handler The event that 2646 | * triggered the request. 2647 | * @return {Promise} 2648 | */ 2649 | 2650 | 2651 | async _handle(request, handler) { 2652 | { 2653 | finalAssertExports.isInstance(request, Request, { 2654 | moduleName: 'workbox-strategies', 2655 | className: this.constructor.name, 2656 | funcName: '_handle', 2657 | paramName: 'request' 2658 | }); 2659 | } 2660 | 2661 | let error = undefined; 2662 | let response; 2663 | 2664 | try { 2665 | const promises = [handler.fetch(request)]; 2666 | 2667 | if (this._networkTimeoutSeconds) { 2668 | const timeoutPromise = timeout(this._networkTimeoutSeconds * 1000); 2669 | promises.push(timeoutPromise); 2670 | } 2671 | 2672 | response = await Promise.race(promises); 2673 | 2674 | if (!response) { 2675 | throw new Error(`Timed out the network response after ` + `${this._networkTimeoutSeconds} seconds.`); 2676 | } 2677 | } catch (err) { 2678 | if (err instanceof Error) { 2679 | error = err; 2680 | } 2681 | } 2682 | 2683 | { 2684 | logger.groupCollapsed(messages.strategyStart(this.constructor.name, request)); 2685 | 2686 | if (response) { 2687 | logger.log(`Got response from network.`); 2688 | } else { 2689 | logger.log(`Unable to get a response from the network.`); 2690 | } 2691 | 2692 | messages.printFinalResponse(response); 2693 | logger.groupEnd(); 2694 | } 2695 | 2696 | if (!response) { 2697 | throw new WorkboxError('no-response', { 2698 | url: request.url, 2699 | error 2700 | }); 2701 | } 2702 | 2703 | return response; 2704 | } 2705 | 2706 | } 2707 | 2708 | /* 2709 | Copyright 2019 Google LLC 2710 | 2711 | Use of this source code is governed by an MIT-style 2712 | license that can be found in the LICENSE file or at 2713 | https://opensource.org/licenses/MIT. 2714 | */ 2715 | /** 2716 | * Claim any currently available clients once the service worker 2717 | * becomes active. This is normally used in conjunction with `skipWaiting()`. 2718 | * 2719 | * @memberof module:workbox-core 2720 | */ 2721 | 2722 | function clientsClaim() { 2723 | self.addEventListener('activate', () => self.clients.claim()); 2724 | } 2725 | 2726 | exports.NetworkFirst = NetworkFirst; 2727 | exports.NetworkOnly = NetworkOnly; 2728 | exports.clientsClaim = clientsClaim; 2729 | exports.registerRoute = registerRoute; 2730 | 2731 | }); 2732 | //# sourceMappingURL=workbox-1ffba242.js.map 2733 | -------------------------------------------------------------------------------- /styles/Auth.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | height: 100vh; 4 | flex-direction: column; 5 | justify-content: center; 6 | align-items: center; 7 | } 8 | -------------------------------------------------------------------------------- /styles/Button.module.css: -------------------------------------------------------------------------------- 1 | /* button */ 2 | 3 | .button { 4 | outline: none; 5 | border: none; 6 | font-weight: 500; 7 | font-family: 'Raleway', sans-serif; 8 | width: 200px; 9 | padding: 11px 0; 10 | cursor: pointer; 11 | color: #ffffff; 12 | border-radius: 3px; 13 | background: radial-gradient( 14 | ellipse at left bottom, 15 | rgba(22, 24, 47, 1) 0%, 16 | rgba(38, 20, 72, 0.9) 59%, 17 | rgba(17, 27, 75, 0.9) 100% 18 | ); 19 | box-shadow: inset 0 5px 10px 3px rgba(17, 27, 75, 0.9), 20 | 0 5px 10px 3px rgba(17, 27, 75, 0.9); 21 | transition: 0.5s all; 22 | } 23 | 24 | .button:hover { 25 | box-shadow: inset 0 0px 10px 3px rgba(17, 27, 75, 0.9), 26 | 0 0px 10px 3px rgba(17, 27, 75, 0.9); 27 | } 28 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | } 10 | 11 | .main { 12 | padding: 5rem 0; 13 | flex: 1; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | 20 | .button { 21 | margin-top: 20px; 22 | outline: none; 23 | border: none; 24 | font-weight: 500; 25 | font-family: 'Raleway', sans-serif; 26 | width: 300px; 27 | padding: 11px 0; 28 | cursor: pointer; 29 | color: #ffffff; 30 | border-radius: 3px; 31 | background: radial-gradient( 32 | ellipse at left bottom, 33 | rgba(22, 24, 47, 1) 0%, 34 | rgba(38, 20, 72, 0.9) 59%, 35 | rgba(17, 27, 75, 0.9) 100% 36 | ); 37 | box-shadow: inset 0 5px 10px 3px rgba(17, 27, 75, 0.9), 38 | 0 5px 10px 3px rgba(17, 27, 75, 0.9); 39 | transition: 0.5s all; 40 | } 41 | 42 | .button:hover { 43 | box-shadow: inset 0 0px 10px 3px rgba(17, 27, 75, 0.9), 44 | 0 0px 10px 3px rgba(17, 27, 75, 0.9); 45 | } 46 | 47 | .user p { 48 | font-weight: 600; 49 | } 50 | -------------------------------------------------------------------------------- /styles/Input.module.css: -------------------------------------------------------------------------------- 1 | /* Input Component Styles */ 2 | 3 | .wrapper { 4 | margin-bottom: 20px; 5 | width: 400px; 6 | } 7 | 8 | .wrapper label { 9 | padding-bottom: 5px; 10 | font-weight: 500; 11 | } 12 | 13 | .wrapper input { 14 | width: 100%; 15 | border: none; 16 | outline: none; 17 | font-size: 19px; 18 | padding: 10px; 19 | background: rgba(225, 237, 248, 0.6); 20 | letter-spacing: 1px; 21 | font-weight: 200; 22 | font-size: 14px; 23 | } 24 | 25 | .err { 26 | padding: 0; 27 | margin: 2px 0; 28 | font-size: 14px; 29 | font-weight: 500; 30 | color: red; 31 | } 32 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | --------------------------------------------------------------------------------