@cloudsignal/pwa-sdk 2.0.0 → 2.1.2

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 CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.1.0] - 2026-04-23
9
+
10
+ ### Added
11
+
12
+ - **Auto-register PWA installations on `initialize()`** — when the SDK detects it is running in standalone / installed mode, it automatically POSTs to `/api/v1/registration/install-only` so the device shows up in the Clients tab without requiring the user to enable push first. A subsequent `registerForPush()` call upgrades the same row server-side (matched by browser fingerprint).
13
+ - `registerInstallation()` public method on `CloudSignalPWA` for manual install tracking.
14
+ - `install:registered` event fires after the backend acknowledges the install registration. Payload: `{ registrationId }`.
15
+ - Installation registrations are guarded by a localStorage flag (`isInstallationRegistered` / `markInstallationRegistered`) so repeat `initialize()` calls within the same storage session are no-ops.
16
+
17
+ ### Fixed
18
+
19
+ - Restores the auto-install-registration feature originally shipped in 1.2.3 that was inadvertently removed in commit `42935abf`.
20
+
8
21
  ## [2.0.0] - 2026-04-23
9
22
 
10
23
  ### Changed (BREAKING)
@@ -93,5 +93,3 @@ function isValidUUID(value) {
93
93
  }
94
94
 
95
95
  export { generateAuthHeaders, generateHMACSignature, isValidUUID, makeAuthenticatedRequest };
96
- //# sourceMappingURL=chunk-IQHSODT4.js.map
97
- //# sourceMappingURL=chunk-IQHSODT4.js.map
@@ -0,0 +1 @@
1
+ export { generateAuthHeaders, generateHMACSignature, isValidUUID, makeAuthenticatedRequest } from './chunk-NSF32IGO.js';
package/dist/index.cjs CHANGED
@@ -1141,6 +1141,15 @@ function removeRegistrationId(organizationId, serviceId) {
1141
1141
  const key = `registration_${organizationId}_${serviceId}`;
1142
1142
  return removeStorageItem(key);
1143
1143
  }
1144
+ function isInstallationRegistered(organizationId, serviceId) {
1145
+ const key = `install_registered_${organizationId}_${serviceId}`;
1146
+ return getStorageItem(key) === true;
1147
+ }
1148
+ function markInstallationRegistered(organizationId, serviceId, registrationId) {
1149
+ const key = `install_registered_${organizationId}_${serviceId}`;
1150
+ setStorageItem(`install_id_${organizationId}_${serviceId}`, registrationId);
1151
+ return setStorageItem(key, true);
1152
+ }
1144
1153
  var IndexedDBStorage = class {
1145
1154
  constructor(dbName = "CloudSignalPWA", dbVersion = 1) {
1146
1155
  this.db = null;
@@ -1451,6 +1460,13 @@ var PushNotificationManager = class {
1451
1460
  this.onTokenExpired
1452
1461
  );
1453
1462
  if (!response.ok) {
1463
+ if (response.status === 422) {
1464
+ const body = await response.clone().json().catch(() => null);
1465
+ if (body?.detail?.code === "user_identity_required") {
1466
+ this.log("Skipping push registration: service requires user identity");
1467
+ return null;
1468
+ }
1469
+ }
1454
1470
  const errorText = await response.text();
1455
1471
  throw new Error(`Registration failed: ${response.status} - ${errorText}`);
1456
1472
  }
@@ -1473,6 +1489,71 @@ var PushNotificationManager = class {
1473
1489
  return null;
1474
1490
  }
1475
1491
  }
1492
+ /**
1493
+ * Register a PWA installation without a push subscription.
1494
+ *
1495
+ * Tracks users who installed the PWA (standalone display mode) but
1496
+ * haven't granted notification permission yet. A subsequent
1497
+ * ``registerForPush()`` call upgrades the same row to a full push
1498
+ * registration (matched server-side by browser fingerprint).
1499
+ *
1500
+ * Guarded by a localStorage flag so repeat ``initialize()`` calls in
1501
+ * the same storage session are no-ops.
1502
+ */
1503
+ async registerInstallation() {
1504
+ try {
1505
+ if (isInstallationRegistered(this.organizationId, this.serviceId)) {
1506
+ this.log("Installation already registered, skipping");
1507
+ return null;
1508
+ }
1509
+ const fingerprint = await generateBrowserFingerprint();
1510
+ const deviceInfo = this.deviceDetector.getDeviceInfo();
1511
+ const platformInfo = this.deviceDetector.getPlatformInfo();
1512
+ const installationData = {
1513
+ service_id: this.serviceId,
1514
+ browser_fingerprint: fingerprint || void 0,
1515
+ device_type: deviceInfo.deviceType,
1516
+ device_model: deviceInfo.deviceModel,
1517
+ browser_name: platformInfo.browser,
1518
+ browser_version: platformInfo.browserVersion,
1519
+ os_name: platformInfo.os,
1520
+ os_version: platformInfo.osVersion,
1521
+ user_agent: platformInfo.userAgent,
1522
+ display_mode: "standalone",
1523
+ is_installed: true,
1524
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1525
+ language: navigator.language || "en-US"
1526
+ };
1527
+ const url = `${this.serviceUrl}/api/v1/registration/install-only`;
1528
+ const response = await makeAuthenticatedRequestWithContext(
1529
+ this.authContext,
1530
+ "POST",
1531
+ url,
1532
+ installationData,
1533
+ this.onTokenExpired
1534
+ );
1535
+ if (!response.ok) {
1536
+ if (response.status === 422) {
1537
+ const body = await response.clone().json().catch(() => null);
1538
+ if (body?.detail?.code === "user_identity_required") {
1539
+ this.log("Skipping install-only registration: service requires user identity");
1540
+ return null;
1541
+ }
1542
+ }
1543
+ const errorText = await response.text();
1544
+ throw new Error(`Installation registration failed: ${response.status} - ${errorText}`);
1545
+ }
1546
+ const result = await response.json();
1547
+ markInstallationRegistered(this.organizationId, this.serviceId, result.registration_id);
1548
+ this.log(`Installation registered: ${result.registration_id}`);
1549
+ return { registrationId: result.registration_id };
1550
+ } catch (error) {
1551
+ const err = error instanceof Error ? error : new Error(String(error));
1552
+ this.log(`Installation registration failed: ${err.message}`, "error");
1553
+ this.onError?.(err);
1554
+ return null;
1555
+ }
1556
+ }
1476
1557
  /**
1477
1558
  * Unregister from push notifications
1478
1559
  */
@@ -3258,11 +3339,19 @@ var CloudSignalPWA = class {
3258
3339
  this.configureNotificationAnalytics(swReg);
3259
3340
  }
3260
3341
  if (this.iosInstallBanner && this.config.iosInstallBanner?.showOnFirstVisit !== false) {
3261
- const installState = this.installationManager.getState();
3262
- if (!installState.isInstalled) {
3342
+ const installState2 = this.installationManager.getState();
3343
+ if (!installState2.isInstalled) {
3263
3344
  this.iosInstallBanner.show();
3264
3345
  }
3265
3346
  }
3347
+ const installState = this.installationManager.getState();
3348
+ if (installState.isInstalled) {
3349
+ this.log("PWA installation detected, registering\u2026");
3350
+ const installResult = await this.pushNotificationManager.registerInstallation();
3351
+ if (installResult) {
3352
+ this.emit("install:registered", { registrationId: installResult.registrationId });
3353
+ }
3354
+ }
3266
3355
  this.initialized = true;
3267
3356
  const result = {
3268
3357
  success: true,
@@ -4171,5 +4260,3 @@ exports.removeStorageItem = removeStorageItem;
4171
4260
  exports.setRegistrationId = setRegistrationId;
4172
4261
  exports.setStorageItem = setStorageItem;
4173
4262
  exports.updateAuthToken = updateAuthToken;
4174
- //# sourceMappingURL=index.cjs.map
4175
- //# sourceMappingURL=index.cjs.map
package/dist/index.d.mts CHANGED
@@ -521,7 +521,7 @@ interface NotificationAction {
521
521
  /**
522
522
  * Event types emitted by the SDK
523
523
  */
524
- type PWAEvent = 'install:available' | 'install:accepted' | 'install:dismissed' | 'install:completed' | 'install:error' | 'push:registered' | 'push:unregistered' | 'push:updated' | 'push:error' | 'push:received' | 'push:clicked' | 'permission:granted' | 'permission:denied' | 'permission:prompt' | 'sw:registered' | 'sw:updated' | 'sw:error' | 'sw:activated' | 'config:loaded' | 'config:error' | 'heartbeat:started' | 'heartbeat:stopped' | 'heartbeat:sent' | 'heartbeat:error' | 'heartbeat:intervalChanged' | 'heartbeat:pausedForBattery' | 'heartbeat:resumedFromBattery' | 'network:online' | 'network:offline' | 'state:changed' | 'wakeLock:acquired' | 'wakeLock:released' | 'wakeLock:error' | 'offlineQueue:queued' | 'offlineQueue:processed' | 'offlineQueue:flushed' | 'iosBanner:shown' | 'iosBanner:dismissed' | 'iosBanner:installClicked' | 'auth:tokenUpdated';
524
+ type PWAEvent = 'install:available' | 'install:accepted' | 'install:dismissed' | 'install:completed' | 'install:registered' | 'install:error' | 'push:registered' | 'push:unregistered' | 'push:updated' | 'push:error' | 'push:received' | 'push:clicked' | 'permission:granted' | 'permission:denied' | 'permission:prompt' | 'sw:registered' | 'sw:updated' | 'sw:error' | 'sw:activated' | 'config:loaded' | 'config:error' | 'heartbeat:started' | 'heartbeat:stopped' | 'heartbeat:sent' | 'heartbeat:error' | 'heartbeat:intervalChanged' | 'heartbeat:pausedForBattery' | 'heartbeat:resumedFromBattery' | 'network:online' | 'network:offline' | 'state:changed' | 'wakeLock:acquired' | 'wakeLock:released' | 'wakeLock:error' | 'offlineQueue:queued' | 'offlineQueue:processed' | 'offlineQueue:flushed' | 'iosBanner:shown' | 'iosBanner:dismissed' | 'iosBanner:installClicked' | 'auth:tokenUpdated';
525
525
  /**
526
526
  * Event handler function
527
527
  */
@@ -1364,6 +1364,20 @@ declare class PushNotificationManager {
1364
1364
  * Register for push notifications
1365
1365
  */
1366
1366
  register(options?: RegisterOptions): Promise<Registration | null>;
1367
+ /**
1368
+ * Register a PWA installation without a push subscription.
1369
+ *
1370
+ * Tracks users who installed the PWA (standalone display mode) but
1371
+ * haven't granted notification permission yet. A subsequent
1372
+ * ``registerForPush()`` call upgrades the same row to a full push
1373
+ * registration (matched server-side by browser fingerprint).
1374
+ *
1375
+ * Guarded by a localStorage flag so repeat ``initialize()`` calls in
1376
+ * the same storage session are no-ops.
1377
+ */
1378
+ registerInstallation(): Promise<{
1379
+ registrationId: string;
1380
+ } | null>;
1367
1381
  /**
1368
1382
  * Unregister from push notifications
1369
1383
  */
package/dist/index.d.ts CHANGED
@@ -521,7 +521,7 @@ interface NotificationAction {
521
521
  /**
522
522
  * Event types emitted by the SDK
523
523
  */
524
- type PWAEvent = 'install:available' | 'install:accepted' | 'install:dismissed' | 'install:completed' | 'install:error' | 'push:registered' | 'push:unregistered' | 'push:updated' | 'push:error' | 'push:received' | 'push:clicked' | 'permission:granted' | 'permission:denied' | 'permission:prompt' | 'sw:registered' | 'sw:updated' | 'sw:error' | 'sw:activated' | 'config:loaded' | 'config:error' | 'heartbeat:started' | 'heartbeat:stopped' | 'heartbeat:sent' | 'heartbeat:error' | 'heartbeat:intervalChanged' | 'heartbeat:pausedForBattery' | 'heartbeat:resumedFromBattery' | 'network:online' | 'network:offline' | 'state:changed' | 'wakeLock:acquired' | 'wakeLock:released' | 'wakeLock:error' | 'offlineQueue:queued' | 'offlineQueue:processed' | 'offlineQueue:flushed' | 'iosBanner:shown' | 'iosBanner:dismissed' | 'iosBanner:installClicked' | 'auth:tokenUpdated';
524
+ type PWAEvent = 'install:available' | 'install:accepted' | 'install:dismissed' | 'install:completed' | 'install:registered' | 'install:error' | 'push:registered' | 'push:unregistered' | 'push:updated' | 'push:error' | 'push:received' | 'push:clicked' | 'permission:granted' | 'permission:denied' | 'permission:prompt' | 'sw:registered' | 'sw:updated' | 'sw:error' | 'sw:activated' | 'config:loaded' | 'config:error' | 'heartbeat:started' | 'heartbeat:stopped' | 'heartbeat:sent' | 'heartbeat:error' | 'heartbeat:intervalChanged' | 'heartbeat:pausedForBattery' | 'heartbeat:resumedFromBattery' | 'network:online' | 'network:offline' | 'state:changed' | 'wakeLock:acquired' | 'wakeLock:released' | 'wakeLock:error' | 'offlineQueue:queued' | 'offlineQueue:processed' | 'offlineQueue:flushed' | 'iosBanner:shown' | 'iosBanner:dismissed' | 'iosBanner:installClicked' | 'auth:tokenUpdated';
525
525
  /**
526
526
  * Event handler function
527
527
  */
@@ -1364,6 +1364,20 @@ declare class PushNotificationManager {
1364
1364
  * Register for push notifications
1365
1365
  */
1366
1366
  register(options?: RegisterOptions): Promise<Registration | null>;
1367
+ /**
1368
+ * Register a PWA installation without a push subscription.
1369
+ *
1370
+ * Tracks users who installed the PWA (standalone display mode) but
1371
+ * haven't granted notification permission yet. A subsequent
1372
+ * ``registerForPush()`` call upgrades the same row to a full push
1373
+ * registration (matched server-side by browser fingerprint).
1374
+ *
1375
+ * Guarded by a localStorage flag so repeat ``initialize()`` calls in
1376
+ * the same storage session are no-ops.
1377
+ */
1378
+ registerInstallation(): Promise<{
1379
+ registrationId: string;
1380
+ } | null>;
1367
1381
  /**
1368
1382
  * Unregister from push notifications
1369
1383
  */