@pocketping/widget 0.1.0 → 0.2.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.
@@ -24,10 +24,17 @@ var PocketPing = (() => {
24
24
  close: () => close,
25
25
  default: () => index_default,
26
26
  destroy: () => destroy,
27
+ getIdentity: () => getIdentity,
28
+ identify: () => identify,
27
29
  init: () => init,
30
+ offEvent: () => offEvent,
31
+ on: () => on,
32
+ onEvent: () => onEvent,
28
33
  open: () => open,
34
+ reset: () => reset,
29
35
  sendMessage: () => sendMessage,
30
- toggle: () => toggle
36
+ toggle: () => toggle,
37
+ trigger: () => trigger
31
38
  });
32
39
 
33
40
  // ../../node_modules/.pnpm/preact@10.28.2/node_modules/preact/dist/preact.mjs
@@ -1036,6 +1043,9 @@ var PocketPing = (() => {
1036
1043
  return null;
1037
1044
  }
1038
1045
 
1046
+ // src/version.ts
1047
+ var VERSION = "0.2.0";
1048
+
1039
1049
  // src/client.ts
1040
1050
  var PocketPingClient = class {
1041
1051
  constructor(config) {
@@ -1043,6 +1053,7 @@ var PocketPing = (() => {
1043
1053
  this.ws = null;
1044
1054
  this.isOpen = false;
1045
1055
  this.listeners = /* @__PURE__ */ new Map();
1056
+ this.customEventHandlers = /* @__PURE__ */ new Map();
1046
1057
  this.reconnectAttempts = 0;
1047
1058
  this.maxReconnectAttempts = 5;
1048
1059
  this.reconnectTimeout = null;
@@ -1054,6 +1065,7 @@ var PocketPing = (() => {
1054
1065
  async connect() {
1055
1066
  const visitorId = this.getOrCreateVisitorId();
1056
1067
  const storedSessionId = this.getStoredSessionId();
1068
+ const storedIdentity = this.getStoredIdentity();
1057
1069
  const response = await this.fetch("/connect", {
1058
1070
  method: "POST",
1059
1071
  body: JSON.stringify({
@@ -1067,14 +1079,17 @@ var PocketPing = (() => {
1067
1079
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1068
1080
  language: navigator.language,
1069
1081
  screenResolution: `${window.screen.width}x${window.screen.height}`
1070
- }
1082
+ },
1083
+ // Include stored identity if available
1084
+ identity: storedIdentity || void 0
1071
1085
  })
1072
1086
  });
1073
1087
  this.session = {
1074
1088
  sessionId: response.sessionId,
1075
1089
  visitorId: response.visitorId,
1076
1090
  operatorOnline: response.operatorOnline ?? false,
1077
- messages: response.messages ?? []
1091
+ messages: response.messages ?? [],
1092
+ identity: response.identity || storedIdentity || void 0
1078
1093
  };
1079
1094
  this.storeSessionId(response.sessionId);
1080
1095
  this.connectWebSocket();
@@ -1197,6 +1212,69 @@ var PocketPing = (() => {
1197
1212
  return this.fetch("/presence", { method: "GET" });
1198
1213
  }
1199
1214
  // ─────────────────────────────────────────────────────────────────
1215
+ // User Identity
1216
+ // ─────────────────────────────────────────────────────────────────
1217
+ /**
1218
+ * Identify the current user with metadata
1219
+ * Call after user logs in or when user data becomes available
1220
+ * @param identity - User identity data with required id field
1221
+ * @example
1222
+ * PocketPing.identify({
1223
+ * id: 'user_123',
1224
+ * email: 'john@example.com',
1225
+ * name: 'John Doe',
1226
+ * plan: 'pro',
1227
+ * company: 'Acme Inc'
1228
+ * })
1229
+ */
1230
+ async identify(identity) {
1231
+ if (!identity?.id) {
1232
+ throw new Error("[PocketPing] identity.id is required");
1233
+ }
1234
+ this.storeIdentity(identity);
1235
+ if (this.session) {
1236
+ try {
1237
+ await this.fetch("/identify", {
1238
+ method: "POST",
1239
+ body: JSON.stringify({
1240
+ sessionId: this.session.sessionId,
1241
+ identity
1242
+ })
1243
+ });
1244
+ this.session.identity = identity;
1245
+ this.emit("identify", identity);
1246
+ } catch (err) {
1247
+ console.error("[PocketPing] Failed to identify:", err);
1248
+ throw err;
1249
+ }
1250
+ }
1251
+ }
1252
+ /**
1253
+ * Reset the user identity and optionally start a new session
1254
+ * Call on user logout to clear user data
1255
+ * @param options - Optional settings: { newSession: boolean }
1256
+ */
1257
+ async reset(options) {
1258
+ this.clearIdentity();
1259
+ if (this.session) {
1260
+ this.session.identity = void 0;
1261
+ }
1262
+ if (options?.newSession) {
1263
+ localStorage.removeItem("pocketping_session_id");
1264
+ localStorage.removeItem("pocketping_visitor_id");
1265
+ this.disconnect();
1266
+ await this.connect();
1267
+ }
1268
+ this.emit("reset", null);
1269
+ }
1270
+ /**
1271
+ * Get the current user identity
1272
+ * @returns UserIdentity or null if not identified
1273
+ */
1274
+ getIdentity() {
1275
+ return this.session?.identity || this.getStoredIdentity();
1276
+ }
1277
+ // ─────────────────────────────────────────────────────────────────
1200
1278
  // State
1201
1279
  // ─────────────────────────────────────────────────────────────────
1202
1280
  getSession() {
@@ -1239,6 +1317,67 @@ var PocketPing = (() => {
1239
1317
  this.listeners.get(event)?.forEach((listener) => listener(data));
1240
1318
  }
1241
1319
  // ─────────────────────────────────────────────────────────────────
1320
+ // Custom Events (bidirectional)
1321
+ // ─────────────────────────────────────────────────────────────────
1322
+ /**
1323
+ * Trigger a custom event to the backend
1324
+ * @param eventName - The name of the event (e.g., 'clicked_pricing', 'viewed_demo')
1325
+ * @param data - Optional payload to send with the event
1326
+ * @example
1327
+ * PocketPing.trigger('clicked_cta', { button: 'signup', page: '/pricing' })
1328
+ */
1329
+ trigger(eventName, data) {
1330
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
1331
+ console.warn("[PocketPing] Cannot trigger event: WebSocket not connected");
1332
+ return;
1333
+ }
1334
+ const event = {
1335
+ name: eventName,
1336
+ data,
1337
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1338
+ };
1339
+ this.ws.send(JSON.stringify({
1340
+ type: "event",
1341
+ data: event
1342
+ }));
1343
+ this.emit(`event:${eventName}`, event);
1344
+ }
1345
+ /**
1346
+ * Subscribe to custom events from the backend
1347
+ * @param eventName - The name of the event to listen for (e.g., 'show_offer', 'open_chat')
1348
+ * @param handler - Callback function when event is received
1349
+ * @returns Unsubscribe function
1350
+ * @example
1351
+ * const unsubscribe = PocketPing.onEvent('show_offer', (data) => {
1352
+ * showPopup(data.message)
1353
+ * })
1354
+ */
1355
+ onEvent(eventName, handler) {
1356
+ if (!this.customEventHandlers.has(eventName)) {
1357
+ this.customEventHandlers.set(eventName, /* @__PURE__ */ new Set());
1358
+ }
1359
+ this.customEventHandlers.get(eventName).add(handler);
1360
+ return () => {
1361
+ this.customEventHandlers.get(eventName)?.delete(handler);
1362
+ };
1363
+ }
1364
+ /**
1365
+ * Unsubscribe from a custom event
1366
+ * @param eventName - The name of the event
1367
+ * @param handler - The handler to remove
1368
+ */
1369
+ offEvent(eventName, handler) {
1370
+ this.customEventHandlers.get(eventName)?.delete(handler);
1371
+ }
1372
+ emitCustomEvent(event) {
1373
+ const handlers = this.customEventHandlers.get(event.name);
1374
+ if (handlers) {
1375
+ handlers.forEach((handler) => handler(event.data, event));
1376
+ }
1377
+ this.emit("event", event);
1378
+ this.emit(`event:${event.name}`, event);
1379
+ }
1380
+ // ─────────────────────────────────────────────────────────────────
1242
1381
  // WebSocket
1243
1382
  // ─────────────────────────────────────────────────────────────────
1244
1383
  connectWebSocket() {
@@ -1336,6 +1475,40 @@ var PocketPing = (() => {
1336
1475
  }
1337
1476
  this.emit("read", readData);
1338
1477
  break;
1478
+ case "event":
1479
+ const customEvent = event.data;
1480
+ this.emitCustomEvent(customEvent);
1481
+ break;
1482
+ case "version_warning":
1483
+ const versionWarning = event.data;
1484
+ this.handleVersionWarning(versionWarning);
1485
+ break;
1486
+ }
1487
+ }
1488
+ // ─────────────────────────────────────────────────────────────────
1489
+ // Version Management
1490
+ // ─────────────────────────────────────────────────────────────────
1491
+ handleVersionWarning(warning) {
1492
+ const prefix = "[PocketPing]";
1493
+ const upgradeHint = warning.upgradeUrl ? ` Upgrade: ${warning.upgradeUrl}` : " Update your widget to the latest version.";
1494
+ switch (warning.severity) {
1495
+ case "error":
1496
+ console.error(`${prefix} \u{1F6A8} VERSION ERROR: ${warning.message}${upgradeHint}`);
1497
+ console.error(`${prefix} Current: ${warning.currentVersion}, Required: ${warning.minVersion || "unknown"}`);
1498
+ break;
1499
+ case "warning":
1500
+ console.warn(`${prefix} \u26A0\uFE0F VERSION WARNING: ${warning.message}${upgradeHint}`);
1501
+ console.warn(`${prefix} Current: ${warning.currentVersion}, Latest: ${warning.latestVersion || "unknown"}`);
1502
+ break;
1503
+ case "info":
1504
+ console.info(`${prefix} \u2139\uFE0F ${warning.message}`);
1505
+ break;
1506
+ }
1507
+ this.emit("versionWarning", warning);
1508
+ this.config.onVersionWarning?.(warning);
1509
+ if (!warning.canContinue) {
1510
+ console.error(`${prefix} Widget is incompatible with backend. Please update immediately.`);
1511
+ this.disconnect();
1339
1512
  }
1340
1513
  }
1341
1514
  scheduleReconnect() {
@@ -1381,15 +1554,46 @@ var PocketPing = (() => {
1381
1554
  ...options,
1382
1555
  headers: {
1383
1556
  "Content-Type": "application/json",
1557
+ "X-PocketPing-Version": VERSION,
1384
1558
  ...options.headers
1385
1559
  }
1386
1560
  });
1561
+ this.checkVersionHeaders(response);
1387
1562
  if (!response.ok) {
1388
1563
  const error = await response.text();
1389
1564
  throw new Error(`PocketPing API error: ${response.status} ${error}`);
1390
1565
  }
1391
1566
  return response.json();
1392
1567
  }
1568
+ checkVersionHeaders(response) {
1569
+ const versionStatus = response.headers.get("X-PocketPing-Version-Status");
1570
+ const minVersion = response.headers.get("X-PocketPing-Min-Version");
1571
+ const latestVersion = response.headers.get("X-PocketPing-Latest-Version");
1572
+ const versionMessage = response.headers.get("X-PocketPing-Version-Message");
1573
+ if (!versionStatus || versionStatus === "ok") {
1574
+ return;
1575
+ }
1576
+ let severity = "info";
1577
+ let canContinue = true;
1578
+ if (versionStatus === "deprecated") {
1579
+ severity = "warning";
1580
+ } else if (versionStatus === "unsupported") {
1581
+ severity = "error";
1582
+ canContinue = false;
1583
+ } else if (versionStatus === "outdated") {
1584
+ severity = "info";
1585
+ }
1586
+ const warning = {
1587
+ severity,
1588
+ message: versionMessage || `Widget version ${VERSION} is ${versionStatus}`,
1589
+ currentVersion: VERSION,
1590
+ minVersion: minVersion || void 0,
1591
+ latestVersion: latestVersion || void 0,
1592
+ canContinue,
1593
+ upgradeUrl: "https://docs.pocketping.io/widget/installation"
1594
+ };
1595
+ this.handleVersionWarning(warning);
1596
+ }
1393
1597
  // ─────────────────────────────────────────────────────────────────
1394
1598
  // Storage
1395
1599
  // ─────────────────────────────────────────────────────────────────
@@ -1408,6 +1612,20 @@ var PocketPing = (() => {
1408
1612
  storeSessionId(sessionId) {
1409
1613
  localStorage.setItem("pocketping_session_id", sessionId);
1410
1614
  }
1615
+ getStoredIdentity() {
1616
+ try {
1617
+ const stored = localStorage.getItem("pocketping_user_identity");
1618
+ return stored ? JSON.parse(stored) : null;
1619
+ } catch {
1620
+ return null;
1621
+ }
1622
+ }
1623
+ storeIdentity(identity) {
1624
+ localStorage.setItem("pocketping_user_identity", JSON.stringify(identity));
1625
+ }
1626
+ clearIdentity() {
1627
+ localStorage.removeItem("pocketping_user_identity");
1628
+ }
1411
1629
  generateId() {
1412
1630
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
1413
1631
  }
@@ -1460,6 +1678,48 @@ var PocketPing = (() => {
1460
1678
  }
1461
1679
  return client.sendMessage(content);
1462
1680
  }
1681
+ function trigger(eventName, data) {
1682
+ if (!client) {
1683
+ console.warn("[PocketPing] Not initialized, cannot trigger event");
1684
+ return;
1685
+ }
1686
+ client.trigger(eventName, data);
1687
+ }
1688
+ function onEvent(eventName, handler) {
1689
+ if (!client) {
1690
+ console.warn("[PocketPing] Not initialized, cannot subscribe to event");
1691
+ return () => {
1692
+ };
1693
+ }
1694
+ return client.onEvent(eventName, handler);
1695
+ }
1696
+ function offEvent(eventName, handler) {
1697
+ client?.offEvent(eventName, handler);
1698
+ }
1699
+ async function identify(identity) {
1700
+ if (!client) {
1701
+ throw new Error("[PocketPing] Not initialized");
1702
+ }
1703
+ return client.identify(identity);
1704
+ }
1705
+ async function reset(options) {
1706
+ if (!client) {
1707
+ console.warn("[PocketPing] Not initialized");
1708
+ return;
1709
+ }
1710
+ return client.reset(options);
1711
+ }
1712
+ function getIdentity() {
1713
+ return client?.getIdentity() || null;
1714
+ }
1715
+ function on(eventName, handler) {
1716
+ if (!client) {
1717
+ console.warn("[PocketPing] Not initialized, cannot subscribe to event");
1718
+ return () => {
1719
+ };
1720
+ }
1721
+ return client.on(eventName, handler);
1722
+ }
1463
1723
  if (typeof document !== "undefined") {
1464
1724
  const script = document.currentScript;
1465
1725
  if (script?.dataset.endpoint) {
@@ -1470,6 +1730,6 @@ var PocketPing = (() => {
1470
1730
  });
1471
1731
  }
1472
1732
  }
1473
- var index_default = { init, destroy, open, close, toggle, sendMessage };
1733
+ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity };
1474
1734
  return __toCommonJS(index_exports);
1475
1735
  })();