@cloudsignal/pwa-sdk 1.2.3 → 1.2.4

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +502 -38
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -21,6 +21,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
21
21
  - `initialize()` now auto-calls `registerInstallation()` when PWA is detected as installed
22
22
  - Prevents duplicate registrations using localStorage tracking
23
23
 
24
+ ### Documentation
25
+
26
+ - **Type Corrections**: Fixed `registration.registrationId`, `InstallationState` object type, `queueOfflineRequest` signature, `processOfflineQueue` return type
27
+ - **Prerequisites Section**: Added environment variable configuration guide
28
+ - **Service Worker Setup**: Added root placement guidance, scope configuration, Workbox/Serwist coexistence patterns
29
+ - **Framework Integrations**: Added Next.js App Router guide with dynamic imports, Supabase Auth integration
30
+ - **Event Documentation**: Complete event list with handler signatures
31
+ - **Troubleshooting**: Service worker, permission, iOS, and token/auth issues
32
+ - **Production Checklist**: Pre-deployment verification steps
33
+ - **Migration Guides**: From Firebase Cloud Messaging and OneSignal
34
+
24
35
  ### Backend Support
25
36
 
26
37
  - `/api/v1/registration/install-only` endpoint now supports JWT authentication
package/README.md CHANGED
@@ -12,6 +12,12 @@ Progressive Web App SDK for CloudSignal platform with push notifications, instal
12
12
  - **Service Worker** - Badge management, notification history, offline support
13
13
  - **TypeScript** - Full type definitions included
14
14
 
15
+ ### v1.2.3 Features
16
+ - **Auto Installation Tracking** - Automatically registers PWA installations with backend when detected
17
+ - `registerInstallation()` method for manual installation tracking
18
+ - `isInstallationRegistered()` / `getInstallationId()` helper methods
19
+ - `install:registered` event for installation tracking
20
+
15
21
  ### v1.2.0 Features
16
22
  - **JWT Authentication** - User-linked registrations via JWT tokens (Supabase, Firebase, Auth0, Clerk)
17
23
  - **Dual Auth Mode** - HMAC for anonymous users, JWT for authenticated users
@@ -44,6 +50,24 @@ npm install @cloudsignal/pwa-sdk
44
50
  <script src="https://cdn.cloudsignal.io/cloudsignal-pwa.v1.0.0.js"></script>
45
51
  ```
46
52
 
53
+ ## Prerequisites
54
+
55
+ ### Environment Variables
56
+
57
+ Set up your environment variables before using the SDK:
58
+
59
+ ```bash
60
+ # .env.local (Next.js) or .env
61
+ NEXT_PUBLIC_CLOUDSIGNAL_ORG_ID=your-org-uuid
62
+ NEXT_PUBLIC_CLOUDSIGNAL_SERVICE_ID=your-service-uuid
63
+ NEXT_PUBLIC_CLOUDSIGNAL_ORG_SECRET=your-secret-key # For HMAC mode only
64
+ ```
65
+
66
+ **Where to get these values:**
67
+ 1. **Organization ID** - From CloudSignal dashboard → Organization Settings
68
+ 2. **Service ID** - From CloudSignal dashboard → PWA Services → Your Service
69
+ 3. **Organization Secret** - From CloudSignal dashboard → API Keys (for HMAC mode)
70
+
47
71
  ## Quick Start
48
72
 
49
73
  ### Basic Usage
@@ -65,8 +89,9 @@ await pwa.initialize()
65
89
  const deviceInfo = pwa.getDeviceInfo()
66
90
  console.log('Device:', deviceInfo.deviceModel, deviceInfo.browser)
67
91
 
68
- // Check installation state
69
- if (pwa.canInstall()) {
92
+ // Check installation state - returns InstallationState object
93
+ const installState = pwa.getInstallationState()
94
+ if (installState.canBeInstalled && !installState.isInstalled) {
70
95
  await pwa.showInstallPrompt()
71
96
  }
72
97
 
@@ -74,6 +99,11 @@ if (pwa.canInstall()) {
74
99
  const registration = await pwa.registerForPush({
75
100
  userEmail: 'user@example.com'
76
101
  })
102
+
103
+ // Access registration ID (note: registrationId, not id)
104
+ if (registration) {
105
+ console.log('Registered with ID:', registration.registrationId)
106
+ }
77
107
  ```
78
108
 
79
109
  ### CDN Usage
@@ -183,11 +213,28 @@ pwa.canInstall(): boolean
183
213
  // Check if PWA is already installed
184
214
  pwa.isInstalled(): boolean
185
215
 
186
- // Get installation state
216
+ // Get installation state (full details)
187
217
  pwa.getInstallationState(): InstallationState
218
+ // Returns: {
219
+ // isInstalled: boolean,
220
+ // canBeInstalled: boolean,
221
+ // needsManualInstall: boolean,
222
+ // showManualInstructions: boolean,
223
+ // installSteps: string[],
224
+ // displayMode: 'browser' | 'standalone' | 'minimal-ui' | 'fullscreen'
225
+ // }
188
226
 
189
227
  // Get install steps for current platform
190
228
  pwa.getInstallSteps(): string[]
229
+
230
+ // Register installation with backend (v1.2.3)
231
+ await pwa.registerInstallation(): Promise<{ registrationId: string } | null>
232
+
233
+ // Check if installation is registered (v1.2.3)
234
+ pwa.isInstallationRegistered(): boolean
235
+
236
+ // Get installation registration ID (v1.2.3)
237
+ pwa.getInstallationId(): string | null
191
238
  ```
192
239
 
193
240
  ### Push Notifications
@@ -273,13 +320,27 @@ pwa.getWakeLockState(): WakeLockState
273
320
 
274
321
  ```typescript
275
322
  // Queue a request for later (when offline)
276
- await pwa.queueOfflineRequest(url, method, body?, headers?): Promise<string | null>
323
+ // Signature: queueRequest(url, method, options)
324
+ await pwa.queueOfflineRequest(
325
+ url: string,
326
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
327
+ options?: {
328
+ headers?: Record<string, string>,
329
+ body?: string,
330
+ requestType?: 'registration' | 'heartbeat' | 'analytics' | 'preferences' | 'unregister' | 'custom',
331
+ priority?: number,
332
+ maxRetries?: number,
333
+ metadata?: Record<string, any>
334
+ }
335
+ ): Promise<number | null> // Returns queue ID or null
277
336
 
278
- // Process queued requests
279
- await pwa.processOfflineQueue(): Promise<QueueProcessResult>
337
+ // Process queued requests - returns array of results
338
+ await pwa.processOfflineQueue(): Promise<QueueProcessResult[]>
339
+ // QueueProcessResult: { id: number, success: boolean, statusCode?: number, error?: string, shouldRetry: boolean }
280
340
 
281
341
  // Get queue statistics
282
342
  await pwa.getOfflineQueueStats(): Promise<OfflineQueueStats>
343
+ // OfflineQueueStats: { totalQueued: number, byType: Record<string, number>, oldestRequest?: number, newestRequest?: number }
283
344
 
284
345
  // Clear all queued requests
285
346
  await pwa.clearOfflineQueue(): Promise<void>
@@ -323,40 +384,75 @@ pwa.off(event: PWAEvent, handler: (data) => void): void
323
384
 
324
385
  **Available Events:**
325
386
 
326
- | Event | Description |
327
- |-------|-------------|
328
- | `install:available` | Install prompt is available |
329
- | `install:accepted` | User accepted install |
330
- | `install:dismissed` | User dismissed install |
331
- | `install:completed` | PWA was installed |
332
- | `push:registered` | Push registration successful |
333
- | `push:unregistered` | Push unregistration successful |
334
- | `push:error` | Push operation failed |
335
- | `permission:denied` | Notification permission denied |
336
- | `config:loaded` | Service config downloaded |
337
- | `config:error` | Config download failed |
338
- | `heartbeat:sent` | Heartbeat sent successfully |
339
- | `heartbeat:error` | Heartbeat failed |
340
- | `heartbeat:intervalChanged` | Heartbeat interval adjusted (v1.1.0) |
341
- | `heartbeat:pausedForBattery` | Heartbeat paused due to low battery (v1.1.0) |
342
- | `heartbeat:resumedFromBattery` | Heartbeat resumed (v1.1.0) |
343
- | `wakeLock:acquired` | Screen wake lock acquired (v1.1.0) |
344
- | `wakeLock:released` | Screen wake lock released (v1.1.0) |
345
- | `wakeLock:error` | Wake lock operation failed (v1.1.0) |
346
- | `offlineQueue:queued` | Request added to offline queue (v1.1.0) |
347
- | `offlineQueue:processed` | Queued request processed (v1.1.0) |
348
- | `offlineQueue:flushed` | All queued requests processed (v1.1.0) |
349
- | `iosBanner:shown` | iOS install banner shown (v1.1.0) |
350
- | `iosBanner:dismissed` | iOS install banner dismissed (v1.1.0) |
351
- | `iosBanner:installClicked` | User clicked install on iOS banner (v1.1.0) |
352
- | `auth:tokenUpdated` | JWT token updated/upgraded (v1.2.0) |
353
- | `network:online` | Network came online |
354
- | `network:offline` | Network went offline |
355
- | `sw:registered` | Service worker registered |
356
- | `sw:updated` | Service worker updated |
387
+ **Installation Events:**
388
+ - `install:available` - Install prompt is available. Handler: `(data: { platforms: string[] }) => void`
389
+ - `install:accepted` - User accepted install. Handler: `(data: { outcome: 'accepted', platform?: string }) => void`
390
+ - `install:dismissed` - User dismissed install. Handler: `(data: { outcome: 'dismissed' }) => void`
391
+ - `install:completed` - PWA was installed
392
+ - `install:registered` - PWA installation registered with backend (v1.2.3). Handler: `(data: { registrationId: string }) => void`
393
+ - `install:error` - Installation error occurred
394
+
395
+ **Push Notification Events:**
396
+ - `push:registered` - Push registration successful. Handler: `(data: { registrationId: string, endpoint: string }) => void`
397
+ - `push:unregistered` - Push unregistration successful
398
+ - `push:updated` - Push registration updated
399
+ - `push:error` - Push operation failed. Handler: `(data: { error: Error }) => void`
400
+ - `push:received` - Push notification received. Handler: `(data: { payload: NotificationPayload, timestamp: number }) => void`
401
+ - `push:clicked` - Notification clicked. Handler: `(data: { action?: string, data?: any, url?: string }) => void`
402
+
403
+ **Permission Events:**
404
+ - `permission:granted` - Notification permission granted
405
+ - `permission:denied` - Notification permission denied
406
+ - `permission:prompt` - Permission prompt shown
407
+
408
+ **Service Worker Events:**
409
+ - `sw:registered` - Service worker registered
410
+ - `sw:updated` - Service worker updated
411
+ - `sw:error` - Service worker error
412
+ - `sw:activated` - Service worker activated
413
+
414
+ **Config Events:**
415
+ - `config:loaded` - Service config downloaded. Handler: `(data: { config: PWAServiceConfig }) => void`
416
+ - `config:error` - Config download failed
417
+
418
+ **Heartbeat Events:**
419
+ - `heartbeat:started` - Heartbeat started
420
+ - `heartbeat:stopped` - Heartbeat stopped
421
+ - `heartbeat:sent` - Heartbeat sent successfully
422
+ - `heartbeat:error` - Heartbeat failed
423
+ - `heartbeat:intervalChanged` - Heartbeat interval adjusted (v1.1.0)
424
+ - `heartbeat:pausedForBattery` - Heartbeat paused due to low battery (v1.1.0)
425
+ - `heartbeat:resumedFromBattery` - Heartbeat resumed (v1.1.0)
426
+
427
+ **Network Events:**
428
+ - `network:online` - Network came online
429
+ - `network:offline` - Network went offline
430
+
431
+ **Wake Lock Events (v1.1.0):**
432
+ - `wakeLock:acquired` - Screen wake lock acquired
433
+ - `wakeLock:released` - Screen wake lock released
434
+ - `wakeLock:error` - Wake lock operation failed
435
+
436
+ **Offline Queue Events (v1.1.0):**
437
+ - `offlineQueue:queued` - Request added to offline queue
438
+ - `offlineQueue:processed` - Queued request processed
439
+ - `offlineQueue:flushed` - All queued requests processed
440
+
441
+ **iOS Banner Events (v1.1.0):**
442
+ - `iosBanner:shown` - iOS install banner shown
443
+ - `iosBanner:dismissed` - iOS install banner dismissed
444
+ - `iosBanner:installClicked` - User clicked install on iOS banner
445
+
446
+ **Authentication Events (v1.2.0):**
447
+ - `auth:tokenUpdated` - JWT token updated/upgraded
448
+
449
+ **State Events:**
450
+ - `state:changed` - SDK state changed
357
451
 
358
452
  ## Service Worker Setup
359
453
 
454
+ **IMPORTANT:** The service worker MUST be placed at your app's root (e.g., `/service-worker.js` or `/public/service-worker.js`) to ensure correct scope. Service workers can only control pages within their scope.
455
+
360
456
  Copy the service worker to your app's root directory:
361
457
 
362
458
  ```bash
@@ -370,6 +466,43 @@ Or download from CDN:
370
466
  curl -o public/service-worker.js https://cdn.cloudsignal.io/service-worker.v1.0.0.js
371
467
  ```
372
468
 
469
+ ### Custom Service Worker Path
470
+
471
+ If you need a different path or filename:
472
+
473
+ ```javascript
474
+ const pwa = new CloudSignalPWA({
475
+ // ...other config
476
+ serviceWorker: {
477
+ path: '/sw.js', // Custom path
478
+ scope: '/', // Must match or be parent of your app routes
479
+ autoRegister: true,
480
+ updateBehavior: 'auto' // 'prompt' | 'auto' | 'manual'
481
+ }
482
+ })
483
+ ```
484
+
485
+ ### Workbox/Serwist Compatibility
486
+
487
+ **IMPORTANT:** CloudSignal's service worker is **not compatible** with Workbox, Serwist, or other PWA service worker libraries. You must use one or the other.
488
+
489
+ **Why?** CloudSignal's service worker handles:
490
+ - Push notification reception and display
491
+ - Dynamic manifest downloading and caching
492
+ - Notification click routing
493
+ - Badge management
494
+ - Offline notification history
495
+
496
+ These features require full control of the service worker lifecycle.
497
+
498
+ **If you're currently using Workbox/Serwist:**
499
+ 1. Remove the existing service worker library (`@serwist/next`, `next-pwa`, `workbox-webpack-plugin`, etc.)
500
+ 2. Use CloudSignal's service worker instead
501
+ 3. If you need precaching, consider using the browser's native Cache API in your application code
502
+
503
+ **If you need features from Workbox (like precaching):**
504
+ Open an issue on GitHub - we may add these capabilities to the CloudSignal service worker in future versions.
505
+
373
506
  ## PWA Manifest
374
507
 
375
508
  Create a `manifest.json` in your app's root:
@@ -437,7 +570,197 @@ info.isOnline // true/false
437
570
  info.connectionType // '4g', '3g', '2g', 'unknown'
438
571
  ```
439
572
 
440
- ## Example: React Integration
573
+ ## Framework Integration Examples
574
+
575
+ ### Next.js (App Router)
576
+
577
+ **IMPORTANT:** The SDK uses browser APIs and must be loaded client-side only.
578
+
579
+ ```tsx
580
+ // components/CloudSignalProvider.tsx
581
+ 'use client'
582
+
583
+ import { useEffect, useState, createContext, useContext, ReactNode } from 'react'
584
+ import type { CloudSignalPWA as CloudSignalPWAType } from '@cloudsignal/pwa-sdk'
585
+
586
+ type PWAContextType = {
587
+ pwa: CloudSignalPWAType | null
588
+ isInitialized: boolean
589
+ canInstall: boolean
590
+ isRegistered: boolean
591
+ }
592
+
593
+ const PWAContext = createContext<PWAContextType>({
594
+ pwa: null,
595
+ isInitialized: false,
596
+ canInstall: false,
597
+ isRegistered: false
598
+ })
599
+
600
+ export function CloudSignalProvider({ children }: { children: ReactNode }) {
601
+ const [pwa, setPwa] = useState<CloudSignalPWAType | null>(null)
602
+ const [isInitialized, setIsInitialized] = useState(false)
603
+ const [canInstall, setCanInstall] = useState(false)
604
+ const [isRegistered, setIsRegistered] = useState(false)
605
+
606
+ useEffect(() => {
607
+ // Dynamic import - required for Next.js App Router
608
+ const initPWA = async () => {
609
+ try {
610
+ const { CloudSignalPWA } = await import('@cloudsignal/pwa-sdk')
611
+
612
+ const instance = new CloudSignalPWA({
613
+ organizationId: process.env.NEXT_PUBLIC_CLOUDSIGNAL_ORG_ID!,
614
+ organizationSecret: process.env.NEXT_PUBLIC_CLOUDSIGNAL_ORG_SECRET!,
615
+ serviceId: process.env.NEXT_PUBLIC_CLOUDSIGNAL_SERVICE_ID!,
616
+ debug: process.env.NODE_ENV === 'development'
617
+ })
618
+
619
+ await instance.initialize()
620
+
621
+ setPwa(instance)
622
+ setIsInitialized(true)
623
+ setCanInstall(instance.canInstall())
624
+ setIsRegistered(instance.isRegistered())
625
+
626
+ // Set up event listeners
627
+ instance.on('install:available', () => setCanInstall(true))
628
+ instance.on('install:completed', () => setCanInstall(false))
629
+ instance.on('push:registered', () => setIsRegistered(true))
630
+ instance.on('push:unregistered', () => setIsRegistered(false))
631
+ } catch (error) {
632
+ console.error('Failed to initialize CloudSignal PWA:', error)
633
+ }
634
+ }
635
+
636
+ initPWA()
637
+ }, [])
638
+
639
+ return (
640
+ <PWAContext.Provider value={{ pwa, isInitialized, canInstall, isRegistered }}>
641
+ {children}
642
+ </PWAContext.Provider>
643
+ )
644
+ }
645
+
646
+ export const usePWA = () => useContext(PWAContext)
647
+ ```
648
+
649
+ ```tsx
650
+ // app/layout.tsx
651
+ import { CloudSignalProvider } from '@/components/CloudSignalProvider'
652
+
653
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
654
+ return (
655
+ <html>
656
+ <body>
657
+ <CloudSignalProvider>
658
+ {children}
659
+ </CloudSignalProvider>
660
+ </body>
661
+ </html>
662
+ )
663
+ }
664
+ ```
665
+
666
+ ```tsx
667
+ // components/InstallButton.tsx
668
+ 'use client'
669
+
670
+ import { usePWA } from './CloudSignalProvider'
671
+
672
+ export function InstallButton() {
673
+ const { pwa, canInstall } = usePWA()
674
+
675
+ if (!canInstall) return null
676
+
677
+ const handleInstall = async () => {
678
+ const result = await pwa?.showInstallPrompt()
679
+ if (result?.accepted) {
680
+ console.log('App installed!')
681
+ }
682
+ }
683
+
684
+ return <button onClick={handleInstall}>Install App</button>
685
+ }
686
+ ```
687
+
688
+ ### Next.js with Supabase Auth
689
+
690
+ ```tsx
691
+ // components/CloudSignalSupabaseProvider.tsx
692
+ 'use client'
693
+
694
+ import { useEffect, useState, createContext, useContext, ReactNode } from 'react'
695
+ import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
696
+ import type { CloudSignalPWA as CloudSignalPWAType } from '@cloudsignal/pwa-sdk'
697
+
698
+ const supabase = createClientComponentClient()
699
+
700
+ type PWAContextType = {
701
+ pwa: CloudSignalPWAType | null
702
+ isInitialized: boolean
703
+ }
704
+
705
+ const PWAContext = createContext<PWAContextType>({ pwa: null, isInitialized: false })
706
+
707
+ export function CloudSignalSupabaseProvider({ children }: { children: ReactNode }) {
708
+ const [pwa, setPwa] = useState<CloudSignalPWAType | null>(null)
709
+ const [isInitialized, setIsInitialized] = useState(false)
710
+
711
+ useEffect(() => {
712
+ let instance: CloudSignalPWAType | null = null
713
+
714
+ const initPWA = async () => {
715
+ const { CloudSignalPWA } = await import('@cloudsignal/pwa-sdk')
716
+ const { data: { session } } = await supabase.auth.getSession()
717
+
718
+ instance = new CloudSignalPWA({
719
+ organizationId: process.env.NEXT_PUBLIC_CLOUDSIGNAL_ORG_ID!,
720
+ serviceId: process.env.NEXT_PUBLIC_CLOUDSIGNAL_SERVICE_ID!,
721
+ // Use JWT if user is logged in, fall back to HMAC
722
+ userToken: session?.access_token,
723
+ organizationSecret: !session ? process.env.NEXT_PUBLIC_CLOUDSIGNAL_ORG_SECRET : undefined,
724
+ onTokenExpired: async () => {
725
+ const { data } = await supabase.auth.refreshSession()
726
+ return data.session?.access_token || ''
727
+ }
728
+ })
729
+
730
+ await instance.initialize()
731
+ setPwa(instance)
732
+ setIsInitialized(true)
733
+ }
734
+
735
+ initPWA()
736
+
737
+ // Listen for auth changes to update token
738
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(
739
+ async (event, session) => {
740
+ if (instance && session?.access_token) {
741
+ instance.setUserToken(session.access_token)
742
+ // Re-register to link to user
743
+ await instance.registerForPush()
744
+ }
745
+ }
746
+ )
747
+
748
+ return () => {
749
+ subscription.unsubscribe()
750
+ }
751
+ }, [])
752
+
753
+ return (
754
+ <PWAContext.Provider value={{ pwa, isInitialized }}>
755
+ {children}
756
+ </PWAContext.Provider>
757
+ )
758
+ }
759
+
760
+ export const usePWA = () => useContext(PWAContext)
761
+ ```
762
+
763
+ ### React (Create React App / Vite)
441
764
 
442
765
  ```jsx
443
766
  import { useEffect, useState } from 'react'
@@ -569,6 +892,147 @@ const pwa = new CloudSignalPWA({
569
892
  })
570
893
  ```
571
894
 
895
+ ## Troubleshooting
896
+
897
+ ### Service Worker Issues
898
+
899
+ **Problem:** Service worker not registering
900
+ ```
901
+ DOMException: Failed to register a ServiceWorker
902
+ ```
903
+
904
+ **Solutions:**
905
+ 1. Ensure service worker is at root path (`/service-worker.js`)
906
+ 2. Check HTTPS is enabled (required except on localhost)
907
+ 3. Verify service worker path in config matches actual file location
908
+ 4. Check browser console for detailed error
909
+
910
+ **Problem:** Push notifications not received after service worker update
911
+
912
+ **Solution:** The SDK handles this automatically with `updateBehavior: 'auto'`. If using manual mode, call `pwa.getServiceWorkerManager().update()` after deploy.
913
+
914
+ ### Permission Issues
915
+
916
+ **Problem:** Permission prompt never appears
917
+
918
+ **Causes:**
919
+ 1. User previously denied permission (check `pwa.getDeviceInfo().notificationPermission`)
920
+ 2. Site not served over HTTPS
921
+ 3. Browser settings blocking notifications globally
922
+
923
+ **Solution for denied permission:**
924
+ ```javascript
925
+ const deviceInfo = pwa.getDeviceInfo()
926
+ if (deviceInfo.notificationPermission === 'denied') {
927
+ // Guide user to browser settings
928
+ alert('Please enable notifications in your browser settings')
929
+ }
930
+ ```
931
+
932
+ ### iOS-Specific Issues
933
+
934
+ **Problem:** Install prompt not showing on iOS Safari
935
+
936
+ **Explanation:** iOS Safari doesn't support the Web App Install Banner API. Use the iOS Install Banner feature:
937
+ ```javascript
938
+ const pwa = new CloudSignalPWA({
939
+ // ...config
940
+ iosInstallBanner: {
941
+ enabled: true,
942
+ appName: 'My App',
943
+ showOnFirstVisit: true
944
+ }
945
+ })
946
+ ```
947
+
948
+ **Problem:** Push notifications not working on iOS
949
+
950
+ **Requirements:**
951
+ - iOS 16.4+
952
+ - PWA must be installed to home screen
953
+ - User must grant permission after installation
954
+
955
+ ### Token/Auth Issues
956
+
957
+ **Problem:** 401 errors after token expires
958
+
959
+ **Solution:** Implement `onTokenExpired` callback:
960
+ ```javascript
961
+ const pwa = new CloudSignalPWA({
962
+ // ...config
963
+ onTokenExpired: async () => {
964
+ // Your token refresh logic
965
+ const newToken = await refreshMyToken()
966
+ return newToken
967
+ }
968
+ })
969
+ ```
970
+
971
+ **Problem:** Registration not linked to user after login
972
+
973
+ **Solution:** Call `setUserToken()` followed by `registerForPush()`:
974
+ ```javascript
975
+ pwa.setUserToken(newJWT)
976
+ await pwa.registerForPush() // Re-registers with user identity
977
+ ```
978
+
979
+ ### Debug Mode
980
+
981
+ Enable debug logging to troubleshoot issues:
982
+ ```javascript
983
+ const pwa = new CloudSignalPWA({
984
+ // ...config
985
+ debug: true // Logs all SDK operations to console
986
+ })
987
+ ```
988
+
989
+ ## Production Checklist
990
+
991
+ Before deploying to production, verify:
992
+
993
+ - [ ] **HTTPS enabled** - Required for service workers and push notifications
994
+ - [ ] **Service worker at root** - `/service-worker.js` or `/public/service-worker.js`
995
+ - [ ] **Manifest.json configured** - Icons, name, start_url, display mode
996
+ - [ ] **Environment variables set** - Organization ID, Service ID, Secret/JWT config
997
+ - [ ] **VAPID keys configured** - In CloudSignal dashboard
998
+ - [ ] **Test on target devices** - iOS Safari, Chrome, Firefox, Edge
999
+ - [ ] **Test install flow** - Both automatic prompt and iOS manual instructions
1000
+ - [ ] **Test push notifications** - Send test notification from dashboard
1001
+ - [ ] **Test offline behavior** - Verify offline queue and reconnection
1002
+ - [ ] **Token refresh tested** - If using JWT, verify refresh flow works
1003
+ - [ ] **Error handling** - Graceful degradation when features unavailable
1004
+
1005
+ ## Migration from Other Services
1006
+
1007
+ ### From Firebase Cloud Messaging (FCM)
1008
+
1009
+ CloudSignal uses Web Push directly (not FCM). Key differences:
1010
+ - No Firebase SDK dependency
1011
+ - VAPID keys instead of FCM server key
1012
+ - Direct Web Push Protocol
1013
+
1014
+ ```javascript
1015
+ // Before (FCM)
1016
+ import { getMessaging, getToken } from 'firebase/messaging'
1017
+ const token = await getToken(messaging, { vapidKey: '...' })
1018
+
1019
+ // After (CloudSignal)
1020
+ import { CloudSignalPWA } from '@cloudsignal/pwa-sdk'
1021
+ const registration = await pwa.registerForPush()
1022
+ ```
1023
+
1024
+ ### From OneSignal
1025
+
1026
+ Remove OneSignal SDK and replace with CloudSignal:
1027
+ ```javascript
1028
+ // Before
1029
+ OneSignal.push(['init', { appId: '...' }])
1030
+
1031
+ // After
1032
+ const pwa = new CloudSignalPWA({ organizationId: '...', serviceId: '...' })
1033
+ await pwa.initialize()
1034
+ ```
1035
+
572
1036
  ## License
573
1037
 
574
1038
  MIT License - Copyright (c) 2024-2025 CloudSignal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudsignal/pwa-sdk",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "CloudSignal PWA SDK - Progressive Web App features with push notifications, JWT/HMAC authentication, installation management, device tracking, offline queue, wake lock, and notification analytics",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",