├── .all-contributorsrc
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example
├── .npmignore
├── index.html
├── index.tsx
├── package.json
├── public
│ └── my-sw.js
├── tsconfig.json
└── yarn.lock
├── package.json
├── src
└── index.tsx
├── test
└── useServiceWorker.test.tsx
├── tsconfig.json
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "ashinga48",
10 | "name": "Ravi Teja",
11 | "avatar_url": "https://avatars1.githubusercontent.com/u/8008140?v=4",
12 | "profile": "https://github.com/ashinga48",
13 | "contributions": [
14 | "code"
15 | ]
16 | }
17 | ],
18 | "contributorsPerLine": 7,
19 | "projectName": "react-hook-use-service-worker",
20 | "projectOwner": "JCofman",
21 | "repoType": "github",
22 | "repoHost": "https://github.com",
23 | "skipCi": true
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 | coverage
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.2.1
4 |
5 | - Update dependencies for `register-service-worker`
6 |
7 | ## 0.2.0
8 |
9 | - Update dependencies
10 | - Run `useEffect` only once on mount
11 |
12 | ## 0.1.3
13 |
14 | - Update readme with a `my-sw.js` example
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 JCofman
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 | # React use service worker hook & Utilities ·
2 |
3 | [](#contributors-)
4 |
5 |
6 | > Get everything out of your PWA
7 |
8 | This is a [React Hook](https://reactjs.org/docs/hooks-overview.html) which can register a service worker.
9 |
10 |
11 |
12 |
13 | **Table of Contents** _generated with [DocToc](https://github.com/thlorenz/doctoc)_
14 |
15 | - [React use service worker hook & Utilities ·](#react-use-service-worker-hook-amp-utilities-middot)
16 | - [Affiliate](#affiliate)
17 | - [🎯 Objective](#-objective)
18 | - [🚀 Installation](#-installation)
19 | - [Usage](#usage)
20 | - [Contribute and Commands](#contribute-and-commands)
21 | - [Jest](#jest)
22 | - [TypeScript](#typescript)
23 | - [Continuous Integration](#continuous-integration)
24 | - [Contributors](#contributors)
25 |
26 |
27 |
28 | ## Affiliate
29 |
30 | If you like to support my OSS work you could "buy me a coffee" or want to take a look on tools I recommend you could checkout.
31 |
32 |
33 |
34 | ## 🎯 Objective
35 |
36 | Make it easier to target low-end devices while progressively adding high-end-only features on top. Using these hooks and utilities can help you give users a great experience best suited to their device and network constraints.
37 |
38 | ## 🚀 Installation
39 |
40 | `npm i react-hook-use-service-worker --save` or `yarn add react-hook-use-service-worker`
41 |
42 | ## Usage
43 |
44 | You can import the hook and use the hook like that
45 |
46 | ```js
47 | import * as React from 'react';
48 | import * as ReactDOM from 'react-dom';
49 | import {
50 | useServiceWorker,
51 | ProvideServiceWorker,
52 | } from 'react-hook-use-service-worker';
53 |
54 | const MyComponent = () => {
55 | const mySW = useServiceWorker();
56 |
57 | return current service worker status: {mySW.serviceWorkerStatus}
;
58 | };
59 |
60 | const App = () => {
61 | return (
62 |
67 | );
68 | };
69 | export default App;
70 | ```
71 |
72 | and your service worker file e.g. `/my-sw.js`. Make sure you host your service worker in the root folder of your project.
73 |
74 | ```js
75 | // my-sw.js
76 | const version = 'my-sw 1';
77 |
78 | const main = async () => {
79 | console.log(`Service Worker ${version} is starting ...`);
80 | };
81 |
82 | const onInstall = async evt => {
83 | console.log(`Service Worker ${version} installed.`);
84 | self.skipWaiting();
85 | };
86 | const onActivate = async evt => {
87 | evt.waitUntil(handleActivation());
88 | };
89 |
90 | const handleActivation = async () => {
91 | await clients.claim();
92 |
93 | console.log(`Service Worker ${version} activated.`);
94 | };
95 |
96 | main().catch(console.error);
97 |
98 | self.addEventListener('install', onInstall);
99 | self.addEventListener('activate', onActivate);
100 | ```
101 |
102 | ## Contribute and Commands
103 |
104 | The recommended workflow is to run TSDX in one terminal:
105 |
106 | ```bash
107 | npm start # or yarn start
108 | ```
109 |
110 | This builds to `/dist` and runs the project in watch mode so any edits you save inside `src` causes a rebuild to `/dist`.
111 |
112 | Then run the example inside another:
113 |
114 | ```bash
115 | cd example
116 | npm i # or yarn to install dependencies
117 | npm start # or yarn start
118 | ```
119 |
120 | The default example imports and live reloads whatever is in `/dist`, so if you are seeing an out of date component, make sure TSDX is running in watch mode like we recommend above. **No symlinking required**, [we use Parcel's aliasing](https://github.com/palmerhq/tsdx/pull/88/files).
121 |
122 | To do a one-off build, use `npm run build` or `yarn build`.
123 |
124 | To run tests, use `npm test` or `yarn test`.
125 |
126 | ### Jest
127 |
128 | Jest tests are set up to run with `npm test` or `yarn test`. This runs the test watcher (Jest) in an interactive mode. By default, runs tests related to files changed since the last commit.
129 |
130 | ### TypeScript
131 |
132 | `tsconfig.json` is set up to interpret `dom` and `esnext` types, as well as `react` for `jsx`. Adjust according to your needs.
133 |
134 | ## Continuous Integration
135 |
136 | ## Contributors
137 |
138 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
139 |
140 |
141 |
142 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
159 |
--------------------------------------------------------------------------------
/example/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | dist
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Playground
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/index.tsx:
--------------------------------------------------------------------------------
1 | import 'react-app-polyfill/ie11';
2 | import * as React from 'react';
3 | import * as ReactDOM from 'react-dom';
4 | import { useServiceWorker, ProvideServiceWorker } from '../.';
5 |
6 | const MyComponent = () => {
7 | const mySW = useServiceWorker();
8 |
9 | return current service worker status: {mySW.serviceWorkerStatus}
;
10 | };
11 |
12 | const App = () => {
13 | return (
14 |
19 | );
20 | };
21 |
22 | ReactDOM.render(, document.getElementById('root'));
23 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "parcel index.html",
8 | "build": "parcel build index.html "
9 | },
10 | "dependencies": {
11 | "react-app-polyfill": "^1.0.6"
12 | },
13 | "alias": {
14 | "react": "../node_modules/react",
15 | "react-dom": "../node_modules/react-dom/profiling",
16 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^16.9.25",
20 | "@types/react-dom": "^16.9.5",
21 | "parcel": "^1.12.4",
22 | "parcel-plugin-static-files-copy": "^2.3.1",
23 | "typescript": "^3.8.3"
24 | },
25 | "staticFiles": {
26 | "staticPath": "public",
27 | "watcherGlob": "**"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/example/public/my-sw.js:
--------------------------------------------------------------------------------
1 | const version = 'my-sw 1';
2 |
3 | const main = async () => {
4 | console.log(`Service Worker ${version} is starting ...`);
5 | };
6 |
7 | const onInstall = async evt => {
8 | console.log(`Service Worker ${version} installed.`);
9 | self.skipWaiting();
10 | };
11 | const onActivate = async evt => {
12 | evt.waitUntil(handleActivation());
13 | };
14 |
15 | const handleActivation = async () => {
16 | await clients.claim();
17 |
18 | console.log(`Service Worker ${version} activated.`);
19 | };
20 |
21 | main().catch(console.error);
22 |
23 | self.addEventListener('install', onInstall);
24 | self.addEventListener('activate', onActivate);
25 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": false,
4 | "target": "es5",
5 | "module": "commonjs",
6 | "jsx": "react",
7 | "moduleResolution": "node",
8 | "noImplicitAny": false,
9 | "noUnusedLocals": false,
10 | "noUnusedParameters": false,
11 | "removeComments": true,
12 | "strictNullChecks": true,
13 | "preserveConstEnums": true,
14 | "sourceMap": true,
15 | "lib": ["es2015", "es2016", "dom"],
16 | "baseUrl": ".",
17 | "types": ["node"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.1",
3 | "license": "MIT",
4 | "main": "dist/index.js",
5 | "typings": "dist/index.d.ts",
6 | "files": [
7 | "dist"
8 | ],
9 | "scripts": {
10 | "start": "tsdx watch",
11 | "build": "tsdx build",
12 | "test": "tsdx test --passWithNoTests",
13 | "lint": "tsdx lint",
14 | "prepare": "tsdx build",
15 | "doctoc": "doctoc README.md"
16 | },
17 | "peerDependencies": {
18 | "react": ">=16"
19 | },
20 | "husky": {
21 | "hooks": {
22 | "pre-commit": "tsdx lint && doctoc README.md"
23 | }
24 | },
25 | "prettier": {
26 | "printWidth": 80,
27 | "semi": true,
28 | "singleQuote": true,
29 | "trailingComma": "es5"
30 | },
31 | "name": "react-hook-use-service-worker",
32 | "author": "jcofman",
33 | "module": "dist/useserviceworker.esm.js",
34 | "devDependencies": {
35 | "@types/jest": "^25.1.4",
36 | "@types/react": "^16.9.25",
37 | "@types/react-dom": "^16.9.5",
38 | "doctoc": "^1.4.0",
39 | "husky": "^4.2.3",
40 | "react": "^16.13.1",
41 | "react-dom": "^16.13.1",
42 | "tsdx": "^0.13.0",
43 | "tslib": "^1.11.1",
44 | "typescript": "^3.8.3"
45 | },
46 | "dependencies": {
47 | "register-service-worker": "^1.7.1"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { register, unregister } from 'register-service-worker';
4 |
5 | export const SERVICE_WORKER_READY = 'SERVICE_WORKER_READY';
6 | export const SERVICE_WORKER_REGISTERED = 'SERVICE_WORKER_REGISTERED';
7 | export const SERVICE_WORKER_CACHED = 'SERVICE_WORKER_CACHED';
8 | export const SERVICE_WORKER_UPDATE_FOUND = 'SERVICE_WORKER_UPDATE_FOUND';
9 | export const SERVICE_WORKER_OFFLINE = 'SERVICE_WORKER_OFFLINE';
10 | export const SERVICE_WORKER_UPDATE_READY = 'SERVICE_WORKER_UPDATE_READY';
11 | export const SERVICE_WORKER_ERROR = 'SERVICE_WORKER_ERROR';
12 |
13 | interface ServiceWorkerReady {
14 | type: typeof SERVICE_WORKER_READY;
15 | payload: ServiceWorker;
16 | }
17 | interface ServiceWorkerRegistered {
18 | type: typeof SERVICE_WORKER_REGISTERED;
19 | payload: ServiceWorker;
20 | }
21 | interface ServiceWorkerCached {
22 | type: typeof SERVICE_WORKER_CACHED;
23 | payload: ServiceWorker;
24 | }
25 | interface ServiceWorkerUpdateFound {
26 | type: typeof SERVICE_WORKER_UPDATE_FOUND;
27 | payload: ServiceWorker;
28 | }
29 | interface ServiceWorkerOffline {
30 | type: typeof SERVICE_WORKER_OFFLINE;
31 | payload: ServiceWorker;
32 | }
33 | interface ServiceWorkerUpdateReady {
34 | type: typeof SERVICE_WORKER_UPDATE_READY;
35 | payload: ServiceWorker;
36 | }
37 | interface ServiceWorkerError {
38 | type: typeof SERVICE_WORKER_ERROR;
39 | payload: ServiceWorker;
40 | }
41 |
42 | export type ServiceWorkerActionTypes =
43 | | ServiceWorkerReady
44 | | ServiceWorkerRegistered
45 | | ServiceWorkerCached
46 | | ServiceWorkerUpdateFound
47 | | ServiceWorkerOffline
48 | | ServiceWorkerUpdateReady
49 | | ServiceWorkerError;
50 |
51 | type ServiceWorkerStatus =
52 | | 'offline'
53 | | 'registered'
54 | | 'register'
55 | | 'ready'
56 | | 'cached'
57 | | 'updates'
58 | | 'updated'
59 | | 'error';
60 |
61 | export interface ServiceWorker {
62 | serviceWorkerStatus: ServiceWorkerStatus;
63 | registration?: null | ServiceWorkerRegistration;
64 | error?: Error;
65 | }
66 |
67 | export interface ServiceWorkerState {
68 | serviceWorkerStatus: ServiceWorkerStatus;
69 | registration?: null | ServiceWorkerRegistration;
70 | error?: Error;
71 | }
72 |
73 | const useServiceWorkerReducer = (
74 | state: ServiceWorkerState,
75 | action: ServiceWorkerActionTypes
76 | ): ServiceWorkerState => {
77 | switch (action.type) {
78 | case 'SERVICE_WORKER_READY':
79 | console.log('Service worker is ready.');
80 | return {
81 | ...state,
82 | serviceWorkerStatus: action.payload.serviceWorkerStatus,
83 | registration: action.payload.registration,
84 | };
85 | case 'SERVICE_WORKER_REGISTERED':
86 | console.log('Service worker has been registered.');
87 | return {
88 | ...state,
89 | serviceWorkerStatus: action.payload.serviceWorkerStatus,
90 | registration: action.payload.registration,
91 | };
92 | case 'SERVICE_WORKER_CACHED':
93 | console.log('Content has been cached for offline use.');
94 | return {
95 | ...state,
96 | serviceWorkerStatus: action.payload.serviceWorkerStatus,
97 | registration: action.payload.registration,
98 | };
99 | case 'SERVICE_WORKER_UPDATE_FOUND':
100 | console.log('New content is downloading.');
101 | return {
102 | ...state,
103 | serviceWorkerStatus: action.payload.serviceWorkerStatus,
104 | registration: action.payload.registration,
105 | };
106 | case 'SERVICE_WORKER_UPDATE_READY':
107 | console.log('New content is available; please refresh.');
108 | return {
109 | ...state,
110 | serviceWorkerStatus: action.payload.serviceWorkerStatus,
111 | registration: action.payload.registration,
112 | };
113 | case 'SERVICE_WORKER_OFFLINE':
114 | console.log(
115 | 'No internet connection found. App is running in offline mode.'
116 | );
117 | return {
118 | ...state,
119 | serviceWorkerStatus: action.payload.serviceWorkerStatus,
120 | };
121 | case 'SERVICE_WORKER_ERROR':
122 | console.error(
123 | 'Error during service worker registration:',
124 | action.payload.error
125 | );
126 | return {
127 | ...state,
128 | serviceWorkerStatus: action.payload.serviceWorkerStatus,
129 | };
130 |
131 | default:
132 | return state;
133 | }
134 | };
135 |
136 | const initialState: ServiceWorkerState = {
137 | registration: null,
138 | serviceWorkerStatus: 'register',
139 | };
140 | const serviceWorkerContext = React.createContext(initialState);
141 |
142 | export function ProvideServiceWorker({
143 | children,
144 | fileName,
145 | registrationOptions
146 | }: {
147 | children: React.ReactNode;
148 | fileName: string;
149 | registrationOptions ?: object;
150 | }) {
151 | const serviceWorker = useProvideServiceWorker(fileName, registrationOptions);
152 | return (
153 |
154 | {children}
155 |
156 | );
157 | }
158 |
159 | export const useServiceWorker = () => {
160 | return React.useContext(serviceWorkerContext);
161 | };
162 |
163 | const useProvideServiceWorker = (file = 'sw.js', registrationOptions = {}) => {
164 | const [swState, dispatch] = React.useReducer(
165 | useServiceWorkerReducer,
166 | initialState
167 | );
168 | React.useEffect(() => {
169 | register(file, {
170 | registrationOptions,
171 | ready: registration => {
172 | dispatch({
173 | type: 'SERVICE_WORKER_READY',
174 | payload: { serviceWorkerStatus: 'ready', registration },
175 | });
176 | },
177 | registered: registration => {
178 | dispatch({
179 | type: 'SERVICE_WORKER_REGISTERED',
180 | payload: { serviceWorkerStatus: 'registered', registration },
181 | });
182 | },
183 | cached: registration => {
184 | dispatch({
185 | type: 'SERVICE_WORKER_REGISTERED',
186 | payload: { serviceWorkerStatus: 'cached', registration },
187 | });
188 | },
189 | updatefound: registration => {
190 | dispatch({
191 | type: 'SERVICE_WORKER_UPDATE_FOUND',
192 | payload: { serviceWorkerStatus: 'updates', registration },
193 | });
194 | },
195 | updated: registration =>
196 | dispatch({
197 | type: 'SERVICE_WORKER_UPDATE_READY',
198 | payload: { serviceWorkerStatus: 'updated', registration },
199 | }),
200 | offline: () => {
201 | dispatch({
202 | type: 'SERVICE_WORKER_OFFLINE',
203 | payload: { serviceWorkerStatus: 'offline' },
204 | });
205 | },
206 | error: error =>
207 | dispatch({
208 | type: 'SERVICE_WORKER_OFFLINE',
209 | payload: { serviceWorkerStatus: 'error', error },
210 | }),
211 | });
212 | return () => {
213 | unregister();
214 | };
215 | }, []);
216 |
217 | return swState;
218 | };
219 |
--------------------------------------------------------------------------------
/test/useServiceWorker.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | // import { ProvideServiceWorker } from '../src';
4 |
5 | describe('it', () => {
6 | it('renders without crashing', () => {
7 | const div = document.createElement('div');
8 | ReactDOM.render(test
, div);
9 | ReactDOM.unmountComponentAtNode(div);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "types", "test"],
3 | "compilerOptions": {
4 | "target": "es5",
5 | "module": "esnext",
6 | "lib": ["dom", "esnext"],
7 | "importHelpers": true,
8 | "declaration": true,
9 | "sourceMap": true,
10 | "rootDir": "./src",
11 | "strict": true,
12 | "noImplicitAny": true,
13 | "strictNullChecks": true,
14 | "strictFunctionTypes": true,
15 | "strictPropertyInitialization": true,
16 | "noImplicitThis": true,
17 | "alwaysStrict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noImplicitReturns": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "moduleResolution": "node",
23 | "baseUrl": "./",
24 | "paths": {
25 | "*": ["src/*", "node_modules/*"]
26 | },
27 | "jsx": "react",
28 | "esModuleInterop": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------