@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.
@@ -1,9 +1,48 @@
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
+
1
20
  // src/index.ts
2
- import { render, h as h2 } from "preact";
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ close: () => close,
24
+ default: () => index_default,
25
+ destroy: () => destroy,
26
+ getIdentity: () => getIdentity,
27
+ getTrackedElements: () => getTrackedElements,
28
+ identify: () => identify,
29
+ init: () => init,
30
+ offEvent: () => offEvent,
31
+ on: () => on,
32
+ onEvent: () => onEvent,
33
+ open: () => open,
34
+ reset: () => reset,
35
+ sendMessage: () => sendMessage,
36
+ setupTrackedElements: () => setupTrackedElements,
37
+ toggle: () => toggle,
38
+ trigger: () => trigger
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+ var import_preact2 = require("preact");
3
42
 
4
43
  // src/components/ChatWidget.tsx
5
- import { Fragment } from "preact";
6
- import { useState, useEffect, useRef, useCallback } from "preact/hooks";
44
+ var import_preact = require("preact");
45
+ var import_hooks = require("preact/hooks");
7
46
 
8
47
  // src/components/styles.ts
9
48
  function styles(primaryColor, theme) {
@@ -348,17 +387,17 @@ function styles(primaryColor, theme) {
348
387
  }
349
388
 
350
389
  // src/components/ChatWidget.tsx
351
- import { Fragment as Fragment2, jsx, jsxs } from "preact/jsx-runtime";
390
+ var import_jsx_runtime = require("preact/jsx-runtime");
352
391
  function ChatWidget({ client: client2, config }) {
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(() => {
392
+ const [isOpen, setIsOpen] = (0, import_hooks.useState)(false);
393
+ const [messages, setMessages] = (0, import_hooks.useState)([]);
394
+ const [inputValue, setInputValue] = (0, import_hooks.useState)("");
395
+ const [isTyping, setIsTyping] = (0, import_hooks.useState)(false);
396
+ const [operatorOnline, setOperatorOnline] = (0, import_hooks.useState)(false);
397
+ const [isConnected, setIsConnected] = (0, import_hooks.useState)(false);
398
+ const messagesEndRef = (0, import_hooks.useRef)(null);
399
+ const inputRef = (0, import_hooks.useRef)(null);
400
+ (0, import_hooks.useEffect)(() => {
362
401
  const unsubOpen = client2.on("openChange", setIsOpen);
363
402
  const unsubMessage = client2.on("message", () => {
364
403
  setMessages([...client2.getMessages()]);
@@ -387,15 +426,15 @@ function ChatWidget({ client: client2, config }) {
387
426
  unsubConnect();
388
427
  };
389
428
  }, [client2]);
390
- useEffect(() => {
429
+ (0, import_hooks.useEffect)(() => {
391
430
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
392
431
  }, [messages]);
393
- useEffect(() => {
432
+ (0, import_hooks.useEffect)(() => {
394
433
  if (isOpen) {
395
434
  inputRef.current?.focus();
396
435
  }
397
436
  }, [isOpen]);
398
- const markMessagesAsRead = useCallback(() => {
437
+ const markMessagesAsRead = (0, import_hooks.useCallback)(() => {
399
438
  if (!isOpen || !isConnected) return;
400
439
  const unreadMessages = messages.filter(
401
440
  (msg) => msg.sender !== "visitor" && msg.status !== "read"
@@ -405,14 +444,14 @@ function ChatWidget({ client: client2, config }) {
405
444
  client2.sendReadStatus(messageIds, "read");
406
445
  }
407
446
  }, [isOpen, isConnected, messages, client2]);
408
- useEffect(() => {
447
+ (0, import_hooks.useEffect)(() => {
409
448
  if (!isOpen || !isConnected) return;
410
449
  const timer = setTimeout(() => {
411
450
  markMessagesAsRead();
412
451
  }, 1e3);
413
452
  return () => clearTimeout(timer);
414
453
  }, [isOpen, isConnected, messages, markMessagesAsRead]);
415
- useEffect(() => {
454
+ (0, import_hooks.useEffect)(() => {
416
455
  const handleVisibilityChange = () => {
417
456
  if (document.visibilityState === "visible" && isOpen) {
418
457
  markMessagesAsRead();
@@ -421,7 +460,7 @@ function ChatWidget({ client: client2, config }) {
421
460
  document.addEventListener("visibilitychange", handleVisibilityChange);
422
461
  return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
423
462
  }, [isOpen, markMessagesAsRead]);
424
- useEffect(() => {
463
+ (0, import_hooks.useEffect)(() => {
425
464
  const unsubRead = client2.on(
426
465
  "read",
427
466
  () => {
@@ -451,71 +490,71 @@ function ChatWidget({ client: client2, config }) {
451
490
  const position = config.position ?? "bottom-right";
452
491
  const theme = getTheme(config.theme ?? "auto");
453
492
  const primaryColor = config.primaryColor ?? "#6366f1";
454
- return /* @__PURE__ */ jsxs(Fragment, { children: [
455
- /* @__PURE__ */ jsx("style", { children: styles(primaryColor, theme) }),
456
- /* @__PURE__ */ jsxs(
493
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_preact.Fragment, { children: [
494
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: styles(primaryColor, theme) }),
495
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
457
496
  "button",
458
497
  {
459
498
  class: `pp-toggle pp-${position}`,
460
499
  onClick: () => client2.toggleOpen(),
461
500
  "aria-label": isOpen ? "Close chat" : "Open chat",
462
501
  children: [
463
- isOpen ? /* @__PURE__ */ jsx(CloseIcon, {}) : /* @__PURE__ */ jsx(ChatIcon, {}),
464
- !isOpen && operatorOnline && /* @__PURE__ */ jsx("span", { class: "pp-online-dot" })
502
+ isOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatIcon, {}),
503
+ !isOpen && operatorOnline && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-online-dot" })
465
504
  ]
466
505
  }
467
506
  ),
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" }),
507
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: `pp-window pp-${position} pp-theme-${theme}`, children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-header", children: [
509
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-header-info", children: [
510
+ config.operatorAvatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: config.operatorAvatar, alt: "", class: "pp-avatar" }),
511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-header-title", children: config.operatorName ?? "Support" }),
513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-header-status", children: operatorOnline ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
514
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-status-dot pp-online" }),
476
515
  " Online"
477
- ] }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
478
- /* @__PURE__ */ jsx("span", { class: "pp-status-dot" }),
516
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-status-dot" }),
479
518
  " Away"
480
519
  ] }) })
481
520
  ] })
482
521
  ] }),
483
- /* @__PURE__ */ jsx(
522
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
484
523
  "button",
485
524
  {
486
525
  class: "pp-close-btn",
487
526
  onClick: () => client2.setOpen(false),
488
527
  "aria-label": "Close chat",
489
- children: /* @__PURE__ */ jsx(CloseIcon, {})
528
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {})
490
529
  }
491
530
  )
492
531
  ] }),
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(
532
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-messages", children: [
533
+ config.welcomeMessage && messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-welcome", children: config.welcomeMessage }),
534
+ messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
496
535
  "div",
497
536
  {
498
537
  class: `pp-message pp-message-${msg.sender}`,
499
538
  children: [
500
- /* @__PURE__ */ jsx("div", { class: "pp-message-content", children: msg.content }),
501
- /* @__PURE__ */ jsxs("div", { class: "pp-message-time", children: [
539
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-message-content", children: msg.content }),
540
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message-time", children: [
502
541
  formatTime(msg.timestamp),
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
+ msg.sender === "ai" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-ai-badge", children: "AI" }),
543
+ 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 }) })
505
544
  ] })
506
545
  ]
507
546
  },
508
547
  msg.id
509
548
  )),
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", {})
549
+ isTyping && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message pp-message-operator pp-typing", children: [
550
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
551
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
552
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {})
514
553
  ] }),
515
- /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
554
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: messagesEndRef })
516
555
  ] }),
517
- /* @__PURE__ */ jsxs("form", { class: "pp-input-form", onSubmit: handleSubmit, children: [
518
- /* @__PURE__ */ jsx(
556
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { class: "pp-input-form", onSubmit: handleSubmit, children: [
557
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
519
558
  "input",
520
559
  {
521
560
  ref: inputRef,
@@ -527,20 +566,20 @@ function ChatWidget({ client: client2, config }) {
527
566
  disabled: !isConnected
528
567
  }
529
568
  ),
530
- /* @__PURE__ */ jsx(
569
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
531
570
  "button",
532
571
  {
533
572
  type: "submit",
534
573
  class: "pp-send-btn",
535
574
  disabled: !inputValue.trim() || !isConnected,
536
575
  "aria-label": "Send message",
537
- children: /* @__PURE__ */ jsx(SendIcon, {})
576
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SendIcon, {})
538
577
  }
539
578
  )
540
579
  ] }),
541
- /* @__PURE__ */ jsxs("div", { class: "pp-footer", children: [
580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-footer", children: [
542
581
  "Powered by ",
543
- /* @__PURE__ */ jsx("a", { href: "https://pocketping.io", target: "_blank", rel: "noopener", children: "PocketPing" })
582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "https://pocketping.io", target: "_blank", rel: "noopener", children: "PocketPing" })
544
583
  ] })
545
584
  ] })
546
585
  ] });
@@ -566,41 +605,41 @@ function formatTime(timestamp) {
566
605
  return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
567
606
  }
568
607
  function ChatIcon() {
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" }) });
608
+ 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" }) });
570
609
  }
571
610
  function CloseIcon() {
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" })
611
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
612
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
575
614
  ] });
576
615
  }
577
616
  function SendIcon() {
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" })
617
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
619
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
581
620
  ] });
582
621
  }
583
622
  function StatusIcon({ status }) {
584
623
  if (!status || status === "sending" || status === "sent") {
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" }) });
624
+ 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" }) });
586
625
  }
587
626
  if (status === "delivered") {
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" })
627
+ 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: [
628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "1 8 5 12 11 4" }),
629
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "7 8 11 12 17 4" })
591
630
  ] });
592
631
  }
593
632
  if (status === "read") {
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" })
633
+ 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: [
634
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "1 8 5 12 11 4" }),
635
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "7 8 11 12 17 4" })
597
636
  ] });
598
637
  }
599
638
  return null;
600
639
  }
601
640
 
602
641
  // src/version.ts
603
- var VERSION = "0.3.3";
642
+ var VERSION = "0.3.5";
604
643
 
605
644
  // src/client.ts
606
645
  var PocketPingClient = class {
@@ -613,6 +652,13 @@ var PocketPingClient = class {
613
652
  this.reconnectAttempts = 0;
614
653
  this.maxReconnectAttempts = 5;
615
654
  this.reconnectTimeout = null;
655
+ this.pollingTimeout = null;
656
+ this.pollingFailures = 0;
657
+ this.maxPollingFailures = 10;
658
+ this.trackedElementCleanups = [];
659
+ this.currentTrackedElements = [];
660
+ this.inspectorMode = false;
661
+ this.inspectorCleanup = null;
616
662
  this.config = config;
617
663
  }
618
664
  // ─────────────────────────────────────────────────────────────────
@@ -622,11 +668,14 @@ var PocketPingClient = class {
622
668
  const visitorId = this.getOrCreateVisitorId();
623
669
  const storedSessionId = this.getStoredSessionId();
624
670
  const storedIdentity = this.getStoredIdentity();
671
+ const urlParams = new URLSearchParams(window.location.search);
672
+ const inspectorToken = urlParams.get("pp_inspector");
625
673
  const response = await this.fetch("/connect", {
626
674
  method: "POST",
627
675
  body: JSON.stringify({
628
676
  visitorId,
629
677
  sessionId: storedSessionId,
678
+ inspectorToken: inspectorToken || void 0,
630
679
  metadata: {
631
680
  url: window.location.href,
632
681
  referrer: document.referrer || void 0,
@@ -649,6 +698,11 @@ var PocketPingClient = class {
649
698
  };
650
699
  this.storeSessionId(response.sessionId);
651
700
  this.connectWebSocket();
701
+ if (response.inspectorMode) {
702
+ this.enableInspectorMode();
703
+ } else if (response.trackedElements?.length) {
704
+ this.setupTrackedElements(response.trackedElements);
705
+ }
652
706
  this.emit("connect", this.session);
653
707
  this.config.onConnect?.(response.sessionId);
654
708
  return this.session;
@@ -660,6 +714,9 @@ var PocketPingClient = class {
660
714
  if (this.reconnectTimeout) {
661
715
  clearTimeout(this.reconnectTimeout);
662
716
  }
717
+ this.stopPolling();
718
+ this.cleanupTrackedElements();
719
+ this.disableInspectorMode();
663
720
  }
664
721
  async sendMessage(content) {
665
722
  if (!this.session) {
@@ -879,10 +936,15 @@ var PocketPingClient = class {
879
936
  * Trigger a custom event to the backend
880
937
  * @param eventName - The name of the event (e.g., 'clicked_pricing', 'viewed_demo')
881
938
  * @param data - Optional payload to send with the event
939
+ * @param options - Optional trigger options (widgetMessage to open chat)
882
940
  * @example
883
- * PocketPing.trigger('clicked_cta', { button: 'signup', page: '/pricing' })
941
+ * // Silent event (just notify bridges)
942
+ * PocketPing.trigger('clicked_cta', { button: 'signup' })
943
+ *
944
+ * // Open widget with message
945
+ * PocketPing.trigger('clicked_pricing', { plan: 'pro' }, { widgetMessage: 'Need help choosing a plan?' })
884
946
  */
885
- trigger(eventName, data) {
947
+ trigger(eventName, data, options) {
886
948
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
887
949
  console.warn("[PocketPing] Cannot trigger event: WebSocket not connected");
888
950
  return;
@@ -897,6 +959,10 @@ var PocketPingClient = class {
897
959
  data: event
898
960
  }));
899
961
  this.emit(`event:${eventName}`, event);
962
+ if (options?.widgetMessage) {
963
+ this.setOpen(true);
964
+ this.emit("triggerMessage", { message: options.widgetMessage, eventName });
965
+ }
900
966
  }
901
967
  /**
902
968
  * Subscribe to custom events from the backend
@@ -934,6 +1000,317 @@ var PocketPingClient = class {
934
1000
  this.emit(`event:${event.name}`, event);
935
1001
  }
936
1002
  // ─────────────────────────────────────────────────────────────────
1003
+ // Tracked Elements (SaaS auto-tracking)
1004
+ // ─────────────────────────────────────────────────────────────────
1005
+ /**
1006
+ * Setup tracked elements from config (used by SaaS dashboard)
1007
+ * @param elements - Array of tracked element configurations
1008
+ */
1009
+ setupTrackedElements(elements) {
1010
+ this.cleanupTrackedElements();
1011
+ this.currentTrackedElements = elements;
1012
+ for (const config of elements) {
1013
+ const eventType = config.event || "click";
1014
+ const handler = (domEvent) => {
1015
+ const elementData = {
1016
+ ...config.data,
1017
+ selector: config.selector,
1018
+ elementText: domEvent.target?.textContent?.trim().slice(0, 100),
1019
+ url: window.location.href
1020
+ };
1021
+ this.trigger(config.name, elementData, {
1022
+ widgetMessage: config.widgetMessage
1023
+ });
1024
+ };
1025
+ const delegatedHandler = (domEvent) => {
1026
+ const target = domEvent.target;
1027
+ if (target?.closest(config.selector)) {
1028
+ handler(domEvent);
1029
+ }
1030
+ };
1031
+ document.addEventListener(eventType, delegatedHandler, true);
1032
+ this.trackedElementCleanups.push(() => {
1033
+ document.removeEventListener(eventType, delegatedHandler, true);
1034
+ });
1035
+ }
1036
+ if (elements.length > 0) {
1037
+ console.info(`[PocketPing] Tracking ${elements.length} element(s)`);
1038
+ }
1039
+ }
1040
+ /**
1041
+ * Cleanup all tracked element listeners
1042
+ */
1043
+ cleanupTrackedElements() {
1044
+ for (const cleanup of this.trackedElementCleanups) {
1045
+ cleanup();
1046
+ }
1047
+ this.trackedElementCleanups = [];
1048
+ this.currentTrackedElements = [];
1049
+ }
1050
+ /**
1051
+ * Get current tracked elements configuration
1052
+ */
1053
+ getTrackedElements() {
1054
+ return [...this.currentTrackedElements];
1055
+ }
1056
+ // ─────────────────────────────────────────────────────────────────
1057
+ // Inspector Mode (SaaS visual element selector)
1058
+ // ─────────────────────────────────────────────────────────────────
1059
+ /**
1060
+ * Enable inspector mode for visual element selection
1061
+ * This shows an overlay that allows clicking on elements to get their CSS selector
1062
+ */
1063
+ enableInspectorMode() {
1064
+ if (this.inspectorMode) return;
1065
+ this.inspectorMode = true;
1066
+ console.info("[PocketPing] \u{1F50D} Inspector mode active - click on any element to select it");
1067
+ const overlay = document.createElement("div");
1068
+ overlay.id = "pp-inspector-overlay";
1069
+ overlay.innerHTML = `
1070
+ <style>
1071
+ #pp-inspector-overlay {
1072
+ position: fixed;
1073
+ top: 0;
1074
+ left: 0;
1075
+ right: 0;
1076
+ bottom: 0;
1077
+ z-index: 999999;
1078
+ pointer-events: none;
1079
+ }
1080
+ #pp-inspector-overlay * {
1081
+ pointer-events: auto;
1082
+ }
1083
+ #pp-inspector-banner {
1084
+ position: fixed;
1085
+ top: 16px;
1086
+ left: 50%;
1087
+ transform: translateX(-50%);
1088
+ background: linear-gradient(135deg, #6366f1, #8b5cf6);
1089
+ color: white;
1090
+ padding: 12px 24px;
1091
+ border-radius: 12px;
1092
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1093
+ font-size: 14px;
1094
+ font-weight: 500;
1095
+ box-shadow: 0 4px 20px rgba(99, 102, 241, 0.4);
1096
+ display: flex;
1097
+ align-items: center;
1098
+ gap: 12px;
1099
+ z-index: 1000000;
1100
+ }
1101
+ #pp-inspector-banner svg {
1102
+ animation: pp-pulse 1.5s ease-in-out infinite;
1103
+ }
1104
+ @keyframes pp-pulse {
1105
+ 0%, 100% { opacity: 1; }
1106
+ 50% { opacity: 0.5; }
1107
+ }
1108
+ #pp-inspector-exit {
1109
+ background: rgba(255,255,255,0.2);
1110
+ border: none;
1111
+ color: white;
1112
+ padding: 6px 12px;
1113
+ border-radius: 6px;
1114
+ cursor: pointer;
1115
+ font-size: 12px;
1116
+ margin-left: 8px;
1117
+ }
1118
+ #pp-inspector-exit:hover {
1119
+ background: rgba(255,255,255,0.3);
1120
+ }
1121
+ .pp-inspector-highlight {
1122
+ outline: 3px dashed #6366f1 !important;
1123
+ outline-offset: 2px !important;
1124
+ background: rgba(99, 102, 241, 0.1) !important;
1125
+ }
1126
+ #pp-inspector-tooltip {
1127
+ position: fixed;
1128
+ background: #1f2937;
1129
+ color: white;
1130
+ padding: 8px 12px;
1131
+ border-radius: 8px;
1132
+ font-family: ui-monospace, SFMono-Regular, monospace;
1133
+ font-size: 12px;
1134
+ pointer-events: none;
1135
+ z-index: 1000001;
1136
+ max-width: 400px;
1137
+ word-break: break-all;
1138
+ display: none;
1139
+ }
1140
+ </style>
1141
+ <div id="pp-inspector-banner">
1142
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1143
+ <circle cx="12" cy="12" r="10"/>
1144
+ <line x1="22" y1="22" x2="16.65" y2="16.65"/>
1145
+ <line x1="12" y1="8" x2="12" y2="12"/>
1146
+ <line x1="12" y1="16" x2="12" y2="16"/>
1147
+ </svg>
1148
+ <span>Inspector Mode - Click an element to capture its selector</span>
1149
+ <button id="pp-inspector-exit">Exit</button>
1150
+ </div>
1151
+ <div id="pp-inspector-tooltip"></div>
1152
+ `;
1153
+ document.body.appendChild(overlay);
1154
+ const tooltip = document.getElementById("pp-inspector-tooltip");
1155
+ let currentHighlight = null;
1156
+ const getSelector = (element) => {
1157
+ if (element.id && !element.id.startsWith("pp-")) {
1158
+ return `#${CSS.escape(element.id)}`;
1159
+ }
1160
+ const classes = Array.from(element.classList).filter((c) => !c.startsWith("pp-"));
1161
+ if (classes.length > 0) {
1162
+ const classSelector = "." + classes.map((c) => CSS.escape(c)).join(".");
1163
+ if (document.querySelectorAll(classSelector).length === 1) {
1164
+ return classSelector;
1165
+ }
1166
+ }
1167
+ for (const attr of Array.from(element.attributes)) {
1168
+ if (attr.name.startsWith("data-") && attr.value) {
1169
+ const dataSelector = `[${attr.name}="${CSS.escape(attr.value)}"]`;
1170
+ if (document.querySelectorAll(dataSelector).length === 1) {
1171
+ return dataSelector;
1172
+ }
1173
+ }
1174
+ }
1175
+ const path = [];
1176
+ let current = element;
1177
+ while (current && current !== document.body) {
1178
+ let selector = current.tagName.toLowerCase();
1179
+ if (current.id && !current.id.startsWith("pp-")) {
1180
+ selector = `#${CSS.escape(current.id)}`;
1181
+ path.unshift(selector);
1182
+ break;
1183
+ }
1184
+ const parent = current.parentElement;
1185
+ if (parent) {
1186
+ const currentTagName = current.tagName;
1187
+ const siblings = Array.from(parent.children).filter(
1188
+ (c) => c.tagName === currentTagName
1189
+ );
1190
+ if (siblings.length > 1) {
1191
+ const index = siblings.indexOf(current) + 1;
1192
+ selector += `:nth-of-type(${index})`;
1193
+ }
1194
+ }
1195
+ path.unshift(selector);
1196
+ current = parent;
1197
+ }
1198
+ return path.join(" > ");
1199
+ };
1200
+ const handleMouseOver = (e) => {
1201
+ const target = e.target;
1202
+ if (target.closest("#pp-inspector-overlay") || target.closest("#pocketping-widget")) {
1203
+ return;
1204
+ }
1205
+ if (currentHighlight) {
1206
+ currentHighlight.classList.remove("pp-inspector-highlight");
1207
+ }
1208
+ target.classList.add("pp-inspector-highlight");
1209
+ currentHighlight = target;
1210
+ const selector = getSelector(target);
1211
+ tooltip.textContent = selector;
1212
+ tooltip.style.display = "block";
1213
+ tooltip.style.left = `${e.clientX + 15}px`;
1214
+ tooltip.style.top = `${e.clientY + 15}px`;
1215
+ const rect = tooltip.getBoundingClientRect();
1216
+ if (rect.right > window.innerWidth) {
1217
+ tooltip.style.left = `${e.clientX - rect.width - 15}px`;
1218
+ }
1219
+ if (rect.bottom > window.innerHeight) {
1220
+ tooltip.style.top = `${e.clientY - rect.height - 15}px`;
1221
+ }
1222
+ };
1223
+ const handleMouseOut = (e) => {
1224
+ const target = e.target;
1225
+ if (target.closest("#pp-inspector-overlay")) return;
1226
+ target.classList.remove("pp-inspector-highlight");
1227
+ tooltip.style.display = "none";
1228
+ };
1229
+ const handleClick = (e) => {
1230
+ const target = e.target;
1231
+ if (target.id === "pp-inspector-exit") {
1232
+ this.disableInspectorMode();
1233
+ return;
1234
+ }
1235
+ if (target.closest("#pp-inspector-overlay") || target.closest("#pocketping-widget")) {
1236
+ return;
1237
+ }
1238
+ e.preventDefault();
1239
+ e.stopPropagation();
1240
+ const selector = getSelector(target);
1241
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1242
+ this.ws.send(JSON.stringify({
1243
+ type: "inspector_select",
1244
+ data: {
1245
+ selector,
1246
+ tagName: target.tagName.toLowerCase(),
1247
+ text: target.textContent?.trim().slice(0, 50) || "",
1248
+ url: window.location.href
1249
+ }
1250
+ }));
1251
+ }
1252
+ this.emit("inspectorSelect", { selector, element: target });
1253
+ target.classList.remove("pp-inspector-highlight");
1254
+ target.classList.add("pp-inspector-highlight");
1255
+ setTimeout(() => {
1256
+ target.classList.remove("pp-inspector-highlight");
1257
+ }, 500);
1258
+ const banner = document.getElementById("pp-inspector-banner");
1259
+ if (banner) {
1260
+ const originalHTML = banner.innerHTML;
1261
+ banner.innerHTML = `
1262
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1263
+ <polyline points="20 6 9 17 4 12"/>
1264
+ </svg>
1265
+ <span>Selector captured: <code style="background:rgba(255,255,255,0.2);padding:2px 6px;border-radius:4px;font-family:monospace;">${selector}</code></span>
1266
+ `;
1267
+ setTimeout(() => {
1268
+ if (banner && this.inspectorMode) {
1269
+ banner.innerHTML = originalHTML;
1270
+ document.getElementById("pp-inspector-exit")?.addEventListener("click", () => {
1271
+ this.disableInspectorMode();
1272
+ });
1273
+ }
1274
+ }, 2e3);
1275
+ }
1276
+ console.info(`[PocketPing] \u{1F4CC} Selector captured: ${selector}`);
1277
+ };
1278
+ document.addEventListener("mouseover", handleMouseOver, true);
1279
+ document.addEventListener("mouseout", handleMouseOut, true);
1280
+ document.addEventListener("click", handleClick, true);
1281
+ this.inspectorCleanup = () => {
1282
+ document.removeEventListener("mouseover", handleMouseOver, true);
1283
+ document.removeEventListener("mouseout", handleMouseOut, true);
1284
+ document.removeEventListener("click", handleClick, true);
1285
+ overlay.remove();
1286
+ if (currentHighlight) {
1287
+ currentHighlight.classList.remove("pp-inspector-highlight");
1288
+ }
1289
+ };
1290
+ document.getElementById("pp-inspector-exit")?.addEventListener("click", () => {
1291
+ this.disableInspectorMode();
1292
+ });
1293
+ }
1294
+ /**
1295
+ * Disable inspector mode
1296
+ */
1297
+ disableInspectorMode() {
1298
+ if (!this.inspectorMode) return;
1299
+ this.inspectorMode = false;
1300
+ if (this.inspectorCleanup) {
1301
+ this.inspectorCleanup();
1302
+ this.inspectorCleanup = null;
1303
+ }
1304
+ console.info("[PocketPing] Inspector mode disabled");
1305
+ this.emit("inspectorDisabled", null);
1306
+ }
1307
+ /**
1308
+ * Check if inspector mode is active
1309
+ */
1310
+ isInspectorModeActive() {
1311
+ return this.inspectorMode;
1312
+ }
1313
+ // ─────────────────────────────────────────────────────────────────
937
1314
  // WebSocket
938
1315
  // ─────────────────────────────────────────────────────────────────
939
1316
  connectWebSocket() {
@@ -1039,6 +1416,13 @@ var PocketPingClient = class {
1039
1416
  const versionWarning = event.data;
1040
1417
  this.handleVersionWarning(versionWarning);
1041
1418
  break;
1419
+ case "config_update":
1420
+ const configData = event.data;
1421
+ if (configData.trackedElements) {
1422
+ this.setupTrackedElements(configData.trackedElements);
1423
+ this.emit("configUpdate", configData);
1424
+ }
1425
+ break;
1042
1426
  }
1043
1427
  }
1044
1428
  // ─────────────────────────────────────────────────────────────────
@@ -1085,6 +1469,7 @@ var PocketPingClient = class {
1085
1469
  try {
1086
1470
  const lastMessageId = this.session.messages[this.session.messages.length - 1]?.id;
1087
1471
  const newMessages = await this.fetchMessages(lastMessageId);
1472
+ this.pollingFailures = 0;
1088
1473
  for (const message of newMessages) {
1089
1474
  if (!this.session.messages.find((m) => m.id === message.id)) {
1090
1475
  this.session.messages.push(message);
@@ -1093,14 +1478,30 @@ var PocketPingClient = class {
1093
1478
  }
1094
1479
  }
1095
1480
  } catch (err) {
1096
- console.error("[PocketPing] Polling error:", err);
1481
+ this.pollingFailures++;
1482
+ if (this.pollingFailures <= 3 || this.pollingFailures % 3 === 0) {
1483
+ console.warn(`[PocketPing] Polling failed (${this.pollingFailures}/${this.maxPollingFailures})`);
1484
+ }
1485
+ if (this.pollingFailures >= this.maxPollingFailures) {
1486
+ console.error("[PocketPing] Polling disabled after too many failures. Real-time updates unavailable.");
1487
+ this.emit("pollingDisabled", { failures: this.pollingFailures });
1488
+ return;
1489
+ }
1097
1490
  }
1098
1491
  if (this.session) {
1099
- setTimeout(poll, 3e3);
1492
+ const delay = this.pollingFailures > 0 ? Math.min(3e3 * Math.pow(2, this.pollingFailures - 1), 3e4) : 3e3;
1493
+ this.pollingTimeout = setTimeout(poll, delay);
1100
1494
  }
1101
1495
  };
1102
1496
  poll();
1103
1497
  }
1498
+ stopPolling() {
1499
+ if (this.pollingTimeout) {
1500
+ clearTimeout(this.pollingTimeout);
1501
+ this.pollingTimeout = null;
1502
+ }
1503
+ this.pollingFailures = 0;
1504
+ }
1104
1505
  // ─────────────────────────────────────────────────────────────────
1105
1506
  // HTTP
1106
1507
  // ─────────────────────────────────────────────────────────────────
@@ -1208,7 +1609,7 @@ function init(config) {
1208
1609
  container = document.createElement("div");
1209
1610
  container.id = "pocketping-container";
1210
1611
  document.body.appendChild(container);
1211
- render(h2(ChatWidget, { client, config }), container);
1612
+ (0, import_preact2.render)((0, import_preact2.h)(ChatWidget, { client, config }), container);
1212
1613
  client.connect().catch((err) => {
1213
1614
  console.error("[PocketPing] Failed to connect:", err);
1214
1615
  });
@@ -1216,7 +1617,7 @@ function init(config) {
1216
1617
  }
1217
1618
  function destroy() {
1218
1619
  if (container) {
1219
- render(null, container);
1620
+ (0, import_preact2.render)(null, container);
1220
1621
  container.remove();
1221
1622
  container = null;
1222
1623
  }
@@ -1240,12 +1641,22 @@ function sendMessage(content) {
1240
1641
  }
1241
1642
  return client.sendMessage(content);
1242
1643
  }
1243
- function trigger(eventName, data) {
1644
+ function trigger(eventName, data, options) {
1244
1645
  if (!client) {
1245
1646
  console.warn("[PocketPing] Not initialized, cannot trigger event");
1246
1647
  return;
1247
1648
  }
1248
- client.trigger(eventName, data);
1649
+ client.trigger(eventName, data, options);
1650
+ }
1651
+ function setupTrackedElements(elements) {
1652
+ if (!client) {
1653
+ console.warn("[PocketPing] Not initialized, cannot setup tracked elements");
1654
+ return;
1655
+ }
1656
+ client.setupTrackedElements(elements);
1657
+ }
1658
+ function getTrackedElements() {
1659
+ return client?.getTrackedElements() || [];
1249
1660
  }
1250
1661
  function onEvent(eventName, handler) {
1251
1662
  if (!client) {
@@ -1297,12 +1708,13 @@ if (typeof document !== "undefined") {
1297
1708
  }
1298
1709
  }
1299
1710
  }
1300
- var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity };
1301
- export {
1711
+ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity, setupTrackedElements, getTrackedElements };
1712
+ // Annotate the CommonJS export names for ESM import in node:
1713
+ 0 && (module.exports = {
1302
1714
  close,
1303
- index_default as default,
1304
1715
  destroy,
1305
1716
  getIdentity,
1717
+ getTrackedElements,
1306
1718
  identify,
1307
1719
  init,
1308
1720
  offEvent,
@@ -1311,6 +1723,7 @@ export {
1311
1723
  open,
1312
1724
  reset,
1313
1725
  sendMessage,
1726
+ setupTrackedElements,
1314
1727
  toggle,
1315
1728
  trigger
1316
- };
1729
+ });