@pocketping/widget 0.3.3 → 0.3.4

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.4";
641
604
 
642
605
  // src/client.ts
643
606
  var PocketPingClient = class {
@@ -650,6 +613,10 @@ var PocketPingClient = class {
650
613
  this.reconnectAttempts = 0;
651
614
  this.maxReconnectAttempts = 5;
652
615
  this.reconnectTimeout = null;
616
+ this.trackedElementCleanups = [];
617
+ this.currentTrackedElements = [];
618
+ this.inspectorMode = false;
619
+ this.inspectorCleanup = null;
653
620
  this.config = config;
654
621
  }
655
622
  // ─────────────────────────────────────────────────────────────────
@@ -659,11 +626,14 @@ var PocketPingClient = class {
659
626
  const visitorId = this.getOrCreateVisitorId();
660
627
  const storedSessionId = this.getStoredSessionId();
661
628
  const storedIdentity = this.getStoredIdentity();
629
+ const urlParams = new URLSearchParams(window.location.search);
630
+ const inspectorToken = urlParams.get("pp_inspector");
662
631
  const response = await this.fetch("/connect", {
663
632
  method: "POST",
664
633
  body: JSON.stringify({
665
634
  visitorId,
666
635
  sessionId: storedSessionId,
636
+ inspectorToken: inspectorToken || void 0,
667
637
  metadata: {
668
638
  url: window.location.href,
669
639
  referrer: document.referrer || void 0,
@@ -686,6 +656,11 @@ var PocketPingClient = class {
686
656
  };
687
657
  this.storeSessionId(response.sessionId);
688
658
  this.connectWebSocket();
659
+ if (response.inspectorMode) {
660
+ this.enableInspectorMode();
661
+ } else if (response.trackedElements?.length) {
662
+ this.setupTrackedElements(response.trackedElements);
663
+ }
689
664
  this.emit("connect", this.session);
690
665
  this.config.onConnect?.(response.sessionId);
691
666
  return this.session;
@@ -697,6 +672,8 @@ var PocketPingClient = class {
697
672
  if (this.reconnectTimeout) {
698
673
  clearTimeout(this.reconnectTimeout);
699
674
  }
675
+ this.cleanupTrackedElements();
676
+ this.disableInspectorMode();
700
677
  }
701
678
  async sendMessage(content) {
702
679
  if (!this.session) {
@@ -916,10 +893,15 @@ var PocketPingClient = class {
916
893
  * Trigger a custom event to the backend
917
894
  * @param eventName - The name of the event (e.g., 'clicked_pricing', 'viewed_demo')
918
895
  * @param data - Optional payload to send with the event
896
+ * @param options - Optional trigger options (widgetMessage to open chat)
919
897
  * @example
920
- * PocketPing.trigger('clicked_cta', { button: 'signup', page: '/pricing' })
898
+ * // Silent event (just notify bridges)
899
+ * PocketPing.trigger('clicked_cta', { button: 'signup' })
900
+ *
901
+ * // Open widget with message
902
+ * PocketPing.trigger('clicked_pricing', { plan: 'pro' }, { widgetMessage: 'Need help choosing a plan?' })
921
903
  */
922
- trigger(eventName, data) {
904
+ trigger(eventName, data, options) {
923
905
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
924
906
  console.warn("[PocketPing] Cannot trigger event: WebSocket not connected");
925
907
  return;
@@ -934,6 +916,10 @@ var PocketPingClient = class {
934
916
  data: event
935
917
  }));
936
918
  this.emit(`event:${eventName}`, event);
919
+ if (options?.widgetMessage) {
920
+ this.setOpen(true);
921
+ this.emit("triggerMessage", { message: options.widgetMessage, eventName });
922
+ }
937
923
  }
938
924
  /**
939
925
  * Subscribe to custom events from the backend
@@ -971,6 +957,317 @@ var PocketPingClient = class {
971
957
  this.emit(`event:${event.name}`, event);
972
958
  }
973
959
  // ─────────────────────────────────────────────────────────────────
960
+ // Tracked Elements (SaaS auto-tracking)
961
+ // ─────────────────────────────────────────────────────────────────
962
+ /**
963
+ * Setup tracked elements from config (used by SaaS dashboard)
964
+ * @param elements - Array of tracked element configurations
965
+ */
966
+ setupTrackedElements(elements) {
967
+ this.cleanupTrackedElements();
968
+ this.currentTrackedElements = elements;
969
+ for (const config of elements) {
970
+ const eventType = config.event || "click";
971
+ const handler = (domEvent) => {
972
+ const elementData = {
973
+ ...config.data,
974
+ selector: config.selector,
975
+ elementText: domEvent.target?.textContent?.trim().slice(0, 100),
976
+ url: window.location.href
977
+ };
978
+ this.trigger(config.name, elementData, {
979
+ widgetMessage: config.widgetMessage
980
+ });
981
+ };
982
+ const delegatedHandler = (domEvent) => {
983
+ const target = domEvent.target;
984
+ if (target?.closest(config.selector)) {
985
+ handler(domEvent);
986
+ }
987
+ };
988
+ document.addEventListener(eventType, delegatedHandler, true);
989
+ this.trackedElementCleanups.push(() => {
990
+ document.removeEventListener(eventType, delegatedHandler, true);
991
+ });
992
+ }
993
+ if (elements.length > 0) {
994
+ console.info(`[PocketPing] Tracking ${elements.length} element(s)`);
995
+ }
996
+ }
997
+ /**
998
+ * Cleanup all tracked element listeners
999
+ */
1000
+ cleanupTrackedElements() {
1001
+ for (const cleanup of this.trackedElementCleanups) {
1002
+ cleanup();
1003
+ }
1004
+ this.trackedElementCleanups = [];
1005
+ this.currentTrackedElements = [];
1006
+ }
1007
+ /**
1008
+ * Get current tracked elements configuration
1009
+ */
1010
+ getTrackedElements() {
1011
+ return [...this.currentTrackedElements];
1012
+ }
1013
+ // ─────────────────────────────────────────────────────────────────
1014
+ // Inspector Mode (SaaS visual element selector)
1015
+ // ─────────────────────────────────────────────────────────────────
1016
+ /**
1017
+ * Enable inspector mode for visual element selection
1018
+ * This shows an overlay that allows clicking on elements to get their CSS selector
1019
+ */
1020
+ enableInspectorMode() {
1021
+ if (this.inspectorMode) return;
1022
+ this.inspectorMode = true;
1023
+ console.info("[PocketPing] \u{1F50D} Inspector mode active - click on any element to select it");
1024
+ const overlay = document.createElement("div");
1025
+ overlay.id = "pp-inspector-overlay";
1026
+ overlay.innerHTML = `
1027
+ <style>
1028
+ #pp-inspector-overlay {
1029
+ position: fixed;
1030
+ top: 0;
1031
+ left: 0;
1032
+ right: 0;
1033
+ bottom: 0;
1034
+ z-index: 999999;
1035
+ pointer-events: none;
1036
+ }
1037
+ #pp-inspector-overlay * {
1038
+ pointer-events: auto;
1039
+ }
1040
+ #pp-inspector-banner {
1041
+ position: fixed;
1042
+ top: 16px;
1043
+ left: 50%;
1044
+ transform: translateX(-50%);
1045
+ background: linear-gradient(135deg, #6366f1, #8b5cf6);
1046
+ color: white;
1047
+ padding: 12px 24px;
1048
+ border-radius: 12px;
1049
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1050
+ font-size: 14px;
1051
+ font-weight: 500;
1052
+ box-shadow: 0 4px 20px rgba(99, 102, 241, 0.4);
1053
+ display: flex;
1054
+ align-items: center;
1055
+ gap: 12px;
1056
+ z-index: 1000000;
1057
+ }
1058
+ #pp-inspector-banner svg {
1059
+ animation: pp-pulse 1.5s ease-in-out infinite;
1060
+ }
1061
+ @keyframes pp-pulse {
1062
+ 0%, 100% { opacity: 1; }
1063
+ 50% { opacity: 0.5; }
1064
+ }
1065
+ #pp-inspector-exit {
1066
+ background: rgba(255,255,255,0.2);
1067
+ border: none;
1068
+ color: white;
1069
+ padding: 6px 12px;
1070
+ border-radius: 6px;
1071
+ cursor: pointer;
1072
+ font-size: 12px;
1073
+ margin-left: 8px;
1074
+ }
1075
+ #pp-inspector-exit:hover {
1076
+ background: rgba(255,255,255,0.3);
1077
+ }
1078
+ .pp-inspector-highlight {
1079
+ outline: 3px dashed #6366f1 !important;
1080
+ outline-offset: 2px !important;
1081
+ background: rgba(99, 102, 241, 0.1) !important;
1082
+ }
1083
+ #pp-inspector-tooltip {
1084
+ position: fixed;
1085
+ background: #1f2937;
1086
+ color: white;
1087
+ padding: 8px 12px;
1088
+ border-radius: 8px;
1089
+ font-family: ui-monospace, SFMono-Regular, monospace;
1090
+ font-size: 12px;
1091
+ pointer-events: none;
1092
+ z-index: 1000001;
1093
+ max-width: 400px;
1094
+ word-break: break-all;
1095
+ display: none;
1096
+ }
1097
+ </style>
1098
+ <div id="pp-inspector-banner">
1099
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1100
+ <circle cx="12" cy="12" r="10"/>
1101
+ <line x1="22" y1="22" x2="16.65" y2="16.65"/>
1102
+ <line x1="12" y1="8" x2="12" y2="12"/>
1103
+ <line x1="12" y1="16" x2="12" y2="16"/>
1104
+ </svg>
1105
+ <span>Inspector Mode - Click an element to capture its selector</span>
1106
+ <button id="pp-inspector-exit">Exit</button>
1107
+ </div>
1108
+ <div id="pp-inspector-tooltip"></div>
1109
+ `;
1110
+ document.body.appendChild(overlay);
1111
+ const tooltip = document.getElementById("pp-inspector-tooltip");
1112
+ let currentHighlight = null;
1113
+ const getSelector = (element) => {
1114
+ if (element.id && !element.id.startsWith("pp-")) {
1115
+ return `#${CSS.escape(element.id)}`;
1116
+ }
1117
+ const classes = Array.from(element.classList).filter((c) => !c.startsWith("pp-"));
1118
+ if (classes.length > 0) {
1119
+ const classSelector = "." + classes.map((c) => CSS.escape(c)).join(".");
1120
+ if (document.querySelectorAll(classSelector).length === 1) {
1121
+ return classSelector;
1122
+ }
1123
+ }
1124
+ for (const attr of Array.from(element.attributes)) {
1125
+ if (attr.name.startsWith("data-") && attr.value) {
1126
+ const dataSelector = `[${attr.name}="${CSS.escape(attr.value)}"]`;
1127
+ if (document.querySelectorAll(dataSelector).length === 1) {
1128
+ return dataSelector;
1129
+ }
1130
+ }
1131
+ }
1132
+ const path = [];
1133
+ let current = element;
1134
+ while (current && current !== document.body) {
1135
+ let selector = current.tagName.toLowerCase();
1136
+ if (current.id && !current.id.startsWith("pp-")) {
1137
+ selector = `#${CSS.escape(current.id)}`;
1138
+ path.unshift(selector);
1139
+ break;
1140
+ }
1141
+ const parent = current.parentElement;
1142
+ if (parent) {
1143
+ const currentTagName = current.tagName;
1144
+ const siblings = Array.from(parent.children).filter(
1145
+ (c) => c.tagName === currentTagName
1146
+ );
1147
+ if (siblings.length > 1) {
1148
+ const index = siblings.indexOf(current) + 1;
1149
+ selector += `:nth-of-type(${index})`;
1150
+ }
1151
+ }
1152
+ path.unshift(selector);
1153
+ current = parent;
1154
+ }
1155
+ return path.join(" > ");
1156
+ };
1157
+ const handleMouseOver = (e) => {
1158
+ const target = e.target;
1159
+ if (target.closest("#pp-inspector-overlay") || target.closest("#pocketping-widget")) {
1160
+ return;
1161
+ }
1162
+ if (currentHighlight) {
1163
+ currentHighlight.classList.remove("pp-inspector-highlight");
1164
+ }
1165
+ target.classList.add("pp-inspector-highlight");
1166
+ currentHighlight = target;
1167
+ const selector = getSelector(target);
1168
+ tooltip.textContent = selector;
1169
+ tooltip.style.display = "block";
1170
+ tooltip.style.left = `${e.clientX + 15}px`;
1171
+ tooltip.style.top = `${e.clientY + 15}px`;
1172
+ const rect = tooltip.getBoundingClientRect();
1173
+ if (rect.right > window.innerWidth) {
1174
+ tooltip.style.left = `${e.clientX - rect.width - 15}px`;
1175
+ }
1176
+ if (rect.bottom > window.innerHeight) {
1177
+ tooltip.style.top = `${e.clientY - rect.height - 15}px`;
1178
+ }
1179
+ };
1180
+ const handleMouseOut = (e) => {
1181
+ const target = e.target;
1182
+ if (target.closest("#pp-inspector-overlay")) return;
1183
+ target.classList.remove("pp-inspector-highlight");
1184
+ tooltip.style.display = "none";
1185
+ };
1186
+ const handleClick = (e) => {
1187
+ const target = e.target;
1188
+ if (target.id === "pp-inspector-exit") {
1189
+ this.disableInspectorMode();
1190
+ return;
1191
+ }
1192
+ if (target.closest("#pp-inspector-overlay") || target.closest("#pocketping-widget")) {
1193
+ return;
1194
+ }
1195
+ e.preventDefault();
1196
+ e.stopPropagation();
1197
+ const selector = getSelector(target);
1198
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1199
+ this.ws.send(JSON.stringify({
1200
+ type: "inspector_select",
1201
+ data: {
1202
+ selector,
1203
+ tagName: target.tagName.toLowerCase(),
1204
+ text: target.textContent?.trim().slice(0, 50) || "",
1205
+ url: window.location.href
1206
+ }
1207
+ }));
1208
+ }
1209
+ this.emit("inspectorSelect", { selector, element: target });
1210
+ target.classList.remove("pp-inspector-highlight");
1211
+ target.classList.add("pp-inspector-highlight");
1212
+ setTimeout(() => {
1213
+ target.classList.remove("pp-inspector-highlight");
1214
+ }, 500);
1215
+ const banner = document.getElementById("pp-inspector-banner");
1216
+ if (banner) {
1217
+ const originalHTML = banner.innerHTML;
1218
+ banner.innerHTML = `
1219
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1220
+ <polyline points="20 6 9 17 4 12"/>
1221
+ </svg>
1222
+ <span>Selector captured: <code style="background:rgba(255,255,255,0.2);padding:2px 6px;border-radius:4px;font-family:monospace;">${selector}</code></span>
1223
+ `;
1224
+ setTimeout(() => {
1225
+ if (banner && this.inspectorMode) {
1226
+ banner.innerHTML = originalHTML;
1227
+ document.getElementById("pp-inspector-exit")?.addEventListener("click", () => {
1228
+ this.disableInspectorMode();
1229
+ });
1230
+ }
1231
+ }, 2e3);
1232
+ }
1233
+ console.info(`[PocketPing] \u{1F4CC} Selector captured: ${selector}`);
1234
+ };
1235
+ document.addEventListener("mouseover", handleMouseOver, true);
1236
+ document.addEventListener("mouseout", handleMouseOut, true);
1237
+ document.addEventListener("click", handleClick, true);
1238
+ this.inspectorCleanup = () => {
1239
+ document.removeEventListener("mouseover", handleMouseOver, true);
1240
+ document.removeEventListener("mouseout", handleMouseOut, true);
1241
+ document.removeEventListener("click", handleClick, true);
1242
+ overlay.remove();
1243
+ if (currentHighlight) {
1244
+ currentHighlight.classList.remove("pp-inspector-highlight");
1245
+ }
1246
+ };
1247
+ document.getElementById("pp-inspector-exit")?.addEventListener("click", () => {
1248
+ this.disableInspectorMode();
1249
+ });
1250
+ }
1251
+ /**
1252
+ * Disable inspector mode
1253
+ */
1254
+ disableInspectorMode() {
1255
+ if (!this.inspectorMode) return;
1256
+ this.inspectorMode = false;
1257
+ if (this.inspectorCleanup) {
1258
+ this.inspectorCleanup();
1259
+ this.inspectorCleanup = null;
1260
+ }
1261
+ console.info("[PocketPing] Inspector mode disabled");
1262
+ this.emit("inspectorDisabled", null);
1263
+ }
1264
+ /**
1265
+ * Check if inspector mode is active
1266
+ */
1267
+ isInspectorModeActive() {
1268
+ return this.inspectorMode;
1269
+ }
1270
+ // ─────────────────────────────────────────────────────────────────
974
1271
  // WebSocket
975
1272
  // ─────────────────────────────────────────────────────────────────
976
1273
  connectWebSocket() {
@@ -1076,6 +1373,13 @@ var PocketPingClient = class {
1076
1373
  const versionWarning = event.data;
1077
1374
  this.handleVersionWarning(versionWarning);
1078
1375
  break;
1376
+ case "config_update":
1377
+ const configData = event.data;
1378
+ if (configData.trackedElements) {
1379
+ this.setupTrackedElements(configData.trackedElements);
1380
+ this.emit("configUpdate", configData);
1381
+ }
1382
+ break;
1079
1383
  }
1080
1384
  }
1081
1385
  // ─────────────────────────────────────────────────────────────────
@@ -1245,7 +1549,7 @@ function init(config) {
1245
1549
  container = document.createElement("div");
1246
1550
  container.id = "pocketping-container";
1247
1551
  document.body.appendChild(container);
1248
- (0, import_preact2.render)((0, import_preact2.h)(ChatWidget, { client, config }), container);
1552
+ render(h2(ChatWidget, { client, config }), container);
1249
1553
  client.connect().catch((err) => {
1250
1554
  console.error("[PocketPing] Failed to connect:", err);
1251
1555
  });
@@ -1253,7 +1557,7 @@ function init(config) {
1253
1557
  }
1254
1558
  function destroy() {
1255
1559
  if (container) {
1256
- (0, import_preact2.render)(null, container);
1560
+ render(null, container);
1257
1561
  container.remove();
1258
1562
  container = null;
1259
1563
  }
@@ -1277,12 +1581,22 @@ function sendMessage(content) {
1277
1581
  }
1278
1582
  return client.sendMessage(content);
1279
1583
  }
1280
- function trigger(eventName, data) {
1584
+ function trigger(eventName, data, options) {
1281
1585
  if (!client) {
1282
1586
  console.warn("[PocketPing] Not initialized, cannot trigger event");
1283
1587
  return;
1284
1588
  }
1285
- client.trigger(eventName, data);
1589
+ client.trigger(eventName, data, options);
1590
+ }
1591
+ function setupTrackedElements(elements) {
1592
+ if (!client) {
1593
+ console.warn("[PocketPing] Not initialized, cannot setup tracked elements");
1594
+ return;
1595
+ }
1596
+ client.setupTrackedElements(elements);
1597
+ }
1598
+ function getTrackedElements() {
1599
+ return client?.getTrackedElements() || [];
1286
1600
  }
1287
1601
  function onEvent(eventName, handler) {
1288
1602
  if (!client) {
@@ -1334,12 +1648,13 @@ if (typeof document !== "undefined") {
1334
1648
  }
1335
1649
  }
1336
1650
  }
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 = {
1651
+ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity, setupTrackedElements, getTrackedElements };
1652
+ export {
1340
1653
  close,
1654
+ index_default as default,
1341
1655
  destroy,
1342
1656
  getIdentity,
1657
+ getTrackedElements,
1343
1658
  identify,
1344
1659
  init,
1345
1660
  offEvent,
@@ -1348,6 +1663,7 @@ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger,
1348
1663
  open,
1349
1664
  reset,
1350
1665
  sendMessage,
1666
+ setupTrackedElements,
1351
1667
  toggle,
1352
1668
  trigger
1353
- });
1669
+ };