├── .editorconfig
├── .env.development
├── .env.production
├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ └── codeql-analysis.yml
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── .prettierrc.json
├── .vscode
├── launch.json
└── settings.json
├── README.md
├── SECURITY.md
├── generators
├── plopfile.js
└── templates
│ ├── Component.tsx.hbs
│ └── styles.ts.hbs
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.tsx
├── assets
│ └── styles
│ │ └── global.ts
├── index.tsx
├── pages
│ └── Home
│ │ └── index.tsx
├── react-app-env.d.ts
├── reportWebVitals.ts
├── service-worker.ts
├── serviceWorkerRegistration.ts
└── setupTests.ts
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | end_of_line = lf
14 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | INLINE_RUNTIME_CHUNK=false
2 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | INLINE_RUNTIME_CHUNK=false
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*.js
2 | node_modules
3 | build
4 | /src/reportWebVitals.ts
5 | /src/service-worker.ts
6 | /src/serviceWorkerRegistration.ts
7 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": [
7 | "plugin:react/recommended",
8 | "airbnb",
9 | "plugin:@typescript-eslint/recommended",
10 | "plugin:react-hooks/recommended"
11 | ],
12 | "parser": "@typescript-eslint/parser",
13 | "parserOptions": {
14 | "ecmaFeatures": {
15 | "jsx": true
16 | },
17 | "ecmaVersion": 12,
18 | "sourceType": "module"
19 | },
20 | "plugins": ["react", "@typescript-eslint", "prettier", "react-hooks"],
21 | "rules": {
22 | "comma-dangle": [
23 | "error",
24 | {
25 | "arrays": "always-multiline",
26 | "objects": "always-multiline",
27 | "imports": "always-multiline",
28 | "exports": "always-multiline",
29 | "functions": "never"
30 | }
31 | ],
32 | "react/jsx-wrap-multilines": [
33 | "error",
34 | { "declaration": false, "assignment": false }
35 | ],
36 | "@typescript-eslint/explicit-module-boundary-types": "off",
37 | "@typescript-eslint/no-use-before-define": ["error"],
38 | "react-hooks/exhaustive-deps": "warn",
39 | "react/jsx-filename-extension": [1, { "extensions": [".tsx", ".ts"] }],
40 | "react/jsx-one-expression-per-line": "off",
41 | "no-use-before-define": "off",
42 | "import/preferer-default-export": "off",
43 | "import/extensions": [
44 | "error",
45 | "ignorePackages",
46 | { "ts": "never", "tsx": "never" }
47 | ]
48 | },
49 | "settings": {
50 | "import/resolver": {
51 | "typescript": {}
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '26 8 * * 0'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v2
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v1
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 | # If this step fails, then you should remove it and run the build manually (see below)
55 | - name: Autobuild
56 | uses: github/codeql-action/autobuild@v1
57 |
58 | # ℹ️ Command-line programs to run using the OS shell.
59 | # 📚 https://git.io/JvXDl
60 |
61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62 | # and modify them (or add more) to build your code if your project
63 | # uses a compiled language
64 |
65 | #- run: |
66 | # make bootstrap
67 | # make release
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v1
71 |
--------------------------------------------------------------------------------
/.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 | .dccache
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "es5",
4 | "arrowParens": "always"
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "pwa-chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "http://localhost:8080",
12 | "webRoot": "${workspaceFolder}"
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | // For ESLint
4 | "source.fixAll.eslint": true,
5 | // For TSLint
6 | "source.fixAll.tslint": true,
7 | // For Stylelint
8 | "source.fixAll.stylelint": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | This Chrome Extension was bootstrapped by [Julio Carneiro](https://github.com/juliocarneiro).
4 |
5 | 1) ```yarn```
6 | 2) ```yarn build```
7 | 3) Go to ```chrome://extensions``` in Chrome
8 | 4) Ensure that the Developer mode checkbox in the top right-hand corner is checked
9 | 5) Click Load unpacked extension to pop up a file-selection dialog & select directory ```build``` in your repository.
10 | 6) Ensure that the enabled box next to your chrome extension is checked so you can see it in action
11 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | ------- | ------------------ |
10 | | 5.1.x | :white_check_mark: |
11 | | 5.0.x | :x: |
12 | | 4.0.x | :white_check_mark: |
13 | | < 4.0 | :x: |
14 |
15 | ## Reporting a Vulnerability
16 |
17 | Use this section to tell people how to report a vulnerability.
18 |
19 | Tell them where to go, how often they can expect to get an update on a
20 | reported vulnerability, what to expect if the vulnerability is accepted or
21 | declined, etc.
22 |
--------------------------------------------------------------------------------
/generators/plopfile.js:
--------------------------------------------------------------------------------
1 | module.exports = (plop) => {
2 | plop.setGenerator('component', {
3 | description: 'Create a component',
4 | prompts: [
5 | {
6 | type: 'input',
7 | name: 'name',
8 | message: 'What is your component name?',
9 | },
10 | ],
11 | actions: [
12 | {
13 | type: 'add',
14 | path: '../src/components/{{pascalCase name}}/index.tsx',
15 | templateFile: 'templates/Component.tsx.hbs',
16 | },
17 | {
18 | type: 'add',
19 | path: '../src/components/{{pascalCase name}}/styles.ts',
20 | templateFile: 'templates/styles.ts.hbs',
21 | },
22 | ],
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/generators/templates/Component.tsx.hbs:
--------------------------------------------------------------------------------
1 | import * as S from './styles'
2 |
3 | const {{pascalCase name}} = () => (
4 |
5 | {{pascalCase name}}
6 |
7 | )
8 |
9 | export default {{pascalCase name}}
10 |
--------------------------------------------------------------------------------
/generators/templates/styles.ts.hbs:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const Wrapper = styled.main``
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-chrome-extension",
3 | "version": "2.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "animate.css": "^4.1.1",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-router-dom": "^5.2.0",
13 | "react-scripts": "5.0.0",
14 | "react-use": "^17.2.4",
15 | "styled-components": "^5.3.1",
16 | "web-vitals": "^0.2.4"
17 | },
18 | "devDependencies": {
19 | "@types/jest": "^26.0.15",
20 | "@types/node": "^12.0.0",
21 | "@types/react": "^16.9.53",
22 | "@types/react-dom": "^16.9.8",
23 | "@types/react-router-dom": "^5.1.8",
24 | "@types/styled-components": "^5.1.13",
25 | "@typescript-eslint/eslint-plugin": "^4.29.3",
26 | "@typescript-eslint/parser": "^4.29.3",
27 | "eslint": "^7.32.0",
28 | "eslint-config-airbnb": "^18.2.1",
29 | "eslint-import-resolver-typescript": "^2.4.0",
30 | "eslint-plugin-import": "^2.22.1",
31 | "eslint-plugin-jsx-a11y": "^6.4.1",
32 | "eslint-plugin-prettier": "^3.4.1",
33 | "eslint-plugin-react": "^7.21.5",
34 | "eslint-plugin-react-hooks": "^4.2.0",
35 | "husky": "^7.0.2",
36 | "lint-staged": "^11.1.2",
37 | "plop": "^2.7.4",
38 | "prettier": "^2.3.2",
39 | "typescript": "4.1.6"
40 | },
41 | "scripts": {
42 | "start": "react-scripts start",
43 | "build": "react-scripts build",
44 | "test": "react-scripts test",
45 | "eject": "react-scripts eject",
46 | "generate": "yarn plop --plopfile generators/plopfile.js",
47 | "lint": "eslint src/ --ext .js,.jsx,.ts,.tsx",
48 | "lint:fix": "yarn lint --fix",
49 | "postinstall": "husky install",
50 | "prepare": "husky install"
51 | },
52 | "lint-staged": {
53 | "src/**/*": [
54 | "yarn lint:fix"
55 | ],
56 | "*.js": "yarn lint:fix"
57 | },
58 | "browserslist": {
59 | "production": [
60 | ">0.2%",
61 | "not dead",
62 | "not op_mini all"
63 | ],
64 | "development": [
65 | "last 1 chrome version",
66 | "last 1 firefox version",
67 | "last 1 safari version"
68 | ]
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/juliocarneiro/react-chrome-extension/b676b786202e3a6d4c93eaebee8fad9137007d72/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | React App
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/juliocarneiro/react-chrome-extension/b676b786202e3a6d4c93eaebee8fad9137007d72/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/juliocarneiro/react-chrome-extension/b676b786202e3a6d4c93eaebee8fad9137007d72/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "manifest_version": 2,
5 | "browser_action": {
6 | "default_popup": "index.html",
7 | "default_title": "React App"
8 | },
9 | "permissions": [
10 | "activeTab"
11 | ],
12 | "icons": {
13 | "16": "logo192.png",
14 | "48": "logo192.png",
15 | "128": "logo512.png"
16 | },
17 | "version": "2.0"
18 | }
19 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Home from 'pages/Home';
3 |
4 | function App() {
5 | return ;
6 | }
7 |
8 | export default App;
9 |
--------------------------------------------------------------------------------
/src/assets/styles/global.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const GlobalStyles = createGlobalStyle`
4 | * {
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: border-box;
8 | }
9 | html {
10 | font-size: 62.5%;
11 | }
12 | html, body, #__next {
13 | height: 100%;
14 | }
15 | `;
16 |
17 | export default GlobalStyles;
18 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | import * as serviceWorkerRegistration from './serviceWorkerRegistration';
6 | import reportWebVitals from './reportWebVitals';
7 |
8 | import 'animate.css';
9 |
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById('root')
15 | );
16 |
17 | serviceWorkerRegistration.register();
18 | reportWebVitals();
19 |
--------------------------------------------------------------------------------
/src/pages/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const Wrapper = styled.div`
5 | width: 400px;
6 | height: 400px;
7 | display: flex;
8 | justify-content: center;
9 | flex-direction: column;
10 | align-items: center;
11 | p {
12 | font-size: 2rem;
13 | margin: 0;
14 | }
15 | span {
16 | font-size: 1rem;
17 | }
18 | `;
19 |
20 | function Home() {
21 | return (
22 |
23 | Chrome Extension
24 | With React JS & Typescript
25 |
26 | );
27 | }
28 |
29 | export default Home;
30 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/src/service-worker.ts:
--------------------------------------------------------------------------------
1 | ///
2 | /* eslint-disable no-restricted-globals */
3 |
4 | // This service worker can be customized!
5 | // See https://developers.google.com/web/tools/workbox/modules
6 | // for the list of available Workbox modules, or add any other
7 | // code you'd like.
8 | // You can also remove this file if you'd prefer not to use a
9 | // service worker, and the Workbox build step will be skipped.
10 |
11 | import { clientsClaim } from 'workbox-core';
12 | import { ExpirationPlugin } from 'workbox-expiration';
13 | import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
14 | import { registerRoute } from 'workbox-routing';
15 | import { StaleWhileRevalidate } from 'workbox-strategies';
16 |
17 | declare const self: ServiceWorkerGlobalScope;
18 |
19 | clientsClaim();
20 |
21 | // Precache all of the assets generated by your build process.
22 | // Their URLs are injected into the manifest variable below.
23 | // This variable must be present somewhere in your service worker file,
24 | // even if you decide not to use precaching. See https://cra.link/PWA
25 | precacheAndRoute(self.__WB_MANIFEST);
26 |
27 | // Set up App Shell-style routing, so that all navigation requests
28 | // are fulfilled with your index.html shell. Learn more at
29 | // https://developers.google.com/web/fundamentals/architecture/app-shell
30 | const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
31 | registerRoute(
32 | // Return false to exempt requests from being fulfilled by index.html.
33 | ({ request, url }: { request: Request; url: URL }) => {
34 | // If this isn't a navigation, skip.
35 | if (request.mode !== 'navigate') {
36 | return false;
37 | }
38 |
39 | // If this is a URL that starts with /_, skip.
40 | if (url.pathname.startsWith('/_')) {
41 | return false;
42 | }
43 |
44 | // If this looks like a URL for a resource, because it contains
45 | // a file extension, skip.
46 | if (url.pathname.match(fileExtensionRegexp)) {
47 | return false;
48 | }
49 |
50 | // Return true to signal that we want to use the handler.
51 | return true;
52 | },
53 | createHandlerBoundToURL(`${process.env.PUBLIC_URL}/index.html`)
54 | );
55 |
56 | // An example runtime caching route for requests that aren't handled by the
57 | // precache, in this case same-origin .png requests like those from in public/
58 | registerRoute(
59 | // Add in any other file extensions or routing criteria as needed.
60 | ({ url }) =>
61 | url.origin === self.location.origin && url.pathname.endsWith('.png'),
62 | // Customize this strategy as needed, e.g., by changing to CacheFirst.
63 | new StaleWhileRevalidate({
64 | cacheName: 'images',
65 | plugins: [
66 | // Ensure that once this runtime cache reaches a maximum size the
67 | // least-recently used images are removed.
68 | new ExpirationPlugin({ maxEntries: 50 }),
69 | ],
70 | })
71 | );
72 |
73 | // This allows the web app to trigger skipWaiting via
74 | // registration.waiting.postMessage({type: 'SKIP_WAITING'})
75 | self.addEventListener('message', (event) => {
76 | if (event.data && event.data.type === 'SKIP_WAITING') {
77 | self.skipWaiting();
78 | }
79 | });
80 |
81 | // Any other custom service worker logic can go here.
82 |
--------------------------------------------------------------------------------
/src/serviceWorkerRegistration.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://cra.link/PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
32 | if (publicUrl.origin !== window.location.origin) {
33 | // Our service worker won't work if PUBLIC_URL is on a different origin
34 | // from what our page is served on. This might happen if a CDN is used to
35 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
36 | return;
37 | }
38 |
39 | window.addEventListener('load', () => {
40 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
41 |
42 | if (isLocalhost) {
43 | // This is running on localhost. Let's check if a service worker still exists or not.
44 | checkValidServiceWorker(swUrl, config);
45 |
46 | // Add some additional logging to localhost, pointing developers to the
47 | // service worker/PWA documentation.
48 | navigator.serviceWorker.ready.then(() => {
49 | console.log(
50 | 'This web app is being served cache-first by a service ' +
51 | 'worker. To learn more, visit https://cra.link/PWA'
52 | );
53 | });
54 | } else {
55 | // Is not localhost. Just register service worker
56 | registerValidSW(swUrl, config);
57 | }
58 | });
59 | }
60 | }
61 |
62 | function registerValidSW(swUrl: string, config?: Config) {
63 | navigator.serviceWorker
64 | .register(swUrl)
65 | .then((registration) => {
66 | registration.onupdatefound = () => {
67 | const installingWorker = registration.installing;
68 | if (installingWorker == null) {
69 | return;
70 | }
71 | installingWorker.onstatechange = () => {
72 | if (installingWorker.state === 'installed') {
73 | if (navigator.serviceWorker.controller) {
74 | // At this point, the updated precached content has been fetched,
75 | // but the previous service worker will still serve the older
76 | // content until all client tabs are closed.
77 | console.log(
78 | 'New content is available and will be used when all ' +
79 | 'tabs for this page are closed. See https://cra.link/PWA.'
80 | );
81 |
82 | // Execute callback
83 | if (config && config.onUpdate) {
84 | config.onUpdate(registration);
85 | }
86 | } else {
87 | // At this point, everything has been precached.
88 | // It's the perfect time to display a
89 | // "Content is cached for offline use." message.
90 | console.log('Content is cached for offline use.');
91 |
92 | // Execute callback
93 | if (config && config.onSuccess) {
94 | config.onSuccess(registration);
95 | }
96 | }
97 | }
98 | };
99 | };
100 | })
101 | .catch((error) => {
102 | console.error('Error during service worker registration:', error);
103 | });
104 | }
105 |
106 | function checkValidServiceWorker(swUrl: string, config?: Config) {
107 | // Check if the service worker can be found. If it can't reload the page.
108 | fetch(swUrl, {
109 | headers: { 'Service-Worker': 'script' },
110 | })
111 | .then((response) => {
112 | // Ensure service worker exists, and that we really are getting a JS file.
113 | const contentType = response.headers.get('content-type');
114 | if (
115 | response.status === 404 ||
116 | (contentType != null && contentType.indexOf('javascript') === -1)
117 | ) {
118 | // No service worker found. Probably a different app. Reload the page.
119 | navigator.serviceWorker.ready.then((registration) => {
120 | registration.unregister().then(() => {
121 | window.location.reload();
122 | });
123 | });
124 | } else {
125 | // Service worker found. Proceed as normal.
126 | registerValidSW(swUrl, config);
127 | }
128 | })
129 | .catch(() => {
130 | console.log(
131 | 'No internet connection found. App is running in offline mode.'
132 | );
133 | });
134 | }
135 |
136 | export function unregister() {
137 | if ('serviceWorker' in navigator) {
138 | navigator.serviceWorker.ready
139 | .then((registration) => {
140 | registration.unregister();
141 | })
142 | .catch((error) => {
143 | console.error(error.message);
144 | });
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src",
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "module": "esnext",
18 | "moduleResolution": "node",
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "noEmit": true,
22 | "jsx": "react-jsx"
23 | },
24 | "include": [
25 | "src"
26 | , "reportWebVitals.ts", "service-worker.ts", "serviceWorkerRegistration.ts" ]
27 | }
28 |
--------------------------------------------------------------------------------