npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cloudsignal/pwa-sdk

v1.2.4

Published

CloudSignal PWA SDK - Progressive Web App features with push notifications, JWT/HMAC authentication, installation management, device tracking, offline queue, wake lock, and notification analytics

Readme

CloudSignal PWA SDK

Progressive Web App SDK for CloudSignal platform with push notifications, installation management, comprehensive device tracking, offline resilience, and notification analytics.

Features

Core Features

  • PWA Installation - Automatic install prompt handling with iOS manual instructions
  • Push Notifications - Web Push Protocol with VAPID authentication
  • Device Detection - 35+ device/browser/OS detection fields
  • Heartbeat - Real-time online status tracking
  • Service Worker - Badge management, notification history, offline support
  • TypeScript - Full type definitions included

v1.2.3 Features

  • Auto Installation Tracking - Automatically registers PWA installations with backend when detected
  • registerInstallation() method for manual installation tracking
  • isInstallationRegistered() / getInstallationId() helper methods
  • install:registered event for installation tracking

v1.2.0 Features

  • JWT Authentication - User-linked registrations via JWT tokens (Supabase, Firebase, Auth0, Clerk)
  • Dual Auth Mode - HMAC for anonymous users, JWT for authenticated users
  • Smart Notification Routing - Enable in-app vs web push based on user online status
  • Token Refresh - Automatic retry on 401 with onTokenExpired callback

v1.1.0 Features

  • Screen Wake Lock - Prevent screen sleep during critical operations
  • Offline Request Queue - IndexedDB-backed queue with auto-retry when online
  • iOS Install Banner - Beautiful modal guiding iOS Safari users to install
  • Network-Aware Heartbeat - Auto-adjusts based on connection quality (4G/3G/2G)
  • Battery-Aware Heartbeat - Pauses when battery <15% to save power
  • Notification Analytics - Track displayed/clicked/dismissed events

Installation

npm

npm install @cloudsignal/pwa-sdk

CDN

<!-- Latest version -->
<script src="https://cdn.cloudsignal.io/CloudSignalPWA.js"></script>

<!-- Specific version -->
<script src="https://cdn.cloudsignal.io/cloudsignal-pwa.v1.0.0.js"></script>

Prerequisites

Environment Variables

Set up your environment variables before using the SDK:

# .env.local (Next.js) or .env
NEXT_PUBLIC_CLOUDSIGNAL_ORG_ID=your-org-uuid
NEXT_PUBLIC_CLOUDSIGNAL_SERVICE_ID=your-service-uuid
NEXT_PUBLIC_CLOUDSIGNAL_ORG_SECRET=your-secret-key  # For HMAC mode only

Where to get these values:

  1. Organization ID - From CloudSignal dashboard → Organization Settings
  2. Service ID - From CloudSignal dashboard → PWA Services → Your Service
  3. Organization Secret - From CloudSignal dashboard → API Keys (for HMAC mode)

Quick Start

Basic Usage

import { CloudSignalPWA } from '@cloudsignal/pwa-sdk'

const pwa = new CloudSignalPWA({
  organizationId: 'your-org-uuid',
  organizationSecret: 'your-secret-key',
  serviceId: 'your-service-uuid',
  debug: true
})

// Initialize (downloads config, registers service worker)
await pwa.initialize()

// Get device information
const deviceInfo = pwa.getDeviceInfo()
console.log('Device:', deviceInfo.deviceModel, deviceInfo.browser)

// Check installation state - returns InstallationState object
const installState = pwa.getInstallationState()
if (installState.canBeInstalled && !installState.isInstalled) {
  await pwa.showInstallPrompt()
}

// Register for push notifications
const registration = await pwa.registerForPush({
  userEmail: '[email protected]'
})

// Access registration ID (note: registrationId, not id)
if (registration) {
  console.log('Registered with ID:', registration.registrationId)
}

CDN Usage

<script src="https://cdn.cloudsignal.io/CloudSignalPWA.js"></script>
<script>
const pwa = new CloudSignalPWA.CloudSignalPWA({
  organizationId: 'your-org-uuid',
  organizationSecret: 'your-secret-key',
  serviceId: 'your-service-uuid'
})

pwa.initialize().then(() => {
  console.log('PWA initialized')
})
</script>

JWT Authentication (v1.2.0)

For authenticated user registrations, use JWT instead of HMAC:

import { CloudSignalPWA } from '@cloudsignal/pwa-sdk'
import { supabase } from './supabase' // Your Supabase client

const pwa = new CloudSignalPWA({
  organizationId: 'your-org-uuid',
  userToken: (await supabase.auth.getSession()).data.session?.access_token,
  serviceId: 'your-service-uuid',
  onTokenExpired: async () => {
    // Refresh token when 401 received
    const { data } = await supabase.auth.refreshSession()
    return data.session?.access_token || ''
  }
})

await pwa.initialize()

// Registration will be linked to the authenticated user
const registration = await pwa.registerForPush()

Upgrading from Anonymous to Authenticated:

// Start with HMAC (anonymous)
const pwa = new CloudSignalPWA({
  organizationId: 'your-org-uuid',
  organizationSecret: 'your-secret-key',
  serviceId: 'your-service-uuid'
})

await pwa.initialize()
await pwa.registerForPush() // Anonymous registration

// Later, when user logs in:
const userToken = await getUserJWT()
pwa.setUserToken(userToken)
await pwa.registerForPush() // Re-register with user identity

API Reference

Constructor

new CloudSignalPWA(config: PWAConfig)

Config Options:

| Option | Type | Required | Description | |--------|------|----------|-------------| | organizationId | string | Yes | CloudSignal organization UUID | | organizationSecret | string | No* | Organization secret key (HMAC mode) | | userToken | string | No* | JWT token from identity provider | | onTokenExpired | function | No | Callback to refresh JWT on 401 | | serviceId | string | Yes | PWA service UUID | | serviceUrl | string | No | Service URL (default: https://pwa.cloudsignal.app) | | debug | boolean | No | Enable debug logging | | serviceWorker | object | No | Service worker config | | heartbeat | object | No | Heartbeat config |

*Either organizationSecret or userToken must be provided

Initialization

// Initialize the PWA client
await pwa.initialize(): Promise<InitializeResult>

// Download service configuration
await pwa.downloadConfig(): Promise<PWAServiceConfig | null>

Installation

// Show install prompt (Chrome/Edge)
await pwa.showInstallPrompt(): Promise<InstallResult>

// Check if PWA can be installed
pwa.canInstall(): boolean

// Check if PWA is already installed
pwa.isInstalled(): boolean

// Get installation state (full details)
pwa.getInstallationState(): InstallationState
// Returns: {
//   isInstalled: boolean,
//   canBeInstalled: boolean,
//   needsManualInstall: boolean,
//   showManualInstructions: boolean,
//   installSteps: string[],
//   displayMode: 'browser' | 'standalone' | 'minimal-ui' | 'fullscreen'
// }

// Get install steps for current platform
pwa.getInstallSteps(): string[]

// Register installation with backend (v1.2.3)
await pwa.registerInstallation(): Promise<{ registrationId: string } | null>

// Check if installation is registered (v1.2.3)
pwa.isInstallationRegistered(): boolean

// Get installation registration ID (v1.2.3)
pwa.getInstallationId(): string | null

Push Notifications

// Register for push notifications
await pwa.registerForPush(options?: RegisterOptions): Promise<Registration | null>

// Unregister from push notifications
await pwa.unregisterFromPush(): Promise<boolean>

// Update notification preferences
await pwa.updatePreferences(prefs: NotificationPreferences): Promise<boolean>

// Check registration status
await pwa.checkRegistrationStatus(): Promise<RegistrationStatusResponse | null>

// Request notification permission
await pwa.requestPermission(): Promise<NotificationPermission>

// Check if registered
pwa.isRegistered(): boolean

// Get registration ID
pwa.getRegistrationId(): string | null

Device Information

// Get comprehensive device info (35+ fields)
pwa.getDeviceInfo(): DeviceInfo

// Get PWA capabilities
pwa.getCapabilities(): PWACapabilities

Heartbeat

// Start heartbeat for online status tracking
pwa.startHeartbeat(): void

// Stop heartbeat
pwa.stopHeartbeat(): void

// Get current interval (may vary with network conditions)
pwa.getHeartbeatInterval(): number

// Get network connection info
pwa.getNetworkInfo(): NetworkConnectionInfo

// Get battery info (if available)
await pwa.getBatteryInfo(): Promise<BatteryInfo | null>

Authentication (v1.2.0)

// Get current authentication mode
pwa.getAuthMode(): 'hmac' | 'jwt'

// Set/upgrade JWT token (for authenticated users)
pwa.setUserToken(token: string): void
// After calling setUserToken, call registerForPush() again to link registration to user

Screen Wake Lock (v1.1.0)

// Request screen wake lock (prevents screen from sleeping)
await pwa.requestWakeLock(): Promise<boolean>

// Release wake lock
pwa.releaseWakeLock(): void

// Get current state
pwa.getWakeLockState(): WakeLockState
// Returns: { isSupported: boolean, isActive: boolean }

Offline Request Queue (v1.1.0)

// Queue a request for later (when offline)
// Signature: queueRequest(url, method, options)
await pwa.queueOfflineRequest(
  url: string,
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
  options?: {
    headers?: Record<string, string>,
    body?: string,
    requestType?: 'registration' | 'heartbeat' | 'analytics' | 'preferences' | 'unregister' | 'custom',
    priority?: number,
    maxRetries?: number,
    metadata?: Record<string, any>
  }
): Promise<number | null>  // Returns queue ID or null

// Process queued requests - returns array of results
await pwa.processOfflineQueue(): Promise<QueueProcessResult[]>
// QueueProcessResult: { id: number, success: boolean, statusCode?: number, error?: string, shouldRetry: boolean }

// Get queue statistics
await pwa.getOfflineQueueStats(): Promise<OfflineQueueStats>
// OfflineQueueStats: { totalQueued: number, byType: Record<string, number>, oldestRequest?: number, newestRequest?: number }

// Clear all queued requests
await pwa.clearOfflineQueue(): Promise<void>

iOS Install Banner (v1.1.0)

// Show iOS install guidance banner
pwa.showIOSInstallBanner(): void

// Hide banner
pwa.hideIOSInstallBanner(): void

// Check if previously dismissed
pwa.wasIOSBannerDismissed(): boolean

// Reset dismissal state
pwa.resetIOSBannerDismissal(): void

Badge Management

// Clear app badge
pwa.clearBadge(): void

// Set app badge count
pwa.setBadge(count: number): void

Events

// Subscribe to events
pwa.on(event: PWAEvent, handler: (data) => void): void

// Unsubscribe from events
pwa.off(event: PWAEvent, handler: (data) => void): void

Available Events:

Installation Events:

  • install:available - Install prompt is available. Handler: (data: { platforms: string[] }) => void
  • install:accepted - User accepted install. Handler: (data: { outcome: 'accepted', platform?: string }) => void
  • install:dismissed - User dismissed install. Handler: (data: { outcome: 'dismissed' }) => void
  • install:completed - PWA was installed
  • install:registered - PWA installation registered with backend (v1.2.3). Handler: (data: { registrationId: string }) => void
  • install:error - Installation error occurred

Push Notification Events:

  • push:registered - Push registration successful. Handler: (data: { registrationId: string, endpoint: string }) => void
  • push:unregistered - Push unregistration successful
  • push:updated - Push registration updated
  • push:error - Push operation failed. Handler: (data: { error: Error }) => void
  • push:received - Push notification received. Handler: (data: { payload: NotificationPayload, timestamp: number }) => void
  • push:clicked - Notification clicked. Handler: (data: { action?: string, data?: any, url?: string }) => void

Permission Events:

  • permission:granted - Notification permission granted
  • permission:denied - Notification permission denied
  • permission:prompt - Permission prompt shown

Service Worker Events:

  • sw:registered - Service worker registered
  • sw:updated - Service worker updated
  • sw:error - Service worker error
  • sw:activated - Service worker activated

Config Events:

  • config:loaded - Service config downloaded. Handler: (data: { config: PWAServiceConfig }) => void
  • config:error - Config download failed

Heartbeat Events:

  • heartbeat:started - Heartbeat started
  • heartbeat:stopped - Heartbeat stopped
  • heartbeat:sent - Heartbeat sent successfully
  • heartbeat:error - Heartbeat failed
  • heartbeat:intervalChanged - Heartbeat interval adjusted (v1.1.0)
  • heartbeat:pausedForBattery - Heartbeat paused due to low battery (v1.1.0)
  • heartbeat:resumedFromBattery - Heartbeat resumed (v1.1.0)

Network Events:

  • network:online - Network came online
  • network:offline - Network went offline

Wake Lock Events (v1.1.0):

  • wakeLock:acquired - Screen wake lock acquired
  • wakeLock:released - Screen wake lock released
  • wakeLock:error - Wake lock operation failed

Offline Queue Events (v1.1.0):

  • offlineQueue:queued - Request added to offline queue
  • offlineQueue:processed - Queued request processed
  • offlineQueue:flushed - All queued requests processed

iOS Banner Events (v1.1.0):

  • iosBanner:shown - iOS install banner shown
  • iosBanner:dismissed - iOS install banner dismissed
  • iosBanner:installClicked - User clicked install on iOS banner

Authentication Events (v1.2.0):

  • auth:tokenUpdated - JWT token updated/upgraded

State Events:

  • state:changed - SDK state changed

Service Worker Setup

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.

Copy the service worker to your app's root directory:

# If using npm
cp node_modules/@cloudsignal/pwa-sdk/dist/service-worker.js public/

Or download from CDN:

curl -o public/service-worker.js https://cdn.cloudsignal.io/service-worker.v1.0.0.js

Custom Service Worker Path

If you need a different path or filename:

const pwa = new CloudSignalPWA({
  // ...other config
  serviceWorker: {
    path: '/sw.js',  // Custom path
    scope: '/',       // Must match or be parent of your app routes
    autoRegister: true,
    updateBehavior: 'auto'  // 'prompt' | 'auto' | 'manual'
  }
})

Workbox/Serwist Compatibility

IMPORTANT: CloudSignal's service worker is not compatible with Workbox, Serwist, or other PWA service worker libraries. You must use one or the other.

Why? CloudSignal's service worker handles:

  • Push notification reception and display
  • Dynamic manifest downloading and caching
  • Notification click routing
  • Badge management
  • Offline notification history

These features require full control of the service worker lifecycle.

If you're currently using Workbox/Serwist:

  1. Remove the existing service worker library (@serwist/next, next-pwa, workbox-webpack-plugin, etc.)
  2. Use CloudSignal's service worker instead
  3. If you need precaching, consider using the browser's native Cache API in your application code

If you need features from Workbox (like precaching): Open an issue on GitHub - we may add these capabilities to the CloudSignal service worker in future versions.

PWA Manifest

Create a manifest.json in your app's root:

{
  "name": "Your App Name",
  "short_name": "App",
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff",
  "icons": [
    {
      "src": "/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Add to your HTML:

<link rel="manifest" href="/manifest.json">

Device Information

The SDK detects 35+ device attributes:

const info = pwa.getDeviceInfo()

// Operating System
info.os           // 'iOS', 'Android', 'Windows', 'macOS', 'Linux'
info.osVersion    // '17.2', '14.0', 'Windows 10/11'

// Device
info.deviceType   // 'iPhone', 'iPad', 'Phone', 'Tablet', 'Desktop'
info.deviceModel  // 'iPhone 15 Pro', 'Samsung Galaxy S24'
info.isMobile     // true/false
info.isTablet     // true/false
info.isDesktop    // true/false

// Browser
info.browser        // 'Chrome', 'Safari', 'Firefox', 'Edge'
info.browserVersion // '120.0.6099.43'

// PWA Capabilities
info.supportLevel          // 'full', 'partial', 'basic', 'none'
info.hasPushManager        // true/false
info.hasServiceWorker      // true/false
info.hasBadgeAPI           // true/false
info.notificationPermission // 'granted', 'denied', 'default'

// Network
info.isOnline        // true/false
info.connectionType  // '4g', '3g', '2g', 'unknown'

Framework Integration Examples

Next.js (App Router)

IMPORTANT: The SDK uses browser APIs and must be loaded client-side only.

// components/CloudSignalProvider.tsx
'use client'

import { useEffect, useState, createContext, useContext, ReactNode } from 'react'
import type { CloudSignalPWA as CloudSignalPWAType } from '@cloudsignal/pwa-sdk'

type PWAContextType = {
  pwa: CloudSignalPWAType | null
  isInitialized: boolean
  canInstall: boolean
  isRegistered: boolean
}

const PWAContext = createContext<PWAContextType>({
  pwa: null,
  isInitialized: false,
  canInstall: false,
  isRegistered: false
})

export function CloudSignalProvider({ children }: { children: ReactNode }) {
  const [pwa, setPwa] = useState<CloudSignalPWAType | null>(null)
  const [isInitialized, setIsInitialized] = useState(false)
  const [canInstall, setCanInstall] = useState(false)
  const [isRegistered, setIsRegistered] = useState(false)

  useEffect(() => {
    // Dynamic import - required for Next.js App Router
    const initPWA = async () => {
      try {
        const { CloudSignalPWA } = await import('@cloudsignal/pwa-sdk')
        
        const instance = new CloudSignalPWA({
          organizationId: process.env.NEXT_PUBLIC_CLOUDSIGNAL_ORG_ID!,
          organizationSecret: process.env.NEXT_PUBLIC_CLOUDSIGNAL_ORG_SECRET!,
          serviceId: process.env.NEXT_PUBLIC_CLOUDSIGNAL_SERVICE_ID!,
          debug: process.env.NODE_ENV === 'development'
        })

        await instance.initialize()
        
        setPwa(instance)
        setIsInitialized(true)
        setCanInstall(instance.canInstall())
        setIsRegistered(instance.isRegistered())

        // Set up event listeners
        instance.on('install:available', () => setCanInstall(true))
        instance.on('install:completed', () => setCanInstall(false))
        instance.on('push:registered', () => setIsRegistered(true))
        instance.on('push:unregistered', () => setIsRegistered(false))
      } catch (error) {
        console.error('Failed to initialize CloudSignal PWA:', error)
      }
    }

    initPWA()
  }, [])

  return (
    <PWAContext.Provider value={{ pwa, isInitialized, canInstall, isRegistered }}>
      {children}
    </PWAContext.Provider>
  )
}

export const usePWA = () => useContext(PWAContext)
// app/layout.tsx
import { CloudSignalProvider } from '@/components/CloudSignalProvider'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <CloudSignalProvider>
          {children}
        </CloudSignalProvider>
      </body>
    </html>
  )
}
// components/InstallButton.tsx
'use client'

import { usePWA } from './CloudSignalProvider'

export function InstallButton() {
  const { pwa, canInstall } = usePWA()

  if (!canInstall) return null

  const handleInstall = async () => {
    const result = await pwa?.showInstallPrompt()
    if (result?.accepted) {
      console.log('App installed!')
    }
  }

  return <button onClick={handleInstall}>Install App</button>
}

Next.js with Supabase Auth

// components/CloudSignalSupabaseProvider.tsx
'use client'

import { useEffect, useState, createContext, useContext, ReactNode } from 'react'
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
import type { CloudSignalPWA as CloudSignalPWAType } from '@cloudsignal/pwa-sdk'

const supabase = createClientComponentClient()

type PWAContextType = {
  pwa: CloudSignalPWAType | null
  isInitialized: boolean
}

const PWAContext = createContext<PWAContextType>({ pwa: null, isInitialized: false })

export function CloudSignalSupabaseProvider({ children }: { children: ReactNode }) {
  const [pwa, setPwa] = useState<CloudSignalPWAType | null>(null)
  const [isInitialized, setIsInitialized] = useState(false)

  useEffect(() => {
    let instance: CloudSignalPWAType | null = null

    const initPWA = async () => {
      const { CloudSignalPWA } = await import('@cloudsignal/pwa-sdk')
      const { data: { session } } = await supabase.auth.getSession()

      instance = new CloudSignalPWA({
        organizationId: process.env.NEXT_PUBLIC_CLOUDSIGNAL_ORG_ID!,
        serviceId: process.env.NEXT_PUBLIC_CLOUDSIGNAL_SERVICE_ID!,
        // Use JWT if user is logged in, fall back to HMAC
        userToken: session?.access_token,
        organizationSecret: !session ? process.env.NEXT_PUBLIC_CLOUDSIGNAL_ORG_SECRET : undefined,
        onTokenExpired: async () => {
          const { data } = await supabase.auth.refreshSession()
          return data.session?.access_token || ''
        }
      })

      await instance.initialize()
      setPwa(instance)
      setIsInitialized(true)
    }

    initPWA()

    // Listen for auth changes to update token
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        if (instance && session?.access_token) {
          instance.setUserToken(session.access_token)
          // Re-register to link to user
          await instance.registerForPush()
        }
      }
    )

    return () => {
      subscription.unsubscribe()
    }
  }, [])

  return (
    <PWAContext.Provider value={{ pwa, isInitialized }}>
      {children}
    </PWAContext.Provider>
  )
}

export const usePWA = () => useContext(PWAContext)

React (Create React App / Vite)

import { useEffect, useState } from 'react'
import { CloudSignalPWA } from '@cloudsignal/pwa-sdk'

const pwa = new CloudSignalPWA({
  organizationId: process.env.REACT_APP_ORG_ID,
  organizationSecret: process.env.REACT_APP_ORG_SECRET,
  serviceId: process.env.REACT_APP_SERVICE_ID
})

function App() {
  const [canInstall, setCanInstall] = useState(false)
  const [isRegistered, setIsRegistered] = useState(false)

  useEffect(() => {
    pwa.initialize().then(() => {
      setCanInstall(pwa.canInstall())
      setIsRegistered(pwa.isRegistered())
    })

    pwa.on('install:available', () => setCanInstall(true))
    pwa.on('push:registered', () => setIsRegistered(true))
  }, [])

  const handleInstall = async () => {
    const result = await pwa.showInstallPrompt()
    if (result.accepted) {
      setCanInstall(false)
    }
  }

  const handleEnableNotifications = async () => {
    const registration = await pwa.registerForPush({
      userEmail: '[email protected]'
    })
    if (registration) {
      setIsRegistered(true)
    }
  }

  return (
    <div>
      {canInstall && (
        <button onClick={handleInstall}>Install App</button>
      )}
      {!isRegistered && (
        <button onClick={handleEnableNotifications}>
          Enable Notifications
        </button>
      )}
    </div>
  )
}

Browser Support

| Browser | PWA Install | Push Notifications | |---------|-------------|-------------------| | Chrome | ✅ | ✅ | | Edge | ✅ | ✅ | | Firefox | ❌ | ✅ | | Safari (macOS) | ❌ | ✅ (macOS 13+) | | Safari (iOS) | Manual | ✅ (iOS 16.4+) | | Samsung Internet | ✅ | ✅ |

TypeScript

Full TypeScript support with exported types:

import { 
  CloudSignalPWA,
  PWAConfig,
  DeviceInfo,
  InstallationState,
  Registration,
  PWAEvent,
  // v1.1.0 types
  WakeLockState,
  WakeLockConfig,
  OfflineQueueConfig,
  IOSInstallBannerConfig,
  NetworkConnectionInfo,
  BatteryInfo,
  QueueProcessResult,
  OfflineQueueStats,
} from '@cloudsignal/pwa-sdk'

v1.1.0 Configuration

const pwa = new CloudSignalPWA({
  organizationId: 'your-org-uuid',
  organizationSecret: 'your-secret-key',
  serviceId: 'your-service-uuid',
  
  // Wake Lock (prevents screen sleep)
  wakeLock: {
    enabled: true,
    autoRequest: false,  // Request manually when needed
    reacquireOnVisibility: true,
  },
  
  // Offline Queue (stores failed requests)
  offlineQueue: {
    enabled: true,
    maxQueueSize: 100,
    maxAgeTTLHours: 24,
    autoProcessOnOnline: true,
  },
  
  // iOS Install Banner
  iosInstallBanner: {
    enabled: true,
    appName: 'My PWA',
    showOnFirstVisit: true,
    showDelay: 3000,
    dismissRememberDays: 7,
  },
  
  // Notification Analytics
  notificationAnalytics: {
    enabled: true,
    endpoint: 'https://api.example.com/analytics',  // Optional custom endpoint
  },
})

Troubleshooting

Service Worker Issues

Problem: Service worker not registering

DOMException: Failed to register a ServiceWorker

Solutions:

  1. Ensure service worker is at root path (/service-worker.js)
  2. Check HTTPS is enabled (required except on localhost)
  3. Verify service worker path in config matches actual file location
  4. Check browser console for detailed error

Problem: Push notifications not received after service worker update

Solution: The SDK handles this automatically with updateBehavior: 'auto'. If using manual mode, call pwa.getServiceWorkerManager().update() after deploy.

Permission Issues

Problem: Permission prompt never appears

Causes:

  1. User previously denied permission (check pwa.getDeviceInfo().notificationPermission)
  2. Site not served over HTTPS
  3. Browser settings blocking notifications globally

Solution for denied permission:

const deviceInfo = pwa.getDeviceInfo()
if (deviceInfo.notificationPermission === 'denied') {
  // Guide user to browser settings
  alert('Please enable notifications in your browser settings')
}

iOS-Specific Issues

Problem: Install prompt not showing on iOS Safari

Explanation: iOS Safari doesn't support the Web App Install Banner API. Use the iOS Install Banner feature:

const pwa = new CloudSignalPWA({
  // ...config
  iosInstallBanner: {
    enabled: true,
    appName: 'My App',
    showOnFirstVisit: true
  }
})

Problem: Push notifications not working on iOS

Requirements:

  • iOS 16.4+
  • PWA must be installed to home screen
  • User must grant permission after installation

Token/Auth Issues

Problem: 401 errors after token expires

Solution: Implement onTokenExpired callback:

const pwa = new CloudSignalPWA({
  // ...config
  onTokenExpired: async () => {
    // Your token refresh logic
    const newToken = await refreshMyToken()
    return newToken
  }
})

Problem: Registration not linked to user after login

Solution: Call setUserToken() followed by registerForPush():

pwa.setUserToken(newJWT)
await pwa.registerForPush()  // Re-registers with user identity

Debug Mode

Enable debug logging to troubleshoot issues:

const pwa = new CloudSignalPWA({
  // ...config
  debug: true  // Logs all SDK operations to console
})

Production Checklist

Before deploying to production, verify:

  • [ ] HTTPS enabled - Required for service workers and push notifications
  • [ ] Service worker at root - /service-worker.js or /public/service-worker.js
  • [ ] Manifest.json configured - Icons, name, start_url, display mode
  • [ ] Environment variables set - Organization ID, Service ID, Secret/JWT config
  • [ ] VAPID keys configured - In CloudSignal dashboard
  • [ ] Test on target devices - iOS Safari, Chrome, Firefox, Edge
  • [ ] Test install flow - Both automatic prompt and iOS manual instructions
  • [ ] Test push notifications - Send test notification from dashboard
  • [ ] Test offline behavior - Verify offline queue and reconnection
  • [ ] Token refresh tested - If using JWT, verify refresh flow works
  • [ ] Error handling - Graceful degradation when features unavailable

Migration from Other Services

From Firebase Cloud Messaging (FCM)

CloudSignal uses Web Push directly (not FCM). Key differences:

  • No Firebase SDK dependency
  • VAPID keys instead of FCM server key
  • Direct Web Push Protocol
// Before (FCM)
import { getMessaging, getToken } from 'firebase/messaging'
const token = await getToken(messaging, { vapidKey: '...' })

// After (CloudSignal)
import { CloudSignalPWA } from '@cloudsignal/pwa-sdk'
const registration = await pwa.registerForPush()

From OneSignal

Remove OneSignal SDK and replace with CloudSignal:

// Before
OneSignal.push(['init', { appId: '...' }])

// After
const pwa = new CloudSignalPWA({ organizationId: '...', serviceId: '...' })
await pwa.initialize()

License

MIT License - Copyright (c) 2024-2025 CloudSignal