@pocketping/widget 1.2.0 → 1.4.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 {
@@ -650,9 +738,8 @@ function styles(primaryColor, theme) {
650
738
  }
651
739
 
652
740
  /* Drag & Drop */
653
- .pp-dragging {
654
- position: relative;
655
- }
741
+ /* Note: .pp-window already has position: fixed which acts as
742
+ containing block for the absolutely positioned overlay */
656
743
 
657
744
  .pp-drop-overlay {
658
745
  position: absolute;
@@ -858,6 +945,8 @@ function styles(primaryColor, theme) {
858
945
  margin-bottom: 6px;
859
946
  border-radius: 0 4px 4px 0;
860
947
  font-size: 12px;
948
+ position: relative;
949
+ z-index: 1;
861
950
  }
862
951
 
863
952
  .pp-reply-sender {
@@ -875,6 +964,17 @@ function styles(primaryColor, theme) {
875
964
  text-overflow: ellipsis;
876
965
  }
877
966
 
967
+ /* Reply quote in visitor message bubble needs higher contrast */
968
+ .pp-message-visitor .pp-reply-quote {
969
+ background: rgba(255, 255, 255, 0.18);
970
+ border-left-color: rgba(255, 255, 255, 0.7);
971
+ }
972
+
973
+ .pp-message-visitor .pp-reply-sender,
974
+ .pp-message-visitor .pp-reply-content {
975
+ color: rgba(255, 255, 255, 0.9);
976
+ }
977
+
878
978
  /* Deleted Message */
879
979
  .pp-message-deleted {
880
980
  opacity: 0.6;
@@ -919,6 +1019,8 @@ function ChatWidget({ client: client2, config: initialConfig }) {
919
1019
  const [editContent, setEditContent] = (0, import_hooks.useState)("");
920
1020
  const [messageMenu, setMessageMenu] = (0, import_hooks.useState)(null);
921
1021
  const [isDragging, setIsDragging] = (0, import_hooks.useState)(false);
1022
+ const [hoveredMessageId, setHoveredMessageId] = (0, import_hooks.useState)(null);
1023
+ const [longPressTimer, setLongPressTimer] = (0, import_hooks.useState)(null);
922
1024
  const [config, setConfig] = (0, import_hooks.useState)(initialConfig);
923
1025
  const messagesEndRef = (0, import_hooks.useRef)(null);
924
1026
  const inputRef = (0, import_hooks.useRef)(null);
@@ -1146,6 +1248,25 @@ function ChatWidget({ client: client2, config: initialConfig }) {
1146
1248
  y: mouseEvent.clientY
1147
1249
  });
1148
1250
  };
1251
+ const handleTouchStart = (message) => {
1252
+ const timer = setTimeout(() => {
1253
+ if (navigator.vibrate) navigator.vibrate(50);
1254
+ setMessageMenu({
1255
+ message,
1256
+ x: window.innerWidth / 2 - 60,
1257
+ // Center horizontally
1258
+ y: window.innerHeight / 2 - 50
1259
+ // Center vertically
1260
+ });
1261
+ }, 500);
1262
+ setLongPressTimer(timer);
1263
+ };
1264
+ const handleTouchEnd = () => {
1265
+ if (longPressTimer) {
1266
+ clearTimeout(longPressTimer);
1267
+ setLongPressTimer(null);
1268
+ }
1269
+ };
1149
1270
  (0, import_hooks.useEffect)(() => {
1150
1271
  if (!messageMenu) return;
1151
1272
  const handleClickOutside = () => setMessageMenu(null);
@@ -1227,6 +1348,7 @@ function ChatWidget({ client: client2, config: initialConfig }) {
1227
1348
  const position = config.position ?? "bottom-right";
1228
1349
  const theme = getTheme(config.theme ?? "auto");
1229
1350
  const primaryColor = config.primaryColor ?? "#6366f1";
1351
+ const actionIconColor = theme === "dark" ? "#9ca3af" : "#6b7280";
1230
1352
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_preact.Fragment, { children: [
1231
1353
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: styles(primaryColor, theme) }),
1232
1354
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -1284,18 +1406,70 @@ function ChatWidget({ client: client2, config: initialConfig }) {
1284
1406
  messages.map((msg) => {
1285
1407
  const isDeleted = !!msg.deletedAt;
1286
1408
  const isEdited = !!msg.editedAt;
1287
- const replyToMsg = msg.replyTo ? messages.find((m) => m.id === msg.replyTo) : null;
1409
+ let replyData = null;
1410
+ if (msg.replyTo) {
1411
+ if (typeof msg.replyTo === "object") {
1412
+ replyData = msg.replyTo;
1413
+ } else {
1414
+ const replyToMsg = messages.find((m) => m.id === msg.replyTo);
1415
+ if (replyToMsg) {
1416
+ replyData = {
1417
+ sender: replyToMsg.sender,
1418
+ content: replyToMsg.content,
1419
+ deleted: !!replyToMsg.deletedAt
1420
+ };
1421
+ }
1422
+ }
1423
+ }
1424
+ const isHovered = hoveredMessageId === msg.id;
1425
+ const showActions = isHovered && !isDeleted;
1288
1426
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1289
1427
  "div",
1290
1428
  {
1291
1429
  class: `pp-message pp-message-${msg.sender} ${isDeleted ? "pp-message-deleted" : ""}`,
1292
1430
  onContextMenu: (e) => handleMessageContextMenu(e, msg),
1431
+ onMouseEnter: () => setHoveredMessageId(msg.id),
1432
+ onMouseLeave: () => setHoveredMessageId(null),
1433
+ onTouchStart: () => handleTouchStart(msg),
1434
+ onTouchEnd: handleTouchEnd,
1435
+ onTouchCancel: handleTouchEnd,
1293
1436
  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" }),
1437
+ showActions && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: `pp-message-actions ${msg.sender === "visitor" ? "pp-actions-left" : "pp-actions-right"}`, children: [
1438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1439
+ "button",
1440
+ {
1441
+ class: "pp-action-btn",
1442
+ onClick: () => handleReply(msg),
1443
+ title: "Reply",
1444
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReplyIcon, { color: actionIconColor })
1445
+ }
1446
+ ),
1447
+ msg.sender === "visitor" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1448
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1449
+ "button",
1450
+ {
1451
+ class: "pp-action-btn",
1452
+ onClick: () => handleStartEdit(msg),
1453
+ title: "Edit",
1454
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditIcon, { color: actionIconColor })
1455
+ }
1456
+ ),
1457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1458
+ "button",
1459
+ {
1460
+ class: "pp-action-btn pp-action-delete",
1461
+ onClick: () => handleDelete(msg),
1462
+ title: "Delete",
1463
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteIcon, { color: actionIconColor })
1464
+ }
1465
+ )
1466
+ ] })
1467
+ ] }),
1468
+ replyData && replyData.content && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-reply-quote", children: [
1469
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-reply-sender", children: replyData.sender === "visitor" ? "You" : "Support" }),
1296
1470
  /* @__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 ? "..." : ""
1471
+ replyData.deleted ? "Message deleted" : (replyData.content || "").slice(0, 50),
1472
+ (replyData.content || "").length > 50 ? "..." : ""
1299
1473
  ] })
1300
1474
  ] }),
1301
1475
  isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message-content pp-deleted-content", children: [
@@ -1330,16 +1504,16 @@ function ChatWidget({ client: client2, config: initialConfig }) {
1330
1504
  style: { top: `${messageMenu.y}px`, left: `${messageMenu.x}px` },
1331
1505
  children: [
1332
1506
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: () => handleReply(messageMenu.message), children: [
1333
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReplyIcon, {}),
1507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReplyIcon, { color: actionIconColor }),
1334
1508
  " Reply"
1335
1509
  ] }),
1336
1510
  messageMenu.message.sender === "visitor" && !messageMenu.message.deletedAt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1337
1511
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: () => handleStartEdit(messageMenu.message), children: [
1338
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditIcon, {}),
1512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditIcon, { color: actionIconColor }),
1339
1513
  " Edit"
1340
1514
  ] }),
1341
1515
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { class: "pp-menu-delete", onClick: () => handleDelete(messageMenu.message), children: [
1342
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteIcon, {}),
1516
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteIcon, { color: "#ef4444" }),
1343
1517
  " Delete"
1344
1518
  ] })
1345
1519
  ] })
@@ -1505,17 +1679,20 @@ function StatusIcon({ status }) {
1505
1679
  function AttachIcon() {
1506
1680
  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
1681
  }
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: [
1682
+ function ReplyIcon({ color, size = 16 }) {
1683
+ const strokeColor = color || "currentColor";
1684
+ 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
1685
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "9 17 4 12 9 7" }),
1511
1686
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
1512
1687
  ] });
1513
1688
  }
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" }) });
1689
+ function EditIcon({ color, size = 16 }) {
1690
+ const strokeColor = color || "currentColor";
1691
+ 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
1692
  }
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: [
1693
+ function DeleteIcon({ color, size = 16 }) {
1694
+ const strokeColor = color || "currentColor";
1695
+ 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
1696
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "3 6 5 6 21 6" }),
1520
1697
  /* @__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
1698
  ] });
@@ -2513,7 +2690,14 @@ var PocketPingClient = class {
2513
2690
  }
2514
2691
  connectSSE() {
2515
2692
  if (!this.session) return;
2516
- const sseUrl = this.config.endpoint.replace(/\/$/, "") + `/stream?sessionId=${this.session.sessionId}`;
2693
+ const params = new URLSearchParams({
2694
+ sessionId: this.session.sessionId
2695
+ });
2696
+ const lastEventTimestamp = this.getLastEventTimestamp();
2697
+ if (lastEventTimestamp) {
2698
+ params.set("after", lastEventTimestamp);
2699
+ }
2700
+ const sseUrl = this.config.endpoint.replace(/\/$/, "") + `/stream?${params.toString()}`;
2517
2701
  try {
2518
2702
  this.sse = new EventSource(sseUrl);
2519
2703
  const connectionTimeout = setTimeout(() => {
@@ -2568,6 +2752,19 @@ var PocketPingClient = class {
2568
2752
  handleRealtimeEvent(event) {
2569
2753
  this.handleWebSocketEvent(event);
2570
2754
  }
2755
+ getLastEventTimestamp() {
2756
+ if (!this.session) return null;
2757
+ let latest = null;
2758
+ for (const msg of this.session.messages) {
2759
+ const candidates = [msg.timestamp, msg.editedAt, msg.deletedAt, msg.deliveredAt, msg.readAt].filter(Boolean).map((value) => new Date(value)).filter((date) => !isNaN(date.getTime()));
2760
+ for (const date of candidates) {
2761
+ if (!latest || date > latest) {
2762
+ latest = date;
2763
+ }
2764
+ }
2765
+ }
2766
+ return latest ? latest.toISOString() : null;
2767
+ }
2571
2768
  handleWebSocketEvent(event) {
2572
2769
  switch (event.type) {
2573
2770
  case "message":
@@ -2590,12 +2787,41 @@ var PocketPingClient = class {
2590
2787
  }
2591
2788
  if (existingIndex >= 0) {
2592
2789
  const existing = this.session.messages[existingIndex];
2790
+ let updated = false;
2593
2791
  if (message.status && message.status !== existing.status) {
2594
2792
  existing.status = message.status;
2595
- if (message.deliveredAt) existing.deliveredAt = message.deliveredAt;
2596
- if (message.readAt) existing.readAt = message.readAt;
2793
+ updated = true;
2794
+ if (message.deliveredAt) {
2795
+ existing.deliveredAt = message.deliveredAt;
2796
+ }
2797
+ if (message.readAt) {
2798
+ existing.readAt = message.readAt;
2799
+ }
2597
2800
  this.emit("read", { messageIds: [message.id], status: message.status });
2598
2801
  }
2802
+ if (message.content !== void 0 && message.content !== existing.content) {
2803
+ existing.content = message.content;
2804
+ updated = true;
2805
+ }
2806
+ if (message.editedAt !== void 0 && message.editedAt !== existing.editedAt) {
2807
+ existing.editedAt = message.editedAt;
2808
+ updated = true;
2809
+ }
2810
+ if (message.deletedAt !== void 0 && message.deletedAt !== existing.deletedAt) {
2811
+ existing.deletedAt = message.deletedAt;
2812
+ updated = true;
2813
+ }
2814
+ if (message.replyTo !== void 0) {
2815
+ existing.replyTo = message.replyTo;
2816
+ updated = true;
2817
+ }
2818
+ if (message.attachments !== void 0) {
2819
+ existing.attachments = message.attachments;
2820
+ updated = true;
2821
+ }
2822
+ if (updated) {
2823
+ this.emit("message", existing);
2824
+ }
2599
2825
  } else {
2600
2826
  this.session.messages.push(message);
2601
2827
  this.emit("message", message);
@@ -2634,6 +2860,29 @@ var PocketPingClient = class {
2634
2860
  }
2635
2861
  this.emit("read", readData);
2636
2862
  break;
2863
+ case "message_edited":
2864
+ if (this.session) {
2865
+ const editData = event.data;
2866
+ const msgIndex = this.session.messages.findIndex((m) => m.id === editData.messageId);
2867
+ if (msgIndex >= 0) {
2868
+ const existing = this.session.messages[msgIndex];
2869
+ existing.content = editData.content;
2870
+ existing.editedAt = editData.editedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2871
+ this.emit("message", existing);
2872
+ }
2873
+ }
2874
+ break;
2875
+ case "message_deleted":
2876
+ if (this.session) {
2877
+ const deleteData = event.data;
2878
+ const msgIndex = this.session.messages.findIndex((m) => m.id === deleteData.messageId);
2879
+ if (msgIndex >= 0) {
2880
+ const existing = this.session.messages[msgIndex];
2881
+ existing.deletedAt = deleteData.deletedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2882
+ this.emit("message", existing);
2883
+ }
2884
+ }
2885
+ break;
2637
2886
  case "event":
2638
2887
  const customEvent = event.data;
2639
2888
  this.emitCustomEvent(customEvent);
@@ -2693,11 +2942,45 @@ var PocketPingClient = class {
2693
2942
  const poll = async () => {
2694
2943
  if (!this.session) return;
2695
2944
  try {
2696
- const lastMessageId = this.session.messages[this.session.messages.length - 1]?.id;
2697
- const newMessages = await this.fetchMessages(lastMessageId);
2945
+ const lastEventTimestamp = this.getLastEventTimestamp();
2946
+ const newMessages = await this.fetchMessages(lastEventTimestamp ?? void 0);
2698
2947
  this.pollingFailures = 0;
2699
2948
  for (const message of newMessages) {
2700
- if (!this.session.messages.find((m) => m.id === message.id)) {
2949
+ const existingIndex = this.session.messages.findIndex((m) => m.id === message.id);
2950
+ if (existingIndex >= 0) {
2951
+ const existing = this.session.messages[existingIndex];
2952
+ let updated = false;
2953
+ if (message.status && message.status !== existing.status) {
2954
+ existing.status = message.status;
2955
+ updated = true;
2956
+ if (message.deliveredAt) existing.deliveredAt = message.deliveredAt;
2957
+ if (message.readAt) existing.readAt = message.readAt;
2958
+ this.emit("read", { messageIds: [message.id], status: message.status });
2959
+ }
2960
+ if (message.content !== void 0 && message.content !== existing.content) {
2961
+ existing.content = message.content;
2962
+ updated = true;
2963
+ }
2964
+ if (message.editedAt !== void 0 && message.editedAt !== existing.editedAt) {
2965
+ existing.editedAt = message.editedAt;
2966
+ updated = true;
2967
+ }
2968
+ if (message.deletedAt !== void 0 && message.deletedAt !== existing.deletedAt) {
2969
+ existing.deletedAt = message.deletedAt;
2970
+ updated = true;
2971
+ }
2972
+ if (message.replyTo !== void 0) {
2973
+ existing.replyTo = message.replyTo;
2974
+ updated = true;
2975
+ }
2976
+ if (message.attachments !== void 0) {
2977
+ existing.attachments = message.attachments;
2978
+ updated = true;
2979
+ }
2980
+ if (updated) {
2981
+ this.emit("message", existing);
2982
+ }
2983
+ } else {
2701
2984
  this.session.messages.push(message);
2702
2985
  this.emit("message", message);
2703
2986
  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 };