@cloudsignal/pwa-sdk 2.1.0 → 2.1.3
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/dist/{chunk-IQHSODT4.js → chunk-NSF32IGO.js} +0 -2
- package/dist/hmac-YQQ5XQWC.js +1 -0
- package/dist/index.cjs +47 -2
- package/dist/index.global.js +11 -12
- package/dist/index.js +50 -5
- package/dist/service-worker.js +0 -2
- package/package.json +2 -2
- package/dist/chunk-IQHSODT4.js.map +0 -1
- package/dist/hmac-WITZIX2O.js +0 -3
- package/dist/hmac-WITZIX2O.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.global.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/service-worker.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isValidUUID } from './chunk-
|
|
2
|
-
export { generateAuthHeaders, generateHMACSignature, isValidUUID, makeAuthenticatedRequest } from './chunk-
|
|
1
|
+
import { isValidUUID } from './chunk-NSF32IGO.js';
|
|
2
|
+
export { generateAuthHeaders, generateHMACSignature, isValidUUID, makeAuthenticatedRequest } from './chunk-NSF32IGO.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* CloudSignal PWA SDK v1.0.0
|
|
@@ -960,7 +960,7 @@ async function makeAuthenticatedRequestWithContext(authContext, method, url, bod
|
|
|
960
960
|
body
|
|
961
961
|
);
|
|
962
962
|
}
|
|
963
|
-
const { makeAuthenticatedRequest: makeAuthenticatedRequest4 } = await import('./hmac-
|
|
963
|
+
const { makeAuthenticatedRequest: makeAuthenticatedRequest4 } = await import('./hmac-YQQ5XQWC.js');
|
|
964
964
|
if (!authContext.organizationPublishableKey) {
|
|
965
965
|
throw new Error("organizationPublishableKey required for HMAC auth mode");
|
|
966
966
|
}
|
|
@@ -1038,6 +1038,14 @@ function markInstallationRegistered(organizationId, serviceId, registrationId) {
|
|
|
1038
1038
|
setStorageItem(`install_id_${organizationId}_${serviceId}`, registrationId);
|
|
1039
1039
|
return setStorageItem(key, true);
|
|
1040
1040
|
}
|
|
1041
|
+
function getLastRegisteredIdentity(organizationId, serviceId) {
|
|
1042
|
+
const key = `last_identity_${organizationId}_${serviceId}`;
|
|
1043
|
+
return getStorageItem(key);
|
|
1044
|
+
}
|
|
1045
|
+
function setLastRegisteredIdentity(organizationId, serviceId, identity) {
|
|
1046
|
+
const key = `last_identity_${organizationId}_${serviceId}`;
|
|
1047
|
+
return setStorageItem(key, identity);
|
|
1048
|
+
}
|
|
1041
1049
|
var IndexedDBStorage = class {
|
|
1042
1050
|
constructor(dbName = "CloudSignalPWA", dbVersion = 1) {
|
|
1043
1051
|
this.db = null;
|
|
@@ -1308,6 +1316,27 @@ var PushNotificationManager = class {
|
|
|
1308
1316
|
this.onPermissionDenied?.();
|
|
1309
1317
|
return null;
|
|
1310
1318
|
}
|
|
1319
|
+
const incomingEmail = options.userEmail ?? null;
|
|
1320
|
+
const incomingUserId = options.userId ?? null;
|
|
1321
|
+
const stored = getLastRegisteredIdentity(this.organizationId, this.serviceId);
|
|
1322
|
+
if (stored) {
|
|
1323
|
+
const emailChanged = stored.email !== null && incomingEmail !== null && stored.email !== incomingEmail;
|
|
1324
|
+
const userIdChanged = stored.userId !== null && incomingUserId !== null && stored.userId !== incomingUserId;
|
|
1325
|
+
if (emailChanged || userIdChanged) {
|
|
1326
|
+
this.log(
|
|
1327
|
+
`Identity changed on this device (was ${stored.email || stored.userId}, now ${incomingEmail || incomingUserId}); dropping stale push subscription`
|
|
1328
|
+
);
|
|
1329
|
+
try {
|
|
1330
|
+
const stale = await this.serviceWorkerRegistration.pushManager.getSubscription();
|
|
1331
|
+
if (stale) {
|
|
1332
|
+
await stale.unsubscribe();
|
|
1333
|
+
this.pushSubscription = null;
|
|
1334
|
+
}
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
this.log(`Stale subscription unsubscribe failed: ${err}`, "error");
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1311
1340
|
const subscription = await this.subscribeToPush();
|
|
1312
1341
|
if (!subscription) {
|
|
1313
1342
|
throw new Error("Failed to subscribe to push notifications");
|
|
@@ -1348,12 +1377,23 @@ var PushNotificationManager = class {
|
|
|
1348
1377
|
this.onTokenExpired
|
|
1349
1378
|
);
|
|
1350
1379
|
if (!response.ok) {
|
|
1380
|
+
if (response.status === 422) {
|
|
1381
|
+
const body = await response.clone().json().catch(() => null);
|
|
1382
|
+
if (body?.detail?.code === "user_identity_required") {
|
|
1383
|
+
this.log("Skipping push registration: service requires user identity");
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1351
1387
|
const errorText = await response.text();
|
|
1352
1388
|
throw new Error(`Registration failed: ${response.status} - ${errorText}`);
|
|
1353
1389
|
}
|
|
1354
1390
|
const result = await response.json();
|
|
1355
1391
|
this.registrationId = result.registration_id;
|
|
1356
1392
|
setRegistrationId(this.organizationId, this.serviceId, this.registrationId);
|
|
1393
|
+
setLastRegisteredIdentity(this.organizationId, this.serviceId, {
|
|
1394
|
+
email: incomingEmail,
|
|
1395
|
+
userId: incomingUserId
|
|
1396
|
+
});
|
|
1357
1397
|
const registration = {
|
|
1358
1398
|
registrationId: result.registration_id,
|
|
1359
1399
|
status: result.status || "active",
|
|
@@ -1414,6 +1454,13 @@ var PushNotificationManager = class {
|
|
|
1414
1454
|
this.onTokenExpired
|
|
1415
1455
|
);
|
|
1416
1456
|
if (!response.ok) {
|
|
1457
|
+
if (response.status === 422) {
|
|
1458
|
+
const body = await response.clone().json().catch(() => null);
|
|
1459
|
+
if (body?.detail?.code === "user_identity_required") {
|
|
1460
|
+
this.log("Skipping install-only registration: service requires user identity");
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1417
1464
|
const errorText = await response.text();
|
|
1418
1465
|
throw new Error(`Installation registration failed: ${response.status} - ${errorText}`);
|
|
1419
1466
|
}
|
|
@@ -4099,5 +4146,3 @@ var NotificationPermissionPrompt = class {
|
|
|
4099
4146
|
var VERSION = "1.2.0";
|
|
4100
4147
|
|
|
4101
4148
|
export { CloudSignalPWA, DeviceDetector, HeartbeatManager, IOSInstallBanner, IOS_BANNER_TRANSLATIONS, IndexedDBStorage, InstallationManager, NOTIFICATION_PROMPT_TRANSLATIONS, NotificationPermissionPrompt, OfflineQueueManager, PushNotificationManager, ServiceWorkerManager, VERSION, WakeLockManager, createAuthContext, CloudSignalPWA_default as default, detectBrowserLanguage, deviceDetector, generateBrowserFingerprint, generateJWTHeaders, generateTrackingId, getRegistrationId, getStorageItem, makeAuthenticatedRequestWithContext, makeJWTAuthenticatedRequest, removeRegistrationId, removeStorageItem, setRegistrationId, setStorageItem, updateAuthToken };
|
|
4102
|
-
//# sourceMappingURL=index.js.map
|
|
4103
|
-
//# sourceMappingURL=index.js.map
|
package/dist/service-worker.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudsignal/pwa-sdk",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
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",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"build:watch": "tsup --watch",
|
|
32
32
|
"typecheck": "tsc --noEmit",
|
|
33
33
|
"clean": "rm -rf dist",
|
|
34
|
-
"prepublishOnly": "npm run clean && npm run build",
|
|
34
|
+
"prepublishOnly": "npm run clean && npm run build && bash ../scripts/check-publish-safe.sh .",
|
|
35
35
|
"test": "vitest run",
|
|
36
36
|
"test:watch": "vitest",
|
|
37
37
|
"test:coverage": "vitest run --coverage"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/hmac.ts"],"names":[],"mappings":"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","file":"chunk-IQHSODT4.js","sourcesContent":["/**\n * HMAC Authentication Utilities\n * Implements CloudSignal's HMAC signature scheme for API authentication\n */\n\n/**\n * Convert ArrayBuffer to hex string\n */\nfunction toHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Convert ArrayBuffer to base64 string\n */\nfunction toBase64(buffer: ArrayBuffer): string {\n return btoa(String.fromCharCode(...new Uint8Array(buffer)))\n}\n\n/**\n * Generate HMAC signature for CloudSignal API requests\n *\n * @param secret - Signing key (organization publishable key, pk_*)\n * @param organizationId - Organization UUID\n * @param timestamp - Unix timestamp string\n * @param method - HTTP method (GET, POST, etc.)\n * @param url - Full URL or path\n * @param body - Request body (optional)\n * @returns Base64-encoded HMAC signature\n */\nexport async function generateHMACSignature(\n secret: string,\n organizationId: string,\n timestamp: string,\n method: string,\n url: string,\n body: string = ''\n): Promise<string> {\n const encoder = new TextEncoder()\n\n // Parse URL to extract path and query\n let path: string\n let query: string\n\n try {\n const urlObj = url.startsWith('http')\n ? new URL(url)\n : new URL(url, 'https://pwa.cloudsignal.app')\n path = urlObj.pathname\n query = urlObj.search ? urlObj.search.slice(1) : ''\n } catch {\n // Fallback for simple paths\n const queryIndex = url.indexOf('?')\n if (queryIndex > -1) {\n path = url.slice(0, queryIndex)\n query = url.slice(queryIndex + 1)\n } else {\n path = url\n query = ''\n }\n }\n\n // Build canonical string parts\n const canonicalParts = [\n method.toUpperCase(),\n path,\n query,\n organizationId,\n timestamp,\n ]\n\n // Add body hash if body is present\n if (body && body.length > 0) {\n const bodyHashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(body))\n const bodyHashHex = toHex(bodyHashBuffer)\n canonicalParts.push(bodyHashHex)\n }\n\n // Join parts with newlines\n const canonicalString = canonicalParts.join('\\n')\n\n // Import secret as HMAC key\n const key = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n\n // Sign the canonical string\n const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(canonicalString))\n\n return toBase64(signature)\n}\n\n/**\n * Generate authentication headers for CloudSignal API requests\n *\n * @param organizationId - Organization UUID\n * @param organizationPublishableKey - Organization publishable key (pk_*)\n * @param method - HTTP method\n * @param url - Request URL\n * @param body - Request body (optional)\n * @returns Headers object with authentication headers\n */\nexport async function generateAuthHeaders(\n organizationId: string,\n organizationPublishableKey: string,\n method: string,\n url: string,\n body?: string\n): Promise<Record<string, string>> {\n const timestamp = Math.floor(Date.now() / 1000).toString()\n\n const signature = await generateHMACSignature(\n organizationPublishableKey,\n organizationId,\n timestamp,\n method,\n url,\n body || ''\n )\n\n return {\n 'X-CloudSignal-Organization-ID': organizationId,\n 'X-CloudSignal-Timestamp': timestamp,\n 'X-CloudSignal-Signature': signature,\n 'Content-Type': 'application/json',\n }\n}\n\n/**\n * Make an authenticated request to CloudSignal API\n *\n * @param organizationId - Organization UUID\n * @param organizationPublishableKey - Organization publishable key (pk_*)\n * @param method - HTTP method\n * @param url - Request URL\n * @param body - Request body (optional)\n * @returns Fetch Response\n */\nexport async function makeAuthenticatedRequest(\n organizationId: string,\n organizationPublishableKey: string,\n method: string,\n url: string,\n body?: Record<string, any>\n): Promise<Response> {\n const bodyStr = body ? JSON.stringify(body) : undefined\n\n const headers = await generateAuthHeaders(\n organizationId,\n organizationPublishableKey,\n method,\n url,\n bodyStr\n )\n\n const options: RequestInit = {\n method,\n headers,\n }\n\n if (bodyStr) {\n options.body = bodyStr\n }\n\n return fetch(url, options)\n}\n\n/**\n * Validate UUID format\n * \n * @param value - String to validate\n * @returns Whether the string is a valid UUID\n */\nexport function isValidUUID(value: string | null | undefined): boolean {\n if (!value || typeof value !== 'string') return false\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)\n}\n"]}
|
package/dist/hmac-WITZIX2O.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"hmac-WITZIX2O.js"}
|