react-pwa-hazhtech 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,410 @@
1
+
2
+ # react-pwa-hazhtech
3
+
4
+ A React library for Progressive Web App (PWA) functionality with install prompts, status tracking, and standalone mode detection.
5
+
6
+ ## Features
7
+
8
+ - 🚀 **PWA Install Prompts**: Easy-to-use install prompt functionality
9
+ - 📱 **Status Tracking**: Track PWA installation and standalone mode status
10
+ - 🎯 **TypeScript Support**: Full TypeScript support with type definitions
11
+ - âš¡ **Lightweight**: Minimal bundle size with tree-shaking support
12
+ - 🔧 **Easy Integration**: Simple React context-based API
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install react-pwa-hazhtech
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Wrap your app with PWAProvider
23
+
24
+ ```tsx
25
+ import React from 'react';
26
+ import { PWAProvider } from 'react-pwa-hazhtech';
27
+ import App from './App';
28
+
29
+ function Root() {
30
+ return (
31
+ <PWAProvider>
32
+ <App />
33
+ </PWAProvider>
34
+ );
35
+ }
36
+
37
+ export default Root;
38
+ ```
39
+
40
+ ### 2. Use the PWA hook in your components
41
+
42
+ ```tsx
43
+ import React from 'react';
44
+ import { usePWA } from 'react-pwa-hazhtech';
45
+
46
+ function InstallButton() {
47
+ const { isInstallable, isInstalled, isStandalone, promptInstall } = usePWA();
48
+
49
+ const handleInstall = async () => {
50
+ try {
51
+ const result = await promptInstall();
52
+ if (result?.outcome === 'accepted') {
53
+ console.log('PWA installed successfully');
54
+ }
55
+ } catch (error) {
56
+ console.error('Installation failed:', error);
57
+ }
58
+ };
59
+
60
+ if (isInstalled || isStandalone) {
61
+ return <div>App is already installed</div>;
62
+ }
63
+
64
+ if (!isInstallable) {
65
+ return <div>App is not installable</div>;
66
+ }
67
+
68
+ return (
69
+ <button onClick={handleInstall}>
70
+ Install App
71
+ </button>
72
+ );
73
+ }
74
+
75
+ export default InstallButton;
76
+ ```
77
+
78
+ ## API Reference
79
+
80
+ ### PWAProvider
81
+
82
+ The `PWAProvider` component provides PWA context to your application.
83
+
84
+ ```tsx
85
+ <PWAProvider>
86
+ {/* Your app components */}
87
+ </PWAProvider>
88
+ ```
89
+
90
+ ### usePWA Hook
91
+
92
+ The `usePWA` hook provides access to PWA functionality and state.
93
+
94
+ ```tsx
95
+ const {
96
+ isInstallable,
97
+ isInstalled,
98
+ isStandalone,
99
+ promptInstall,
100
+ installPromptEvent
101
+ } = usePWA();
102
+ ```
103
+
104
+ #### Properties
105
+
106
+ | Property | Type | Description |
107
+ |----------|------|-------------|
108
+ | `isInstallable` | `boolean` | Whether the app can be installed |
109
+ | `isInstalled` | `boolean` | Whether the app has been installed |
110
+ | `isStandalone` | `boolean` | Whether the app is running in standalone mode |
111
+ | `promptInstall` | `() => Promise<{outcome: 'accepted' \| 'dismissed'} \| null>` | Function to trigger the install prompt |
112
+ | `installPromptEvent` | `BeforeInstallPromptEvent \| null` | Raw browser install prompt event |
113
+
114
+ #### Methods
115
+
116
+ ##### promptInstall()
117
+
118
+ Triggers the PWA installation prompt.
119
+
120
+ ```tsx
121
+ const result = await promptInstall();
122
+ if (result?.outcome === 'accepted') {
123
+ // User accepted the installation
124
+ } else if (result?.outcome === 'dismissed') {
125
+ // User dismissed the installation
126
+ } else {
127
+ // Install prompt not available
128
+ }
129
+ ```
130
+
131
+ **Returns:** `Promise<{outcome: 'accepted' | 'dismissed'} | null>`
132
+
133
+ ### Standalone Functions
134
+
135
+ These utility functions can be used without the PWA context:
136
+
137
+ #### triggerPWAInstall()
138
+
139
+ Triggers PWA installation without using the hook context.
140
+
141
+ ```tsx
142
+ import { triggerPWAInstall } from 'react-pwa-hazhtech';
143
+
144
+ const handleInstall = async () => {
145
+ const result = await triggerPWAInstall();
146
+ if (result?.outcome === 'accepted') {
147
+ console.log('Installation successful');
148
+ }
149
+ };
150
+ ```
151
+
152
+ **Returns:** `Promise<{outcome: 'accepted' | 'dismissed'} | null>`
153
+
154
+ #### isPWAInstallAvailable()
155
+
156
+ Checks if PWA installation is available.
157
+
158
+ ```tsx
159
+ import { isPWAInstallAvailable } from 'react-pwa-hazhtech';
160
+
161
+ if (isPWAInstallAvailable()) {
162
+ // Show install button
163
+ }
164
+ ```
165
+
166
+ **Returns:** `boolean`
167
+
168
+ #### isPWAMode()
169
+
170
+ Checks if the app is running as a PWA (standalone mode).
171
+
172
+ ```tsx
173
+ import { isPWAMode } from 'react-pwa-hazhtech';
174
+
175
+ if (isPWAMode()) {
176
+ // App is running as PWA
177
+ }
178
+ ```
179
+
180
+ **Returns:** `boolean`
181
+
182
+ ## TypeScript Support
183
+
184
+ This library includes full TypeScript support. The `BeforeInstallPromptEvent` type is automatically available when using the library.
185
+
186
+ ```tsx
187
+ import { PWAContextType } from 'react-pwa-hazhtech';
188
+
189
+ // PWAContextType includes all the hook properties and methods
190
+ ```
191
+
192
+ ## Advanced Usage
193
+
194
+ ### Using the Hook in Components
195
+
196
+ ```tsx
197
+ import { usePWA } from 'react-pwa-hazhtech';
198
+
199
+ function PWAStatus() {
200
+ const { isInstalled, isInstallable, isStandalone } = usePWA();
201
+
202
+ return (
203
+ <div>
204
+ <p>Installable: {isInstallable ? 'Yes' : 'No'}</p>
205
+ <p>Installed: {isInstalled ? 'Yes' : 'No'}</p>
206
+ <p>Standalone: {isStandalone ? 'Yes' : 'No'}</p>
207
+ </div>
208
+ );
209
+ }
210
+ ```
211
+
212
+ ### Using Standalone Functions
213
+
214
+ For cases where you need to trigger installation outside of the React context:
215
+
216
+ ```tsx
217
+ import { triggerPWAInstall, isPWAInstallAvailable } from 'react-pwa-hazhtech';
218
+
219
+ // In any JavaScript function
220
+ const installApp = async () => {
221
+ if (isPWAInstallAvailable()) {
222
+ const result = await triggerPWAInstall();
223
+ if (result?.outcome === 'accepted') {
224
+ console.log('App installed successfully!');
225
+ }
226
+ }
227
+ };
228
+
229
+ // Can be called from anywhere
230
+ document.getElementById('install-btn')?.addEventListener('click', installApp);
231
+ ```
232
+
233
+ ### Custom Install Flow
234
+
235
+ ```tsx
236
+ import { usePWA } from 'react-pwa-hazhtech';
237
+
238
+ function CustomInstallFlow() {
239
+ const { isInstallable, promptInstall, installPromptEvent } = usePWA();
240
+ const [showPrompt, setShowPrompt] = useState(false);
241
+
242
+ useEffect(() => {
243
+ if (isInstallable && !localStorage.getItem('installPromptShown')) {
244
+ setShowPrompt(true);
245
+ }
246
+ }, [isInstallable]);
247
+
248
+ const handleInstall = async () => {
249
+ try {
250
+ const result = await promptInstall();
251
+ if (result?.outcome === 'accepted') {
252
+ console.log('Installation successful');
253
+ }
254
+ setShowPrompt(false);
255
+ localStorage.setItem('installPromptShown', 'true');
256
+ } catch (error) {
257
+ console.error('Installation error:', error);
258
+ }
259
+ };
260
+
261
+ const handleDismiss = () => {
262
+ setShowPrompt(false);
263
+ localStorage.setItem('installPromptShown', 'true');
264
+ };
265
+
266
+ if (!showPrompt) return null;
267
+
268
+ return (
269
+ <div className="install-prompt">
270
+ <h3>Install Our App</h3>
271
+ <p>Install our app for the best experience!</p>
272
+ <button onClick={handleInstall}>Install</button>
273
+ <button onClick={handleDismiss}>Maybe Later</button>
274
+ </div>
275
+ );
276
+ }
277
+ ```
278
+
279
+ ## PWA Setup Requirements
280
+
281
+ To use this library effectively, your app needs to meet PWA requirements:
282
+
283
+ ### 1. Service Worker
284
+
285
+ Create a `public/sw.js` file:
286
+
287
+ ```js
288
+ const CACHE_NAME = 'my-app-v1';
289
+
290
+ self.addEventListener('install', (event) => {
291
+ console.log('[Service Worker] Installed');
292
+ self.skipWaiting();
293
+ });
294
+
295
+ self.addEventListener('activate', (event) => {
296
+ console.log('[Service Worker] Activated');
297
+ event.waitUntil(clients.claim());
298
+ });
299
+
300
+ self.addEventListener('fetch', (event) => {
301
+ event.respondWith(
302
+ caches.match(event.request)
303
+ .then(response => {
304
+ return response || fetch(event.request);
305
+ })
306
+ );
307
+ });
308
+ ```
309
+
310
+ ### 2. Register Service Worker
311
+
312
+ In your main app file:
313
+
314
+ ```tsx
315
+ // Register service worker
316
+ if ('serviceWorker' in navigator) {
317
+ window.addEventListener('load', () => {
318
+ navigator.serviceWorker.register('/sw.js')
319
+ .then((registration) => {
320
+ console.log('SW registered: ', registration);
321
+ })
322
+ .catch((registrationError) => {
323
+ console.log('SW registration failed: ', registrationError);
324
+ });
325
+ });
326
+ }
327
+ ```
328
+
329
+ ### 3. Web App Manifest
330
+
331
+ Create a `public/manifest.json` file:
332
+
333
+ ```json
334
+ {
335
+ "name": "My PWA App",
336
+ "short_name": "MyApp",
337
+ "description": "My Progressive Web App",
338
+ "start_url": "/",
339
+ "display": "standalone",
340
+ "theme_color": "#000000",
341
+ "background_color": "#ffffff",
342
+ "icons": [
343
+ {
344
+ "src": "icon-192x192.png",
345
+ "sizes": "192x192",
346
+ "type": "image/png"
347
+ },
348
+ {
349
+ "src": "icon-512x512.png",
350
+ "sizes": "512x512",
351
+ "type": "image/png"
352
+ }
353
+ ]
354
+ }
355
+ ```
356
+
357
+ Add to your HTML head:
358
+
359
+ ```html
360
+ <link rel="manifest" href="/manifest.json">
361
+ <meta name="theme-color" content="#000000">
362
+ ```
363
+
364
+ ## Browser Support
365
+
366
+ This library supports all modern browsers that implement the PWA standards:
367
+
368
+ - Chrome 68+
369
+ - Edge 79+
370
+ - Safari 14.1+ (iOS 14.5+)
371
+ - Firefox 85+ (Android only)
372
+
373
+ ## Contributing
374
+
375
+ Contributions are welcome! Please feel free to submit a Pull Request.
376
+
377
+ ## License
378
+
379
+ MIT © [HazhTech](https://github.com/hazhtech/react-pwa-hazhtech)
380
+
381
+ ## Support
382
+
383
+ If you have any questions or issues, please [open an issue](https://github.com/hazhtech/react-pwa-hazhtech/issues) on GitHub.-worker.js`
384
+ To enable basic service worker behavior in the consuming app, add the following `service-worker.js`:
385
+
386
+ ```js
387
+ self.addEventListener('install', (event) => {
388
+ console.log('[Service Worker] Installed');
389
+ });
390
+
391
+ self.addEventListener('activate', (event) => {
392
+ console.log('[Service Worker] Activated');
393
+ });
394
+
395
+ self.addEventListener('fetch', (event) => {
396
+ event.respondWith(fetch(event.request));
397
+ });
398
+ ```
399
+
400
+ Register the service worker in your app’s `index.tsx`:
401
+
402
+ ```ts
403
+ if ('serviceWorker' in navigator) {
404
+ window.addEventListener('load', () => {
405
+ navigator.serviceWorker.register('/service-worker.js')
406
+ .then(registration => console.log('SW registered:', registration))
407
+ .catch(err => console.error('SW registration failed:', err));
408
+ });
409
+ }
410
+ ```
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+
3
+ interface PWAContextProps {
4
+ isInstalled: boolean;
5
+ isInstallable: boolean;
6
+ promptInstall: () => Promise<{
7
+ outcome: 'accepted' | 'dismissed';
8
+ } | null>;
9
+ installPromptEvent: BeforeInstallPromptEvent | null;
10
+ isStandalone: boolean;
11
+ }
12
+ declare const PWAContext: React.Context<PWAContextProps>;
13
+ declare global {
14
+ interface WindowEventMap {
15
+ beforeinstallprompt: BeforeInstallPromptEvent;
16
+ appinstalled: Event;
17
+ }
18
+ interface BeforeInstallPromptEvent extends Event {
19
+ prompt: () => Promise<void>;
20
+ userChoice: Promise<{
21
+ outcome: 'accepted' | 'dismissed';
22
+ }>;
23
+ }
24
+ }
25
+ interface PWAProviderProps {
26
+ children: React.ReactNode;
27
+ onInstallPromptAvailable?: (event: BeforeInstallPromptEvent) => void;
28
+ onAppInstalled?: () => void;
29
+ }
30
+ declare const PWAProvider: React.FC<PWAProviderProps>;
31
+ declare const triggerPWAInstall: () => Promise<{
32
+ outcome: "accepted" | "dismissed";
33
+ } | null>;
34
+ declare const isPWAInstallAvailable: () => boolean;
35
+ declare const isPWAMode: () => boolean;
36
+
37
+ declare const usePWA: () => PWAContextProps;
38
+
39
+ export { PWAContext, type PWAContextProps, PWAProvider, isPWAInstallAvailable, isPWAMode, triggerPWAInstall, usePWA };
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+
3
+ interface PWAContextProps {
4
+ isInstalled: boolean;
5
+ isInstallable: boolean;
6
+ promptInstall: () => Promise<{
7
+ outcome: 'accepted' | 'dismissed';
8
+ } | null>;
9
+ installPromptEvent: BeforeInstallPromptEvent | null;
10
+ isStandalone: boolean;
11
+ }
12
+ declare const PWAContext: React.Context<PWAContextProps>;
13
+ declare global {
14
+ interface WindowEventMap {
15
+ beforeinstallprompt: BeforeInstallPromptEvent;
16
+ appinstalled: Event;
17
+ }
18
+ interface BeforeInstallPromptEvent extends Event {
19
+ prompt: () => Promise<void>;
20
+ userChoice: Promise<{
21
+ outcome: 'accepted' | 'dismissed';
22
+ }>;
23
+ }
24
+ }
25
+ interface PWAProviderProps {
26
+ children: React.ReactNode;
27
+ onInstallPromptAvailable?: (event: BeforeInstallPromptEvent) => void;
28
+ onAppInstalled?: () => void;
29
+ }
30
+ declare const PWAProvider: React.FC<PWAProviderProps>;
31
+ declare const triggerPWAInstall: () => Promise<{
32
+ outcome: "accepted" | "dismissed";
33
+ } | null>;
34
+ declare const isPWAInstallAvailable: () => boolean;
35
+ declare const isPWAMode: () => boolean;
36
+
37
+ declare const usePWA: () => PWAContextProps;
38
+
39
+ export { PWAContext, type PWAContextProps, PWAProvider, isPWAInstallAvailable, isPWAMode, triggerPWAInstall, usePWA };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var i=react.createContext({isInstalled:false,isInstallable:false,promptInstall:async()=>null,installPromptEvent:null,isStandalone:false}),E=({children:d,onInstallPromptAvailable:a,onAppInstalled:l})=>{let[n,r]=react.useState(null),[f,p]=react.useState(false),[P,c]=react.useState(false),[w,v]=react.useState(false);react.useEffect(()=>{let e=()=>{let m=window.matchMedia("(display-mode: standalone)").matches||window.navigator.standalone||document.referrer.includes("android-app://");v(m),p(m);};e();let t=window.matchMedia("(display-mode: standalone)"),o=()=>e();return t.addEventListener?t.addEventListener("change",o):t.addListener(o),()=>{t.removeEventListener?t.removeEventListener("change",o):t.removeListener(o);}},[]),react.useEffect(()=>{let e=o=>{o.preventDefault(),r(o),c(true),a?.(o);},t=()=>{p(true),c(false),r(null),l?.();};return window.addEventListener("beforeinstallprompt",e),window.addEventListener("appinstalled",t),()=>{window.removeEventListener("beforeinstallprompt",e),window.removeEventListener("appinstalled",t);}},[a,l]);let I=react.useCallback(async()=>{if(!n)return console.warn("Install prompt is not available. Make sure the app meets PWA criteria."),null;try{await n.prompt();let e=await n.userChoice;return e.outcome==="accepted"&&(p(!0),c(!1)),r(null),e}catch(e){throw console.error("Error during install prompt:",e),e}},[n]);return jsxRuntime.jsx(i.Provider,{value:{isInstalled:f,isInstallable:P,promptInstall:I,installPromptEvent:n,isStandalone:w},children:d})},W=async()=>typeof window>"u"?(console.warn("triggerPWAInstall: Not in browser environment"),null):window.matchMedia("(display-mode: standalone)").matches||window.navigator.standalone||document.referrer.includes("android-app://")?(console.warn("triggerPWAInstall: App is already installed"),null):new Promise(a=>{let l=async n=>{n.preventDefault();try{await n.prompt();let r=await n.userChoice;a(r);}catch(r){console.error("Error during install prompt:",r),a(null);}};window.deferredPrompt?l(window.deferredPrompt):(window.addEventListener("beforeinstallprompt",l,{once:true}),setTimeout(()=>{window.removeEventListener("beforeinstallprompt",l),console.warn("triggerPWAInstall: Install prompt not available within timeout"),a(null);},5e3));}),b=()=>typeof window>"u"?false:!(window.matchMedia("(display-mode: standalone)").matches||window.navigator.standalone||document.referrer.includes("android-app://"))&&(window.deferredPrompt!==void 0||"serviceWorker"in navigator),y=()=>typeof window>"u"?false:window.matchMedia("(display-mode: standalone)").matches||window.navigator.standalone||document.referrer.includes("android-app://");var C=()=>react.useContext(i);
2
+ exports.PWAContext=i;exports.PWAProvider=E;exports.isPWAInstallAvailable=b;exports.isPWAMode=y;exports.triggerPWAInstall=W;exports.usePWA=C;//# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/PWAProvider.tsx","../src/usePWA.ts"],"names":["PWAContext","createContext","PWAProvider","children","onInstallPromptAvailable","onAppInstalled","deferredPrompt","setDeferredPrompt","useState","isInstalled","setIsInstalled","isInstallable","setIsInstallable","isStandalone","setIsStandalone","useEffect","checkStandalone","standalone","mediaQuery","handleChange","handleBeforeInstall","e","handleInstalled","promptInstall","useCallback","result","error","jsx","triggerPWAInstall","resolve","isPWAInstallAvailable","isPWAMode","usePWA","useContext"],"mappings":"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","file":"index.js","sourcesContent":["import React, { createContext, useEffect, useState, useCallback } from 'react';\n\nexport interface PWAContextProps {\n isInstalled: boolean;\n isInstallable: boolean;\n promptInstall: () => Promise<{ outcome: 'accepted' | 'dismissed' } | null>;\n installPromptEvent: BeforeInstallPromptEvent | null;\n isStandalone: boolean;\n}\n\nexport const PWAContext = createContext<PWAContextProps>({\n isInstalled: false,\n isInstallable: false,\n promptInstall: async () => null,\n installPromptEvent: null,\n isStandalone: false,\n});\n\ndeclare global {\n interface WindowEventMap {\n beforeinstallprompt: BeforeInstallPromptEvent;\n appinstalled: Event;\n }\n \n interface BeforeInstallPromptEvent extends Event {\n prompt: () => Promise<void>;\n userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;\n }\n}\n\nexport interface PWAProviderProps {\n children: React.ReactNode;\n onInstallPromptAvailable?: (event: BeforeInstallPromptEvent) => void;\n onAppInstalled?: () => void;\n}\n\nexport const PWAProvider: React.FC<PWAProviderProps> = ({ \n children, \n onInstallPromptAvailable,\n onAppInstalled \n}) => {\n const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);\n const [isInstalled, setIsInstalled] = useState<boolean>(false);\n const [isInstallable, setIsInstallable] = useState<boolean>(false);\n const [isStandalone, setIsStandalone] = useState<boolean>(false);\n\n // Check if app is running in standalone mode\n useEffect(() => {\n const checkStandalone = () => {\n const standalone = window.matchMedia('(display-mode: standalone)').matches ||\n (window.navigator as any).standalone ||\n document.referrer.includes('android-app://');\n setIsStandalone(standalone);\n setIsInstalled(standalone);\n };\n\n checkStandalone();\n \n // Listen for changes in display mode\n const mediaQuery = window.matchMedia('(display-mode: standalone)');\n const handleChange = () => checkStandalone();\n \n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener('change', handleChange);\n } else {\n // Fallback for older browsers\n mediaQuery.addListener(handleChange);\n }\n\n return () => {\n if (mediaQuery.removeEventListener) {\n mediaQuery.removeEventListener('change', handleChange);\n } else {\n mediaQuery.removeListener(handleChange);\n }\n };\n }, []);\n\n useEffect(() => {\n const handleBeforeInstall = (e: BeforeInstallPromptEvent) => {\n e.preventDefault();\n setDeferredPrompt(e);\n setIsInstallable(true);\n onInstallPromptAvailable?.(e);\n };\n\n const handleInstalled = () => {\n setIsInstalled(true);\n setIsInstallable(false);\n setDeferredPrompt(null);\n onAppInstalled?.();\n };\n\n window.addEventListener('beforeinstallprompt', handleBeforeInstall);\n window.addEventListener('appinstalled', handleInstalled);\n\n return () => {\n window.removeEventListener('beforeinstallprompt', handleBeforeInstall);\n window.removeEventListener('appinstalled', handleInstalled);\n };\n }, [onInstallPromptAvailable, onAppInstalled]);\n\n const promptInstall = useCallback(async (): Promise<{ outcome: 'accepted' | 'dismissed' } | null> => {\n if (!deferredPrompt) {\n console.warn('Install prompt is not available. Make sure the app meets PWA criteria.');\n return null;\n }\n\n try {\n await deferredPrompt.prompt();\n const result = await deferredPrompt.userChoice;\n \n if (result.outcome === 'accepted') {\n setIsInstalled(true);\n setIsInstallable(false);\n }\n \n setDeferredPrompt(null);\n return result;\n } catch (error) {\n console.error('Error during install prompt:', error);\n throw error;\n }\n }, [deferredPrompt]);\n\n return (\n <PWAContext.Provider value={{ \n isInstalled, \n isInstallable,\n promptInstall, \n installPromptEvent: deferredPrompt,\n isStandalone\n }}>\n {children}\n </PWAContext.Provider>\n );\n};\n\n// Utility function to trigger PWA install without using the hook\nexport const triggerPWAInstall = async (): Promise<{ outcome: 'accepted' | 'dismissed' } | null> => {\n // Check if we're in a browser environment\n if (typeof window === 'undefined') {\n console.warn('triggerPWAInstall: Not in browser environment');\n return null;\n }\n\n // Check if the app is already installed\n const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||\n (window.navigator as any).standalone ||\n document.referrer.includes('android-app://');\n \n if (isStandalone) {\n console.warn('triggerPWAInstall: App is already installed');\n return null;\n }\n\n return new Promise((resolve) => {\n const handleBeforeInstall = async (e: BeforeInstallPromptEvent) => {\n e.preventDefault();\n \n try {\n await e.prompt();\n const result = await e.userChoice;\n resolve(result);\n } catch (error) {\n console.error('Error during install prompt:', error);\n resolve(null);\n }\n };\n\n // Check if there's already a deferred prompt available\n if ((window as any).deferredPrompt) {\n handleBeforeInstall((window as any).deferredPrompt);\n } else {\n // Listen for the beforeinstallprompt event\n window.addEventListener('beforeinstallprompt', handleBeforeInstall, { once: true });\n \n // Timeout after 5 seconds if no install prompt is available\n setTimeout(() => {\n window.removeEventListener('beforeinstallprompt', handleBeforeInstall);\n console.warn('triggerPWAInstall: Install prompt not available within timeout');\n resolve(null);\n }, 5000);\n }\n });\n};\n\n// Utility function to check if PWA install is available\nexport const isPWAInstallAvailable = (): boolean => {\n if (typeof window === 'undefined') return false;\n \n const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||\n (window.navigator as any).standalone ||\n document.referrer.includes('android-app://');\n \n return !isStandalone && (\n (window as any).deferredPrompt !== undefined ||\n 'serviceWorker' in navigator\n );\n};\n\n// Utility function to check if app is running as PWA\nexport const isPWAMode = (): boolean => {\n if (typeof window === 'undefined') return false;\n \n return window.matchMedia('(display-mode: standalone)').matches ||\n (window.navigator as any).standalone ||\n document.referrer.includes('android-app://');\n};","import { useContext } from 'react';\nimport { PWAContext } from './PWAProvider';\n\nexport const usePWA = () => useContext(PWAContext);"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import {createContext,useContext,useState,useEffect,useCallback}from'react';import {jsx}from'react/jsx-runtime';var i=createContext({isInstalled:false,isInstallable:false,promptInstall:async()=>null,installPromptEvent:null,isStandalone:false}),E=({children:d,onInstallPromptAvailable:a,onAppInstalled:l})=>{let[n,r]=useState(null),[f,p]=useState(false),[P,c]=useState(false),[w,v]=useState(false);useEffect(()=>{let e=()=>{let m=window.matchMedia("(display-mode: standalone)").matches||window.navigator.standalone||document.referrer.includes("android-app://");v(m),p(m);};e();let t=window.matchMedia("(display-mode: standalone)"),o=()=>e();return t.addEventListener?t.addEventListener("change",o):t.addListener(o),()=>{t.removeEventListener?t.removeEventListener("change",o):t.removeListener(o);}},[]),useEffect(()=>{let e=o=>{o.preventDefault(),r(o),c(true),a?.(o);},t=()=>{p(true),c(false),r(null),l?.();};return window.addEventListener("beforeinstallprompt",e),window.addEventListener("appinstalled",t),()=>{window.removeEventListener("beforeinstallprompt",e),window.removeEventListener("appinstalled",t);}},[a,l]);let I=useCallback(async()=>{if(!n)return console.warn("Install prompt is not available. Make sure the app meets PWA criteria."),null;try{await n.prompt();let e=await n.userChoice;return e.outcome==="accepted"&&(p(!0),c(!1)),r(null),e}catch(e){throw console.error("Error during install prompt:",e),e}},[n]);return jsx(i.Provider,{value:{isInstalled:f,isInstallable:P,promptInstall:I,installPromptEvent:n,isStandalone:w},children:d})},W=async()=>typeof window>"u"?(console.warn("triggerPWAInstall: Not in browser environment"),null):window.matchMedia("(display-mode: standalone)").matches||window.navigator.standalone||document.referrer.includes("android-app://")?(console.warn("triggerPWAInstall: App is already installed"),null):new Promise(a=>{let l=async n=>{n.preventDefault();try{await n.prompt();let r=await n.userChoice;a(r);}catch(r){console.error("Error during install prompt:",r),a(null);}};window.deferredPrompt?l(window.deferredPrompt):(window.addEventListener("beforeinstallprompt",l,{once:true}),setTimeout(()=>{window.removeEventListener("beforeinstallprompt",l),console.warn("triggerPWAInstall: Install prompt not available within timeout"),a(null);},5e3));}),b=()=>typeof window>"u"?false:!(window.matchMedia("(display-mode: standalone)").matches||window.navigator.standalone||document.referrer.includes("android-app://"))&&(window.deferredPrompt!==void 0||"serviceWorker"in navigator),y=()=>typeof window>"u"?false:window.matchMedia("(display-mode: standalone)").matches||window.navigator.standalone||document.referrer.includes("android-app://");var C=()=>useContext(i);
2
+ export{i as PWAContext,E as PWAProvider,b as isPWAInstallAvailable,y as isPWAMode,W as triggerPWAInstall,C as usePWA};//# sourceMappingURL=index.mjs.map
3
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/PWAProvider.tsx","../src/usePWA.ts"],"names":["PWAContext","createContext","PWAProvider","children","onInstallPromptAvailable","onAppInstalled","deferredPrompt","setDeferredPrompt","useState","isInstalled","setIsInstalled","isInstallable","setIsInstallable","isStandalone","setIsStandalone","useEffect","checkStandalone","standalone","mediaQuery","handleChange","handleBeforeInstall","e","handleInstalled","promptInstall","useCallback","result","error","jsx","triggerPWAInstall","resolve","isPWAInstallAvailable","isPWAMode","usePWA","useContext"],"mappings":"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","file":"index.mjs","sourcesContent":["import React, { createContext, useEffect, useState, useCallback } from 'react';\n\nexport interface PWAContextProps {\n isInstalled: boolean;\n isInstallable: boolean;\n promptInstall: () => Promise<{ outcome: 'accepted' | 'dismissed' } | null>;\n installPromptEvent: BeforeInstallPromptEvent | null;\n isStandalone: boolean;\n}\n\nexport const PWAContext = createContext<PWAContextProps>({\n isInstalled: false,\n isInstallable: false,\n promptInstall: async () => null,\n installPromptEvent: null,\n isStandalone: false,\n});\n\ndeclare global {\n interface WindowEventMap {\n beforeinstallprompt: BeforeInstallPromptEvent;\n appinstalled: Event;\n }\n \n interface BeforeInstallPromptEvent extends Event {\n prompt: () => Promise<void>;\n userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>;\n }\n}\n\nexport interface PWAProviderProps {\n children: React.ReactNode;\n onInstallPromptAvailable?: (event: BeforeInstallPromptEvent) => void;\n onAppInstalled?: () => void;\n}\n\nexport const PWAProvider: React.FC<PWAProviderProps> = ({ \n children, \n onInstallPromptAvailable,\n onAppInstalled \n}) => {\n const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);\n const [isInstalled, setIsInstalled] = useState<boolean>(false);\n const [isInstallable, setIsInstallable] = useState<boolean>(false);\n const [isStandalone, setIsStandalone] = useState<boolean>(false);\n\n // Check if app is running in standalone mode\n useEffect(() => {\n const checkStandalone = () => {\n const standalone = window.matchMedia('(display-mode: standalone)').matches ||\n (window.navigator as any).standalone ||\n document.referrer.includes('android-app://');\n setIsStandalone(standalone);\n setIsInstalled(standalone);\n };\n\n checkStandalone();\n \n // Listen for changes in display mode\n const mediaQuery = window.matchMedia('(display-mode: standalone)');\n const handleChange = () => checkStandalone();\n \n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener('change', handleChange);\n } else {\n // Fallback for older browsers\n mediaQuery.addListener(handleChange);\n }\n\n return () => {\n if (mediaQuery.removeEventListener) {\n mediaQuery.removeEventListener('change', handleChange);\n } else {\n mediaQuery.removeListener(handleChange);\n }\n };\n }, []);\n\n useEffect(() => {\n const handleBeforeInstall = (e: BeforeInstallPromptEvent) => {\n e.preventDefault();\n setDeferredPrompt(e);\n setIsInstallable(true);\n onInstallPromptAvailable?.(e);\n };\n\n const handleInstalled = () => {\n setIsInstalled(true);\n setIsInstallable(false);\n setDeferredPrompt(null);\n onAppInstalled?.();\n };\n\n window.addEventListener('beforeinstallprompt', handleBeforeInstall);\n window.addEventListener('appinstalled', handleInstalled);\n\n return () => {\n window.removeEventListener('beforeinstallprompt', handleBeforeInstall);\n window.removeEventListener('appinstalled', handleInstalled);\n };\n }, [onInstallPromptAvailable, onAppInstalled]);\n\n const promptInstall = useCallback(async (): Promise<{ outcome: 'accepted' | 'dismissed' } | null> => {\n if (!deferredPrompt) {\n console.warn('Install prompt is not available. Make sure the app meets PWA criteria.');\n return null;\n }\n\n try {\n await deferredPrompt.prompt();\n const result = await deferredPrompt.userChoice;\n \n if (result.outcome === 'accepted') {\n setIsInstalled(true);\n setIsInstallable(false);\n }\n \n setDeferredPrompt(null);\n return result;\n } catch (error) {\n console.error('Error during install prompt:', error);\n throw error;\n }\n }, [deferredPrompt]);\n\n return (\n <PWAContext.Provider value={{ \n isInstalled, \n isInstallable,\n promptInstall, \n installPromptEvent: deferredPrompt,\n isStandalone\n }}>\n {children}\n </PWAContext.Provider>\n );\n};\n\n// Utility function to trigger PWA install without using the hook\nexport const triggerPWAInstall = async (): Promise<{ outcome: 'accepted' | 'dismissed' } | null> => {\n // Check if we're in a browser environment\n if (typeof window === 'undefined') {\n console.warn('triggerPWAInstall: Not in browser environment');\n return null;\n }\n\n // Check if the app is already installed\n const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||\n (window.navigator as any).standalone ||\n document.referrer.includes('android-app://');\n \n if (isStandalone) {\n console.warn('triggerPWAInstall: App is already installed');\n return null;\n }\n\n return new Promise((resolve) => {\n const handleBeforeInstall = async (e: BeforeInstallPromptEvent) => {\n e.preventDefault();\n \n try {\n await e.prompt();\n const result = await e.userChoice;\n resolve(result);\n } catch (error) {\n console.error('Error during install prompt:', error);\n resolve(null);\n }\n };\n\n // Check if there's already a deferred prompt available\n if ((window as any).deferredPrompt) {\n handleBeforeInstall((window as any).deferredPrompt);\n } else {\n // Listen for the beforeinstallprompt event\n window.addEventListener('beforeinstallprompt', handleBeforeInstall, { once: true });\n \n // Timeout after 5 seconds if no install prompt is available\n setTimeout(() => {\n window.removeEventListener('beforeinstallprompt', handleBeforeInstall);\n console.warn('triggerPWAInstall: Install prompt not available within timeout');\n resolve(null);\n }, 5000);\n }\n });\n};\n\n// Utility function to check if PWA install is available\nexport const isPWAInstallAvailable = (): boolean => {\n if (typeof window === 'undefined') return false;\n \n const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||\n (window.navigator as any).standalone ||\n document.referrer.includes('android-app://');\n \n return !isStandalone && (\n (window as any).deferredPrompt !== undefined ||\n 'serviceWorker' in navigator\n );\n};\n\n// Utility function to check if app is running as PWA\nexport const isPWAMode = (): boolean => {\n if (typeof window === 'undefined') return false;\n \n return window.matchMedia('(display-mode: standalone)').matches ||\n (window.navigator as any).standalone ||\n document.referrer.includes('android-app://');\n};","import { useContext } from 'react';\nimport { PWAContext } from './PWAProvider';\n\nexport const usePWA = () => useContext(PWAContext);"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "react-pwa-hazhtech",
3
+ "version": "1.0.0",
4
+ "description": "A React library for Progressive Web App (PWA) functionality with install prompts and status tracking",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.esm.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "dev": "tsup --watch",
15
+ "prepublishOnly": "npm run build",
16
+ "clean": "rm -rf dist"
17
+ },
18
+ "keywords": [
19
+ "react",
20
+ "pwa",
21
+ "progressive-web-app",
22
+ "install",
23
+ "status",
24
+ "hazhtech",
25
+ "beforeinstallprompt",
26
+ "service-worker"
27
+ ],
28
+ "author": "HazhTech",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/hazhtech/react-pwa-hazhtech.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/hazhtech/react-pwa-hazhtech/issues"
36
+ },
37
+ "homepage": "https://github.com/hazhtech/react-pwa-hazhtech#readme",
38
+ "peerDependencies": {
39
+ "react": ">=16.8.0",
40
+ "react-dom": ">=16.8.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/react": "^18.2.0",
44
+ "react": "^18.2.0",
45
+ "react-dom": "^18.2.0",
46
+ "tsup": "^8.0.0",
47
+ "typescript": "^5.0.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=14.0.0"
51
+ }
52
+ }