├── .gitignore
├── LICENCE
├── README.md
├── assets
└── header.png
├── package-lock.json
├── package.json
├── src
├── global.d.ts
├── hooks
│ ├── usePwa.ts
│ └── usePwaAppSize.ts
├── index.ts
└── utils.ts
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib/
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | Copyright 2021 .mind
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | @dotmind/react-use-pwa
6 |
7 |
8 | Prompt to install Progressive Web App and more with React hooks. Builded by .mind.io
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ## Menu
26 |
27 | - [Menu](#menu)
28 | - [✋ Disclaimer](#-disclaimer)
29 | - [💻 Installation](#-installation)
30 | - [👷♂️ How it's work](#️-how-its-work)
31 | - [usePwa usage](#usepwa-usage)
32 | - [usePwaAppSize usage](#usepwaappsize-usage)
33 | - [⚡️ Contributing](#️-contributing)
34 | - [🔐 License](#-license)
35 |
36 |
37 | ## ✋ Disclaimer
38 |
39 | This package help to prompt to install and manage Progressive Web App (PWA) installed with React hooks.
40 |
41 | Please check if your app have the required criteria before use : [web.dev/install-criteria](https://web.dev/install-criteria/#criteria)
42 |
43 | Want to make a good PWA ? [Read this](https://web.dev/pwa-checklist/)
44 |
45 | SSR support ✅
46 |
47 | ## 💻 Installation
48 |
49 | ```bash
50 | yarn add @dotmind/react-use-pwa
51 | ```
52 |
53 | or
54 |
55 | ```bash
56 | npm i @dotmind/react-use-pwa --save
57 | ```
58 |
59 | ## 👷♂️ How it's work
60 |
61 | ### usePwa usage
62 |
63 | ```javascript
64 | import { useEffect, useCallback } from 'react';
65 | import { usePwa } from '@dotmind/react-use-pwa';
66 |
67 | const App = () => {
68 | const {
69 | installPrompt,
70 | isInstalled,
71 | isStandalone,
72 | isOffline,
73 | canInstall,
74 | } = usePwa();
75 |
76 | const handleInstallPrompt = useCallback(() => {
77 | if (canInstall) {
78 | installPrompt();
79 | }
80 | }, [canInstall, installPrompt]);
81 |
82 | if (isOffline) {
83 | return Please check your network 📶
;
84 | }
85 |
86 | if (!isInstalled || !isStandalone) {
87 | return (
88 |
89 | Hey install our app 👋
90 |
91 | );
92 | }
93 |
94 | return (
95 | Welcome to our new app 🚀
96 | );
97 | };
98 |
99 | ```
100 |
101 | | | description | type |
102 | |-|-|-|
103 | | installPrompt | Show install prompt | `() => Promise` |
104 | | isInstalled | Is app installed on device | `boolean` |
105 | | isStandalone | Is app run in standalone mode | `boolean` |
106 | | isOffline | Is app run in offline mode | `boolean` |
107 | | canInstall | Device can install app | `boolean` |
108 | | userChoice | Prompt user choice | `'accepted' \| 'dismissed' \| 'unknow'` |
109 |
110 | ### usePwaAppSize usage
111 |
112 | Choose app launching width and height (only in desktop standalone mode).
113 |
114 | ```javascript
115 | import { usePwaAppSize } from '@dotmind/react-use-pwa';
116 |
117 | const App = () => {
118 | usePwaAppSize(400, 560);
119 |
120 | return ;
121 | };
122 | ```
123 |
124 | | arguments | description | required | default value |
125 | |-|-|-|-|
126 | | width | App width | false | `800` |
127 | | height | App height | false | `800` |
128 | | options | App options | false | `{ fixed: false }` |
129 |
130 | **App options**
131 |
132 | | option | description | type
133 | |-|-|-|
134 | | fixed | User can't resize app width & height | `boolean`
135 |
136 | ## ⚡️ Contributing
137 |
138 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
139 |
140 | Please make sure to update tests as appropriate.
141 |
142 | ## 🔐 License
143 |
144 | [MIT](https://choosealicense.com/licenses/mit/)
145 |
--------------------------------------------------------------------------------
/assets/header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotmind/react-use-pwa/622ae02a9ccf52de24f5418a5c267d68d6f4e11b/assets/header.png
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@dotmind/react-use-pwa",
3 | "version": "1.0.4",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@babel/code-frame": {
8 | "version": "7.12.13",
9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
10 | "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
11 | "dev": true,
12 | "requires": {
13 | "@babel/highlight": "^7.12.13"
14 | }
15 | },
16 | "@babel/helper-validator-identifier": {
17 | "version": "7.12.11",
18 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
19 | "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
20 | "dev": true
21 | },
22 | "@babel/highlight": {
23 | "version": "7.13.10",
24 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
25 | "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
26 | "dev": true,
27 | "requires": {
28 | "@babel/helper-validator-identifier": "^7.12.11",
29 | "chalk": "^2.0.0",
30 | "js-tokens": "^4.0.0"
31 | }
32 | },
33 | "@tsconfig/recommended": {
34 | "version": "1.0.1",
35 | "resolved": "https://registry.npmjs.org/@tsconfig/recommended/-/recommended-1.0.1.tgz",
36 | "integrity": "sha512-2xN+iGTbPBEzGSnVp/Hd64vKJCJWxsi9gfs88x4PPMyEjHJoA3o5BY9r5OLPHIZU2pAQxkSAsJFqn6itClP8mQ==",
37 | "dev": true
38 | },
39 | "@types/prop-types": {
40 | "version": "15.7.3",
41 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
42 | "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
43 | "dev": true
44 | },
45 | "@types/react": {
46 | "version": "17.0.3",
47 | "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.3.tgz",
48 | "integrity": "sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==",
49 | "dev": true,
50 | "requires": {
51 | "@types/prop-types": "*",
52 | "@types/scheduler": "*",
53 | "csstype": "^3.0.2"
54 | }
55 | },
56 | "@types/scheduler": {
57 | "version": "0.16.1",
58 | "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz",
59 | "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==",
60 | "dev": true
61 | },
62 | "ansi-styles": {
63 | "version": "3.2.1",
64 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
65 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
66 | "dev": true,
67 | "requires": {
68 | "color-convert": "^1.9.0"
69 | }
70 | },
71 | "argparse": {
72 | "version": "1.0.10",
73 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
74 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
75 | "dev": true,
76 | "requires": {
77 | "sprintf-js": "~1.0.2"
78 | }
79 | },
80 | "balanced-match": {
81 | "version": "1.0.2",
82 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
83 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
84 | "dev": true
85 | },
86 | "brace-expansion": {
87 | "version": "1.1.11",
88 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
89 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
90 | "dev": true,
91 | "requires": {
92 | "balanced-match": "^1.0.0",
93 | "concat-map": "0.0.1"
94 | }
95 | },
96 | "builtin-modules": {
97 | "version": "1.1.1",
98 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
99 | "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
100 | "dev": true
101 | },
102 | "chalk": {
103 | "version": "2.4.2",
104 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
105 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
106 | "dev": true,
107 | "requires": {
108 | "ansi-styles": "^3.2.1",
109 | "escape-string-regexp": "^1.0.5",
110 | "supports-color": "^5.3.0"
111 | }
112 | },
113 | "color-convert": {
114 | "version": "1.9.3",
115 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
116 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
117 | "dev": true,
118 | "requires": {
119 | "color-name": "1.1.3"
120 | }
121 | },
122 | "color-name": {
123 | "version": "1.1.3",
124 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
125 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
126 | "dev": true
127 | },
128 | "commander": {
129 | "version": "2.20.3",
130 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
131 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
132 | "dev": true
133 | },
134 | "concat-map": {
135 | "version": "0.0.1",
136 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
137 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
138 | "dev": true
139 | },
140 | "csstype": {
141 | "version": "3.0.8",
142 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
143 | "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==",
144 | "dev": true
145 | },
146 | "diff": {
147 | "version": "4.0.2",
148 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
149 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
150 | "dev": true
151 | },
152 | "escape-string-regexp": {
153 | "version": "1.0.5",
154 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
155 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
156 | "dev": true
157 | },
158 | "esprima": {
159 | "version": "4.0.1",
160 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
161 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
162 | "dev": true
163 | },
164 | "fs.realpath": {
165 | "version": "1.0.0",
166 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
167 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
168 | "dev": true
169 | },
170 | "function-bind": {
171 | "version": "1.1.1",
172 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
173 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
174 | "dev": true
175 | },
176 | "glob": {
177 | "version": "7.1.6",
178 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
179 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
180 | "dev": true,
181 | "requires": {
182 | "fs.realpath": "^1.0.0",
183 | "inflight": "^1.0.4",
184 | "inherits": "2",
185 | "minimatch": "^3.0.4",
186 | "once": "^1.3.0",
187 | "path-is-absolute": "^1.0.0"
188 | }
189 | },
190 | "has": {
191 | "version": "1.0.3",
192 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
193 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
194 | "dev": true,
195 | "requires": {
196 | "function-bind": "^1.1.1"
197 | }
198 | },
199 | "has-flag": {
200 | "version": "3.0.0",
201 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
202 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
203 | "dev": true
204 | },
205 | "inflight": {
206 | "version": "1.0.6",
207 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
208 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
209 | "dev": true,
210 | "requires": {
211 | "once": "^1.3.0",
212 | "wrappy": "1"
213 | }
214 | },
215 | "inherits": {
216 | "version": "2.0.4",
217 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
218 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
219 | "dev": true
220 | },
221 | "is-core-module": {
222 | "version": "2.2.0",
223 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
224 | "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
225 | "dev": true,
226 | "requires": {
227 | "has": "^1.0.3"
228 | }
229 | },
230 | "js-tokens": {
231 | "version": "4.0.0",
232 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
233 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
234 | "dev": true
235 | },
236 | "js-yaml": {
237 | "version": "3.14.1",
238 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
239 | "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
240 | "dev": true,
241 | "requires": {
242 | "argparse": "^1.0.7",
243 | "esprima": "^4.0.0"
244 | }
245 | },
246 | "loose-envify": {
247 | "version": "1.4.0",
248 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
249 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
250 | "dev": true,
251 | "requires": {
252 | "js-tokens": "^3.0.0 || ^4.0.0"
253 | }
254 | },
255 | "minimatch": {
256 | "version": "3.0.4",
257 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
258 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
259 | "dev": true,
260 | "requires": {
261 | "brace-expansion": "^1.1.7"
262 | }
263 | },
264 | "minimist": {
265 | "version": "1.2.5",
266 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
267 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
268 | "dev": true
269 | },
270 | "mkdirp": {
271 | "version": "0.5.5",
272 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
273 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
274 | "dev": true,
275 | "requires": {
276 | "minimist": "^1.2.5"
277 | }
278 | },
279 | "object-assign": {
280 | "version": "4.1.1",
281 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
282 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
283 | "dev": true
284 | },
285 | "once": {
286 | "version": "1.4.0",
287 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
288 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
289 | "dev": true,
290 | "requires": {
291 | "wrappy": "1"
292 | }
293 | },
294 | "path-is-absolute": {
295 | "version": "1.0.1",
296 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
297 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
298 | "dev": true
299 | },
300 | "path-parse": {
301 | "version": "1.0.6",
302 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
303 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
304 | "dev": true
305 | },
306 | "react": {
307 | "version": "17.0.2",
308 | "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
309 | "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
310 | "dev": true,
311 | "requires": {
312 | "loose-envify": "^1.1.0",
313 | "object-assign": "^4.1.1"
314 | }
315 | },
316 | "resolve": {
317 | "version": "1.20.0",
318 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
319 | "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
320 | "dev": true,
321 | "requires": {
322 | "is-core-module": "^2.2.0",
323 | "path-parse": "^1.0.6"
324 | }
325 | },
326 | "semver": {
327 | "version": "5.7.1",
328 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
329 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
330 | "dev": true
331 | },
332 | "sprintf-js": {
333 | "version": "1.0.3",
334 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
335 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
336 | "dev": true
337 | },
338 | "supports-color": {
339 | "version": "5.5.0",
340 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
341 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
342 | "dev": true,
343 | "requires": {
344 | "has-flag": "^3.0.0"
345 | }
346 | },
347 | "tslib": {
348 | "version": "1.14.1",
349 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
350 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
351 | "dev": true
352 | },
353 | "tslint": {
354 | "version": "6.1.3",
355 | "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz",
356 | "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==",
357 | "dev": true,
358 | "requires": {
359 | "@babel/code-frame": "^7.0.0",
360 | "builtin-modules": "^1.1.1",
361 | "chalk": "^2.3.0",
362 | "commander": "^2.12.1",
363 | "diff": "^4.0.1",
364 | "glob": "^7.1.1",
365 | "js-yaml": "^3.13.1",
366 | "minimatch": "^3.0.4",
367 | "mkdirp": "^0.5.3",
368 | "resolve": "^1.3.2",
369 | "semver": "^5.3.0",
370 | "tslib": "^1.13.0",
371 | "tsutils": "^2.29.0"
372 | }
373 | },
374 | "tsutils": {
375 | "version": "2.29.0",
376 | "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
377 | "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
378 | "dev": true,
379 | "requires": {
380 | "tslib": "^1.8.1"
381 | }
382 | },
383 | "typescript": {
384 | "version": "4.2.4",
385 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
386 | "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
387 | "dev": true
388 | },
389 | "wrappy": {
390 | "version": "1.0.2",
391 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
392 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
393 | "dev": true
394 | }
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@dotmind/react-use-pwa",
3 | "version": "1.0.4",
4 | "description": "Prompt to install Progressive Web App and more with React hooks",
5 | "keywords": [
6 | "dotmind",
7 | "react",
8 | "hooks",
9 | "pwa",
10 | "prompt",
11 | "standalone",
12 | "typescript"
13 | ],
14 | "files": [
15 | "lib/**/*"
16 | ],
17 | "homepage": "https://github.com/dotmind/react-use-pwa",
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/dotmind/react-use-pwa"
21 | },
22 | "license": "MIT",
23 | "author": "Nicolas Bellot (https://github.com/bltnico)",
24 | "main": "lib/index.js",
25 | "types": "lib/index.d.ts",
26 | "scripts": {
27 | "test": "echo \"Error: no test specified\" && exit 1",
28 | "build": "rm -rf lib && tsc --project ./tsconfig.json",
29 | "lint": "tslint --fix -p ./tsconfig.json",
30 | "prepare": "npm run build",
31 | "prepublishOnly": "npm run lint"
32 | },
33 | "devDependencies": {
34 | "@tsconfig/recommended": "1.0.1",
35 | "@types/react": "17.0.3",
36 | "react": "17.0.2",
37 | "tslint": "6.1.3",
38 | "typescript": "4.2.4"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | interface Navigator {
2 | standalone? : boolean;
3 | };
4 |
--------------------------------------------------------------------------------
/src/hooks/usePwa.ts:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2 |
3 | import { isServer } from './../utils';
4 |
5 | enum UserChoice {
6 | ACCEPTED = 'accepted',
7 | DISMISSED = 'dismissed',
8 | };
9 |
10 | interface BeforeInstallPromptEvent extends Event {
11 | readonly userChoice: Promise<{
12 | outcome: UserChoice,
13 | platform: string,
14 | }>
15 |
16 | prompt(): Promise;
17 | };
18 |
19 | interface IusePwa {
20 | installPrompt: () => Promise;
21 | isInstalled: boolean;
22 | isStandalone: boolean;
23 | isOffline: boolean;
24 | canInstall: boolean;
25 | userChoice: UserChoice | 'unknow';
26 | };
27 |
28 | const usePwa = (): IusePwa => {
29 | const [canInstall, setCanInstall] = useState(false);
30 | const [isInstalled, setInstalled] = useState(false);
31 | const [isOffline, setOffline] = useState(false);
32 | const [userChoice, setUserChoice] = useState('unknow');
33 | const deferredPrompt = useRef() as React.MutableRefObject;
34 |
35 | const handleInstallEvent = useCallback(() => setInstalled(true), []);
36 |
37 | const handleBeforePromptEvent = useCallback((event: Event) => {
38 | event.preventDefault();
39 | deferredPrompt.current = event as BeforeInstallPromptEvent;
40 | setCanInstall(true);
41 | }, []);
42 |
43 | const handleOfflineEvent = useCallback((offline: boolean) => () => {
44 | setOffline(offline);
45 | }, []);
46 |
47 | useEffect(() => {
48 | if (isServer()) {
49 | return;
50 | }
51 |
52 | window.addEventListener('beforeinstallprompt', handleBeforePromptEvent);
53 | return () => window.removeEventListener('beforeinstallprompt', handleBeforePromptEvent)
54 | }, [handleBeforePromptEvent]);
55 |
56 | useEffect(() => {
57 | if (isServer()) {
58 | return;
59 | }
60 |
61 | window.addEventListener('appinstalled', handleInstallEvent);
62 | return () => window.removeEventListener('appinstalled', handleInstallEvent);
63 | }, [handleInstallEvent]);
64 |
65 | useEffect(() => {
66 | if (isServer()) {
67 | return;
68 | }
69 |
70 | if (navigator) {
71 | setOffline(!navigator.onLine);
72 | }
73 |
74 | window.addEventListener('online', handleOfflineEvent(false));
75 | window.addEventListener('offline', handleOfflineEvent(true));
76 | return () => {
77 | window.removeEventListener('online', handleOfflineEvent(false));
78 | window.removeEventListener('offline', handleOfflineEvent(true));
79 | };
80 | }, [handleOfflineEvent]);
81 |
82 | const installPrompt = useCallback(async () => {
83 | if (!deferredPrompt.current || isServer()) {
84 | return;
85 | }
86 |
87 | deferredPrompt.current.prompt();
88 | const choiceResult = await deferredPrompt.current.userChoice;
89 | deferredPrompt.current = null;
90 | setUserChoice(choiceResult.outcome);
91 | }, []);
92 |
93 | const isStandalone = useMemo(() => (
94 | !isServer() && (navigator.standalone || window.matchMedia('(display-mode: standalone)').matches)
95 | ), []);
96 |
97 | return {
98 | installPrompt,
99 | isInstalled,
100 | isStandalone,
101 | isOffline,
102 | canInstall,
103 | userChoice,
104 | };
105 | };
106 |
107 | export default usePwa;
108 |
--------------------------------------------------------------------------------
/src/hooks/usePwaAppSize.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import usePwa from './usePwa';
4 | import { isServer } from './../utils';
5 |
6 | interface Options {
7 | fixed: boolean;
8 | };
9 |
10 | const defaultOptions: Options = { fixed: false };
11 |
12 | const usePwaAppSize = (
13 | width: number = 800,
14 | height: number = 800,
15 | options: Options = defaultOptions,
16 | ) => {
17 | const { isStandalone } = usePwa();
18 |
19 | useEffect(() => {
20 | if (isServer()) {
21 | return;
22 | }
23 |
24 | const resizeWindow = (): void => window.resizeTo(width, height);
25 |
26 | if (isStandalone) {
27 | window.addEventListener('load', resizeWindow);
28 | if (options.fixed) {
29 | window.addEventListener('resize', resizeWindow);
30 | }
31 | }
32 |
33 | return () => {
34 | window.removeEventListener('load', resizeWindow);
35 | if (options.fixed) {
36 | window.removeEventListener('resize', resizeWindow);
37 | }
38 | };
39 | }, [isStandalone]);
40 | };
41 |
42 | export default usePwaAppSize;
43 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import usePwa from './hooks/usePwa';
2 | import usePwaAppSize from './hooks/usePwaAppSize';
3 |
4 | export { usePwa, usePwaAppSize };
5 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export const isServer = (): boolean => typeof window === 'undefined';
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/recommended/tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react",
5 | "declaration": true,
6 | "outDir": "./lib",
7 | "strict": true,
8 | "removeComments": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "lib": ["es2015", "dom"],
13 | "baseUrl": "src/"
14 | },
15 | "include": ["src"],
16 | "exclude": [
17 | "node_modules",
18 | "**/__tests__/*"
19 | ],
20 | }
21 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint:recommended"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------