├── .eslintrc.json ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── issue--✨-feature-request.md ├── .gitignore ├── CODE OF CONDUCT.md ├── CONTRIBUTIONS GUIDE.md ├── Components ├── NavBarHome.tsx ├── PWADownloadButton.js └── allComponents.tsx ├── LICENSE ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── about.tsx ├── api │ ├── hello.ts │ └── my-sitemap.ts ├── contact.tsx ├── disclaimer.tsx ├── faq.tsx ├── index.tsx ├── termsandcondition.tsx └── underprogress.tsx ├── postcss.config.js ├── public ├── bottom01.svg ├── bottom02.svg ├── bottom03.svg ├── bottom04.svg ├── brandhive.svg ├── favicon.ico ├── manifest.json ├── next.svg ├── robots.txt ├── shufflepay.png ├── shufflepaybrand.png ├── shufflepaybrand192.png ├── shufflepaybrand256.png ├── shufflepaybrand384.png ├── shufflepaybrand512.png ├── sw.js ├── sw.js.map ├── thirteen.svg ├── up01.svg ├── up02.svg ├── vercel.svg ├── workbox-327c579b.js └── workbox-327c579b.js.map ├── styles ├── Home.module.css └── globals.css ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue--✨-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Issue: ✨ Feature Request' 3 | about: Suggest an idea for this project 4 | title: "[Feat]:" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Ideas is 11 | -------------------------------------------------------------------------------- /.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | generator.tsx -------------------------------------------------------------------------------- /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 | . 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 | -------------------------------------------------------------------------------- /CONTRIBUTIONS GUIDE.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Do you think there is something better that this project can offer and you can add it. We cordially invite you to contribute to this project and make it better. 4 |
5 | To start contributing, follow the guidelines given below: 6 | 7 | **1.** Fork [this](https://github.com/PiyushKalyanpy/UPI-QR-Code-Generator) repository. 8 | 9 | **2.** Clone your forked copy of the project. 10 | 11 | ``` 12 | git clone https://github.com//UPI-QR-Code-Generator.git 13 | ``` 14 | 15 | **3.** Navigate to the project directory in your local system. 16 | 17 | ``` 18 | cd UPI-QR-Code-Generator 19 | ``` 20 | 21 | **4.1** Add a reference (remote) to the original repository. 22 | 23 | ``` 24 | git remote add upstream 25 | https://github.com/PiyushKalyanpy/UPI-QR-Code-Generator.git 26 | 27 | ``` 28 | **4.2** Add a reference (remote) to your forked repository 29 | ``` 30 | git remote add origin 31 | https://github.com//UPI-QR-Code-Generator.git 32 | ``` 33 | 34 | **5.** Check the remotes for this repository. 35 | 36 | ``` 37 | git remote -v 38 | ``` 39 | 40 | **6.** Always take a pull from the upstream repository to your main branch to keep it updated with the original repository. 41 | 42 | ``` 43 | git pull upstream main 44 | ``` 45 | 46 | **7.** Always create a new branch. 47 | 48 | ``` 49 | git checkout -b 50 | ``` 51 | 52 | **8.** Perfom your desired changes to the code base. 53 | 54 | **9.** Track your changes. 55 | 56 | ``` 57 | git add . 58 | ``` 59 | 60 | **10.** Commit your changes. 61 | 62 | ``` 63 | git commit -m "suitable message" 64 | ``` 65 | 66 | **11.** Push the committed changes in your feature branch to your remote repo. 67 | 68 | ``` 69 | git push -u origin 70 | ``` 71 | 72 | **12.** To create a pull request, click on `Compare And Pull Requests`. 73 | 74 | **13.** Add appropriate title and description to your pull request explaining what your changes do (with suitable explanation and screenshots). 75 | 76 | **14.** Click on `Create Pull Request`. 77 | 78 | 79 | **15** Congrats !! You made your PR with desired changes. Now once the PR is reviewed, your PR will be merged to the original code base for everyone to see and use. 80 | 81 |
82 | Thank you so much for contributing. Hope to see you again soon........ 83 | -------------------------------------------------------------------------------- /Components/NavBarHome.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | const NavBarHome = () => { 4 | return ( 5 |
6 | {/* Branding : logo */} 7 |

8 | {" "} 9 | ShufflePay 10 |

11 | 12 | {/* Nav bar links for md and lg */} 13 |
14 |
    15 |
  • 16 | 20 | About 21 | 22 |
  • 23 |
  • 24 | 28 | Contact 29 | 30 |
  • 31 |
  • 32 | 36 | FAQ 37 | 38 |
  • 39 |
  • 40 | 44 | Disclaimer 45 | 46 |
  • 47 |
48 |
49 | 50 | {/* Nav bar links for sm */} 51 | 52 | {/* 🔴 add code here for Menu button (sidebar) */} 53 |
54 | ); 55 | }; 56 | 57 | export default NavBarHome; 58 | -------------------------------------------------------------------------------- /Components/PWADownloadButton.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | function PWADownloadButton() { 4 | const [deferredPrompt, setDeferredPrompt] = useState(null); 5 | 6 | 7 | 8 | return ( 9 |
10 | {/* Your app content */}sdf 11 | 18 |
19 | ); 20 | } 21 | 22 | export default PWADownloadButton; 23 | 24 | -------------------------------------------------------------------------------- /Components/allComponents.tsx: -------------------------------------------------------------------------------- 1 | import NavBarHome from "./NavBarHome"; 2 | 3 | export { NavBarHome }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Piyush Kalyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

UPI QR Code Generator with Transaction Amount Input

2 | 3 | 4 | 5 | # What Is Shufflepay 6 | 7 | ShuflePay is a convenient web application designed to simplify the process of generating UPI QR codes for payments. With this app, users can easily create a QR code that can be scanned by any UPI-compliant mobile app to initiate a payment transaction. The user-friendly interface of ShuflePay allows users to input their UPI ID, amount, and description for the transaction, and the app will quickly generate a unique QR code. ShuflePay is an efficient and time-saving tool for individuals and businesses who frequently make UPI payments, eliminating the need to manually create QR codes or share payment links. Whether it's for personal transactions or business purposes, ShuflePay makes generating UPI QR codes a breeze. 8 | 9 | 10 | # Tech Stack Used 11 | - [Next.js](https://nextjs.org/) - a framework for building server-rendered React applications 12 | - [Tailwind CSS](https://tailwindcss.com/) - a CSS framework for styling 13 | 14 | 15 | # Getting Started 16 | ## How To SetUp 17 | - Clone This Project 18 | 19 | - Go to directory 20 | ``` 21 | $ cd Shufflepay 22 | ``` 23 | - Install Dependencies 24 | ``` 25 | $ npm i 26 | ``` 27 | - Start LocalHost Server 28 | ``` 29 | $ npm run dev 30 | ``` 31 | ## [How To Contribute](CONTRIBUTIONS%20GUIDE.md) 32 | Thanks for for contributing to this project. We greatly appreciate any work contributed, no matter how small! 33 |
34 | ### Issues & Pull Requests 35 | When you are ready to start work on an issue: 36 | - Let us know by leaving a comment on the issue (or you can also raise a new issue if you want to work on something completely new in the project) 37 | - Once you are assigned the issue (or once you have claimed the issue) only then proceed to make the Pull Request. This will help avoid multiple PRs pertaining to the same issue. 38 | - Please check out the [contribution guide](CONTRIBUTIONS%20GUIDE.md) 39 | 40 | # [Code of Conduct](CODE%20OF%20CONDUCT.md) 41 | # License 42 | ShufflePay is licensed under the MIT License - see the [License File](LICENSE) for details. 43 | 44 | # Contributors 45 | A hall of fame for those who have contributed to this repo. Thanks once again for your valuable contributions. Kudos !!! 46 |
47 | 48 | 49 | 50 | 51 | 52 | # Support 53 | Do give us a ⭐️ for showing your support............... 54 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | // // /** @type {import('next').NextConfig} */ 2 | // const nextConfig = { 3 | // reactStrictMode: true, 4 | // }; 5 | // const withPWA = require("next-pwa"); 6 | 7 | // module.exports = nextConfig 8 | 9 | // module.exports = withPWA({ 10 | // pwa: { 11 | // dest: "public", 12 | // register: true, 13 | // disable: process.env.NODE_ENV === "development", 14 | // skipWaiting: true, 15 | // }, 16 | // }); 17 | 18 | const runtimeCaching = require("next-pwa/cache"); 19 | const withPWA = require("next-pwa")({ 20 | dest: "public", 21 | register: true, 22 | skipWaiting: true, 23 | runtimeCaching, 24 | buildExcludes: [/middleware-manifest.json$/], 25 | }); 26 | 27 | const nextConfig = withPWA({ 28 | // next config 29 | }); 30 | module.exports = nextConfig; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "upi-qr-code-generator", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@bradgarropy/next-google-analytics": "^1.0.2", 13 | "@types/node": "18.15.5", 14 | "@types/react": "18.0.28", 15 | "@types/react-dom": "18.0.11", 16 | "downloadjs": "^1.4.7", 17 | "eslint": "8.36.0", 18 | "eslint-config-next": "13.2.4", 19 | "html2canvas": "^1.4.1", 20 | "next": "13.2.4", 21 | "next-pwa": "^5.6.0", 22 | "react": "18.2.0", 23 | "react-dom": "18.2.0", 24 | "react-qr-code": "^2.0.11", 25 | "sitemap": "^7.1.1", 26 | "typescript": "5.0.2" 27 | }, 28 | "devDependencies": { 29 | "@types/downloadjs": "^1.4.3", 30 | "autoprefixer": "^10.4.14", 31 | "postcss": "^8.4.21", 32 | "tailwindcss": "^3.2.7" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "@/styles/globals.css"; 2 | import type { AppProps } from "next/app"; 3 | import Script from "next/script"; 4 | 5 | export default function App({ Component, pageProps }: AppProps) { 6 | return ( 7 | <> 8 | 20 | 21 | 22 | 23 | ); 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /pages/about.tsx: -------------------------------------------------------------------------------- 1 | const About = () => { 2 | return ( 3 |
4 | about 5 |
6 | ); 7 | } 8 | 9 | export default About; 10 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /pages/api/my-sitemap.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiyushKalyanpy/UPI-QR-Code-Generator/5d797b681bb0a281008be57a05cc6196e463d0bd/pages/api/my-sitemap.ts -------------------------------------------------------------------------------- /pages/contact.tsx: -------------------------------------------------------------------------------- 1 | const Contact = () => { 2 | return ( 3 |
4 |

Contact

5 |
6 | ); 7 | }; 8 | 9 | export default Contact; 10 | -------------------------------------------------------------------------------- /pages/disclaimer.tsx: -------------------------------------------------------------------------------- 1 | 2 | const Discalimer = () => { 3 | return ( 4 |
5 | disclaimer 6 |
7 | ); 8 | } 9 | 10 | export default Discalimer; 11 | -------------------------------------------------------------------------------- /pages/faq.tsx: -------------------------------------------------------------------------------- 1 | const Faq = () => { 2 | return ( 3 | 4 |
5 | faq 6 |
7 | ); 8 | } 9 | 10 | export default Faq; 11 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Image from "next/image"; 3 | import { Inter } from "next/font/google"; 4 | import styles from "@/styles/Home.module.css"; 5 | import { useRouter } from "next/router"; 6 | import QRCode from "react-qr-code"; 7 | import { useState, useEffect } from "react"; 8 | import downloadjs from "downloadjs"; 9 | import html2canvas from "html2canvas"; 10 | import PWADownloadButton from "../Components/PWADownloadButton"; 11 | import {NavBarHome } from "../Components/allComponents"; 12 | 13 | export default function Home() { 14 | const router = useRouter(); 15 | const [qrCodeValue, setQrCodeValue] = useState(""); 16 | const [showDownloadButton, setShowDownloadButton] = useState(true); 17 | const [formData, setFormData] = useState({ 18 | payeeName: "", 19 | upiId: "", 20 | transactionAmount: "", 21 | description: "", 22 | }); 23 | if (typeof window !== "undefined") { 24 | if (localStorage.getItem("count") == null) { 25 | localStorage.setItem("count", String(0)); 26 | } 27 | } 28 | const generateQR = (e: any) => { 29 | e.preventDefault(); 30 | 31 | if ( 32 | formData.payeeName == "" || 33 | formData.upiId == "" || 34 | formData.transactionAmount == "" || 35 | formData.description == "" 36 | ) { 37 | alert("Please fill all the fields"); 38 | return; 39 | } 40 | if (typeof window !== "undefined") { 41 | let count = localStorage.getItem("count"); 42 | localStorage.setItem("count", String(Number(count) + 1)); 43 | } 44 | const qrValue = `upi://pay?pa=${formData.upiId}&pn=${formData.payeeName}&am=${formData.transactionAmount}&tn=${formData.description}&cu=INR`; 45 | setQrCodeValue(qrValue); 46 | }; 47 | const handleCaptureClick = async () => { 48 | const qrCodeElement = document.querySelector(".qrCode"); 49 | if (!qrCodeElement) return; 50 | const canvas = await html2canvas(qrCodeElement); 51 | const dataURL = canvas.toDataURL("image/png"); 52 | downloadjs( 53 | dataURL, 54 | `${formData.payeeName + formData.transactionAmount}.png`, 55 | "image/png" 56 | ); 57 | }; 58 | 59 | useEffect(() => { 60 | setQrCodeValue('') 61 | },[formData]) 62 | 63 | return ( 64 | <> 65 | 66 | ShufflePay - Generate UPI QR code 67 | 68 | 72 | 73 | 74 | {/* add href land and content for serp */} 75 | 76 | 80 | 81 | 82 | 83 | 84 | 85 | 89 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | {/* Main Section */} 101 |
102 |
103 | 104 |
105 |
106 |

107 | Generate UPI QR code{" "} 108 |

109 |

110 | Simplify your payment process with 111 |

112 | 113 |
114 |
115 | {/* div for User Inputs */} 116 |
121 |
122 | 125 | 130 | setFormData({ ...formData, payeeName: e.target.value }) 131 | } 132 | type="text" 133 | className="w-full p-2 border border-slate-300 rounded-lg focus:outline-none focus:ring focus:border-slate-400" 134 | placeholder="Payee Name" 135 | /> 136 |
137 |
138 | 141 | 147 | setFormData({ ...formData, upiId: e.target.value }) 148 | } 149 | type="text" 150 | className="w-full p-2 border border-slate-300 rounded-lg focus:outline-none focus:ring focus:border-slate-400" 151 | placeholder="UPI ID" 152 | /> 153 |
154 |
155 | {" "} 156 | 159 | 165 | setFormData({ 166 | ...formData, 167 | transactionAmount: e.target.value, 168 | }) 169 | } 170 | type="number" 171 | className="w-full p-2 border border-slate-300 rounded-lg focus:outline-none focus:ring focus:border-slate-400" 172 | placeholder="Transaction Amount" 173 | /> 174 |
175 |
176 | {" "} 177 | 180 | 185 | setFormData({ ...formData, description: e.target.value }) 186 | } 187 | type="text" 188 | className="w-full p-2 border border-slate-300 rounded-lg focus:outline-none focus:ring focus:border-slate-400" 189 | placeholder="Description" 190 | /> 191 |
192 | 198 |
199 | {/* div for QR Code */} 200 |
201 | {qrCodeValue && showDownloadButton ? ( 202 |
203 |
207 | 213 | 214 | 215 |
216 |
217 | ) : null} 218 | 219 |
220 | {qrCodeValue != "" && ( 221 | <> 222 | {/*

shufflepay

*/} 223 | 224 | 228 | 229 | )} 230 |
231 | 232 |
233 |
234 |
235 | gpay 241 | gpay 247 |
248 |
249 | gpay 255 | gpay 261 | 262 | gpay 268 | 269 | gpay 275 |
276 |
277 |
278 |
279 | {/* div for Copyright and Tag */} 280 |
281 |
282 |
283 | Copyright © 2023 | developed by 284 | 285 | 286 | {" "} 287 | Piyush Kalyan{" "} 288 | 289 | 290 |
291 |
292 |
293 |
294 |
295 | 296 | ); 297 | } 298 | -------------------------------------------------------------------------------- /pages/termsandcondition.tsx: -------------------------------------------------------------------------------- 1 | 2 | const TermsAndCondition = () => { 3 | return ( 4 |
5 | terms and conditions 6 |
7 | ); 8 | } 9 | 10 | export default TermsAndCondition; 11 | -------------------------------------------------------------------------------- /pages/underprogress.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | const UnderProgress = () => { 3 | const router = useRouter(); 4 | return ( 5 |
6 |
7 | Under Construction 8 | 9 |
10 | 11 |
12 | 13 | ); 14 | } 15 | 16 | export default UnderProgress; -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/bottom01.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/bottom02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/bottom03.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/bottom04.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/brandhive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiyushKalyanpy/UPI-QR-Code-Generator/5d797b681bb0a281008be57a05cc6196e463d0bd/public/favicon.ico -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme_color": "#0284c7", 3 | "background_color": "#ffffff", 4 | "display": "standalone", 5 | "scope": "/", 6 | "start_url": "/", 7 | "name": "ShufflePay", 8 | "short_name": "ShufflePay", 9 | "description": "Generate UPI QR Codes", 10 | "icons": [ 11 | { 12 | "src": "/shufflepaybrand192.png", 13 | "sizes": "192x192", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "/shufflepaybrand256.png", 18 | "sizes": "256x256", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "/shufflepaybrand384.png", 23 | "sizes": "384x384", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "/shufflepaybrand512.png", 28 | "sizes": "512x512", 29 | "type": "image/png" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | 4 | Sitemap: https://localhost:3000/api/my-sitemap -------------------------------------------------------------------------------- /public/shufflepay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiyushKalyanpy/UPI-QR-Code-Generator/5d797b681bb0a281008be57a05cc6196e463d0bd/public/shufflepay.png -------------------------------------------------------------------------------- /public/shufflepaybrand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiyushKalyanpy/UPI-QR-Code-Generator/5d797b681bb0a281008be57a05cc6196e463d0bd/public/shufflepaybrand.png -------------------------------------------------------------------------------- /public/shufflepaybrand192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiyushKalyanpy/UPI-QR-Code-Generator/5d797b681bb0a281008be57a05cc6196e463d0bd/public/shufflepaybrand192.png -------------------------------------------------------------------------------- /public/shufflepaybrand256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiyushKalyanpy/UPI-QR-Code-Generator/5d797b681bb0a281008be57a05cc6196e463d0bd/public/shufflepaybrand256.png -------------------------------------------------------------------------------- /public/shufflepaybrand384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiyushKalyanpy/UPI-QR-Code-Generator/5d797b681bb0a281008be57a05cc6196e463d0bd/public/shufflepaybrand384.png -------------------------------------------------------------------------------- /public/shufflepaybrand512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiyushKalyanpy/UPI-QR-Code-Generator/5d797b681bb0a281008be57a05cc6196e463d0bd/public/shufflepaybrand512.png -------------------------------------------------------------------------------- /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 | let registry = {}; 17 | 18 | // Used for `eval` and `importScripts` where we can't get script URL by other means. 19 | // In both cases, it's safe to use a global var because those functions are synchronous. 20 | let nextDefineUri; 21 | 22 | const singleRequire = (uri, parentUri) => { 23 | uri = new URL(uri + ".js", parentUri).href; 24 | return registry[uri] || ( 25 | 26 | new Promise(resolve => { 27 | if ("document" in self) { 28 | const script = document.createElement("script"); 29 | script.src = uri; 30 | script.onload = resolve; 31 | document.head.appendChild(script); 32 | } else { 33 | nextDefineUri = uri; 34 | importScripts(uri); 35 | resolve(); 36 | } 37 | }) 38 | 39 | .then(() => { 40 | let promise = registry[uri]; 41 | if (!promise) { 42 | throw new Error(`Module ${uri} didn’t register its module`); 43 | } 44 | return promise; 45 | }) 46 | ); 47 | }; 48 | 49 | self.define = (depsNames, factory) => { 50 | const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href; 51 | if (registry[uri]) { 52 | // Module is already loading or loaded. 53 | return; 54 | } 55 | let exports = {}; 56 | const require = depUri => singleRequire(depUri, uri); 57 | const specialDeps = { 58 | module: { uri }, 59 | exports, 60 | require 61 | }; 62 | registry[uri] = Promise.all(depsNames.map( 63 | depName => specialDeps[depName] || require(depName) 64 | )).then(deps => { 65 | factory(...deps); 66 | return exports; 67 | }); 68 | }; 69 | } 70 | define(['./workbox-327c579b'], (function (workbox) { 'use strict'; 71 | 72 | importScripts(); 73 | self.skipWaiting(); 74 | workbox.clientsClaim(); 75 | workbox.registerRoute("/", new workbox.NetworkFirst({ 76 | "cacheName": "start-url", 77 | plugins: [{ 78 | cacheWillUpdate: async ({ 79 | request, 80 | response, 81 | event, 82 | state 83 | }) => { 84 | if (response && response.type === 'opaqueredirect') { 85 | return new Response(response.body, { 86 | status: 200, 87 | statusText: 'OK', 88 | headers: response.headers 89 | }); 90 | } 91 | return response; 92 | } 93 | }] 94 | }), 'GET'); 95 | workbox.registerRoute(/.*/i, new workbox.NetworkOnly({ 96 | "cacheName": "dev", 97 | plugins: [] 98 | }), 'GET'); 99 | 100 | })); 101 | //# sourceMappingURL=sw.js.map 102 | -------------------------------------------------------------------------------- /public/sw.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"sw.js","sources":["C:/Users/piyus/AppData/Local/Temp/77534129b7e16ce87684e5009d3c0679/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from 'D:/💻 Projects (WEB)/upi-qr-code-generator/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from 'D:/💻 Projects (WEB)/upi-qr-code-generator/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from 'D:/💻 Projects (WEB)/upi-qr-code-generator/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from 'D:/💻 Projects (WEB)/upi-qr-code-generator/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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAa,EAEZ,CAAA;EAQDC,CAAI,CAAA,CAAA,CAAA,CAACC,CAAW,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAE,CAAA;AAElBC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAyB,EAAE,CAAA;AAI3BC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAA6B,CAAC,CAAA,CAAA,CAAG,CAAE,CAAA,CAAA,CAAA,CAAA,CAAIC,oBAA+B,CAAC,CAAA;EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAW,EAAC,CAAW,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;EAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAO,EAAE,CAAC,CAAA;GAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAe,EAAE,CAAO,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;QAAEC,CAAO,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;QAAEC,CAAQ,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;QAAEC,CAAK,CAAA,CAAA,CAAA,CAAA,CAAA;AAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA;AAAM,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAC,CAAK,CAAA,CAAA,CAAA,CAAA,CAAA;EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAIF,QAAQ,CAAIA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAQ,CAACG,CAAI,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAK,gBAAgB,CAAE,CAAA,CAAA;AAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,OAAO,CAAIC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAQ,CAACJ,CAAQ,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAACK,IAAI,CAAE,CAAA,CAAA;EAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAM,EAAE,CAAG,CAAA,CAAA,CAAA;EAAEC,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAU,EAAE,CAAI,CAAA,CAAA,CAAA,CAAA;YAAEC,CAAO,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAER,CAAQ,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAACQ,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA;AAAQ,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAC,CAAC,CAAA;EAAC,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAOR,QAAQ,CAAA;EAAC,CAAA,CAAA,CAAA,CAAA,CAAA;KAAG,CAAA;AAAE,CAAA,CAAA,CAAC,CAAC,CAAA,CAAE,CAAK,CAAA,CAAA,CAAA,CAAA,CAAC,CAAA;AACxWL,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAA6B,CAAC,CAAA,CAAA,CAAA,CAAA,CAAK,CAAE,CAAA,CAAA,CAAA,CAAA,CAAIc,mBAA8B,CAAC,CAAA;EAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAW,EAAC,CAAK,CAAA,CAAA,CAAA,CAAA,CAAA;EAAEZ,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAAA,CAAO,EAAE,CAAA,CAAA;EAAG,CAAC,CAAC,CAAE,CAAA,CAAA,CAAA,CAAA,CAAA,CAAK,CAAC,CAAA;;"} -------------------------------------------------------------------------------- /public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/up01.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /public/up02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/workbox-327c579b.js: -------------------------------------------------------------------------------- 1 | define(['exports'], (function (exports) { 'use strict'; 2 | 3 | // @ts-ignore 4 | try { 5 | self['workbox:core:6.5.3'] && _(); 6 | } catch (e) {} 7 | 8 | /* 9 | Copyright 2019 Google LLC 10 | Use of this source code is governed by an MIT-style 11 | license that can be found in the LICENSE file or at 12 | https://opensource.org/licenses/MIT. 13 | */ 14 | const logger = (() => { 15 | // Don't overwrite this value if it's already set. 16 | // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923 17 | if (!('__WB_DISABLE_DEV_LOGS' in self)) { 18 | self.__WB_DISABLE_DEV_LOGS = false; 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 // No colored prefix on groupEnd 28 | }; 29 | 30 | const print = function (method, args) { 31 | if (self.__WB_DISABLE_DEV_LOGS) { 32 | return; 33 | } 34 | if (method === 'groupCollapsed') { 35 | // Safari doesn't print all console.groupCollapsed() arguments: 36 | // https://bugs.webkit.org/show_bug.cgi?id=182754 37 | if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) { 38 | console[method](...args); 39 | return; 40 | } 41 | } 42 | const styles = [`background: ${methodToColorMap[method]}`, `border-radius: 0.5em`, `color: white`, `font-weight: bold`, `padding: 2px 0.5em`]; 43 | // When in a group, the workbox prefix is not displayed. 44 | const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')]; 45 | console[method](...logPrefix, ...args); 46 | if (method === 'groupCollapsed') { 47 | inGroup = true; 48 | } 49 | if (method === 'groupEnd') { 50 | inGroup = false; 51 | } 52 | }; 53 | // eslint-disable-next-line @typescript-eslint/ban-types 54 | const api = {}; 55 | const loggerMethods = Object.keys(methodToColorMap); 56 | for (const key of loggerMethods) { 57 | const method = key; 58 | api[method] = (...args) => { 59 | print(method, args); 60 | }; 61 | } 62 | return api; 63 | })(); 64 | 65 | /* 66 | Copyright 2018 Google LLC 67 | 68 | Use of this source code is governed by an MIT-style 69 | license that can be found in the LICENSE file or at 70 | https://opensource.org/licenses/MIT. 71 | */ 72 | const messages$1 = { 73 | 'invalid-value': ({ 74 | paramName, 75 | validValueDescription, 76 | value 77 | }) => { 78 | if (!paramName || !validValueDescription) { 79 | throw new Error(`Unexpected input to 'invalid-value' error.`); 80 | } 81 | return `The '${paramName}' parameter was given a value with an ` + `unexpected value. ${validValueDescription} Received a value of ` + `${JSON.stringify(value)}.`; 82 | }, 83 | 'not-an-array': ({ 84 | moduleName, 85 | className, 86 | funcName, 87 | paramName 88 | }) => { 89 | if (!moduleName || !className || !funcName || !paramName) { 90 | throw new Error(`Unexpected input to 'not-an-array' error.`); 91 | } 92 | return `The parameter '${paramName}' passed into ` + `'${moduleName}.${className}.${funcName}()' must be an array.`; 93 | }, 94 | 'incorrect-type': ({ 95 | expectedType, 96 | paramName, 97 | moduleName, 98 | className, 99 | funcName 100 | }) => { 101 | if (!expectedType || !paramName || !moduleName || !funcName) { 102 | throw new Error(`Unexpected input to 'incorrect-type' error.`); 103 | } 104 | const classNameStr = className ? `${className}.` : ''; 105 | return `The parameter '${paramName}' passed into ` + `'${moduleName}.${classNameStr}` + `${funcName}()' must be of type ${expectedType}.`; 106 | }, 107 | 'incorrect-class': ({ 108 | expectedClassName, 109 | paramName, 110 | moduleName, 111 | className, 112 | funcName, 113 | isReturnValueProblem 114 | }) => { 115 | if (!expectedClassName || !moduleName || !funcName) { 116 | throw new Error(`Unexpected input to 'incorrect-class' error.`); 117 | } 118 | const classNameStr = className ? `${className}.` : ''; 119 | if (isReturnValueProblem) { 120 | return `The return value from ` + `'${moduleName}.${classNameStr}${funcName}()' ` + `must be an instance of class ${expectedClassName}.`; 121 | } 122 | return `The parameter '${paramName}' passed into ` + `'${moduleName}.${classNameStr}${funcName}()' ` + `must be an instance of class ${expectedClassName}.`; 123 | }, 124 | 'missing-a-method': ({ 125 | expectedMethod, 126 | paramName, 127 | moduleName, 128 | className, 129 | funcName 130 | }) => { 131 | if (!expectedMethod || !paramName || !moduleName || !className || !funcName) { 132 | throw new Error(`Unexpected input to 'missing-a-method' error.`); 133 | } 134 | return `${moduleName}.${className}.${funcName}() expected the ` + `'${paramName}' parameter to expose a '${expectedMethod}' method.`; 135 | }, 136 | 'add-to-cache-list-unexpected-type': ({ 137 | entry 138 | }) => { 139 | 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.`; 140 | }, 141 | 'add-to-cache-list-conflicting-entries': ({ 142 | firstEntry, 143 | secondEntry 144 | }) => { 145 | if (!firstEntry || !secondEntry) { 146 | throw new Error(`Unexpected input to ` + `'add-to-cache-list-duplicate-entries' error.`); 147 | } 148 | 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.`; 149 | }, 150 | 'plugin-error-request-will-fetch': ({ 151 | thrownErrorMessage 152 | }) => { 153 | if (!thrownErrorMessage) { 154 | throw new Error(`Unexpected input to ` + `'plugin-error-request-will-fetch', error.`); 155 | } 156 | return `An error was thrown by a plugins 'requestWillFetch()' method. ` + `The thrown error message was: '${thrownErrorMessage}'.`; 157 | }, 158 | 'invalid-cache-name': ({ 159 | cacheNameId, 160 | value 161 | }) => { 162 | if (!cacheNameId) { 163 | throw new Error(`Expected a 'cacheNameId' for error 'invalid-cache-name'`); 164 | } 165 | return `You must provide a name containing at least one character for ` + `setCacheDetails({${cacheNameId}: '...'}). Received a value of ` + `'${JSON.stringify(value)}'`; 166 | }, 167 | 'unregister-route-but-not-found-with-method': ({ 168 | method 169 | }) => { 170 | if (!method) { 171 | throw new Error(`Unexpected input to ` + `'unregister-route-but-not-found-with-method' error.`); 172 | } 173 | return `The route you're trying to unregister was not previously ` + `registered for the method type '${method}'.`; 174 | }, 175 | 'unregister-route-route-not-registered': () => { 176 | return `The route you're trying to unregister was not previously ` + `registered.`; 177 | }, 178 | 'queue-replay-failed': ({ 179 | name 180 | }) => { 181 | return `Replaying the background sync queue '${name}' failed.`; 182 | }, 183 | 'duplicate-queue-name': ({ 184 | name 185 | }) => { 186 | return `The Queue name '${name}' is already being used. ` + `All instances of backgroundSync.Queue must be given unique names.`; 187 | }, 188 | 'expired-test-without-max-age': ({ 189 | methodName, 190 | paramName 191 | }) => { 192 | return `The '${methodName}()' method can only be used when the ` + `'${paramName}' is used in the constructor.`; 193 | }, 194 | 'unsupported-route-type': ({ 195 | moduleName, 196 | className, 197 | funcName, 198 | paramName 199 | }) => { 200 | return `The supplied '${paramName}' parameter was an unsupported type. ` + `Please check the docs for ${moduleName}.${className}.${funcName} for ` + `valid input types.`; 201 | }, 202 | 'not-array-of-class': ({ 203 | value, 204 | expectedClass, 205 | moduleName, 206 | className, 207 | funcName, 208 | paramName 209 | }) => { 210 | 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.`; 211 | }, 212 | 'max-entries-or-age-required': ({ 213 | moduleName, 214 | className, 215 | funcName 216 | }) => { 217 | return `You must define either config.maxEntries or config.maxAgeSeconds` + `in ${moduleName}.${className}.${funcName}`; 218 | }, 219 | 'statuses-or-headers-required': ({ 220 | moduleName, 221 | className, 222 | funcName 223 | }) => { 224 | return `You must define either config.statuses or config.headers` + `in ${moduleName}.${className}.${funcName}`; 225 | }, 226 | 'invalid-string': ({ 227 | moduleName, 228 | funcName, 229 | paramName 230 | }) => { 231 | if (!paramName || !moduleName || !funcName) { 232 | throw new Error(`Unexpected input to 'invalid-string' error.`); 233 | } 234 | 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.`; 235 | }, 236 | 'channel-name-required': () => { 237 | return `You must provide a channelName to construct a ` + `BroadcastCacheUpdate instance.`; 238 | }, 239 | 'invalid-responses-are-same-args': () => { 240 | return `The arguments passed into responsesAreSame() appear to be ` + `invalid. Please ensure valid Responses are used.`; 241 | }, 242 | 'expire-custom-caches-only': () => { 243 | return `You must provide a 'cacheName' property when using the ` + `expiration plugin with a runtime caching strategy.`; 244 | }, 245 | 'unit-must-be-bytes': ({ 246 | normalizedRangeHeader 247 | }) => { 248 | if (!normalizedRangeHeader) { 249 | throw new Error(`Unexpected input to 'unit-must-be-bytes' error.`); 250 | } 251 | return `The 'unit' portion of the Range header must be set to 'bytes'. ` + `The Range header provided was "${normalizedRangeHeader}"`; 252 | }, 253 | 'single-range-only': ({ 254 | normalizedRangeHeader 255 | }) => { 256 | if (!normalizedRangeHeader) { 257 | throw new Error(`Unexpected input to 'single-range-only' error.`); 258 | } 259 | return `Multiple ranges are not supported. Please use a single start ` + `value, and optional end value. The Range header provided was ` + `"${normalizedRangeHeader}"`; 260 | }, 261 | 'invalid-range-values': ({ 262 | normalizedRangeHeader 263 | }) => { 264 | if (!normalizedRangeHeader) { 265 | throw new Error(`Unexpected input to 'invalid-range-values' error.`); 266 | } 267 | 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}"`; 268 | }, 269 | 'no-range-header': () => { 270 | return `No Range header was found in the Request provided.`; 271 | }, 272 | 'range-not-satisfiable': ({ 273 | size, 274 | start, 275 | end 276 | }) => { 277 | return `The start (${start}) and end (${end}) values in the Range are ` + `not satisfiable by the cached response, which is ${size} bytes.`; 278 | }, 279 | 'attempt-to-cache-non-get-request': ({ 280 | url, 281 | method 282 | }) => { 283 | return `Unable to cache '${url}' because it is a '${method}' request and ` + `only 'GET' requests can be cached.`; 284 | }, 285 | 'cache-put-with-no-response': ({ 286 | url 287 | }) => { 288 | return `There was an attempt to cache '${url}' but the response was not ` + `defined.`; 289 | }, 290 | 'no-response': ({ 291 | url, 292 | error 293 | }) => { 294 | let message = `The strategy could not generate a response for '${url}'.`; 295 | if (error) { 296 | message += ` The underlying error is ${error}.`; 297 | } 298 | return message; 299 | }, 300 | 'bad-precaching-response': ({ 301 | url, 302 | status 303 | }) => { 304 | return `The precaching request for '${url}' failed` + (status ? ` with an HTTP status of ${status}.` : `.`); 305 | }, 306 | 'non-precached-url': ({ 307 | url 308 | }) => { 309 | return `createHandlerBoundToURL('${url}') was called, but that URL is ` + `not precached. Please pass in a URL that is precached instead.`; 310 | }, 311 | 'add-to-cache-list-conflicting-integrities': ({ 312 | url 313 | }) => { 314 | return `Two of the entries passed to ` + `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` + `${url} with different integrity values. Please remove one of them.`; 315 | }, 316 | 'missing-precache-entry': ({ 317 | cacheName, 318 | url 319 | }) => { 320 | return `Unable to find a precached response in ${cacheName} for ${url}.`; 321 | }, 322 | 'cross-origin-copy-response': ({ 323 | origin 324 | }) => { 325 | return `workbox-core.copyResponse() can only be used with same-origin ` + `responses. It was passed a response with origin ${origin}.`; 326 | }, 327 | 'opaque-streams-source': ({ 328 | type 329 | }) => { 330 | const message = `One of the workbox-streams sources resulted in an ` + `'${type}' response.`; 331 | if (type === 'opaqueredirect') { 332 | return `${message} Please do not use a navigation request that results ` + `in a redirect as a source.`; 333 | } 334 | return `${message} Please ensure your sources are CORS-enabled.`; 335 | } 336 | }; 337 | 338 | /* 339 | Copyright 2018 Google LLC 340 | 341 | Use of this source code is governed by an MIT-style 342 | license that can be found in the LICENSE file or at 343 | https://opensource.org/licenses/MIT. 344 | */ 345 | const generatorFunction = (code, details = {}) => { 346 | const message = messages$1[code]; 347 | if (!message) { 348 | throw new Error(`Unable to find message for code '${code}'.`); 349 | } 350 | return message(details); 351 | }; 352 | const messageGenerator = generatorFunction; 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 | * Workbox errors should be thrown with this class. 363 | * This allows use to ensure the type easily in tests, 364 | * helps developers identify errors from workbox 365 | * easily and allows use to optimise error 366 | * messages correctly. 367 | * 368 | * @private 369 | */ 370 | class WorkboxError extends Error { 371 | /** 372 | * 373 | * @param {string} errorCode The error code that 374 | * identifies this particular error. 375 | * @param {Object=} details Any relevant arguments 376 | * that will help developers identify issues should 377 | * be added as a key on the context object. 378 | */ 379 | constructor(errorCode, details) { 380 | const message = messageGenerator(errorCode, details); 381 | super(message); 382 | this.name = errorCode; 383 | this.details = details; 384 | } 385 | } 386 | 387 | /* 388 | Copyright 2018 Google LLC 389 | 390 | Use of this source code is governed by an MIT-style 391 | license that can be found in the LICENSE file or at 392 | https://opensource.org/licenses/MIT. 393 | */ 394 | /* 395 | * This method throws if the supplied value is not an array. 396 | * The destructed values are required to produce a meaningful error for users. 397 | * The destructed and restructured object is so it's clear what is 398 | * needed. 399 | */ 400 | const isArray = (value, details) => { 401 | if (!Array.isArray(value)) { 402 | throw new WorkboxError('not-an-array', details); 403 | } 404 | }; 405 | const hasMethod = (object, expectedMethod, details) => { 406 | const type = typeof object[expectedMethod]; 407 | if (type !== 'function') { 408 | details['expectedMethod'] = expectedMethod; 409 | throw new WorkboxError('missing-a-method', details); 410 | } 411 | }; 412 | const isType = (object, expectedType, details) => { 413 | if (typeof object !== expectedType) { 414 | details['expectedType'] = expectedType; 415 | throw new WorkboxError('incorrect-type', details); 416 | } 417 | }; 418 | const isInstance = (object, 419 | // Need the general type to do the check later. 420 | // eslint-disable-next-line @typescript-eslint/ban-types 421 | expectedClass, details) => { 422 | if (!(object instanceof expectedClass)) { 423 | details['expectedClassName'] = expectedClass.name; 424 | throw new WorkboxError('incorrect-class', details); 425 | } 426 | }; 427 | const isOneOf = (value, validValues, details) => { 428 | if (!validValues.includes(value)) { 429 | details['validValueDescription'] = `Valid values are ${JSON.stringify(validValues)}.`; 430 | throw new WorkboxError('invalid-value', details); 431 | } 432 | }; 433 | const isArrayOfClass = (value, 434 | // Need general type to do check later. 435 | expectedClass, 436 | // eslint-disable-line 437 | details) => { 438 | const error = new WorkboxError('not-array-of-class', details); 439 | if (!Array.isArray(value)) { 440 | throw error; 441 | } 442 | for (const item of value) { 443 | if (!(item instanceof expectedClass)) { 444 | throw error; 445 | } 446 | } 447 | }; 448 | const finalAssertExports = { 449 | hasMethod, 450 | isArray, 451 | isInstance, 452 | isOneOf, 453 | isType, 454 | isArrayOfClass 455 | }; 456 | 457 | // @ts-ignore 458 | try { 459 | self['workbox:routing:6.5.3'] && _(); 460 | } catch (e) {} 461 | 462 | /* 463 | Copyright 2018 Google LLC 464 | 465 | Use of this source code is governed by an MIT-style 466 | license that can be found in the LICENSE file or at 467 | https://opensource.org/licenses/MIT. 468 | */ 469 | /** 470 | * The default HTTP method, 'GET', used when there's no specific method 471 | * configured for a route. 472 | * 473 | * @type {string} 474 | * 475 | * @private 476 | */ 477 | const defaultMethod = 'GET'; 478 | /** 479 | * The list of valid HTTP methods associated with requests that could be routed. 480 | * 481 | * @type {Array} 482 | * 483 | * @private 484 | */ 485 | const validMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT']; 486 | 487 | /* 488 | Copyright 2018 Google LLC 489 | 490 | Use of this source code is governed by an MIT-style 491 | license that can be found in the LICENSE file or at 492 | https://opensource.org/licenses/MIT. 493 | */ 494 | /** 495 | * @param {function()|Object} handler Either a function, or an object with a 496 | * 'handle' method. 497 | * @return {Object} An object with a handle method. 498 | * 499 | * @private 500 | */ 501 | const normalizeHandler = handler => { 502 | if (handler && typeof handler === 'object') { 503 | { 504 | finalAssertExports.hasMethod(handler, 'handle', { 505 | moduleName: 'workbox-routing', 506 | className: 'Route', 507 | funcName: 'constructor', 508 | paramName: 'handler' 509 | }); 510 | } 511 | return handler; 512 | } else { 513 | { 514 | finalAssertExports.isType(handler, 'function', { 515 | moduleName: 'workbox-routing', 516 | className: 'Route', 517 | funcName: 'constructor', 518 | paramName: 'handler' 519 | }); 520 | } 521 | return { 522 | handle: handler 523 | }; 524 | } 525 | }; 526 | 527 | /* 528 | Copyright 2018 Google LLC 529 | 530 | Use of this source code is governed by an MIT-style 531 | license that can be found in the LICENSE file or at 532 | https://opensource.org/licenses/MIT. 533 | */ 534 | /** 535 | * A `Route` consists of a pair of callback functions, "match" and "handler". 536 | * The "match" callback determine if a route should be used to "handle" a 537 | * request by returning a non-falsy value if it can. The "handler" callback 538 | * is called when there is a match and should return a Promise that resolves 539 | * to a `Response`. 540 | * 541 | * @memberof workbox-routing 542 | */ 543 | class Route { 544 | /** 545 | * Constructor for Route class. 546 | * 547 | * @param {workbox-routing~matchCallback} match 548 | * A callback function that determines whether the route matches a given 549 | * `fetch` event by returning a non-falsy value. 550 | * @param {workbox-routing~handlerCallback} handler A callback 551 | * function that returns a Promise resolving to a Response. 552 | * @param {string} [method='GET'] The HTTP method to match the Route 553 | * against. 554 | */ 555 | constructor(match, handler, method = defaultMethod) { 556 | { 557 | finalAssertExports.isType(match, 'function', { 558 | moduleName: 'workbox-routing', 559 | className: 'Route', 560 | funcName: 'constructor', 561 | paramName: 'match' 562 | }); 563 | if (method) { 564 | finalAssertExports.isOneOf(method, validMethods, { 565 | paramName: 'method' 566 | }); 567 | } 568 | } 569 | // These values are referenced directly by Router so cannot be 570 | // altered by minificaton. 571 | this.handler = normalizeHandler(handler); 572 | this.match = match; 573 | this.method = method; 574 | } 575 | /** 576 | * 577 | * @param {workbox-routing-handlerCallback} handler A callback 578 | * function that returns a Promise resolving to a Response 579 | */ 580 | setCatchHandler(handler) { 581 | this.catchHandler = normalizeHandler(handler); 582 | } 583 | } 584 | 585 | /* 586 | Copyright 2018 Google LLC 587 | 588 | Use of this source code is governed by an MIT-style 589 | license that can be found in the LICENSE file or at 590 | https://opensource.org/licenses/MIT. 591 | */ 592 | /** 593 | * RegExpRoute makes it easy to create a regular expression based 594 | * {@link workbox-routing.Route}. 595 | * 596 | * For same-origin requests the RegExp only needs to match part of the URL. For 597 | * requests against third-party servers, you must define a RegExp that matches 598 | * the start of the URL. 599 | * 600 | * @memberof workbox-routing 601 | * @extends workbox-routing.Route 602 | */ 603 | class RegExpRoute extends Route { 604 | /** 605 | * If the regular expression contains 606 | * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references}, 607 | * the captured values will be passed to the 608 | * {@link workbox-routing~handlerCallback} `params` 609 | * argument. 610 | * 611 | * @param {RegExp} regExp The regular expression to match against URLs. 612 | * @param {workbox-routing~handlerCallback} handler A callback 613 | * function that returns a Promise resulting in a Response. 614 | * @param {string} [method='GET'] The HTTP method to match the Route 615 | * against. 616 | */ 617 | constructor(regExp, handler, method) { 618 | { 619 | finalAssertExports.isInstance(regExp, RegExp, { 620 | moduleName: 'workbox-routing', 621 | className: 'RegExpRoute', 622 | funcName: 'constructor', 623 | paramName: 'pattern' 624 | }); 625 | } 626 | const match = ({ 627 | url 628 | }) => { 629 | const result = regExp.exec(url.href); 630 | // Return immediately if there's no match. 631 | if (!result) { 632 | return; 633 | } 634 | // Require that the match start at the first character in the URL string 635 | // if it's a cross-origin request. 636 | // See https://github.com/GoogleChrome/workbox/issues/281 for the context 637 | // behind this behavior. 638 | if (url.origin !== location.origin && result.index !== 0) { 639 | { 640 | 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.`); 641 | } 642 | return; 643 | } 644 | // If the route matches, but there aren't any capture groups defined, then 645 | // this will return [], which is truthy and therefore sufficient to 646 | // indicate a match. 647 | // If there are capture groups, then it will return their values. 648 | return result.slice(1); 649 | }; 650 | super(match, handler, method); 651 | } 652 | } 653 | 654 | /* 655 | Copyright 2018 Google LLC 656 | 657 | Use of this source code is governed by an MIT-style 658 | license that can be found in the LICENSE file or at 659 | https://opensource.org/licenses/MIT. 660 | */ 661 | const getFriendlyURL = url => { 662 | const urlObj = new URL(String(url), location.href); 663 | // See https://github.com/GoogleChrome/workbox/issues/2323 664 | // We want to include everything, except for the origin if it's same-origin. 665 | return urlObj.href.replace(new RegExp(`^${location.origin}`), ''); 666 | }; 667 | 668 | /* 669 | Copyright 2018 Google LLC 670 | 671 | Use of this source code is governed by an MIT-style 672 | license that can be found in the LICENSE file or at 673 | https://opensource.org/licenses/MIT. 674 | */ 675 | /** 676 | * The Router can be used to process a `FetchEvent` using one or more 677 | * {@link workbox-routing.Route}, responding with a `Response` if 678 | * a matching route exists. 679 | * 680 | * If no route matches a given a request, the Router will use a "default" 681 | * handler if one is defined. 682 | * 683 | * Should the matching Route throw an error, the Router will use a "catch" 684 | * handler if one is defined to gracefully deal with issues and respond with a 685 | * Request. 686 | * 687 | * If a request matches multiple routes, the **earliest** registered route will 688 | * be used to respond to the request. 689 | * 690 | * @memberof workbox-routing 691 | */ 692 | class Router { 693 | /** 694 | * Initializes a new Router. 695 | */ 696 | constructor() { 697 | this._routes = new Map(); 698 | this._defaultHandlerMap = new Map(); 699 | } 700 | /** 701 | * @return {Map>} routes A `Map` of HTTP 702 | * method name ('GET', etc.) to an array of all the corresponding `Route` 703 | * instances that are registered. 704 | */ 705 | get routes() { 706 | return this._routes; 707 | } 708 | /** 709 | * Adds a fetch event listener to respond to events when a route matches 710 | * the event's request. 711 | */ 712 | addFetchListener() { 713 | // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705 714 | self.addEventListener('fetch', event => { 715 | const { 716 | request 717 | } = event; 718 | const responsePromise = this.handleRequest({ 719 | request, 720 | event 721 | }); 722 | if (responsePromise) { 723 | event.respondWith(responsePromise); 724 | } 725 | }); 726 | } 727 | /** 728 | * Adds a message event listener for URLs to cache from the window. 729 | * This is useful to cache resources loaded on the page prior to when the 730 | * service worker started controlling it. 731 | * 732 | * The format of the message data sent from the window should be as follows. 733 | * Where the `urlsToCache` array may consist of URL strings or an array of 734 | * URL string + `requestInit` object (the same as you'd pass to `fetch()`). 735 | * 736 | * ``` 737 | * { 738 | * type: 'CACHE_URLS', 739 | * payload: { 740 | * urlsToCache: [ 741 | * './script1.js', 742 | * './script2.js', 743 | * ['./script3.js', {mode: 'no-cors'}], 744 | * ], 745 | * }, 746 | * } 747 | * ``` 748 | */ 749 | addCacheListener() { 750 | // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705 751 | self.addEventListener('message', event => { 752 | // event.data is type 'any' 753 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 754 | if (event.data && event.data.type === 'CACHE_URLS') { 755 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 756 | const { 757 | payload 758 | } = event.data; 759 | { 760 | logger.debug(`Caching URLs from the window`, payload.urlsToCache); 761 | } 762 | const requestPromises = Promise.all(payload.urlsToCache.map(entry => { 763 | if (typeof entry === 'string') { 764 | entry = [entry]; 765 | } 766 | const request = new Request(...entry); 767 | return this.handleRequest({ 768 | request, 769 | event 770 | }); 771 | // TODO(philipwalton): TypeScript errors without this typecast for 772 | // some reason (probably a bug). The real type here should work but 773 | // doesn't: `Array | undefined>`. 774 | })); // TypeScript 775 | event.waitUntil(requestPromises); 776 | // If a MessageChannel was used, reply to the message on success. 777 | if (event.ports && event.ports[0]) { 778 | void requestPromises.then(() => event.ports[0].postMessage(true)); 779 | } 780 | } 781 | }); 782 | } 783 | /** 784 | * Apply the routing rules to a FetchEvent object to get a Response from an 785 | * appropriate Route's handler. 786 | * 787 | * @param {Object} options 788 | * @param {Request} options.request The request to handle. 789 | * @param {ExtendableEvent} options.event The event that triggered the 790 | * request. 791 | * @return {Promise|undefined} A promise is returned if a 792 | * registered route can handle the request. If there is no matching 793 | * route and there's no `defaultHandler`, `undefined` is returned. 794 | */ 795 | handleRequest({ 796 | request, 797 | event 798 | }) { 799 | { 800 | finalAssertExports.isInstance(request, Request, { 801 | moduleName: 'workbox-routing', 802 | className: 'Router', 803 | funcName: 'handleRequest', 804 | paramName: 'options.request' 805 | }); 806 | } 807 | const url = new URL(request.url, location.href); 808 | if (!url.protocol.startsWith('http')) { 809 | { 810 | logger.debug(`Workbox Router only supports URLs that start with 'http'.`); 811 | } 812 | return; 813 | } 814 | const sameOrigin = url.origin === location.origin; 815 | const { 816 | params, 817 | route 818 | } = this.findMatchingRoute({ 819 | event, 820 | request, 821 | sameOrigin, 822 | url 823 | }); 824 | let handler = route && route.handler; 825 | const debugMessages = []; 826 | { 827 | if (handler) { 828 | debugMessages.push([`Found a route to handle this request:`, route]); 829 | if (params) { 830 | debugMessages.push([`Passing the following params to the route's handler:`, params]); 831 | } 832 | } 833 | } 834 | // If we don't have a handler because there was no matching route, then 835 | // fall back to defaultHandler if that's defined. 836 | const method = request.method; 837 | if (!handler && this._defaultHandlerMap.has(method)) { 838 | { 839 | debugMessages.push(`Failed to find a matching route. Falling ` + `back to the default handler for ${method}.`); 840 | } 841 | handler = this._defaultHandlerMap.get(method); 842 | } 843 | if (!handler) { 844 | { 845 | // No handler so Workbox will do nothing. If logs is set of debug 846 | // i.e. verbose, we should print out this information. 847 | logger.debug(`No route found for: ${getFriendlyURL(url)}`); 848 | } 849 | return; 850 | } 851 | { 852 | // We have a handler, meaning Workbox is going to handle the route. 853 | // print the routing details to the console. 854 | logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`); 855 | debugMessages.forEach(msg => { 856 | if (Array.isArray(msg)) { 857 | logger.log(...msg); 858 | } else { 859 | logger.log(msg); 860 | } 861 | }); 862 | logger.groupEnd(); 863 | } 864 | // Wrap in try and catch in case the handle method throws a synchronous 865 | // error. It should still callback to the catch handler. 866 | let responsePromise; 867 | try { 868 | responsePromise = handler.handle({ 869 | url, 870 | request, 871 | event, 872 | params 873 | }); 874 | } catch (err) { 875 | responsePromise = Promise.reject(err); 876 | } 877 | // Get route's catch handler, if it exists 878 | const catchHandler = route && route.catchHandler; 879 | if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) { 880 | responsePromise = responsePromise.catch(async err => { 881 | // If there's a route catch handler, process that first 882 | if (catchHandler) { 883 | { 884 | // Still include URL here as it will be async from the console group 885 | // and may not make sense without the URL 886 | logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`); 887 | logger.error(`Error thrown by:`, route); 888 | logger.error(err); 889 | logger.groupEnd(); 890 | } 891 | try { 892 | return await catchHandler.handle({ 893 | url, 894 | request, 895 | event, 896 | params 897 | }); 898 | } catch (catchErr) { 899 | if (catchErr instanceof Error) { 900 | err = catchErr; 901 | } 902 | } 903 | } 904 | if (this._catchHandler) { 905 | { 906 | // Still include URL here as it will be async from the console group 907 | // and may not make sense without the URL 908 | logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL(url)}. Falling back to global Catch Handler.`); 909 | logger.error(`Error thrown by:`, route); 910 | logger.error(err); 911 | logger.groupEnd(); 912 | } 913 | return this._catchHandler.handle({ 914 | url, 915 | request, 916 | event 917 | }); 918 | } 919 | throw err; 920 | }); 921 | } 922 | return responsePromise; 923 | } 924 | /** 925 | * Checks a request and URL (and optionally an event) against the list of 926 | * registered routes, and if there's a match, returns the corresponding 927 | * route along with any params generated by the match. 928 | * 929 | * @param {Object} options 930 | * @param {URL} options.url 931 | * @param {boolean} options.sameOrigin The result of comparing `url.origin` 932 | * against the current origin. 933 | * @param {Request} options.request The request to match. 934 | * @param {Event} options.event The corresponding event. 935 | * @return {Object} An object with `route` and `params` properties. 936 | * They are populated if a matching route was found or `undefined` 937 | * otherwise. 938 | */ 939 | findMatchingRoute({ 940 | url, 941 | sameOrigin, 942 | request, 943 | event 944 | }) { 945 | const routes = this._routes.get(request.method) || []; 946 | for (const route of routes) { 947 | let params; 948 | // route.match returns type any, not possible to change right now. 949 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 950 | const matchResult = route.match({ 951 | url, 952 | sameOrigin, 953 | request, 954 | event 955 | }); 956 | if (matchResult) { 957 | { 958 | // Warn developers that using an async matchCallback is almost always 959 | // not the right thing to do. 960 | if (matchResult instanceof Promise) { 961 | logger.warn(`While routing ${getFriendlyURL(url)}, an async ` + `matchCallback function was used. Please convert the ` + `following route to use a synchronous matchCallback function:`, route); 962 | } 963 | } 964 | // See https://github.com/GoogleChrome/workbox/issues/2079 965 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 966 | params = matchResult; 967 | if (Array.isArray(params) && params.length === 0) { 968 | // Instead of passing an empty array in as params, use undefined. 969 | params = undefined; 970 | } else if (matchResult.constructor === Object && 971 | // eslint-disable-line 972 | Object.keys(matchResult).length === 0) { 973 | // Instead of passing an empty object in as params, use undefined. 974 | params = undefined; 975 | } else if (typeof matchResult === 'boolean') { 976 | // For the boolean value true (rather than just something truth-y), 977 | // don't set params. 978 | // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353 979 | params = undefined; 980 | } 981 | // Return early if have a match. 982 | return { 983 | route, 984 | params 985 | }; 986 | } 987 | } 988 | // If no match was found above, return and empty object. 989 | return {}; 990 | } 991 | /** 992 | * Define a default `handler` that's called when no routes explicitly 993 | * match the incoming request. 994 | * 995 | * Each HTTP method ('GET', 'POST', etc.) gets its own default handler. 996 | * 997 | * Without a default handler, unmatched requests will go against the 998 | * network as if there were no service worker present. 999 | * 1000 | * @param {workbox-routing~handlerCallback} handler A callback 1001 | * function that returns a Promise resulting in a Response. 1002 | * @param {string} [method='GET'] The HTTP method to associate with this 1003 | * default handler. Each method has its own default. 1004 | */ 1005 | setDefaultHandler(handler, method = defaultMethod) { 1006 | this._defaultHandlerMap.set(method, normalizeHandler(handler)); 1007 | } 1008 | /** 1009 | * If a Route throws an error while handling a request, this `handler` 1010 | * will be called and given a chance to provide a response. 1011 | * 1012 | * @param {workbox-routing~handlerCallback} handler A callback 1013 | * function that returns a Promise resulting in a Response. 1014 | */ 1015 | setCatchHandler(handler) { 1016 | this._catchHandler = normalizeHandler(handler); 1017 | } 1018 | /** 1019 | * Registers a route with the router. 1020 | * 1021 | * @param {workbox-routing.Route} route The route to register. 1022 | */ 1023 | registerRoute(route) { 1024 | { 1025 | finalAssertExports.isType(route, 'object', { 1026 | moduleName: 'workbox-routing', 1027 | className: 'Router', 1028 | funcName: 'registerRoute', 1029 | paramName: 'route' 1030 | }); 1031 | finalAssertExports.hasMethod(route, 'match', { 1032 | moduleName: 'workbox-routing', 1033 | className: 'Router', 1034 | funcName: 'registerRoute', 1035 | paramName: 'route' 1036 | }); 1037 | finalAssertExports.isType(route.handler, 'object', { 1038 | moduleName: 'workbox-routing', 1039 | className: 'Router', 1040 | funcName: 'registerRoute', 1041 | paramName: 'route' 1042 | }); 1043 | finalAssertExports.hasMethod(route.handler, 'handle', { 1044 | moduleName: 'workbox-routing', 1045 | className: 'Router', 1046 | funcName: 'registerRoute', 1047 | paramName: 'route.handler' 1048 | }); 1049 | finalAssertExports.isType(route.method, 'string', { 1050 | moduleName: 'workbox-routing', 1051 | className: 'Router', 1052 | funcName: 'registerRoute', 1053 | paramName: 'route.method' 1054 | }); 1055 | } 1056 | if (!this._routes.has(route.method)) { 1057 | this._routes.set(route.method, []); 1058 | } 1059 | // Give precedence to all of the earlier routes by adding this additional 1060 | // route to the end of the array. 1061 | this._routes.get(route.method).push(route); 1062 | } 1063 | /** 1064 | * Unregisters a route with the router. 1065 | * 1066 | * @param {workbox-routing.Route} route The route to unregister. 1067 | */ 1068 | unregisterRoute(route) { 1069 | if (!this._routes.has(route.method)) { 1070 | throw new WorkboxError('unregister-route-but-not-found-with-method', { 1071 | method: route.method 1072 | }); 1073 | } 1074 | const routeIndex = this._routes.get(route.method).indexOf(route); 1075 | if (routeIndex > -1) { 1076 | this._routes.get(route.method).splice(routeIndex, 1); 1077 | } else { 1078 | throw new WorkboxError('unregister-route-route-not-registered'); 1079 | } 1080 | } 1081 | } 1082 | 1083 | /* 1084 | Copyright 2019 Google LLC 1085 | 1086 | Use of this source code is governed by an MIT-style 1087 | license that can be found in the LICENSE file or at 1088 | https://opensource.org/licenses/MIT. 1089 | */ 1090 | let defaultRouter; 1091 | /** 1092 | * Creates a new, singleton Router instance if one does not exist. If one 1093 | * does already exist, that instance is returned. 1094 | * 1095 | * @private 1096 | * @return {Router} 1097 | */ 1098 | const getOrCreateDefaultRouter = () => { 1099 | if (!defaultRouter) { 1100 | defaultRouter = new Router(); 1101 | // The helpers that use the default Router assume these listeners exist. 1102 | defaultRouter.addFetchListener(); 1103 | defaultRouter.addCacheListener(); 1104 | } 1105 | return defaultRouter; 1106 | }; 1107 | 1108 | /* 1109 | Copyright 2019 Google LLC 1110 | 1111 | Use of this source code is governed by an MIT-style 1112 | license that can be found in the LICENSE file or at 1113 | https://opensource.org/licenses/MIT. 1114 | */ 1115 | /** 1116 | * Easily register a RegExp, string, or function with a caching 1117 | * strategy to a singleton Router instance. 1118 | * 1119 | * This method will generate a Route for you if needed and 1120 | * call {@link workbox-routing.Router#registerRoute}. 1121 | * 1122 | * @param {RegExp|string|workbox-routing.Route~matchCallback|workbox-routing.Route} capture 1123 | * If the capture param is a `Route`, all other arguments will be ignored. 1124 | * @param {workbox-routing~handlerCallback} [handler] A callback 1125 | * function that returns a Promise resulting in a Response. This parameter 1126 | * is required if `capture` is not a `Route` object. 1127 | * @param {string} [method='GET'] The HTTP method to match the Route 1128 | * against. 1129 | * @return {workbox-routing.Route} The generated `Route`. 1130 | * 1131 | * @memberof workbox-routing 1132 | */ 1133 | function registerRoute(capture, handler, method) { 1134 | let route; 1135 | if (typeof capture === 'string') { 1136 | const captureUrl = new URL(capture, location.href); 1137 | { 1138 | if (!(capture.startsWith('/') || capture.startsWith('http'))) { 1139 | throw new WorkboxError('invalid-string', { 1140 | moduleName: 'workbox-routing', 1141 | funcName: 'registerRoute', 1142 | paramName: 'capture' 1143 | }); 1144 | } 1145 | // We want to check if Express-style wildcards are in the pathname only. 1146 | // TODO: Remove this log message in v4. 1147 | const valueToCheck = capture.startsWith('http') ? captureUrl.pathname : capture; 1148 | // See https://github.com/pillarjs/path-to-regexp#parameters 1149 | const wildcards = '[*:?+]'; 1150 | if (new RegExp(`${wildcards}`).exec(valueToCheck)) { 1151 | 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.`); 1152 | } 1153 | } 1154 | const matchCallback = ({ 1155 | url 1156 | }) => { 1157 | { 1158 | if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) { 1159 | 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.`); 1160 | } 1161 | } 1162 | return url.href === captureUrl.href; 1163 | }; 1164 | // If `capture` is a string then `handler` and `method` must be present. 1165 | route = new Route(matchCallback, handler, method); 1166 | } else if (capture instanceof RegExp) { 1167 | // If `capture` is a `RegExp` then `handler` and `method` must be present. 1168 | route = new RegExpRoute(capture, handler, method); 1169 | } else if (typeof capture === 'function') { 1170 | // If `capture` is a function then `handler` and `method` must be present. 1171 | route = new Route(capture, handler, method); 1172 | } else if (capture instanceof Route) { 1173 | route = capture; 1174 | } else { 1175 | throw new WorkboxError('unsupported-route-type', { 1176 | moduleName: 'workbox-routing', 1177 | funcName: 'registerRoute', 1178 | paramName: 'capture' 1179 | }); 1180 | } 1181 | const defaultRouter = getOrCreateDefaultRouter(); 1182 | defaultRouter.registerRoute(route); 1183 | return route; 1184 | } 1185 | 1186 | // @ts-ignore 1187 | try { 1188 | self['workbox:strategies:6.5.3'] && _(); 1189 | } catch (e) {} 1190 | 1191 | /* 1192 | Copyright 2018 Google LLC 1193 | 1194 | Use of this source code is governed by an MIT-style 1195 | license that can be found in the LICENSE file or at 1196 | https://opensource.org/licenses/MIT. 1197 | */ 1198 | const cacheOkAndOpaquePlugin = { 1199 | /** 1200 | * Returns a valid response (to allow caching) if the status is 200 (OK) or 1201 | * 0 (opaque). 1202 | * 1203 | * @param {Object} options 1204 | * @param {Response} options.response 1205 | * @return {Response|null} 1206 | * 1207 | * @private 1208 | */ 1209 | cacheWillUpdate: async ({ 1210 | response 1211 | }) => { 1212 | if (response.status === 200 || response.status === 0) { 1213 | return response; 1214 | } 1215 | return null; 1216 | } 1217 | }; 1218 | 1219 | /* 1220 | Copyright 2018 Google LLC 1221 | 1222 | Use of this source code is governed by an MIT-style 1223 | license that can be found in the LICENSE file or at 1224 | https://opensource.org/licenses/MIT. 1225 | */ 1226 | const _cacheNameDetails = { 1227 | googleAnalytics: 'googleAnalytics', 1228 | precache: 'precache-v2', 1229 | prefix: 'workbox', 1230 | runtime: 'runtime', 1231 | suffix: typeof registration !== 'undefined' ? registration.scope : '' 1232 | }; 1233 | const _createCacheName = cacheName => { 1234 | return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix].filter(value => value && value.length > 0).join('-'); 1235 | }; 1236 | const eachCacheNameDetail = fn => { 1237 | for (const key of Object.keys(_cacheNameDetails)) { 1238 | fn(key); 1239 | } 1240 | }; 1241 | const cacheNames = { 1242 | updateDetails: details => { 1243 | eachCacheNameDetail(key => { 1244 | if (typeof details[key] === 'string') { 1245 | _cacheNameDetails[key] = details[key]; 1246 | } 1247 | }); 1248 | }, 1249 | getGoogleAnalyticsName: userCacheName => { 1250 | return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics); 1251 | }, 1252 | getPrecacheName: userCacheName => { 1253 | return userCacheName || _createCacheName(_cacheNameDetails.precache); 1254 | }, 1255 | getPrefix: () => { 1256 | return _cacheNameDetails.prefix; 1257 | }, 1258 | getRuntimeName: userCacheName => { 1259 | return userCacheName || _createCacheName(_cacheNameDetails.runtime); 1260 | }, 1261 | getSuffix: () => { 1262 | return _cacheNameDetails.suffix; 1263 | } 1264 | }; 1265 | 1266 | /* 1267 | Copyright 2020 Google LLC 1268 | Use of this source code is governed by an MIT-style 1269 | license that can be found in the LICENSE file or at 1270 | https://opensource.org/licenses/MIT. 1271 | */ 1272 | function stripParams(fullURL, ignoreParams) { 1273 | const strippedURL = new URL(fullURL); 1274 | for (const param of ignoreParams) { 1275 | strippedURL.searchParams.delete(param); 1276 | } 1277 | return strippedURL.href; 1278 | } 1279 | /** 1280 | * Matches an item in the cache, ignoring specific URL params. This is similar 1281 | * to the `ignoreSearch` option, but it allows you to ignore just specific 1282 | * params (while continuing to match on the others). 1283 | * 1284 | * @private 1285 | * @param {Cache} cache 1286 | * @param {Request} request 1287 | * @param {Object} matchOptions 1288 | * @param {Array} ignoreParams 1289 | * @return {Promise} 1290 | */ 1291 | async function cacheMatchIgnoreParams(cache, request, ignoreParams, matchOptions) { 1292 | const strippedRequestURL = stripParams(request.url, ignoreParams); 1293 | // If the request doesn't include any ignored params, match as normal. 1294 | if (request.url === strippedRequestURL) { 1295 | return cache.match(request, matchOptions); 1296 | } 1297 | // Otherwise, match by comparing keys 1298 | const keysOptions = Object.assign(Object.assign({}, matchOptions), { 1299 | ignoreSearch: true 1300 | }); 1301 | const cacheKeys = await cache.keys(request, keysOptions); 1302 | for (const cacheKey of cacheKeys) { 1303 | const strippedCacheKeyURL = stripParams(cacheKey.url, ignoreParams); 1304 | if (strippedRequestURL === strippedCacheKeyURL) { 1305 | return cache.match(cacheKey, matchOptions); 1306 | } 1307 | } 1308 | return; 1309 | } 1310 | 1311 | /* 1312 | Copyright 2018 Google LLC 1313 | 1314 | Use of this source code is governed by an MIT-style 1315 | license that can be found in the LICENSE file or at 1316 | https://opensource.org/licenses/MIT. 1317 | */ 1318 | /** 1319 | * The Deferred class composes Promises in a way that allows for them to be 1320 | * resolved or rejected from outside the constructor. In most cases promises 1321 | * should be used directly, but Deferreds can be necessary when the logic to 1322 | * resolve a promise must be separate. 1323 | * 1324 | * @private 1325 | */ 1326 | class Deferred { 1327 | /** 1328 | * Creates a promise and exposes its resolve and reject functions as methods. 1329 | */ 1330 | constructor() { 1331 | this.promise = new Promise((resolve, reject) => { 1332 | this.resolve = resolve; 1333 | this.reject = reject; 1334 | }); 1335 | } 1336 | } 1337 | 1338 | /* 1339 | Copyright 2018 Google LLC 1340 | 1341 | Use of this source code is governed by an MIT-style 1342 | license that can be found in the LICENSE file or at 1343 | https://opensource.org/licenses/MIT. 1344 | */ 1345 | // Callbacks to be executed whenever there's a quota error. 1346 | // Can't change Function type right now. 1347 | // eslint-disable-next-line @typescript-eslint/ban-types 1348 | const quotaErrorCallbacks = new Set(); 1349 | 1350 | /* 1351 | Copyright 2018 Google LLC 1352 | 1353 | Use of this source code is governed by an MIT-style 1354 | license that can be found in the LICENSE file or at 1355 | https://opensource.org/licenses/MIT. 1356 | */ 1357 | /** 1358 | * Runs all of the callback functions, one at a time sequentially, in the order 1359 | * in which they were registered. 1360 | * 1361 | * @memberof workbox-core 1362 | * @private 1363 | */ 1364 | async function executeQuotaErrorCallbacks() { 1365 | { 1366 | logger.log(`About to run ${quotaErrorCallbacks.size} ` + `callbacks to clean up caches.`); 1367 | } 1368 | for (const callback of quotaErrorCallbacks) { 1369 | await callback(); 1370 | { 1371 | logger.log(callback, 'is complete.'); 1372 | } 1373 | } 1374 | { 1375 | logger.log('Finished running callbacks.'); 1376 | } 1377 | } 1378 | 1379 | /* 1380 | Copyright 2019 Google LLC 1381 | Use of this source code is governed by an MIT-style 1382 | license that can be found in the LICENSE file or at 1383 | https://opensource.org/licenses/MIT. 1384 | */ 1385 | /** 1386 | * Returns a promise that resolves and the passed number of milliseconds. 1387 | * This utility is an async/await-friendly version of `setTimeout`. 1388 | * 1389 | * @param {number} ms 1390 | * @return {Promise} 1391 | * @private 1392 | */ 1393 | function timeout(ms) { 1394 | return new Promise(resolve => setTimeout(resolve, ms)); 1395 | } 1396 | 1397 | /* 1398 | Copyright 2020 Google LLC 1399 | 1400 | Use of this source code is governed by an MIT-style 1401 | license that can be found in the LICENSE file or at 1402 | https://opensource.org/licenses/MIT. 1403 | */ 1404 | function toRequest(input) { 1405 | return typeof input === 'string' ? new Request(input) : input; 1406 | } 1407 | /** 1408 | * A class created every time a Strategy instance instance calls 1409 | * {@link workbox-strategies.Strategy~handle} or 1410 | * {@link workbox-strategies.Strategy~handleAll} that wraps all fetch and 1411 | * cache actions around plugin callbacks and keeps track of when the strategy 1412 | * is "done" (i.e. all added `event.waitUntil()` promises have resolved). 1413 | * 1414 | * @memberof workbox-strategies 1415 | */ 1416 | class StrategyHandler { 1417 | /** 1418 | * Creates a new instance associated with the passed strategy and event 1419 | * that's handling the request. 1420 | * 1421 | * The constructor also initializes the state that will be passed to each of 1422 | * the plugins handling this request. 1423 | * 1424 | * @param {workbox-strategies.Strategy} strategy 1425 | * @param {Object} options 1426 | * @param {Request|string} options.request A request to run this strategy for. 1427 | * @param {ExtendableEvent} options.event The event associated with the 1428 | * request. 1429 | * @param {URL} [options.url] 1430 | * @param {*} [options.params] The return value from the 1431 | * {@link workbox-routing~matchCallback} (if applicable). 1432 | */ 1433 | constructor(strategy, options) { 1434 | this._cacheKeys = {}; 1435 | /** 1436 | * The request the strategy is performing (passed to the strategy's 1437 | * `handle()` or `handleAll()` method). 1438 | * @name request 1439 | * @instance 1440 | * @type {Request} 1441 | * @memberof workbox-strategies.StrategyHandler 1442 | */ 1443 | /** 1444 | * The event associated with this request. 1445 | * @name event 1446 | * @instance 1447 | * @type {ExtendableEvent} 1448 | * @memberof workbox-strategies.StrategyHandler 1449 | */ 1450 | /** 1451 | * A `URL` instance of `request.url` (if passed to the strategy's 1452 | * `handle()` or `handleAll()` method). 1453 | * Note: the `url` param will be present if the strategy was invoked 1454 | * from a workbox `Route` object. 1455 | * @name url 1456 | * @instance 1457 | * @type {URL|undefined} 1458 | * @memberof workbox-strategies.StrategyHandler 1459 | */ 1460 | /** 1461 | * A `param` value (if passed to the strategy's 1462 | * `handle()` or `handleAll()` method). 1463 | * Note: the `param` param will be present if the strategy was invoked 1464 | * from a workbox `Route` object and the 1465 | * {@link workbox-routing~matchCallback} returned 1466 | * a truthy value (it will be that value). 1467 | * @name params 1468 | * @instance 1469 | * @type {*|undefined} 1470 | * @memberof workbox-strategies.StrategyHandler 1471 | */ 1472 | { 1473 | finalAssertExports.isInstance(options.event, ExtendableEvent, { 1474 | moduleName: 'workbox-strategies', 1475 | className: 'StrategyHandler', 1476 | funcName: 'constructor', 1477 | paramName: 'options.event' 1478 | }); 1479 | } 1480 | Object.assign(this, options); 1481 | this.event = options.event; 1482 | this._strategy = strategy; 1483 | this._handlerDeferred = new Deferred(); 1484 | this._extendLifetimePromises = []; 1485 | // Copy the plugins list (since it's mutable on the strategy), 1486 | // so any mutations don't affect this handler instance. 1487 | this._plugins = [...strategy.plugins]; 1488 | this._pluginStateMap = new Map(); 1489 | for (const plugin of this._plugins) { 1490 | this._pluginStateMap.set(plugin, {}); 1491 | } 1492 | this.event.waitUntil(this._handlerDeferred.promise); 1493 | } 1494 | /** 1495 | * Fetches a given request (and invokes any applicable plugin callback 1496 | * methods) using the `fetchOptions` (for non-navigation requests) and 1497 | * `plugins` defined on the `Strategy` object. 1498 | * 1499 | * The following plugin lifecycle methods are invoked when using this method: 1500 | * - `requestWillFetch()` 1501 | * - `fetchDidSucceed()` 1502 | * - `fetchDidFail()` 1503 | * 1504 | * @param {Request|string} input The URL or request to fetch. 1505 | * @return {Promise} 1506 | */ 1507 | async fetch(input) { 1508 | const { 1509 | event 1510 | } = this; 1511 | let request = toRequest(input); 1512 | if (request.mode === 'navigate' && event instanceof FetchEvent && event.preloadResponse) { 1513 | const possiblePreloadResponse = await event.preloadResponse; 1514 | if (possiblePreloadResponse) { 1515 | { 1516 | logger.log(`Using a preloaded navigation response for ` + `'${getFriendlyURL(request.url)}'`); 1517 | } 1518 | return possiblePreloadResponse; 1519 | } 1520 | } 1521 | // If there is a fetchDidFail plugin, we need to save a clone of the 1522 | // original request before it's either modified by a requestWillFetch 1523 | // plugin or before the original request's body is consumed via fetch(). 1524 | const originalRequest = this.hasCallback('fetchDidFail') ? request.clone() : null; 1525 | try { 1526 | for (const cb of this.iterateCallbacks('requestWillFetch')) { 1527 | request = await cb({ 1528 | request: request.clone(), 1529 | event 1530 | }); 1531 | } 1532 | } catch (err) { 1533 | if (err instanceof Error) { 1534 | throw new WorkboxError('plugin-error-request-will-fetch', { 1535 | thrownErrorMessage: err.message 1536 | }); 1537 | } 1538 | } 1539 | // The request can be altered by plugins with `requestWillFetch` making 1540 | // the original request (most likely from a `fetch` event) different 1541 | // from the Request we make. Pass both to `fetchDidFail` to aid debugging. 1542 | const pluginFilteredRequest = request.clone(); 1543 | try { 1544 | let fetchResponse; 1545 | // See https://github.com/GoogleChrome/workbox/issues/1796 1546 | fetchResponse = await fetch(request, request.mode === 'navigate' ? undefined : this._strategy.fetchOptions); 1547 | if ("development" !== 'production') { 1548 | logger.debug(`Network request for ` + `'${getFriendlyURL(request.url)}' returned a response with ` + `status '${fetchResponse.status}'.`); 1549 | } 1550 | for (const callback of this.iterateCallbacks('fetchDidSucceed')) { 1551 | fetchResponse = await callback({ 1552 | event, 1553 | request: pluginFilteredRequest, 1554 | response: fetchResponse 1555 | }); 1556 | } 1557 | return fetchResponse; 1558 | } catch (error) { 1559 | { 1560 | logger.log(`Network request for ` + `'${getFriendlyURL(request.url)}' threw an error.`, error); 1561 | } 1562 | // `originalRequest` will only exist if a `fetchDidFail` callback 1563 | // is being used (see above). 1564 | if (originalRequest) { 1565 | await this.runCallbacks('fetchDidFail', { 1566 | error: error, 1567 | event, 1568 | originalRequest: originalRequest.clone(), 1569 | request: pluginFilteredRequest.clone() 1570 | }); 1571 | } 1572 | throw error; 1573 | } 1574 | } 1575 | /** 1576 | * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on 1577 | * the response generated by `this.fetch()`. 1578 | * 1579 | * The call to `this.cachePut()` automatically invokes `this.waitUntil()`, 1580 | * so you do not have to manually call `waitUntil()` on the event. 1581 | * 1582 | * @param {Request|string} input The request or URL to fetch and cache. 1583 | * @return {Promise} 1584 | */ 1585 | async fetchAndCachePut(input) { 1586 | const response = await this.fetch(input); 1587 | const responseClone = response.clone(); 1588 | void this.waitUntil(this.cachePut(input, responseClone)); 1589 | return response; 1590 | } 1591 | /** 1592 | * Matches a request from the cache (and invokes any applicable plugin 1593 | * callback methods) using the `cacheName`, `matchOptions`, and `plugins` 1594 | * defined on the strategy object. 1595 | * 1596 | * The following plugin lifecycle methods are invoked when using this method: 1597 | * - cacheKeyWillByUsed() 1598 | * - cachedResponseWillByUsed() 1599 | * 1600 | * @param {Request|string} key The Request or URL to use as the cache key. 1601 | * @return {Promise} A matching response, if found. 1602 | */ 1603 | async cacheMatch(key) { 1604 | const request = toRequest(key); 1605 | let cachedResponse; 1606 | const { 1607 | cacheName, 1608 | matchOptions 1609 | } = this._strategy; 1610 | const effectiveRequest = await this.getCacheKey(request, 'read'); 1611 | const multiMatchOptions = Object.assign(Object.assign({}, matchOptions), { 1612 | cacheName 1613 | }); 1614 | cachedResponse = await caches.match(effectiveRequest, multiMatchOptions); 1615 | { 1616 | if (cachedResponse) { 1617 | logger.debug(`Found a cached response in '${cacheName}'.`); 1618 | } else { 1619 | logger.debug(`No cached response found in '${cacheName}'.`); 1620 | } 1621 | } 1622 | for (const callback of this.iterateCallbacks('cachedResponseWillBeUsed')) { 1623 | cachedResponse = (await callback({ 1624 | cacheName, 1625 | matchOptions, 1626 | cachedResponse, 1627 | request: effectiveRequest, 1628 | event: this.event 1629 | })) || undefined; 1630 | } 1631 | return cachedResponse; 1632 | } 1633 | /** 1634 | * Puts a request/response pair in the cache (and invokes any applicable 1635 | * plugin callback methods) using the `cacheName` and `plugins` defined on 1636 | * the strategy object. 1637 | * 1638 | * The following plugin lifecycle methods are invoked when using this method: 1639 | * - cacheKeyWillByUsed() 1640 | * - cacheWillUpdate() 1641 | * - cacheDidUpdate() 1642 | * 1643 | * @param {Request|string} key The request or URL to use as the cache key. 1644 | * @param {Response} response The response to cache. 1645 | * @return {Promise} `false` if a cacheWillUpdate caused the response 1646 | * not be cached, and `true` otherwise. 1647 | */ 1648 | async cachePut(key, response) { 1649 | const request = toRequest(key); 1650 | // Run in the next task to avoid blocking other cache reads. 1651 | // https://github.com/w3c/ServiceWorker/issues/1397 1652 | await timeout(0); 1653 | const effectiveRequest = await this.getCacheKey(request, 'write'); 1654 | { 1655 | if (effectiveRequest.method && effectiveRequest.method !== 'GET') { 1656 | throw new WorkboxError('attempt-to-cache-non-get-request', { 1657 | url: getFriendlyURL(effectiveRequest.url), 1658 | method: effectiveRequest.method 1659 | }); 1660 | } 1661 | // See https://github.com/GoogleChrome/workbox/issues/2818 1662 | const vary = response.headers.get('Vary'); 1663 | if (vary) { 1664 | 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.`); 1665 | } 1666 | } 1667 | if (!response) { 1668 | { 1669 | logger.error(`Cannot cache non-existent response for ` + `'${getFriendlyURL(effectiveRequest.url)}'.`); 1670 | } 1671 | throw new WorkboxError('cache-put-with-no-response', { 1672 | url: getFriendlyURL(effectiveRequest.url) 1673 | }); 1674 | } 1675 | const responseToCache = await this._ensureResponseSafeToCache(response); 1676 | if (!responseToCache) { 1677 | { 1678 | logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' ` + `will not be cached.`, responseToCache); 1679 | } 1680 | return false; 1681 | } 1682 | const { 1683 | cacheName, 1684 | matchOptions 1685 | } = this._strategy; 1686 | const cache = await self.caches.open(cacheName); 1687 | const hasCacheUpdateCallback = this.hasCallback('cacheDidUpdate'); 1688 | const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams( 1689 | // TODO(philipwalton): the `__WB_REVISION__` param is a precaching 1690 | // feature. Consider into ways to only add this behavior if using 1691 | // precaching. 1692 | cache, effectiveRequest.clone(), ['__WB_REVISION__'], matchOptions) : null; 1693 | { 1694 | logger.debug(`Updating the '${cacheName}' cache with a new Response ` + `for ${getFriendlyURL(effectiveRequest.url)}.`); 1695 | } 1696 | try { 1697 | await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache); 1698 | } catch (error) { 1699 | if (error instanceof Error) { 1700 | // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError 1701 | if (error.name === 'QuotaExceededError') { 1702 | await executeQuotaErrorCallbacks(); 1703 | } 1704 | throw error; 1705 | } 1706 | } 1707 | for (const callback of this.iterateCallbacks('cacheDidUpdate')) { 1708 | await callback({ 1709 | cacheName, 1710 | oldResponse, 1711 | newResponse: responseToCache.clone(), 1712 | request: effectiveRequest, 1713 | event: this.event 1714 | }); 1715 | } 1716 | return true; 1717 | } 1718 | /** 1719 | * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and 1720 | * executes any of those callbacks found in sequence. The final `Request` 1721 | * object returned by the last plugin is treated as the cache key for cache 1722 | * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have 1723 | * been registered, the passed request is returned unmodified 1724 | * 1725 | * @param {Request} request 1726 | * @param {string} mode 1727 | * @return {Promise} 1728 | */ 1729 | async getCacheKey(request, mode) { 1730 | const key = `${request.url} | ${mode}`; 1731 | if (!this._cacheKeys[key]) { 1732 | let effectiveRequest = request; 1733 | for (const callback of this.iterateCallbacks('cacheKeyWillBeUsed')) { 1734 | effectiveRequest = toRequest(await callback({ 1735 | mode, 1736 | request: effectiveRequest, 1737 | event: this.event, 1738 | // params has a type any can't change right now. 1739 | params: this.params // eslint-disable-line 1740 | })); 1741 | } 1742 | 1743 | this._cacheKeys[key] = effectiveRequest; 1744 | } 1745 | return this._cacheKeys[key]; 1746 | } 1747 | /** 1748 | * Returns true if the strategy has at least one plugin with the given 1749 | * callback. 1750 | * 1751 | * @param {string} name The name of the callback to check for. 1752 | * @return {boolean} 1753 | */ 1754 | hasCallback(name) { 1755 | for (const plugin of this._strategy.plugins) { 1756 | if (name in plugin) { 1757 | return true; 1758 | } 1759 | } 1760 | return false; 1761 | } 1762 | /** 1763 | * Runs all plugin callbacks matching the given name, in order, passing the 1764 | * given param object (merged ith the current plugin state) as the only 1765 | * argument. 1766 | * 1767 | * Note: since this method runs all plugins, it's not suitable for cases 1768 | * where the return value of a callback needs to be applied prior to calling 1769 | * the next callback. See 1770 | * {@link workbox-strategies.StrategyHandler#iterateCallbacks} 1771 | * below for how to handle that case. 1772 | * 1773 | * @param {string} name The name of the callback to run within each plugin. 1774 | * @param {Object} param The object to pass as the first (and only) param 1775 | * when executing each callback. This object will be merged with the 1776 | * current plugin state prior to callback execution. 1777 | */ 1778 | async runCallbacks(name, param) { 1779 | for (const callback of this.iterateCallbacks(name)) { 1780 | // TODO(philipwalton): not sure why `any` is needed. It seems like 1781 | // this should work with `as WorkboxPluginCallbackParam[C]`. 1782 | await callback(param); 1783 | } 1784 | } 1785 | /** 1786 | * Accepts a callback and returns an iterable of matching plugin callbacks, 1787 | * where each callback is wrapped with the current handler state (i.e. when 1788 | * you call each callback, whatever object parameter you pass it will 1789 | * be merged with the plugin's current state). 1790 | * 1791 | * @param {string} name The name fo the callback to run 1792 | * @return {Array} 1793 | */ 1794 | *iterateCallbacks(name) { 1795 | for (const plugin of this._strategy.plugins) { 1796 | if (typeof plugin[name] === 'function') { 1797 | const state = this._pluginStateMap.get(plugin); 1798 | const statefulCallback = param => { 1799 | const statefulParam = Object.assign(Object.assign({}, param), { 1800 | state 1801 | }); 1802 | // TODO(philipwalton): not sure why `any` is needed. It seems like 1803 | // this should work with `as WorkboxPluginCallbackParam[C]`. 1804 | return plugin[name](statefulParam); 1805 | }; 1806 | yield statefulCallback; 1807 | } 1808 | } 1809 | } 1810 | /** 1811 | * Adds a promise to the 1812 | * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises} 1813 | * of the event event associated with the request being handled (usually a 1814 | * `FetchEvent`). 1815 | * 1816 | * Note: you can await 1817 | * {@link workbox-strategies.StrategyHandler~doneWaiting} 1818 | * to know when all added promises have settled. 1819 | * 1820 | * @param {Promise} promise A promise to add to the extend lifetime promises 1821 | * of the event that triggered the request. 1822 | */ 1823 | waitUntil(promise) { 1824 | this._extendLifetimePromises.push(promise); 1825 | return promise; 1826 | } 1827 | /** 1828 | * Returns a promise that resolves once all promises passed to 1829 | * {@link workbox-strategies.StrategyHandler~waitUntil} 1830 | * have settled. 1831 | * 1832 | * Note: any work done after `doneWaiting()` settles should be manually 1833 | * passed to an event's `waitUntil()` method (not this handler's 1834 | * `waitUntil()` method), otherwise the service worker thread my be killed 1835 | * prior to your work completing. 1836 | */ 1837 | async doneWaiting() { 1838 | let promise; 1839 | while (promise = this._extendLifetimePromises.shift()) { 1840 | await promise; 1841 | } 1842 | } 1843 | /** 1844 | * Stops running the strategy and immediately resolves any pending 1845 | * `waitUntil()` promises. 1846 | */ 1847 | destroy() { 1848 | this._handlerDeferred.resolve(null); 1849 | } 1850 | /** 1851 | * This method will call cacheWillUpdate on the available plugins (or use 1852 | * status === 200) to determine if the Response is safe and valid to cache. 1853 | * 1854 | * @param {Request} options.request 1855 | * @param {Response} options.response 1856 | * @return {Promise} 1857 | * 1858 | * @private 1859 | */ 1860 | async _ensureResponseSafeToCache(response) { 1861 | let responseToCache = response; 1862 | let pluginsUsed = false; 1863 | for (const callback of this.iterateCallbacks('cacheWillUpdate')) { 1864 | responseToCache = (await callback({ 1865 | request: this.request, 1866 | response: responseToCache, 1867 | event: this.event 1868 | })) || undefined; 1869 | pluginsUsed = true; 1870 | if (!responseToCache) { 1871 | break; 1872 | } 1873 | } 1874 | if (!pluginsUsed) { 1875 | if (responseToCache && responseToCache.status !== 200) { 1876 | responseToCache = undefined; 1877 | } 1878 | { 1879 | if (responseToCache) { 1880 | if (responseToCache.status !== 200) { 1881 | if (responseToCache.status === 0) { 1882 | 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.`); 1883 | } else { 1884 | logger.debug(`The response for '${this.request.url}' ` + `returned a status code of '${response.status}' and won't ` + `be cached as a result.`); 1885 | } 1886 | } 1887 | } 1888 | } 1889 | } 1890 | return responseToCache; 1891 | } 1892 | } 1893 | 1894 | /* 1895 | Copyright 2020 Google LLC 1896 | 1897 | Use of this source code is governed by an MIT-style 1898 | license that can be found in the LICENSE file or at 1899 | https://opensource.org/licenses/MIT. 1900 | */ 1901 | /** 1902 | * An abstract base class that all other strategy classes must extend from: 1903 | * 1904 | * @memberof workbox-strategies 1905 | */ 1906 | class Strategy { 1907 | /** 1908 | * Creates a new instance of the strategy and sets all documented option 1909 | * properties as public instance properties. 1910 | * 1911 | * Note: if a custom strategy class extends the base Strategy class and does 1912 | * not need more than these properties, it does not need to define its own 1913 | * constructor. 1914 | * 1915 | * @param {Object} [options] 1916 | * @param {string} [options.cacheName] Cache name to store and retrieve 1917 | * requests. Defaults to the cache names provided by 1918 | * {@link workbox-core.cacheNames}. 1919 | * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} 1920 | * to use in conjunction with this caching strategy. 1921 | * @param {Object} [options.fetchOptions] Values passed along to the 1922 | * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) 1923 | * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796) 1924 | * `fetch()` requests made by this strategy. 1925 | * @param {Object} [options.matchOptions] The 1926 | * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions} 1927 | * for any `cache.match()` or `cache.put()` calls made by this strategy. 1928 | */ 1929 | constructor(options = {}) { 1930 | /** 1931 | * Cache name to store and retrieve 1932 | * requests. Defaults to the cache names provided by 1933 | * {@link workbox-core.cacheNames}. 1934 | * 1935 | * @type {string} 1936 | */ 1937 | this.cacheName = cacheNames.getRuntimeName(options.cacheName); 1938 | /** 1939 | * The list 1940 | * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} 1941 | * used by this strategy. 1942 | * 1943 | * @type {Array} 1944 | */ 1945 | this.plugins = options.plugins || []; 1946 | /** 1947 | * Values passed along to the 1948 | * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters} 1949 | * of all fetch() requests made by this strategy. 1950 | * 1951 | * @type {Object} 1952 | */ 1953 | this.fetchOptions = options.fetchOptions; 1954 | /** 1955 | * The 1956 | * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions} 1957 | * for any `cache.match()` or `cache.put()` calls made by this strategy. 1958 | * 1959 | * @type {Object} 1960 | */ 1961 | this.matchOptions = options.matchOptions; 1962 | } 1963 | /** 1964 | * Perform a request strategy and returns a `Promise` that will resolve with 1965 | * a `Response`, invoking all relevant plugin callbacks. 1966 | * 1967 | * When a strategy instance is registered with a Workbox 1968 | * {@link workbox-routing.Route}, this method is automatically 1969 | * called when the route matches. 1970 | * 1971 | * Alternatively, this method can be used in a standalone `FetchEvent` 1972 | * listener by passing it to `event.respondWith()`. 1973 | * 1974 | * @param {FetchEvent|Object} options A `FetchEvent` or an object with the 1975 | * properties listed below. 1976 | * @param {Request|string} options.request A request to run this strategy for. 1977 | * @param {ExtendableEvent} options.event The event associated with the 1978 | * request. 1979 | * @param {URL} [options.url] 1980 | * @param {*} [options.params] 1981 | */ 1982 | handle(options) { 1983 | const [responseDone] = this.handleAll(options); 1984 | return responseDone; 1985 | } 1986 | /** 1987 | * Similar to {@link workbox-strategies.Strategy~handle}, but 1988 | * instead of just returning a `Promise` that resolves to a `Response` it 1989 | * it will return an tuple of `[response, done]` promises, where the former 1990 | * (`response`) is equivalent to what `handle()` returns, and the latter is a 1991 | * Promise that will resolve once any promises that were added to 1992 | * `event.waitUntil()` as part of performing the strategy have completed. 1993 | * 1994 | * You can await the `done` promise to ensure any extra work performed by 1995 | * the strategy (usually caching responses) completes successfully. 1996 | * 1997 | * @param {FetchEvent|Object} options A `FetchEvent` or an object with the 1998 | * properties listed below. 1999 | * @param {Request|string} options.request A request to run this strategy for. 2000 | * @param {ExtendableEvent} options.event The event associated with the 2001 | * request. 2002 | * @param {URL} [options.url] 2003 | * @param {*} [options.params] 2004 | * @return {Array} A tuple of [response, done] 2005 | * promises that can be used to determine when the response resolves as 2006 | * well as when the handler has completed all its work. 2007 | */ 2008 | handleAll(options) { 2009 | // Allow for flexible options to be passed. 2010 | if (options instanceof FetchEvent) { 2011 | options = { 2012 | event: options, 2013 | request: options.request 2014 | }; 2015 | } 2016 | const event = options.event; 2017 | const request = typeof options.request === 'string' ? new Request(options.request) : options.request; 2018 | const params = 'params' in options ? options.params : undefined; 2019 | const handler = new StrategyHandler(this, { 2020 | event, 2021 | request, 2022 | params 2023 | }); 2024 | const responseDone = this._getResponse(handler, request, event); 2025 | const handlerDone = this._awaitComplete(responseDone, handler, request, event); 2026 | // Return an array of promises, suitable for use with Promise.all(). 2027 | return [responseDone, handlerDone]; 2028 | } 2029 | async _getResponse(handler, request, event) { 2030 | await handler.runCallbacks('handlerWillStart', { 2031 | event, 2032 | request 2033 | }); 2034 | let response = undefined; 2035 | try { 2036 | response = await this._handle(request, handler); 2037 | // The "official" Strategy subclasses all throw this error automatically, 2038 | // but in case a third-party Strategy doesn't, ensure that we have a 2039 | // consistent failure when there's no response or an error response. 2040 | if (!response || response.type === 'error') { 2041 | throw new WorkboxError('no-response', { 2042 | url: request.url 2043 | }); 2044 | } 2045 | } catch (error) { 2046 | if (error instanceof Error) { 2047 | for (const callback of handler.iterateCallbacks('handlerDidError')) { 2048 | response = await callback({ 2049 | error, 2050 | event, 2051 | request 2052 | }); 2053 | if (response) { 2054 | break; 2055 | } 2056 | } 2057 | } 2058 | if (!response) { 2059 | throw error; 2060 | } else { 2061 | 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.`); 2062 | } 2063 | } 2064 | for (const callback of handler.iterateCallbacks('handlerWillRespond')) { 2065 | response = await callback({ 2066 | event, 2067 | request, 2068 | response 2069 | }); 2070 | } 2071 | return response; 2072 | } 2073 | async _awaitComplete(responseDone, handler, request, event) { 2074 | let response; 2075 | let error; 2076 | try { 2077 | response = await responseDone; 2078 | } catch (error) { 2079 | // Ignore errors, as response errors should be caught via the `response` 2080 | // promise above. The `done` promise will only throw for errors in 2081 | // promises passed to `handler.waitUntil()`. 2082 | } 2083 | try { 2084 | await handler.runCallbacks('handlerDidRespond', { 2085 | event, 2086 | request, 2087 | response 2088 | }); 2089 | await handler.doneWaiting(); 2090 | } catch (waitUntilError) { 2091 | if (waitUntilError instanceof Error) { 2092 | error = waitUntilError; 2093 | } 2094 | } 2095 | await handler.runCallbacks('handlerDidComplete', { 2096 | event, 2097 | request, 2098 | response, 2099 | error: error 2100 | }); 2101 | handler.destroy(); 2102 | if (error) { 2103 | throw error; 2104 | } 2105 | } 2106 | } 2107 | /** 2108 | * Classes extending the `Strategy` based class should implement this method, 2109 | * and leverage the {@link workbox-strategies.StrategyHandler} 2110 | * arg to perform all fetching and cache logic, which will ensure all relevant 2111 | * cache, cache options, fetch options and plugins are used (per the current 2112 | * strategy instance). 2113 | * 2114 | * @name _handle 2115 | * @instance 2116 | * @abstract 2117 | * @function 2118 | * @param {Request} request 2119 | * @param {workbox-strategies.StrategyHandler} handler 2120 | * @return {Promise} 2121 | * 2122 | * @memberof workbox-strategies.Strategy 2123 | */ 2124 | 2125 | /* 2126 | Copyright 2018 Google LLC 2127 | 2128 | Use of this source code is governed by an MIT-style 2129 | license that can be found in the LICENSE file or at 2130 | https://opensource.org/licenses/MIT. 2131 | */ 2132 | const messages = { 2133 | strategyStart: (strategyName, request) => `Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`, 2134 | printFinalResponse: response => { 2135 | if (response) { 2136 | logger.groupCollapsed(`View the final response here.`); 2137 | logger.log(response || '[No response returned]'); 2138 | logger.groupEnd(); 2139 | } 2140 | } 2141 | }; 2142 | 2143 | /* 2144 | Copyright 2018 Google LLC 2145 | 2146 | Use of this source code is governed by an MIT-style 2147 | license that can be found in the LICENSE file or at 2148 | https://opensource.org/licenses/MIT. 2149 | */ 2150 | /** 2151 | * An implementation of a 2152 | * [network first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network-first-falling-back-to-cache) 2153 | * request strategy. 2154 | * 2155 | * By default, this strategy will cache responses with a 200 status code as 2156 | * well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses). 2157 | * Opaque responses are are cross-origin requests where the response doesn't 2158 | * support [CORS](https://enable-cors.org/). 2159 | * 2160 | * If the network request fails, and there is no cache match, this will throw 2161 | * a `WorkboxError` exception. 2162 | * 2163 | * @extends workbox-strategies.Strategy 2164 | * @memberof workbox-strategies 2165 | */ 2166 | class NetworkFirst extends Strategy { 2167 | /** 2168 | * @param {Object} [options] 2169 | * @param {string} [options.cacheName] Cache name to store and retrieve 2170 | * requests. Defaults to cache names provided by 2171 | * {@link workbox-core.cacheNames}. 2172 | * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} 2173 | * to use in conjunction with this caching strategy. 2174 | * @param {Object} [options.fetchOptions] Values passed along to the 2175 | * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) 2176 | * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796) 2177 | * `fetch()` requests made by this strategy. 2178 | * @param {Object} [options.matchOptions] [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions) 2179 | * @param {number} [options.networkTimeoutSeconds] If set, any network requests 2180 | * that fail to respond within the timeout will fallback to the cache. 2181 | * 2182 | * This option can be used to combat 2183 | * "[lie-fi]{@link https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi}" 2184 | * scenarios. 2185 | */ 2186 | constructor(options = {}) { 2187 | super(options); 2188 | // If this instance contains no plugins with a 'cacheWillUpdate' callback, 2189 | // prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list. 2190 | if (!this.plugins.some(p => 'cacheWillUpdate' in p)) { 2191 | this.plugins.unshift(cacheOkAndOpaquePlugin); 2192 | } 2193 | this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0; 2194 | { 2195 | if (this._networkTimeoutSeconds) { 2196 | finalAssertExports.isType(this._networkTimeoutSeconds, 'number', { 2197 | moduleName: 'workbox-strategies', 2198 | className: this.constructor.name, 2199 | funcName: 'constructor', 2200 | paramName: 'networkTimeoutSeconds' 2201 | }); 2202 | } 2203 | } 2204 | } 2205 | /** 2206 | * @private 2207 | * @param {Request|string} request A request to run this strategy for. 2208 | * @param {workbox-strategies.StrategyHandler} handler The event that 2209 | * triggered the request. 2210 | * @return {Promise} 2211 | */ 2212 | async _handle(request, handler) { 2213 | const logs = []; 2214 | { 2215 | finalAssertExports.isInstance(request, Request, { 2216 | moduleName: 'workbox-strategies', 2217 | className: this.constructor.name, 2218 | funcName: 'handle', 2219 | paramName: 'makeRequest' 2220 | }); 2221 | } 2222 | const promises = []; 2223 | let timeoutId; 2224 | if (this._networkTimeoutSeconds) { 2225 | const { 2226 | id, 2227 | promise 2228 | } = this._getTimeoutPromise({ 2229 | request, 2230 | logs, 2231 | handler 2232 | }); 2233 | timeoutId = id; 2234 | promises.push(promise); 2235 | } 2236 | const networkPromise = this._getNetworkPromise({ 2237 | timeoutId, 2238 | request, 2239 | logs, 2240 | handler 2241 | }); 2242 | promises.push(networkPromise); 2243 | const response = await handler.waitUntil((async () => { 2244 | // Promise.race() will resolve as soon as the first promise resolves. 2245 | return (await handler.waitUntil(Promise.race(promises))) || ( 2246 | // If Promise.race() resolved with null, it might be due to a network 2247 | // timeout + a cache miss. If that were to happen, we'd rather wait until 2248 | // the networkPromise resolves instead of returning null. 2249 | // Note that it's fine to await an already-resolved promise, so we don't 2250 | // have to check to see if it's still "in flight". 2251 | await networkPromise); 2252 | })()); 2253 | { 2254 | logger.groupCollapsed(messages.strategyStart(this.constructor.name, request)); 2255 | for (const log of logs) { 2256 | logger.log(log); 2257 | } 2258 | messages.printFinalResponse(response); 2259 | logger.groupEnd(); 2260 | } 2261 | if (!response) { 2262 | throw new WorkboxError('no-response', { 2263 | url: request.url 2264 | }); 2265 | } 2266 | return response; 2267 | } 2268 | /** 2269 | * @param {Object} options 2270 | * @param {Request} options.request 2271 | * @param {Array} options.logs A reference to the logs array 2272 | * @param {Event} options.event 2273 | * @return {Promise} 2274 | * 2275 | * @private 2276 | */ 2277 | _getTimeoutPromise({ 2278 | request, 2279 | logs, 2280 | handler 2281 | }) { 2282 | let timeoutId; 2283 | const timeoutPromise = new Promise(resolve => { 2284 | const onNetworkTimeout = async () => { 2285 | { 2286 | logs.push(`Timing out the network response at ` + `${this._networkTimeoutSeconds} seconds.`); 2287 | } 2288 | resolve(await handler.cacheMatch(request)); 2289 | }; 2290 | timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000); 2291 | }); 2292 | return { 2293 | promise: timeoutPromise, 2294 | id: timeoutId 2295 | }; 2296 | } 2297 | /** 2298 | * @param {Object} options 2299 | * @param {number|undefined} options.timeoutId 2300 | * @param {Request} options.request 2301 | * @param {Array} options.logs A reference to the logs Array. 2302 | * @param {Event} options.event 2303 | * @return {Promise} 2304 | * 2305 | * @private 2306 | */ 2307 | async _getNetworkPromise({ 2308 | timeoutId, 2309 | request, 2310 | logs, 2311 | handler 2312 | }) { 2313 | let error; 2314 | let response; 2315 | try { 2316 | response = await handler.fetchAndCachePut(request); 2317 | } catch (fetchError) { 2318 | if (fetchError instanceof Error) { 2319 | error = fetchError; 2320 | } 2321 | } 2322 | if (timeoutId) { 2323 | clearTimeout(timeoutId); 2324 | } 2325 | { 2326 | if (response) { 2327 | logs.push(`Got response from network.`); 2328 | } else { 2329 | logs.push(`Unable to get a response from the network. Will respond ` + `with a cached response.`); 2330 | } 2331 | } 2332 | if (error || !response) { 2333 | response = await handler.cacheMatch(request); 2334 | { 2335 | if (response) { 2336 | logs.push(`Found a cached response in the '${this.cacheName}'` + ` cache.`); 2337 | } else { 2338 | logs.push(`No response found in the '${this.cacheName}' cache.`); 2339 | } 2340 | } 2341 | } 2342 | return response; 2343 | } 2344 | } 2345 | 2346 | /* 2347 | Copyright 2018 Google LLC 2348 | 2349 | Use of this source code is governed by an MIT-style 2350 | license that can be found in the LICENSE file or at 2351 | https://opensource.org/licenses/MIT. 2352 | */ 2353 | /** 2354 | * An implementation of a 2355 | * [network-only](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network-only) 2356 | * request strategy. 2357 | * 2358 | * This class is useful if you want to take advantage of any 2359 | * [Workbox plugins](https://developer.chrome.com/docs/workbox/using-plugins/). 2360 | * 2361 | * If the network request fails, this will throw a `WorkboxError` exception. 2362 | * 2363 | * @extends workbox-strategies.Strategy 2364 | * @memberof workbox-strategies 2365 | */ 2366 | class NetworkOnly extends Strategy { 2367 | /** 2368 | * @param {Object} [options] 2369 | * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins} 2370 | * to use in conjunction with this caching strategy. 2371 | * @param {Object} [options.fetchOptions] Values passed along to the 2372 | * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters) 2373 | * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796) 2374 | * `fetch()` requests made by this strategy. 2375 | * @param {number} [options.networkTimeoutSeconds] If set, any network requests 2376 | * that fail to respond within the timeout will result in a network error. 2377 | */ 2378 | constructor(options = {}) { 2379 | super(options); 2380 | this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0; 2381 | } 2382 | /** 2383 | * @private 2384 | * @param {Request|string} request A request to run this strategy for. 2385 | * @param {workbox-strategies.StrategyHandler} handler The event that 2386 | * triggered the request. 2387 | * @return {Promise} 2388 | */ 2389 | async _handle(request, handler) { 2390 | { 2391 | finalAssertExports.isInstance(request, Request, { 2392 | moduleName: 'workbox-strategies', 2393 | className: this.constructor.name, 2394 | funcName: '_handle', 2395 | paramName: 'request' 2396 | }); 2397 | } 2398 | let error = undefined; 2399 | let response; 2400 | try { 2401 | const promises = [handler.fetch(request)]; 2402 | if (this._networkTimeoutSeconds) { 2403 | const timeoutPromise = timeout(this._networkTimeoutSeconds * 1000); 2404 | promises.push(timeoutPromise); 2405 | } 2406 | response = await Promise.race(promises); 2407 | if (!response) { 2408 | throw new Error(`Timed out the network response after ` + `${this._networkTimeoutSeconds} seconds.`); 2409 | } 2410 | } catch (err) { 2411 | if (err instanceof Error) { 2412 | error = err; 2413 | } 2414 | } 2415 | { 2416 | logger.groupCollapsed(messages.strategyStart(this.constructor.name, request)); 2417 | if (response) { 2418 | logger.log(`Got response from network.`); 2419 | } else { 2420 | logger.log(`Unable to get a response from the network.`); 2421 | } 2422 | messages.printFinalResponse(response); 2423 | logger.groupEnd(); 2424 | } 2425 | if (!response) { 2426 | throw new WorkboxError('no-response', { 2427 | url: request.url, 2428 | error 2429 | }); 2430 | } 2431 | return response; 2432 | } 2433 | } 2434 | 2435 | /* 2436 | Copyright 2019 Google LLC 2437 | 2438 | Use of this source code is governed by an MIT-style 2439 | license that can be found in the LICENSE file or at 2440 | https://opensource.org/licenses/MIT. 2441 | */ 2442 | /** 2443 | * Claim any currently available clients once the service worker 2444 | * becomes active. This is normally used in conjunction with `skipWaiting()`. 2445 | * 2446 | * @memberof workbox-core 2447 | */ 2448 | function clientsClaim() { 2449 | self.addEventListener('activate', () => self.clients.claim()); 2450 | } 2451 | 2452 | exports.NetworkFirst = NetworkFirst; 2453 | exports.NetworkOnly = NetworkOnly; 2454 | exports.clientsClaim = clientsClaim; 2455 | exports.registerRoute = registerRoute; 2456 | 2457 | })); 2458 | //# sourceMappingURL=workbox-327c579b.js.map 2459 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PiyushKalyanpy/UPI-QR-Code-Generator/5d797b681bb0a281008be57a05cc6196e463d0bd/styles/Home.module.css -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Urbanist:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | 7 | input:-webkit-autofill { 8 | -webkit-background-clip: text; 9 | } 10 | 11 | *{ 12 | font-family: 'Inter', sans-serif; 13 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./app/**/*.{js,ts,jsx,tsx}", 5 | "./pages/**/*.{js,ts,jsx,tsx}", 6 | "./Components/**/*.{js,ts,jsx,tsx}", 7 | 8 | // Or if using `src` directory: 9 | "./src/**/*.{js,ts,jsx,tsx}", 10 | ], 11 | important: true, 12 | 13 | theme: { 14 | extend: { 15 | fontFamily: { 16 | inter: ["Inter", "sans-serif"], 17 | urbanist: ["Urbanist", "sans-serif"], 18 | }, 19 | }, 20 | }, 21 | plugins: [], 22 | }; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "paths": { 18 | "@/*": ["./*"] 19 | } 20 | }, 21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 22 | "exclude": ["node_modules"] 23 | } 24 | --------------------------------------------------------------------------------