@cloudsignal/pwa-sdk 1.2.4 → 2.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/CHANGELOG.md +15 -26
- package/README.md +45 -509
- package/dist/{chunk-IMM7VF4N.js → chunk-IQHSODT4.js} +6 -6
- package/dist/chunk-IQHSODT4.js.map +1 -0
- package/dist/hmac-WITZIX2O.js +3 -0
- package/dist/{hmac-LWLR6F7Z.js.map → hmac-WITZIX2O.js.map} +1 -1
- package/dist/index.cjs +20 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +18 -36
- package/dist/index.d.ts +18 -36
- package/dist/index.global.js +6 -6
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +19 -103
- package/dist/index.js.map +1 -1
- package/dist/service-worker.js +1 -1
- package/dist/service-worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-IMM7VF4N.js.map +0 -1
- package/dist/hmac-LWLR6F7Z.js +0 -3
package/README.md
CHANGED
|
@@ -12,12 +12,6 @@ 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
|
-
|
|
21
15
|
### v1.2.0 Features
|
|
22
16
|
- **JWT Authentication** - User-linked registrations via JWT tokens (Supabase, Firebase, Auth0, Clerk)
|
|
23
17
|
- **Dual Auth Mode** - HMAC for anonymous users, JWT for authenticated users
|
|
@@ -50,24 +44,6 @@ npm install @cloudsignal/pwa-sdk
|
|
|
50
44
|
<script src="https://cdn.cloudsignal.io/cloudsignal-pwa.v1.0.0.js"></script>
|
|
51
45
|
```
|
|
52
46
|
|
|
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
|
-
|
|
71
47
|
## Quick Start
|
|
72
48
|
|
|
73
49
|
### Basic Usage
|
|
@@ -77,7 +53,7 @@ import { CloudSignalPWA } from '@cloudsignal/pwa-sdk'
|
|
|
77
53
|
|
|
78
54
|
const pwa = new CloudSignalPWA({
|
|
79
55
|
organizationId: 'your-org-uuid',
|
|
80
|
-
|
|
56
|
+
organizationPublishableKey: 'pk_your_publishable_key',
|
|
81
57
|
serviceId: 'your-service-uuid',
|
|
82
58
|
debug: true
|
|
83
59
|
})
|
|
@@ -89,9 +65,8 @@ await pwa.initialize()
|
|
|
89
65
|
const deviceInfo = pwa.getDeviceInfo()
|
|
90
66
|
console.log('Device:', deviceInfo.deviceModel, deviceInfo.browser)
|
|
91
67
|
|
|
92
|
-
// Check installation state
|
|
93
|
-
|
|
94
|
-
if (installState.canBeInstalled && !installState.isInstalled) {
|
|
68
|
+
// Check installation state
|
|
69
|
+
if (pwa.canInstall()) {
|
|
95
70
|
await pwa.showInstallPrompt()
|
|
96
71
|
}
|
|
97
72
|
|
|
@@ -99,11 +74,6 @@ if (installState.canBeInstalled && !installState.isInstalled) {
|
|
|
99
74
|
const registration = await pwa.registerForPush({
|
|
100
75
|
userEmail: 'user@example.com'
|
|
101
76
|
})
|
|
102
|
-
|
|
103
|
-
// Access registration ID (note: registrationId, not id)
|
|
104
|
-
if (registration) {
|
|
105
|
-
console.log('Registered with ID:', registration.registrationId)
|
|
106
|
-
}
|
|
107
77
|
```
|
|
108
78
|
|
|
109
79
|
### CDN Usage
|
|
@@ -113,7 +83,7 @@ if (registration) {
|
|
|
113
83
|
<script>
|
|
114
84
|
const pwa = new CloudSignalPWA.CloudSignalPWA({
|
|
115
85
|
organizationId: 'your-org-uuid',
|
|
116
|
-
|
|
86
|
+
organizationPublishableKey: 'pk_your_publishable_key',
|
|
117
87
|
serviceId: 'your-service-uuid'
|
|
118
88
|
})
|
|
119
89
|
|
|
@@ -154,7 +124,7 @@ const registration = await pwa.registerForPush()
|
|
|
154
124
|
// Start with HMAC (anonymous)
|
|
155
125
|
const pwa = new CloudSignalPWA({
|
|
156
126
|
organizationId: 'your-org-uuid',
|
|
157
|
-
|
|
127
|
+
organizationPublishableKey: 'pk_your_publishable_key',
|
|
158
128
|
serviceId: 'your-service-uuid'
|
|
159
129
|
})
|
|
160
130
|
|
|
@@ -180,7 +150,7 @@ new CloudSignalPWA(config: PWAConfig)
|
|
|
180
150
|
| Option | Type | Required | Description |
|
|
181
151
|
|--------|------|----------|-------------|
|
|
182
152
|
| `organizationId` | string | Yes | CloudSignal organization UUID |
|
|
183
|
-
| `
|
|
153
|
+
| `organizationPublishableKey` | string | No* | Organization publishable key `pk_*` (HMAC mode) |
|
|
184
154
|
| `userToken` | string | No* | JWT token from identity provider |
|
|
185
155
|
| `onTokenExpired` | function | No | Callback to refresh JWT on 401 |
|
|
186
156
|
| `serviceId` | string | Yes | PWA service UUID |
|
|
@@ -189,7 +159,7 @@ new CloudSignalPWA(config: PWAConfig)
|
|
|
189
159
|
| `serviceWorker` | object | No | Service worker config |
|
|
190
160
|
| `heartbeat` | object | No | Heartbeat config |
|
|
191
161
|
|
|
192
|
-
*Either `
|
|
162
|
+
*Either `organizationPublishableKey` or `userToken` must be provided
|
|
193
163
|
|
|
194
164
|
### Initialization
|
|
195
165
|
|
|
@@ -213,28 +183,11 @@ pwa.canInstall(): boolean
|
|
|
213
183
|
// Check if PWA is already installed
|
|
214
184
|
pwa.isInstalled(): boolean
|
|
215
185
|
|
|
216
|
-
// Get installation state
|
|
186
|
+
// Get installation state
|
|
217
187
|
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
|
-
// }
|
|
226
188
|
|
|
227
189
|
// Get install steps for current platform
|
|
228
190
|
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
|
|
238
191
|
```
|
|
239
192
|
|
|
240
193
|
### Push Notifications
|
|
@@ -320,27 +273,13 @@ pwa.getWakeLockState(): WakeLockState
|
|
|
320
273
|
|
|
321
274
|
```typescript
|
|
322
275
|
// Queue a request for later (when offline)
|
|
323
|
-
|
|
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
|
|
276
|
+
await pwa.queueOfflineRequest(url, method, body?, headers?): Promise<string | null>
|
|
336
277
|
|
|
337
|
-
// Process queued requests
|
|
338
|
-
await pwa.processOfflineQueue(): Promise<QueueProcessResult
|
|
339
|
-
// QueueProcessResult: { id: number, success: boolean, statusCode?: number, error?: string, shouldRetry: boolean }
|
|
278
|
+
// Process queued requests
|
|
279
|
+
await pwa.processOfflineQueue(): Promise<QueueProcessResult>
|
|
340
280
|
|
|
341
281
|
// Get queue statistics
|
|
342
282
|
await pwa.getOfflineQueueStats(): Promise<OfflineQueueStats>
|
|
343
|
-
// OfflineQueueStats: { totalQueued: number, byType: Record<string, number>, oldestRequest?: number, newestRequest?: number }
|
|
344
283
|
|
|
345
284
|
// Clear all queued requests
|
|
346
285
|
await pwa.clearOfflineQueue(): Promise<void>
|
|
@@ -384,75 +323,40 @@ pwa.off(event: PWAEvent, handler: (data) => void): void
|
|
|
384
323
|
|
|
385
324
|
**Available Events:**
|
|
386
325
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
|
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 |
|
|
451
357
|
|
|
452
358
|
## Service Worker Setup
|
|
453
359
|
|
|
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
|
-
|
|
456
360
|
Copy the service worker to your app's root directory:
|
|
457
361
|
|
|
458
362
|
```bash
|
|
@@ -466,43 +370,6 @@ Or download from CDN:
|
|
|
466
370
|
curl -o public/service-worker.js https://cdn.cloudsignal.io/service-worker.v1.0.0.js
|
|
467
371
|
```
|
|
468
372
|
|
|
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
|
-
|
|
506
373
|
## PWA Manifest
|
|
507
374
|
|
|
508
375
|
Create a `manifest.json` in your app's root:
|
|
@@ -570,197 +437,7 @@ info.isOnline // true/false
|
|
|
570
437
|
info.connectionType // '4g', '3g', '2g', 'unknown'
|
|
571
438
|
```
|
|
572
439
|
|
|
573
|
-
##
|
|
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)
|
|
440
|
+
## Example: React Integration
|
|
764
441
|
|
|
765
442
|
```jsx
|
|
766
443
|
import { useEffect, useState } from 'react'
|
|
@@ -768,7 +445,7 @@ import { CloudSignalPWA } from '@cloudsignal/pwa-sdk'
|
|
|
768
445
|
|
|
769
446
|
const pwa = new CloudSignalPWA({
|
|
770
447
|
organizationId: process.env.REACT_APP_ORG_ID,
|
|
771
|
-
|
|
448
|
+
organizationPublishableKey: process.env.REACT_APP_CLOUDSIGNAL_PUBLISHABLE_KEY,
|
|
772
449
|
serviceId: process.env.REACT_APP_SERVICE_ID
|
|
773
450
|
})
|
|
774
451
|
|
|
@@ -857,7 +534,7 @@ import {
|
|
|
857
534
|
```javascript
|
|
858
535
|
const pwa = new CloudSignalPWA({
|
|
859
536
|
organizationId: 'your-org-uuid',
|
|
860
|
-
|
|
537
|
+
organizationPublishableKey: 'pk_your_publishable_key',
|
|
861
538
|
serviceId: 'your-service-uuid',
|
|
862
539
|
|
|
863
540
|
// Wake Lock (prevents screen sleep)
|
|
@@ -892,147 +569,6 @@ const pwa = new CloudSignalPWA({
|
|
|
892
569
|
})
|
|
893
570
|
```
|
|
894
571
|
|
|
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
|
-
|
|
1036
572
|
## License
|
|
1037
573
|
|
|
1038
574
|
MIT License - Copyright (c) 2024-2025 CloudSignal
|