@pocketping/widget 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -269,6 +269,94 @@ function styles(primaryColor, theme) {
269
269
  padding: 10px 14px;
270
270
  border-radius: 16px;
271
271
  word-wrap: break-word;
272
+ position: relative;
273
+ user-select: text;
274
+ -webkit-user-select: text;
275
+ }
276
+
277
+ /* Hover actions container - positioned above message (Slack style) */
278
+ .pp-message-actions {
279
+ position: absolute;
280
+ top: -28px;
281
+ display: flex;
282
+ gap: 2px;
283
+ background: ${colors.bg};
284
+ border: 1px solid ${colors.border};
285
+ border-radius: 6px;
286
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
287
+ padding: 2px;
288
+ opacity: 0;
289
+ animation: pp-actions-fade-in 0.12s ease forwards;
290
+ z-index: 10;
291
+ /* Reset color inheritance from message */
292
+ color: ${colors.textSecondary};
293
+ }
294
+
295
+ @keyframes pp-actions-fade-in {
296
+ from { opacity: 0; transform: translateY(4px); }
297
+ to { opacity: 1; transform: translateY(0); }
298
+ }
299
+
300
+ /* Visitor messages: actions aligned right */
301
+ .pp-actions-left {
302
+ right: 0;
303
+ }
304
+
305
+ /* Operator messages: actions aligned left */
306
+ .pp-actions-right {
307
+ left: 0;
308
+ }
309
+
310
+ .pp-message-actions .pp-action-btn {
311
+ width: 24px;
312
+ height: 24px;
313
+ border: none;
314
+ background: transparent;
315
+ border-radius: 4px;
316
+ cursor: pointer;
317
+ display: flex;
318
+ align-items: center;
319
+ justify-content: center;
320
+ color: ${colors.textSecondary} !important;
321
+ transition: background 0.1s, color 0.1s;
322
+ }
323
+
324
+ .pp-message-actions .pp-action-btn:hover {
325
+ background: ${colors.bgSecondary};
326
+ color: ${colors.text} !important;
327
+ }
328
+
329
+ .pp-message-actions .pp-action-btn svg {
330
+ width: 14px;
331
+ height: 14px;
332
+ stroke: ${colors.textSecondary};
333
+ }
334
+
335
+ .pp-message-actions .pp-action-btn:hover svg {
336
+ stroke: ${colors.text};
337
+ }
338
+
339
+ .pp-message-actions .pp-action-delete:hover {
340
+ background: #fef2f2;
341
+ }
342
+
343
+ .pp-message-actions .pp-action-delete:hover svg {
344
+ stroke: #ef4444;
345
+ }
346
+
347
+ .pp-theme-dark .pp-message-actions .pp-action-delete:hover {
348
+ background: #7f1d1d;
349
+ }
350
+
351
+ .pp-theme-dark .pp-message-actions .pp-action-delete:hover svg {
352
+ stroke: #fca5a5;
353
+ }
354
+
355
+ /* Hide hover actions on mobile */
356
+ @media (hover: none) and (pointer: coarse) {
357
+ .pp-message-actions {
358
+ display: none;
359
+ }
272
360
  }
273
361
 
274
362
  .pp-message-visitor {
@@ -858,6 +946,8 @@ function styles(primaryColor, theme) {
858
946
  margin-bottom: 6px;
859
947
  border-radius: 0 4px 4px 0;
860
948
  font-size: 12px;
949
+ position: relative;
950
+ z-index: 1;
861
951
  }
862
952
 
863
953
  .pp-reply-sender {
@@ -875,6 +965,17 @@ function styles(primaryColor, theme) {
875
965
  text-overflow: ellipsis;
876
966
  }
877
967
 
968
+ /* Reply quote in visitor message bubble needs higher contrast */
969
+ .pp-message-visitor .pp-reply-quote {
970
+ background: rgba(255, 255, 255, 0.18);
971
+ border-left-color: rgba(255, 255, 255, 0.7);
972
+ }
973
+
974
+ .pp-message-visitor .pp-reply-sender,
975
+ .pp-message-visitor .pp-reply-content {
976
+ color: rgba(255, 255, 255, 0.9);
977
+ }
978
+
878
979
  /* Deleted Message */
879
980
  .pp-message-deleted {
880
981
  opacity: 0.6;
@@ -919,6 +1020,8 @@ function ChatWidget({ client: client2, config: initialConfig }) {
919
1020
  const [editContent, setEditContent] = (0, import_hooks.useState)("");
920
1021
  const [messageMenu, setMessageMenu] = (0, import_hooks.useState)(null);
921
1022
  const [isDragging, setIsDragging] = (0, import_hooks.useState)(false);
1023
+ const [hoveredMessageId, setHoveredMessageId] = (0, import_hooks.useState)(null);
1024
+ const [longPressTimer, setLongPressTimer] = (0, import_hooks.useState)(null);
922
1025
  const [config, setConfig] = (0, import_hooks.useState)(initialConfig);
923
1026
  const messagesEndRef = (0, import_hooks.useRef)(null);
924
1027
  const inputRef = (0, import_hooks.useRef)(null);
@@ -1146,6 +1249,25 @@ function ChatWidget({ client: client2, config: initialConfig }) {
1146
1249
  y: mouseEvent.clientY
1147
1250
  });
1148
1251
  };
1252
+ const handleTouchStart = (message) => {
1253
+ const timer = setTimeout(() => {
1254
+ if (navigator.vibrate) navigator.vibrate(50);
1255
+ setMessageMenu({
1256
+ message,
1257
+ x: window.innerWidth / 2 - 60,
1258
+ // Center horizontally
1259
+ y: window.innerHeight / 2 - 50
1260
+ // Center vertically
1261
+ });
1262
+ }, 500);
1263
+ setLongPressTimer(timer);
1264
+ };
1265
+ const handleTouchEnd = () => {
1266
+ if (longPressTimer) {
1267
+ clearTimeout(longPressTimer);
1268
+ setLongPressTimer(null);
1269
+ }
1270
+ };
1149
1271
  (0, import_hooks.useEffect)(() => {
1150
1272
  if (!messageMenu) return;
1151
1273
  const handleClickOutside = () => setMessageMenu(null);
@@ -1227,6 +1349,7 @@ function ChatWidget({ client: client2, config: initialConfig }) {
1227
1349
  const position = config.position ?? "bottom-right";
1228
1350
  const theme = getTheme(config.theme ?? "auto");
1229
1351
  const primaryColor = config.primaryColor ?? "#6366f1";
1352
+ const actionIconColor = theme === "dark" ? "#9ca3af" : "#6b7280";
1230
1353
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_preact.Fragment, { children: [
1231
1354
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: styles(primaryColor, theme) }),
1232
1355
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -1284,18 +1407,70 @@ function ChatWidget({ client: client2, config: initialConfig }) {
1284
1407
  messages.map((msg) => {
1285
1408
  const isDeleted = !!msg.deletedAt;
1286
1409
  const isEdited = !!msg.editedAt;
1287
- const replyToMsg = msg.replyTo ? messages.find((m) => m.id === msg.replyTo) : null;
1410
+ let replyData = null;
1411
+ if (msg.replyTo) {
1412
+ if (typeof msg.replyTo === "object") {
1413
+ replyData = msg.replyTo;
1414
+ } else {
1415
+ const replyToMsg = messages.find((m) => m.id === msg.replyTo);
1416
+ if (replyToMsg) {
1417
+ replyData = {
1418
+ sender: replyToMsg.sender,
1419
+ content: replyToMsg.content,
1420
+ deleted: !!replyToMsg.deletedAt
1421
+ };
1422
+ }
1423
+ }
1424
+ }
1425
+ const isHovered = hoveredMessageId === msg.id;
1426
+ const showActions = isHovered && !isDeleted;
1288
1427
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1289
1428
  "div",
1290
1429
  {
1291
1430
  class: `pp-message pp-message-${msg.sender} ${isDeleted ? "pp-message-deleted" : ""}`,
1292
1431
  onContextMenu: (e) => handleMessageContextMenu(e, msg),
1432
+ onMouseEnter: () => setHoveredMessageId(msg.id),
1433
+ onMouseLeave: () => setHoveredMessageId(null),
1434
+ onTouchStart: () => handleTouchStart(msg),
1435
+ onTouchEnd: handleTouchEnd,
1436
+ onTouchCancel: handleTouchEnd,
1293
1437
  children: [
1294
- replyToMsg && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-reply-quote", children: [
1295
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-reply-sender", children: replyToMsg.sender === "visitor" ? "You" : "Support" }),
1438
+ showActions && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: `pp-message-actions ${msg.sender === "visitor" ? "pp-actions-left" : "pp-actions-right"}`, children: [
1439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1440
+ "button",
1441
+ {
1442
+ class: "pp-action-btn",
1443
+ onClick: () => handleReply(msg),
1444
+ title: "Reply",
1445
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReplyIcon, { color: actionIconColor })
1446
+ }
1447
+ ),
1448
+ msg.sender === "visitor" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1449
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1450
+ "button",
1451
+ {
1452
+ class: "pp-action-btn",
1453
+ onClick: () => handleStartEdit(msg),
1454
+ title: "Edit",
1455
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditIcon, { color: actionIconColor })
1456
+ }
1457
+ ),
1458
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1459
+ "button",
1460
+ {
1461
+ class: "pp-action-btn pp-action-delete",
1462
+ onClick: () => handleDelete(msg),
1463
+ title: "Delete",
1464
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteIcon, { color: actionIconColor })
1465
+ }
1466
+ )
1467
+ ] })
1468
+ ] }),
1469
+ replyData && replyData.content && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-reply-quote", children: [
1470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-reply-sender", children: replyData.sender === "visitor" ? "You" : "Support" }),
1296
1471
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { class: "pp-reply-content", children: [
1297
- replyToMsg.deletedAt ? "Message deleted" : replyToMsg.content.slice(0, 50),
1298
- replyToMsg.content.length > 50 ? "..." : ""
1472
+ replyData.deleted ? "Message deleted" : (replyData.content || "").slice(0, 50),
1473
+ (replyData.content || "").length > 50 ? "..." : ""
1299
1474
  ] })
1300
1475
  ] }),
1301
1476
  isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message-content pp-deleted-content", children: [
@@ -1330,16 +1505,16 @@ function ChatWidget({ client: client2, config: initialConfig }) {
1330
1505
  style: { top: `${messageMenu.y}px`, left: `${messageMenu.x}px` },
1331
1506
  children: [
1332
1507
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: () => handleReply(messageMenu.message), children: [
1333
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReplyIcon, {}),
1508
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReplyIcon, { color: actionIconColor }),
1334
1509
  " Reply"
1335
1510
  ] }),
1336
1511
  messageMenu.message.sender === "visitor" && !messageMenu.message.deletedAt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1337
1512
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: () => handleStartEdit(messageMenu.message), children: [
1338
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditIcon, {}),
1513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditIcon, { color: actionIconColor }),
1339
1514
  " Edit"
1340
1515
  ] }),
1341
1516
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { class: "pp-menu-delete", onClick: () => handleDelete(messageMenu.message), children: [
1342
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteIcon, {}),
1517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteIcon, { color: "#ef4444" }),
1343
1518
  " Delete"
1344
1519
  ] })
1345
1520
  ] })
@@ -1505,17 +1680,20 @@ function StatusIcon({ status }) {
1505
1680
  function AttachIcon() {
1506
1681
  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.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
1507
1682
  }
1508
- function ReplyIcon() {
1509
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1683
+ function ReplyIcon({ color, size = 16 }) {
1684
+ const strokeColor = color || "currentColor";
1685
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", "stroke-width": "2", style: { stroke: strokeColor, width: `${size}px`, minWidth: `${size}px`, height: `${size}px`, display: "block", flexShrink: 0 }, children: [
1510
1686
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "9 17 4 12 9 7" }),
1511
1687
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
1512
1688
  ] });
1513
1689
  }
1514
- function EditIcon() {
1515
- 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: "M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z" }) });
1690
+ function EditIcon({ color, size = 16 }) {
1691
+ const strokeColor = color || "currentColor";
1692
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", "stroke-width": "2", style: { stroke: strokeColor, width: `${size}px`, minWidth: `${size}px`, height: `${size}px`, display: "block", flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z" }) });
1516
1693
  }
1517
- function DeleteIcon() {
1518
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1694
+ function DeleteIcon({ color, size = 16 }) {
1695
+ const strokeColor = color || "currentColor";
1696
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", "stroke-width": "2", style: { stroke: strokeColor, width: `${size}px`, minWidth: `${size}px`, height: `${size}px`, display: "block", flexShrink: 0 }, children: [
1519
1697
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "3 6 5 6 21 6" }),
1520
1698
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
1521
1699
  ] });
@@ -2513,7 +2691,14 @@ var PocketPingClient = class {
2513
2691
  }
2514
2692
  connectSSE() {
2515
2693
  if (!this.session) return;
2516
- const sseUrl = this.config.endpoint.replace(/\/$/, "") + `/stream?sessionId=${this.session.sessionId}`;
2694
+ const params = new URLSearchParams({
2695
+ sessionId: this.session.sessionId
2696
+ });
2697
+ const lastEventTimestamp = this.getLastEventTimestamp();
2698
+ if (lastEventTimestamp) {
2699
+ params.set("after", lastEventTimestamp);
2700
+ }
2701
+ const sseUrl = this.config.endpoint.replace(/\/$/, "") + `/stream?${params.toString()}`;
2517
2702
  try {
2518
2703
  this.sse = new EventSource(sseUrl);
2519
2704
  const connectionTimeout = setTimeout(() => {
@@ -2568,6 +2753,19 @@ var PocketPingClient = class {
2568
2753
  handleRealtimeEvent(event) {
2569
2754
  this.handleWebSocketEvent(event);
2570
2755
  }
2756
+ getLastEventTimestamp() {
2757
+ if (!this.session) return null;
2758
+ let latest = null;
2759
+ for (const msg of this.session.messages) {
2760
+ const candidates = [msg.timestamp, msg.editedAt, msg.deletedAt, msg.deliveredAt, msg.readAt].filter(Boolean).map((value) => new Date(value)).filter((date) => !isNaN(date.getTime()));
2761
+ for (const date of candidates) {
2762
+ if (!latest || date > latest) {
2763
+ latest = date;
2764
+ }
2765
+ }
2766
+ }
2767
+ return latest ? latest.toISOString() : null;
2768
+ }
2571
2769
  handleWebSocketEvent(event) {
2572
2770
  switch (event.type) {
2573
2771
  case "message":
@@ -2590,12 +2788,41 @@ var PocketPingClient = class {
2590
2788
  }
2591
2789
  if (existingIndex >= 0) {
2592
2790
  const existing = this.session.messages[existingIndex];
2791
+ let updated = false;
2593
2792
  if (message.status && message.status !== existing.status) {
2594
2793
  existing.status = message.status;
2595
- if (message.deliveredAt) existing.deliveredAt = message.deliveredAt;
2596
- if (message.readAt) existing.readAt = message.readAt;
2794
+ updated = true;
2795
+ if (message.deliveredAt) {
2796
+ existing.deliveredAt = message.deliveredAt;
2797
+ }
2798
+ if (message.readAt) {
2799
+ existing.readAt = message.readAt;
2800
+ }
2597
2801
  this.emit("read", { messageIds: [message.id], status: message.status });
2598
2802
  }
2803
+ if (message.content !== void 0 && message.content !== existing.content) {
2804
+ existing.content = message.content;
2805
+ updated = true;
2806
+ }
2807
+ if (message.editedAt !== void 0 && message.editedAt !== existing.editedAt) {
2808
+ existing.editedAt = message.editedAt;
2809
+ updated = true;
2810
+ }
2811
+ if (message.deletedAt !== void 0 && message.deletedAt !== existing.deletedAt) {
2812
+ existing.deletedAt = message.deletedAt;
2813
+ updated = true;
2814
+ }
2815
+ if (message.replyTo !== void 0) {
2816
+ existing.replyTo = message.replyTo;
2817
+ updated = true;
2818
+ }
2819
+ if (message.attachments !== void 0) {
2820
+ existing.attachments = message.attachments;
2821
+ updated = true;
2822
+ }
2823
+ if (updated) {
2824
+ this.emit("message", existing);
2825
+ }
2599
2826
  } else {
2600
2827
  this.session.messages.push(message);
2601
2828
  this.emit("message", message);
@@ -2634,6 +2861,29 @@ var PocketPingClient = class {
2634
2861
  }
2635
2862
  this.emit("read", readData);
2636
2863
  break;
2864
+ case "message_edited":
2865
+ if (this.session) {
2866
+ const editData = event.data;
2867
+ const msgIndex = this.session.messages.findIndex((m) => m.id === editData.messageId);
2868
+ if (msgIndex >= 0) {
2869
+ const existing = this.session.messages[msgIndex];
2870
+ existing.content = editData.content;
2871
+ existing.editedAt = editData.editedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2872
+ this.emit("message", existing);
2873
+ }
2874
+ }
2875
+ break;
2876
+ case "message_deleted":
2877
+ if (this.session) {
2878
+ const deleteData = event.data;
2879
+ const msgIndex = this.session.messages.findIndex((m) => m.id === deleteData.messageId);
2880
+ if (msgIndex >= 0) {
2881
+ const existing = this.session.messages[msgIndex];
2882
+ existing.deletedAt = deleteData.deletedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2883
+ this.emit("message", existing);
2884
+ }
2885
+ }
2886
+ break;
2637
2887
  case "event":
2638
2888
  const customEvent = event.data;
2639
2889
  this.emitCustomEvent(customEvent);
@@ -2693,11 +2943,45 @@ var PocketPingClient = class {
2693
2943
  const poll = async () => {
2694
2944
  if (!this.session) return;
2695
2945
  try {
2696
- const lastMessageId = this.session.messages[this.session.messages.length - 1]?.id;
2697
- const newMessages = await this.fetchMessages(lastMessageId);
2946
+ const lastEventTimestamp = this.getLastEventTimestamp();
2947
+ const newMessages = await this.fetchMessages(lastEventTimestamp ?? void 0);
2698
2948
  this.pollingFailures = 0;
2699
2949
  for (const message of newMessages) {
2700
- if (!this.session.messages.find((m) => m.id === message.id)) {
2950
+ const existingIndex = this.session.messages.findIndex((m) => m.id === message.id);
2951
+ if (existingIndex >= 0) {
2952
+ const existing = this.session.messages[existingIndex];
2953
+ let updated = false;
2954
+ if (message.status && message.status !== existing.status) {
2955
+ existing.status = message.status;
2956
+ updated = true;
2957
+ if (message.deliveredAt) existing.deliveredAt = message.deliveredAt;
2958
+ if (message.readAt) existing.readAt = message.readAt;
2959
+ this.emit("read", { messageIds: [message.id], status: message.status });
2960
+ }
2961
+ if (message.content !== void 0 && message.content !== existing.content) {
2962
+ existing.content = message.content;
2963
+ updated = true;
2964
+ }
2965
+ if (message.editedAt !== void 0 && message.editedAt !== existing.editedAt) {
2966
+ existing.editedAt = message.editedAt;
2967
+ updated = true;
2968
+ }
2969
+ if (message.deletedAt !== void 0 && message.deletedAt !== existing.deletedAt) {
2970
+ existing.deletedAt = message.deletedAt;
2971
+ updated = true;
2972
+ }
2973
+ if (message.replyTo !== void 0) {
2974
+ existing.replyTo = message.replyTo;
2975
+ updated = true;
2976
+ }
2977
+ if (message.attachments !== void 0) {
2978
+ existing.attachments = message.attachments;
2979
+ updated = true;
2980
+ }
2981
+ if (updated) {
2982
+ this.emit("message", existing);
2983
+ }
2984
+ } else {
2701
2985
  this.session.messages.push(message);
2702
2986
  this.emit("message", message);
2703
2987
  this.config.onMessage?.(message);
package/dist/index.d.cts CHANGED
@@ -75,13 +75,21 @@ interface Attachment {
75
75
  thumbnailUrl?: string;
76
76
  status: AttachmentStatus;
77
77
  }
78
+ /** Reply reference - either a string ID or embedded data from SSE */
79
+ interface ReplyToData {
80
+ id: string;
81
+ content: string;
82
+ sender: string;
83
+ deleted?: boolean;
84
+ }
78
85
  interface Message {
79
86
  id: string;
80
87
  sessionId: string;
81
88
  content: string;
82
89
  sender: 'visitor' | 'operator' | 'ai';
83
90
  timestamp: string;
84
- replyTo?: string;
91
+ /** Reply reference - string ID when sending, object with data from SSE */
92
+ replyTo?: string | ReplyToData;
85
93
  metadata?: Record<string, unknown>;
86
94
  attachments?: Attachment[];
87
95
  status?: MessageStatus;
@@ -341,6 +349,7 @@ declare class PocketPingClient {
341
349
  private connectSSE;
342
350
  private handleWsFailure;
343
351
  private handleRealtimeEvent;
352
+ private getLastEventTimestamp;
344
353
  private handleWebSocketEvent;
345
354
  private handleVersionWarning;
346
355
  private scheduleReconnect;
@@ -496,4 +505,4 @@ declare const _default: {
496
505
  getTrackedElements: typeof getTrackedElements;
497
506
  };
498
507
 
499
- export { type Attachment, type CustomEvent, type CustomEventHandler, type Message, type PocketPingConfig, type TrackedElement, type TriggerOptions, type UserIdentity, type VersionWarning, close, _default as default, destroy, getIdentity, getTrackedElements, identify, init, offEvent, on, onEvent, open, reset, sendMessage, setupTrackedElements, toggle, trigger, uploadFile, uploadFiles };
508
+ export { type Attachment, type CustomEvent, type CustomEventHandler, type Message, type PocketPingConfig, type ReplyToData, type TrackedElement, type TriggerOptions, type UserIdentity, type VersionWarning, close, _default as default, destroy, getIdentity, getTrackedElements, identify, init, offEvent, on, onEvent, open, reset, sendMessage, setupTrackedElements, toggle, trigger, uploadFile, uploadFiles };
package/dist/index.d.ts CHANGED
@@ -75,13 +75,21 @@ interface Attachment {
75
75
  thumbnailUrl?: string;
76
76
  status: AttachmentStatus;
77
77
  }
78
+ /** Reply reference - either a string ID or embedded data from SSE */
79
+ interface ReplyToData {
80
+ id: string;
81
+ content: string;
82
+ sender: string;
83
+ deleted?: boolean;
84
+ }
78
85
  interface Message {
79
86
  id: string;
80
87
  sessionId: string;
81
88
  content: string;
82
89
  sender: 'visitor' | 'operator' | 'ai';
83
90
  timestamp: string;
84
- replyTo?: string;
91
+ /** Reply reference - string ID when sending, object with data from SSE */
92
+ replyTo?: string | ReplyToData;
85
93
  metadata?: Record<string, unknown>;
86
94
  attachments?: Attachment[];
87
95
  status?: MessageStatus;
@@ -341,6 +349,7 @@ declare class PocketPingClient {
341
349
  private connectSSE;
342
350
  private handleWsFailure;
343
351
  private handleRealtimeEvent;
352
+ private getLastEventTimestamp;
344
353
  private handleWebSocketEvent;
345
354
  private handleVersionWarning;
346
355
  private scheduleReconnect;
@@ -496,4 +505,4 @@ declare const _default: {
496
505
  getTrackedElements: typeof getTrackedElements;
497
506
  };
498
507
 
499
- export { type Attachment, type CustomEvent, type CustomEventHandler, type Message, type PocketPingConfig, type TrackedElement, type TriggerOptions, type UserIdentity, type VersionWarning, close, _default as default, destroy, getIdentity, getTrackedElements, identify, init, offEvent, on, onEvent, open, reset, sendMessage, setupTrackedElements, toggle, trigger, uploadFile, uploadFiles };
508
+ export { type Attachment, type CustomEvent, type CustomEventHandler, type Message, type PocketPingConfig, type ReplyToData, type TrackedElement, type TriggerOptions, type UserIdentity, type VersionWarning, close, _default as default, destroy, getIdentity, getTrackedElements, identify, init, offEvent, on, onEvent, open, reset, sendMessage, setupTrackedElements, toggle, trigger, uploadFile, uploadFiles };