@pocketping/widget 0.3.3 → 0.3.5

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/index.js CHANGED
@@ -1,46 +1,9 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
1
  // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- close: () => close,
24
- default: () => index_default,
25
- destroy: () => destroy,
26
- getIdentity: () => getIdentity,
27
- identify: () => identify,
28
- init: () => init,
29
- offEvent: () => offEvent,
30
- on: () => on,
31
- onEvent: () => onEvent,
32
- open: () => open,
33
- reset: () => reset,
34
- sendMessage: () => sendMessage,
35
- toggle: () => toggle,
36
- trigger: () => trigger
37
- });
38
- module.exports = __toCommonJS(index_exports);
39
- var import_preact2 = require("preact");
2
+ import { render, h as h2 } from "preact";
40
3
 
41
4
  // src/components/ChatWidget.tsx
42
- var import_preact = require("preact");
43
- var import_hooks = require("preact/hooks");
5
+ import { Fragment } from "preact";
6
+ import { useState, useEffect, useRef, useCallback } from "preact/hooks";
44
7
 
45
8
  // src/components/styles.ts
46
9
  function styles(primaryColor, theme) {
@@ -385,17 +348,17 @@ function styles(primaryColor, theme) {
385
348
  }
386
349
 
387
350
  // src/components/ChatWidget.tsx
388
- var import_jsx_runtime = require("preact/jsx-runtime");
351
+ import { Fragment as Fragment2, jsx, jsxs } from "preact/jsx-runtime";
389
352
  function ChatWidget({ client: client2, config }) {
390
- const [isOpen, setIsOpen] = (0, import_hooks.useState)(false);
391
- const [messages, setMessages] = (0, import_hooks.useState)([]);
392
- const [inputValue, setInputValue] = (0, import_hooks.useState)("");
393
- const [isTyping, setIsTyping] = (0, import_hooks.useState)(false);
394
- const [operatorOnline, setOperatorOnline] = (0, import_hooks.useState)(false);
395
- const [isConnected, setIsConnected] = (0, import_hooks.useState)(false);
396
- const messagesEndRef = (0, import_hooks.useRef)(null);
397
- const inputRef = (0, import_hooks.useRef)(null);
398
- (0, import_hooks.useEffect)(() => {
353
+ const [isOpen, setIsOpen] = useState(false);
354
+ const [messages, setMessages] = useState([]);
355
+ const [inputValue, setInputValue] = useState("");
356
+ const [isTyping, setIsTyping] = useState(false);
357
+ const [operatorOnline, setOperatorOnline] = useState(false);
358
+ const [isConnected, setIsConnected] = useState(false);
359
+ const messagesEndRef = useRef(null);
360
+ const inputRef = useRef(null);
361
+ useEffect(() => {
399
362
  const unsubOpen = client2.on("openChange", setIsOpen);
400
363
  const unsubMessage = client2.on("message", () => {
401
364
  setMessages([...client2.getMessages()]);
@@ -424,15 +387,15 @@ function ChatWidget({ client: client2, config }) {
424
387
  unsubConnect();
425
388
  };
426
389
  }, [client2]);
427
- (0, import_hooks.useEffect)(() => {
390
+ useEffect(() => {
428
391
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
429
392
  }, [messages]);
430
- (0, import_hooks.useEffect)(() => {
393
+ useEffect(() => {
431
394
  if (isOpen) {
432
395
  inputRef.current?.focus();
433
396
  }
434
397
  }, [isOpen]);
435
- const markMessagesAsRead = (0, import_hooks.useCallback)(() => {
398
+ const markMessagesAsRead = useCallback(() => {
436
399
  if (!isOpen || !isConnected) return;
437
400
  const unreadMessages = messages.filter(
438
401
  (msg) => msg.sender !== "visitor" && msg.status !== "read"
@@ -442,14 +405,14 @@ function ChatWidget({ client: client2, config }) {
442
405
  client2.sendReadStatus(messageIds, "read");
443
406
  }
444
407
  }, [isOpen, isConnected, messages, client2]);
445
- (0, import_hooks.useEffect)(() => {
408
+ useEffect(() => {
446
409
  if (!isOpen || !isConnected) return;
447
410
  const timer = setTimeout(() => {
448
411
  markMessagesAsRead();
449
412
  }, 1e3);
450
413
  return () => clearTimeout(timer);
451
414
  }, [isOpen, isConnected, messages, markMessagesAsRead]);
452
- (0, import_hooks.useEffect)(() => {
415
+ useEffect(() => {
453
416
  const handleVisibilityChange = () => {
454
417
  if (document.visibilityState === "visible" && isOpen) {
455
418
  markMessagesAsRead();
@@ -458,7 +421,7 @@ function ChatWidget({ client: client2, config }) {
458
421
  document.addEventListener("visibilitychange", handleVisibilityChange);
459
422
  return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
460
423
  }, [isOpen, markMessagesAsRead]);
461
- (0, import_hooks.useEffect)(() => {
424
+ useEffect(() => {
462
425
  const unsubRead = client2.on(
463
426
  "read",
464
427
  () => {
@@ -488,71 +451,71 @@ function ChatWidget({ client: client2, config }) {
488
451
  const position = config.position ?? "bottom-right";
489
452
  const theme = getTheme(config.theme ?? "auto");
490
453
  const primaryColor = config.primaryColor ?? "#6366f1";
491
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_preact.Fragment, { children: [
492
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: styles(primaryColor, theme) }),
493
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
454
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
455
+ /* @__PURE__ */ jsx("style", { children: styles(primaryColor, theme) }),
456
+ /* @__PURE__ */ jsxs(
494
457
  "button",
495
458
  {
496
459
  class: `pp-toggle pp-${position}`,
497
460
  onClick: () => client2.toggleOpen(),
498
461
  "aria-label": isOpen ? "Close chat" : "Open chat",
499
462
  children: [
500
- isOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatIcon, {}),
501
- !isOpen && operatorOnline && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-online-dot" })
463
+ isOpen ? /* @__PURE__ */ jsx(CloseIcon, {}) : /* @__PURE__ */ jsx(ChatIcon, {}),
464
+ !isOpen && operatorOnline && /* @__PURE__ */ jsx("span", { class: "pp-online-dot" })
502
465
  ]
503
466
  }
504
467
  ),
505
- isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: `pp-window pp-${position} pp-theme-${theme}`, children: [
506
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-header", children: [
507
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-header-info", children: [
508
- config.operatorAvatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: config.operatorAvatar, alt: "", class: "pp-avatar" }),
509
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
510
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-header-title", children: config.operatorName ?? "Support" }),
511
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-header-status", children: operatorOnline ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
512
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-status-dot pp-online" }),
468
+ isOpen && /* @__PURE__ */ jsxs("div", { class: `pp-window pp-${position} pp-theme-${theme}`, children: [
469
+ /* @__PURE__ */ jsxs("div", { class: "pp-header", children: [
470
+ /* @__PURE__ */ jsxs("div", { class: "pp-header-info", children: [
471
+ config.operatorAvatar && /* @__PURE__ */ jsx("img", { src: config.operatorAvatar, alt: "", class: "pp-avatar" }),
472
+ /* @__PURE__ */ jsxs("div", { children: [
473
+ /* @__PURE__ */ jsx("div", { class: "pp-header-title", children: config.operatorName ?? "Support" }),
474
+ /* @__PURE__ */ jsx("div", { class: "pp-header-status", children: operatorOnline ? /* @__PURE__ */ jsxs(Fragment2, { children: [
475
+ /* @__PURE__ */ jsx("span", { class: "pp-status-dot pp-online" }),
513
476
  " Online"
514
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
515
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-status-dot" }),
477
+ ] }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
478
+ /* @__PURE__ */ jsx("span", { class: "pp-status-dot" }),
516
479
  " Away"
517
480
  ] }) })
518
481
  ] })
519
482
  ] }),
520
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
483
+ /* @__PURE__ */ jsx(
521
484
  "button",
522
485
  {
523
486
  class: "pp-close-btn",
524
487
  onClick: () => client2.setOpen(false),
525
488
  "aria-label": "Close chat",
526
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {})
489
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
527
490
  }
528
491
  )
529
492
  ] }),
530
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-messages", children: [
531
- config.welcomeMessage && messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-welcome", children: config.welcomeMessage }),
532
- messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
493
+ /* @__PURE__ */ jsxs("div", { class: "pp-messages", children: [
494
+ config.welcomeMessage && messages.length === 0 && /* @__PURE__ */ jsx("div", { class: "pp-welcome", children: config.welcomeMessage }),
495
+ messages.map((msg) => /* @__PURE__ */ jsxs(
533
496
  "div",
534
497
  {
535
498
  class: `pp-message pp-message-${msg.sender}`,
536
499
  children: [
537
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-message-content", children: msg.content }),
538
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message-time", children: [
500
+ /* @__PURE__ */ jsx("div", { class: "pp-message-content", children: msg.content }),
501
+ /* @__PURE__ */ jsxs("div", { class: "pp-message-time", children: [
539
502
  formatTime(msg.timestamp),
540
- msg.sender === "ai" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-ai-badge", children: "AI" }),
541
- msg.sender === "visitor" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: `pp-status pp-status-${msg.status ?? "sent"}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusIcon, { status: msg.status }) })
503
+ msg.sender === "ai" && /* @__PURE__ */ jsx("span", { class: "pp-ai-badge", children: "AI" }),
504
+ msg.sender === "visitor" && /* @__PURE__ */ jsx("span", { class: `pp-status pp-status-${msg.status ?? "sent"}`, children: /* @__PURE__ */ jsx(StatusIcon, { status: msg.status }) })
542
505
  ] })
543
506
  ]
544
507
  },
545
508
  msg.id
546
509
  )),
547
- isTyping && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message pp-message-operator pp-typing", children: [
548
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
549
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
550
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {})
510
+ isTyping && /* @__PURE__ */ jsxs("div", { class: "pp-message pp-message-operator pp-typing", children: [
511
+ /* @__PURE__ */ jsx("span", {}),
512
+ /* @__PURE__ */ jsx("span", {}),
513
+ /* @__PURE__ */ jsx("span", {})
551
514
  ] }),
552
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: messagesEndRef })
515
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
553
516
  ] }),
554
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { class: "pp-input-form", onSubmit: handleSubmit, children: [
555
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
517
+ /* @__PURE__ */ jsxs("form", { class: "pp-input-form", onSubmit: handleSubmit, children: [
518
+ /* @__PURE__ */ jsx(
556
519
  "input",
557
520
  {
558
521
  ref: inputRef,
@@ -564,20 +527,20 @@ function ChatWidget({ client: client2, config }) {
564
527
  disabled: !isConnected
565
528
  }
566
529
  ),
567
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
530
+ /* @__PURE__ */ jsx(
568
531
  "button",
569
532
  {
570
533
  type: "submit",
571
534
  class: "pp-send-btn",
572
535
  disabled: !inputValue.trim() || !isConnected,
573
536
  "aria-label": "Send message",
574
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SendIcon, {})
537
+ children: /* @__PURE__ */ jsx(SendIcon, {})
575
538
  }
576
539
  )
577
540
  ] }),
578
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-footer", children: [
541
+ /* @__PURE__ */ jsxs("div", { class: "pp-footer", children: [
579
542
  "Powered by ",
580
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "https://pocketping.io", target: "_blank", rel: "noopener", children: "PocketPing" })
543
+ /* @__PURE__ */ jsx("a", { href: "https://pocketping.io", target: "_blank", rel: "noopener", children: "PocketPing" })
581
544
  ] })
582
545
  ] })
583
546
  ] });
@@ -603,41 +566,41 @@ function formatTime(timestamp) {
603
566
  return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
604
567
  }
605
568
  function ChatIcon() {
606
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
569
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
607
570
  }
608
571
  function CloseIcon() {
609
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
610
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
611
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
572
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
573
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
574
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
612
575
  ] });
613
576
  }
614
577
  function SendIcon() {
615
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
616
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
617
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
578
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
579
+ /* @__PURE__ */ jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
580
+ /* @__PURE__ */ jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
618
581
  ] });
619
582
  }
620
583
  function StatusIcon({ status }) {
621
584
  if (!status || status === "sending" || status === "sent") {
622
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", "stroke-width": "2", class: "pp-check", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "3 8 7 12 13 4" }) });
585
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", "stroke-width": "2", class: "pp-check", children: /* @__PURE__ */ jsx("polyline", { points: "3 8 7 12 13 4" }) });
623
586
  }
624
587
  if (status === "delivered") {
625
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 20 16", fill: "none", stroke: "currentColor", "stroke-width": "2", class: "pp-check-double", children: [
626
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "1 8 5 12 11 4" }),
627
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "7 8 11 12 17 4" })
588
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 16", fill: "none", stroke: "currentColor", "stroke-width": "2", class: "pp-check-double", children: [
589
+ /* @__PURE__ */ jsx("polyline", { points: "1 8 5 12 11 4" }),
590
+ /* @__PURE__ */ jsx("polyline", { points: "7 8 11 12 17 4" })
628
591
  ] });
629
592
  }
630
593
  if (status === "read") {
631
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 20 16", fill: "none", stroke: "currentColor", "stroke-width": "2", class: "pp-check-double pp-check-read", children: [
632
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "1 8 5 12 11 4" }),
633
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "7 8 11 12 17 4" })
594
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 16", fill: "none", stroke: "currentColor", "stroke-width": "2", class: "pp-check-double pp-check-read", children: [
595
+ /* @__PURE__ */ jsx("polyline", { points: "1 8 5 12 11 4" }),
596
+ /* @__PURE__ */ jsx("polyline", { points: "7 8 11 12 17 4" })
634
597
  ] });
635
598
  }
636
599
  return null;
637
600
  }
638
601
 
639
602
  // src/version.ts
640
- var VERSION = "0.3.3";
603
+ var VERSION = "0.3.5";
641
604
 
642
605
  // src/client.ts
643
606
  var PocketPingClient = class {
@@ -650,6 +613,13 @@ var PocketPingClient = class {
650
613
  this.reconnectAttempts = 0;
651
614
  this.maxReconnectAttempts = 5;
652
615
  this.reconnectTimeout = null;
616
+ this.pollingTimeout = null;
617
+ this.pollingFailures = 0;
618
+ this.maxPollingFailures = 10;
619
+ this.trackedElementCleanups = [];
620
+ this.currentTrackedElements = [];
621
+ this.inspectorMode = false;
622
+ this.inspectorCleanup = null;
653
623
  this.config = config;
654
624
  }
655
625
  // ─────────────────────────────────────────────────────────────────
@@ -659,11 +629,14 @@ var PocketPingClient = class {
659
629
  const visitorId = this.getOrCreateVisitorId();
660
630
  const storedSessionId = this.getStoredSessionId();
661
631
  const storedIdentity = this.getStoredIdentity();
632
+ const urlParams = new URLSearchParams(window.location.search);
633
+ const inspectorToken = urlParams.get("pp_inspector");
662
634
  const response = await this.fetch("/connect", {
663
635
  method: "POST",
664
636
  body: JSON.stringify({
665
637
  visitorId,
666
638
  sessionId: storedSessionId,
639
+ inspectorToken: inspectorToken || void 0,
667
640
  metadata: {
668
641
  url: window.location.href,
669
642
  referrer: document.referrer || void 0,
@@ -686,6 +659,11 @@ var PocketPingClient = class {
686
659
  };
687
660
  this.storeSessionId(response.sessionId);
688
661
  this.connectWebSocket();
662
+ if (response.inspectorMode) {
663
+ this.enableInspectorMode();
664
+ } else if (response.trackedElements?.length) {
665
+ this.setupTrackedElements(response.trackedElements);
666
+ }
689
667
  this.emit("connect", this.session);
690
668
  this.config.onConnect?.(response.sessionId);
691
669
  return this.session;
@@ -697,6 +675,9 @@ var PocketPingClient = class {
697
675
  if (this.reconnectTimeout) {
698
676
  clearTimeout(this.reconnectTimeout);
699
677
  }
678
+ this.stopPolling();
679
+ this.cleanupTrackedElements();
680
+ this.disableInspectorMode();
700
681
  }
701
682
  async sendMessage(content) {
702
683
  if (!this.session) {
@@ -916,10 +897,15 @@ var PocketPingClient = class {
916
897
  * Trigger a custom event to the backend
917
898
  * @param eventName - The name of the event (e.g., 'clicked_pricing', 'viewed_demo')
918
899
  * @param data - Optional payload to send with the event
900
+ * @param options - Optional trigger options (widgetMessage to open chat)
919
901
  * @example
920
- * PocketPing.trigger('clicked_cta', { button: 'signup', page: '/pricing' })
902
+ * // Silent event (just notify bridges)
903
+ * PocketPing.trigger('clicked_cta', { button: 'signup' })
904
+ *
905
+ * // Open widget with message
906
+ * PocketPing.trigger('clicked_pricing', { plan: 'pro' }, { widgetMessage: 'Need help choosing a plan?' })
921
907
  */
922
- trigger(eventName, data) {
908
+ trigger(eventName, data, options) {
923
909
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
924
910
  console.warn("[PocketPing] Cannot trigger event: WebSocket not connected");
925
911
  return;
@@ -934,6 +920,10 @@ var PocketPingClient = class {
934
920
  data: event
935
921
  }));
936
922
  this.emit(`event:${eventName}`, event);
923
+ if (options?.widgetMessage) {
924
+ this.setOpen(true);
925
+ this.emit("triggerMessage", { message: options.widgetMessage, eventName });
926
+ }
937
927
  }
938
928
  /**
939
929
  * Subscribe to custom events from the backend
@@ -971,6 +961,317 @@ var PocketPingClient = class {
971
961
  this.emit(`event:${event.name}`, event);
972
962
  }
973
963
  // ─────────────────────────────────────────────────────────────────
964
+ // Tracked Elements (SaaS auto-tracking)
965
+ // ─────────────────────────────────────────────────────────────────
966
+ /**
967
+ * Setup tracked elements from config (used by SaaS dashboard)
968
+ * @param elements - Array of tracked element configurations
969
+ */
970
+ setupTrackedElements(elements) {
971
+ this.cleanupTrackedElements();
972
+ this.currentTrackedElements = elements;
973
+ for (const config of elements) {
974
+ const eventType = config.event || "click";
975
+ const handler = (domEvent) => {
976
+ const elementData = {
977
+ ...config.data,
978
+ selector: config.selector,
979
+ elementText: domEvent.target?.textContent?.trim().slice(0, 100),
980
+ url: window.location.href
981
+ };
982
+ this.trigger(config.name, elementData, {
983
+ widgetMessage: config.widgetMessage
984
+ });
985
+ };
986
+ const delegatedHandler = (domEvent) => {
987
+ const target = domEvent.target;
988
+ if (target?.closest(config.selector)) {
989
+ handler(domEvent);
990
+ }
991
+ };
992
+ document.addEventListener(eventType, delegatedHandler, true);
993
+ this.trackedElementCleanups.push(() => {
994
+ document.removeEventListener(eventType, delegatedHandler, true);
995
+ });
996
+ }
997
+ if (elements.length > 0) {
998
+ console.info(`[PocketPing] Tracking ${elements.length} element(s)`);
999
+ }
1000
+ }
1001
+ /**
1002
+ * Cleanup all tracked element listeners
1003
+ */
1004
+ cleanupTrackedElements() {
1005
+ for (const cleanup of this.trackedElementCleanups) {
1006
+ cleanup();
1007
+ }
1008
+ this.trackedElementCleanups = [];
1009
+ this.currentTrackedElements = [];
1010
+ }
1011
+ /**
1012
+ * Get current tracked elements configuration
1013
+ */
1014
+ getTrackedElements() {
1015
+ return [...this.currentTrackedElements];
1016
+ }
1017
+ // ─────────────────────────────────────────────────────────────────
1018
+ // Inspector Mode (SaaS visual element selector)
1019
+ // ─────────────────────────────────────────────────────────────────
1020
+ /**
1021
+ * Enable inspector mode for visual element selection
1022
+ * This shows an overlay that allows clicking on elements to get their CSS selector
1023
+ */
1024
+ enableInspectorMode() {
1025
+ if (this.inspectorMode) return;
1026
+ this.inspectorMode = true;
1027
+ console.info("[PocketPing] \u{1F50D} Inspector mode active - click on any element to select it");
1028
+ const overlay = document.createElement("div");
1029
+ overlay.id = "pp-inspector-overlay";
1030
+ overlay.innerHTML = `
1031
+ <style>
1032
+ #pp-inspector-overlay {
1033
+ position: fixed;
1034
+ top: 0;
1035
+ left: 0;
1036
+ right: 0;
1037
+ bottom: 0;
1038
+ z-index: 999999;
1039
+ pointer-events: none;
1040
+ }
1041
+ #pp-inspector-overlay * {
1042
+ pointer-events: auto;
1043
+ }
1044
+ #pp-inspector-banner {
1045
+ position: fixed;
1046
+ top: 16px;
1047
+ left: 50%;
1048
+ transform: translateX(-50%);
1049
+ background: linear-gradient(135deg, #6366f1, #8b5cf6);
1050
+ color: white;
1051
+ padding: 12px 24px;
1052
+ border-radius: 12px;
1053
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1054
+ font-size: 14px;
1055
+ font-weight: 500;
1056
+ box-shadow: 0 4px 20px rgba(99, 102, 241, 0.4);
1057
+ display: flex;
1058
+ align-items: center;
1059
+ gap: 12px;
1060
+ z-index: 1000000;
1061
+ }
1062
+ #pp-inspector-banner svg {
1063
+ animation: pp-pulse 1.5s ease-in-out infinite;
1064
+ }
1065
+ @keyframes pp-pulse {
1066
+ 0%, 100% { opacity: 1; }
1067
+ 50% { opacity: 0.5; }
1068
+ }
1069
+ #pp-inspector-exit {
1070
+ background: rgba(255,255,255,0.2);
1071
+ border: none;
1072
+ color: white;
1073
+ padding: 6px 12px;
1074
+ border-radius: 6px;
1075
+ cursor: pointer;
1076
+ font-size: 12px;
1077
+ margin-left: 8px;
1078
+ }
1079
+ #pp-inspector-exit:hover {
1080
+ background: rgba(255,255,255,0.3);
1081
+ }
1082
+ .pp-inspector-highlight {
1083
+ outline: 3px dashed #6366f1 !important;
1084
+ outline-offset: 2px !important;
1085
+ background: rgba(99, 102, 241, 0.1) !important;
1086
+ }
1087
+ #pp-inspector-tooltip {
1088
+ position: fixed;
1089
+ background: #1f2937;
1090
+ color: white;
1091
+ padding: 8px 12px;
1092
+ border-radius: 8px;
1093
+ font-family: ui-monospace, SFMono-Regular, monospace;
1094
+ font-size: 12px;
1095
+ pointer-events: none;
1096
+ z-index: 1000001;
1097
+ max-width: 400px;
1098
+ word-break: break-all;
1099
+ display: none;
1100
+ }
1101
+ </style>
1102
+ <div id="pp-inspector-banner">
1103
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1104
+ <circle cx="12" cy="12" r="10"/>
1105
+ <line x1="22" y1="22" x2="16.65" y2="16.65"/>
1106
+ <line x1="12" y1="8" x2="12" y2="12"/>
1107
+ <line x1="12" y1="16" x2="12" y2="16"/>
1108
+ </svg>
1109
+ <span>Inspector Mode - Click an element to capture its selector</span>
1110
+ <button id="pp-inspector-exit">Exit</button>
1111
+ </div>
1112
+ <div id="pp-inspector-tooltip"></div>
1113
+ `;
1114
+ document.body.appendChild(overlay);
1115
+ const tooltip = document.getElementById("pp-inspector-tooltip");
1116
+ let currentHighlight = null;
1117
+ const getSelector = (element) => {
1118
+ if (element.id && !element.id.startsWith("pp-")) {
1119
+ return `#${CSS.escape(element.id)}`;
1120
+ }
1121
+ const classes = Array.from(element.classList).filter((c) => !c.startsWith("pp-"));
1122
+ if (classes.length > 0) {
1123
+ const classSelector = "." + classes.map((c) => CSS.escape(c)).join(".");
1124
+ if (document.querySelectorAll(classSelector).length === 1) {
1125
+ return classSelector;
1126
+ }
1127
+ }
1128
+ for (const attr of Array.from(element.attributes)) {
1129
+ if (attr.name.startsWith("data-") && attr.value) {
1130
+ const dataSelector = `[${attr.name}="${CSS.escape(attr.value)}"]`;
1131
+ if (document.querySelectorAll(dataSelector).length === 1) {
1132
+ return dataSelector;
1133
+ }
1134
+ }
1135
+ }
1136
+ const path = [];
1137
+ let current = element;
1138
+ while (current && current !== document.body) {
1139
+ let selector = current.tagName.toLowerCase();
1140
+ if (current.id && !current.id.startsWith("pp-")) {
1141
+ selector = `#${CSS.escape(current.id)}`;
1142
+ path.unshift(selector);
1143
+ break;
1144
+ }
1145
+ const parent = current.parentElement;
1146
+ if (parent) {
1147
+ const currentTagName = current.tagName;
1148
+ const siblings = Array.from(parent.children).filter(
1149
+ (c) => c.tagName === currentTagName
1150
+ );
1151
+ if (siblings.length > 1) {
1152
+ const index = siblings.indexOf(current) + 1;
1153
+ selector += `:nth-of-type(${index})`;
1154
+ }
1155
+ }
1156
+ path.unshift(selector);
1157
+ current = parent;
1158
+ }
1159
+ return path.join(" > ");
1160
+ };
1161
+ const handleMouseOver = (e) => {
1162
+ const target = e.target;
1163
+ if (target.closest("#pp-inspector-overlay") || target.closest("#pocketping-widget")) {
1164
+ return;
1165
+ }
1166
+ if (currentHighlight) {
1167
+ currentHighlight.classList.remove("pp-inspector-highlight");
1168
+ }
1169
+ target.classList.add("pp-inspector-highlight");
1170
+ currentHighlight = target;
1171
+ const selector = getSelector(target);
1172
+ tooltip.textContent = selector;
1173
+ tooltip.style.display = "block";
1174
+ tooltip.style.left = `${e.clientX + 15}px`;
1175
+ tooltip.style.top = `${e.clientY + 15}px`;
1176
+ const rect = tooltip.getBoundingClientRect();
1177
+ if (rect.right > window.innerWidth) {
1178
+ tooltip.style.left = `${e.clientX - rect.width - 15}px`;
1179
+ }
1180
+ if (rect.bottom > window.innerHeight) {
1181
+ tooltip.style.top = `${e.clientY - rect.height - 15}px`;
1182
+ }
1183
+ };
1184
+ const handleMouseOut = (e) => {
1185
+ const target = e.target;
1186
+ if (target.closest("#pp-inspector-overlay")) return;
1187
+ target.classList.remove("pp-inspector-highlight");
1188
+ tooltip.style.display = "none";
1189
+ };
1190
+ const handleClick = (e) => {
1191
+ const target = e.target;
1192
+ if (target.id === "pp-inspector-exit") {
1193
+ this.disableInspectorMode();
1194
+ return;
1195
+ }
1196
+ if (target.closest("#pp-inspector-overlay") || target.closest("#pocketping-widget")) {
1197
+ return;
1198
+ }
1199
+ e.preventDefault();
1200
+ e.stopPropagation();
1201
+ const selector = getSelector(target);
1202
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1203
+ this.ws.send(JSON.stringify({
1204
+ type: "inspector_select",
1205
+ data: {
1206
+ selector,
1207
+ tagName: target.tagName.toLowerCase(),
1208
+ text: target.textContent?.trim().slice(0, 50) || "",
1209
+ url: window.location.href
1210
+ }
1211
+ }));
1212
+ }
1213
+ this.emit("inspectorSelect", { selector, element: target });
1214
+ target.classList.remove("pp-inspector-highlight");
1215
+ target.classList.add("pp-inspector-highlight");
1216
+ setTimeout(() => {
1217
+ target.classList.remove("pp-inspector-highlight");
1218
+ }, 500);
1219
+ const banner = document.getElementById("pp-inspector-banner");
1220
+ if (banner) {
1221
+ const originalHTML = banner.innerHTML;
1222
+ banner.innerHTML = `
1223
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1224
+ <polyline points="20 6 9 17 4 12"/>
1225
+ </svg>
1226
+ <span>Selector captured: <code style="background:rgba(255,255,255,0.2);padding:2px 6px;border-radius:4px;font-family:monospace;">${selector}</code></span>
1227
+ `;
1228
+ setTimeout(() => {
1229
+ if (banner && this.inspectorMode) {
1230
+ banner.innerHTML = originalHTML;
1231
+ document.getElementById("pp-inspector-exit")?.addEventListener("click", () => {
1232
+ this.disableInspectorMode();
1233
+ });
1234
+ }
1235
+ }, 2e3);
1236
+ }
1237
+ console.info(`[PocketPing] \u{1F4CC} Selector captured: ${selector}`);
1238
+ };
1239
+ document.addEventListener("mouseover", handleMouseOver, true);
1240
+ document.addEventListener("mouseout", handleMouseOut, true);
1241
+ document.addEventListener("click", handleClick, true);
1242
+ this.inspectorCleanup = () => {
1243
+ document.removeEventListener("mouseover", handleMouseOver, true);
1244
+ document.removeEventListener("mouseout", handleMouseOut, true);
1245
+ document.removeEventListener("click", handleClick, true);
1246
+ overlay.remove();
1247
+ if (currentHighlight) {
1248
+ currentHighlight.classList.remove("pp-inspector-highlight");
1249
+ }
1250
+ };
1251
+ document.getElementById("pp-inspector-exit")?.addEventListener("click", () => {
1252
+ this.disableInspectorMode();
1253
+ });
1254
+ }
1255
+ /**
1256
+ * Disable inspector mode
1257
+ */
1258
+ disableInspectorMode() {
1259
+ if (!this.inspectorMode) return;
1260
+ this.inspectorMode = false;
1261
+ if (this.inspectorCleanup) {
1262
+ this.inspectorCleanup();
1263
+ this.inspectorCleanup = null;
1264
+ }
1265
+ console.info("[PocketPing] Inspector mode disabled");
1266
+ this.emit("inspectorDisabled", null);
1267
+ }
1268
+ /**
1269
+ * Check if inspector mode is active
1270
+ */
1271
+ isInspectorModeActive() {
1272
+ return this.inspectorMode;
1273
+ }
1274
+ // ─────────────────────────────────────────────────────────────────
974
1275
  // WebSocket
975
1276
  // ─────────────────────────────────────────────────────────────────
976
1277
  connectWebSocket() {
@@ -1076,6 +1377,13 @@ var PocketPingClient = class {
1076
1377
  const versionWarning = event.data;
1077
1378
  this.handleVersionWarning(versionWarning);
1078
1379
  break;
1380
+ case "config_update":
1381
+ const configData = event.data;
1382
+ if (configData.trackedElements) {
1383
+ this.setupTrackedElements(configData.trackedElements);
1384
+ this.emit("configUpdate", configData);
1385
+ }
1386
+ break;
1079
1387
  }
1080
1388
  }
1081
1389
  // ─────────────────────────────────────────────────────────────────
@@ -1122,6 +1430,7 @@ var PocketPingClient = class {
1122
1430
  try {
1123
1431
  const lastMessageId = this.session.messages[this.session.messages.length - 1]?.id;
1124
1432
  const newMessages = await this.fetchMessages(lastMessageId);
1433
+ this.pollingFailures = 0;
1125
1434
  for (const message of newMessages) {
1126
1435
  if (!this.session.messages.find((m) => m.id === message.id)) {
1127
1436
  this.session.messages.push(message);
@@ -1130,14 +1439,30 @@ var PocketPingClient = class {
1130
1439
  }
1131
1440
  }
1132
1441
  } catch (err) {
1133
- console.error("[PocketPing] Polling error:", err);
1442
+ this.pollingFailures++;
1443
+ if (this.pollingFailures <= 3 || this.pollingFailures % 3 === 0) {
1444
+ console.warn(`[PocketPing] Polling failed (${this.pollingFailures}/${this.maxPollingFailures})`);
1445
+ }
1446
+ if (this.pollingFailures >= this.maxPollingFailures) {
1447
+ console.error("[PocketPing] Polling disabled after too many failures. Real-time updates unavailable.");
1448
+ this.emit("pollingDisabled", { failures: this.pollingFailures });
1449
+ return;
1450
+ }
1134
1451
  }
1135
1452
  if (this.session) {
1136
- setTimeout(poll, 3e3);
1453
+ const delay = this.pollingFailures > 0 ? Math.min(3e3 * Math.pow(2, this.pollingFailures - 1), 3e4) : 3e3;
1454
+ this.pollingTimeout = setTimeout(poll, delay);
1137
1455
  }
1138
1456
  };
1139
1457
  poll();
1140
1458
  }
1459
+ stopPolling() {
1460
+ if (this.pollingTimeout) {
1461
+ clearTimeout(this.pollingTimeout);
1462
+ this.pollingTimeout = null;
1463
+ }
1464
+ this.pollingFailures = 0;
1465
+ }
1141
1466
  // ─────────────────────────────────────────────────────────────────
1142
1467
  // HTTP
1143
1468
  // ─────────────────────────────────────────────────────────────────
@@ -1245,7 +1570,7 @@ function init(config) {
1245
1570
  container = document.createElement("div");
1246
1571
  container.id = "pocketping-container";
1247
1572
  document.body.appendChild(container);
1248
- (0, import_preact2.render)((0, import_preact2.h)(ChatWidget, { client, config }), container);
1573
+ render(h2(ChatWidget, { client, config }), container);
1249
1574
  client.connect().catch((err) => {
1250
1575
  console.error("[PocketPing] Failed to connect:", err);
1251
1576
  });
@@ -1253,7 +1578,7 @@ function init(config) {
1253
1578
  }
1254
1579
  function destroy() {
1255
1580
  if (container) {
1256
- (0, import_preact2.render)(null, container);
1581
+ render(null, container);
1257
1582
  container.remove();
1258
1583
  container = null;
1259
1584
  }
@@ -1277,12 +1602,22 @@ function sendMessage(content) {
1277
1602
  }
1278
1603
  return client.sendMessage(content);
1279
1604
  }
1280
- function trigger(eventName, data) {
1605
+ function trigger(eventName, data, options) {
1281
1606
  if (!client) {
1282
1607
  console.warn("[PocketPing] Not initialized, cannot trigger event");
1283
1608
  return;
1284
1609
  }
1285
- client.trigger(eventName, data);
1610
+ client.trigger(eventName, data, options);
1611
+ }
1612
+ function setupTrackedElements(elements) {
1613
+ if (!client) {
1614
+ console.warn("[PocketPing] Not initialized, cannot setup tracked elements");
1615
+ return;
1616
+ }
1617
+ client.setupTrackedElements(elements);
1618
+ }
1619
+ function getTrackedElements() {
1620
+ return client?.getTrackedElements() || [];
1286
1621
  }
1287
1622
  function onEvent(eventName, handler) {
1288
1623
  if (!client) {
@@ -1334,12 +1669,13 @@ if (typeof document !== "undefined") {
1334
1669
  }
1335
1670
  }
1336
1671
  }
1337
- var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity };
1338
- // Annotate the CommonJS export names for ESM import in node:
1339
- 0 && (module.exports = {
1672
+ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity, setupTrackedElements, getTrackedElements };
1673
+ export {
1340
1674
  close,
1675
+ index_default as default,
1341
1676
  destroy,
1342
1677
  getIdentity,
1678
+ getTrackedElements,
1343
1679
  identify,
1344
1680
  init,
1345
1681
  offEvent,
@@ -1348,6 +1684,7 @@ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger,
1348
1684
  open,
1349
1685
  reset,
1350
1686
  sendMessage,
1687
+ setupTrackedElements,
1351
1688
  toggle,
1352
1689
  trigger
1353
- });
1690
+ };