@pocketping/widget 1.0.2 → 1.2.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
@@ -35,7 +35,9 @@ __export(index_exports, {
35
35
  sendMessage: () => sendMessage,
36
36
  setupTrackedElements: () => setupTrackedElements,
37
37
  toggle: () => toggle,
38
- trigger: () => trigger
38
+ trigger: () => trigger,
39
+ uploadFile: () => uploadFile,
40
+ uploadFiles: () => uploadFiles
39
41
  });
40
42
  module.exports = __toCommonJS(index_exports);
41
43
  var import_preact2 = require("preact");
@@ -229,6 +231,12 @@ function styles(primaryColor, theme) {
229
231
  border-radius: 4px;
230
232
  opacity: 0.8;
231
233
  transition: opacity 0.2s;
234
+ flex-shrink: 0;
235
+ width: 28px;
236
+ height: 28px;
237
+ display: flex;
238
+ align-items: center;
239
+ justify-content: center;
232
240
  }
233
241
 
234
242
  .pp-close-btn:hover {
@@ -236,8 +244,8 @@ function styles(primaryColor, theme) {
236
244
  }
237
245
 
238
246
  .pp-close-btn svg {
239
- width: 20px;
240
- height: 20px;
247
+ width: 16px;
248
+ height: 16px;
241
249
  }
242
250
 
243
251
  .pp-messages {
@@ -411,6 +419,486 @@ function styles(primaryColor, theme) {
411
419
  .pp-footer a:hover {
412
420
  text-decoration: underline;
413
421
  }
422
+
423
+ /* Attachment Styles */
424
+ .pp-file-input {
425
+ /* Use offscreen positioning instead of display:none for better browser compatibility */
426
+ position: absolute;
427
+ width: 1px;
428
+ height: 1px;
429
+ padding: 0;
430
+ margin: -1px;
431
+ overflow: hidden;
432
+ clip: rect(0, 0, 0, 0);
433
+ white-space: nowrap;
434
+ border: 0;
435
+ }
436
+
437
+ .pp-attach-btn {
438
+ width: 40px;
439
+ height: 40px;
440
+ border-radius: 50%;
441
+ background: transparent;
442
+ color: ${colors.textSecondary};
443
+ border: 1px solid ${colors.border};
444
+ cursor: pointer;
445
+ display: flex;
446
+ align-items: center;
447
+ justify-content: center;
448
+ transition: color 0.2s, border-color 0.2s;
449
+ flex-shrink: 0;
450
+ }
451
+
452
+ .pp-attach-btn:hover:not(:disabled) {
453
+ color: ${primaryColor};
454
+ border-color: ${primaryColor};
455
+ }
456
+
457
+ .pp-attach-btn:disabled {
458
+ opacity: 0.5;
459
+ cursor: not-allowed;
460
+ }
461
+
462
+ .pp-attach-btn svg {
463
+ width: 18px;
464
+ height: 18px;
465
+ }
466
+
467
+ .pp-attachments-preview {
468
+ display: flex;
469
+ gap: 8px;
470
+ padding: 8px 12px;
471
+ border-top: 1px solid ${colors.border};
472
+ overflow-x: auto;
473
+ background: ${colors.bgSecondary};
474
+ }
475
+
476
+ .pp-attachment-preview {
477
+ position: relative;
478
+ width: 60px;
479
+ height: 60px;
480
+ border-radius: 8px;
481
+ overflow: hidden;
482
+ flex-shrink: 0;
483
+ background: ${colors.bg};
484
+ border: 1px solid ${colors.border};
485
+ }
486
+
487
+ .pp-preview-img {
488
+ width: 100%;
489
+ height: 100%;
490
+ object-fit: cover;
491
+ }
492
+
493
+ .pp-preview-file {
494
+ width: 100%;
495
+ height: 100%;
496
+ display: flex;
497
+ align-items: center;
498
+ justify-content: center;
499
+ color: ${colors.textSecondary};
500
+ }
501
+
502
+ .pp-preview-file svg {
503
+ width: 24px;
504
+ height: 24px;
505
+ }
506
+
507
+ .pp-remove-attachment {
508
+ position: absolute;
509
+ top: 2px;
510
+ right: 2px;
511
+ width: 18px;
512
+ height: 18px;
513
+ border-radius: 50%;
514
+ background: rgba(0, 0, 0, 0.6);
515
+ color: white;
516
+ border: none;
517
+ cursor: pointer;
518
+ display: flex;
519
+ align-items: center;
520
+ justify-content: center;
521
+ padding: 0;
522
+ }
523
+
524
+ .pp-remove-attachment svg {
525
+ width: 10px;
526
+ height: 10px;
527
+ }
528
+
529
+ .pp-upload-progress {
530
+ position: absolute;
531
+ bottom: 0;
532
+ left: 0;
533
+ height: 3px;
534
+ background: ${primaryColor};
535
+ transition: width 0.1s;
536
+ }
537
+
538
+ .pp-upload-error {
539
+ position: absolute;
540
+ top: 50%;
541
+ left: 50%;
542
+ transform: translate(-50%, -50%);
543
+ width: 24px;
544
+ height: 24px;
545
+ border-radius: 50%;
546
+ background: #ef4444;
547
+ color: white;
548
+ font-weight: bold;
549
+ display: flex;
550
+ align-items: center;
551
+ justify-content: center;
552
+ font-size: 14px;
553
+ }
554
+
555
+ .pp-attachment-uploading {
556
+ opacity: 0.7;
557
+ }
558
+
559
+ .pp-attachment-error {
560
+ border-color: #ef4444;
561
+ }
562
+
563
+ /* Message Attachments */
564
+ .pp-message-attachments {
565
+ display: flex;
566
+ flex-direction: column;
567
+ gap: 8px;
568
+ margin-top: 4px;
569
+ }
570
+
571
+ .pp-attachment {
572
+ display: block;
573
+ text-decoration: none;
574
+ color: inherit;
575
+ border-radius: 8px;
576
+ overflow: hidden;
577
+ }
578
+
579
+ .pp-attachment-image img {
580
+ max-width: 200px;
581
+ max-height: 200px;
582
+ border-radius: 8px;
583
+ display: block;
584
+ }
585
+
586
+ .pp-attachment-audio {
587
+ display: flex;
588
+ flex-direction: column;
589
+ gap: 4px;
590
+ }
591
+
592
+ .pp-attachment-audio audio {
593
+ width: 200px;
594
+ height: 36px;
595
+ }
596
+
597
+ .pp-attachment-audio .pp-attachment-name {
598
+ font-size: 11px;
599
+ opacity: 0.7;
600
+ white-space: nowrap;
601
+ overflow: hidden;
602
+ text-overflow: ellipsis;
603
+ max-width: 200px;
604
+ }
605
+
606
+ .pp-attachment-video video {
607
+ max-width: 200px;
608
+ max-height: 200px;
609
+ border-radius: 8px;
610
+ display: block;
611
+ }
612
+
613
+ .pp-attachment-file {
614
+ display: flex;
615
+ align-items: center;
616
+ gap: 8px;
617
+ padding: 8px 12px;
618
+ background: ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
619
+ border-radius: 8px;
620
+ transition: background 0.2s;
621
+ }
622
+
623
+ .pp-attachment-file:hover {
624
+ background: ${isDark ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.08)"};
625
+ }
626
+
627
+ .pp-attachment-file svg {
628
+ width: 24px;
629
+ height: 24px;
630
+ flex-shrink: 0;
631
+ }
632
+
633
+ .pp-attachment-info {
634
+ display: flex;
635
+ flex-direction: column;
636
+ min-width: 0;
637
+ }
638
+
639
+ .pp-attachment-name {
640
+ font-size: 13px;
641
+ font-weight: 500;
642
+ white-space: nowrap;
643
+ overflow: hidden;
644
+ text-overflow: ellipsis;
645
+ }
646
+
647
+ .pp-attachment-size {
648
+ font-size: 11px;
649
+ opacity: 0.7;
650
+ }
651
+
652
+ /* Drag & Drop */
653
+ .pp-dragging {
654
+ position: relative;
655
+ }
656
+
657
+ .pp-drop-overlay {
658
+ position: absolute;
659
+ inset: 0;
660
+ background: ${isDark ? "rgba(0,0,0,0.9)" : "rgba(255,255,255,0.95)"};
661
+ display: flex;
662
+ flex-direction: column;
663
+ align-items: center;
664
+ justify-content: center;
665
+ gap: 12px;
666
+ z-index: 100;
667
+ border: 3px dashed ${primaryColor};
668
+ border-radius: 16px;
669
+ margin: 4px;
670
+ pointer-events: none;
671
+ }
672
+
673
+ .pp-drop-icon svg {
674
+ width: 48px;
675
+ height: 48px;
676
+ color: ${primaryColor};
677
+ }
678
+
679
+ .pp-drop-text {
680
+ font-size: 16px;
681
+ font-weight: 500;
682
+ color: ${colors.text};
683
+ }
684
+
685
+ /* Message Context Menu */
686
+ .pp-message-menu {
687
+ position: fixed;
688
+ background: ${colors.bg};
689
+ border: 1px solid ${colors.border};
690
+ border-radius: 8px;
691
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
692
+ padding: 4px;
693
+ z-index: 200;
694
+ min-width: 120px;
695
+ }
696
+
697
+ .pp-message-menu button {
698
+ display: flex;
699
+ align-items: center;
700
+ gap: 8px;
701
+ width: 100%;
702
+ padding: 8px 12px;
703
+ border: none;
704
+ background: transparent;
705
+ color: ${colors.text};
706
+ font-size: 13px;
707
+ cursor: pointer;
708
+ border-radius: 4px;
709
+ text-align: left;
710
+ }
711
+
712
+ .pp-message-menu button:hover {
713
+ background: ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
714
+ }
715
+
716
+ .pp-message-menu button svg {
717
+ width: 16px;
718
+ height: 16px;
719
+ }
720
+
721
+ .pp-menu-delete {
722
+ color: #ef4444 !important;
723
+ }
724
+
725
+ /* Edit Modal */
726
+ .pp-edit-modal {
727
+ position: absolute;
728
+ bottom: 80px;
729
+ left: 12px;
730
+ right: 12px;
731
+ background: ${colors.bg};
732
+ border: 1px solid ${colors.border};
733
+ border-radius: 12px;
734
+ box-shadow: 0 4px 20px rgba(0,0,0,0.15);
735
+ z-index: 150;
736
+ overflow: hidden;
737
+ }
738
+
739
+ .pp-edit-header {
740
+ display: flex;
741
+ justify-content: space-between;
742
+ align-items: center;
743
+ padding: 12px 16px;
744
+ border-bottom: 1px solid ${colors.border};
745
+ font-weight: 500;
746
+ }
747
+
748
+ .pp-edit-header button {
749
+ background: transparent;
750
+ border: none;
751
+ color: ${colors.textSecondary};
752
+ cursor: pointer;
753
+ padding: 4px;
754
+ }
755
+
756
+ .pp-edit-header button svg {
757
+ width: 18px;
758
+ height: 18px;
759
+ }
760
+
761
+ .pp-edit-input {
762
+ width: 100%;
763
+ padding: 12px 16px;
764
+ border: none;
765
+ background: transparent;
766
+ color: ${colors.text};
767
+ font-size: 14px;
768
+ resize: none;
769
+ min-height: 80px;
770
+ outline: none;
771
+ }
772
+
773
+ .pp-edit-actions {
774
+ display: flex;
775
+ justify-content: flex-end;
776
+ gap: 8px;
777
+ padding: 12px 16px;
778
+ border-top: 1px solid ${colors.border};
779
+ }
780
+
781
+ .pp-edit-cancel {
782
+ padding: 8px 16px;
783
+ border: 1px solid ${colors.border};
784
+ border-radius: 6px;
785
+ background: transparent;
786
+ color: ${colors.text};
787
+ font-size: 13px;
788
+ cursor: pointer;
789
+ }
790
+
791
+ .pp-edit-save {
792
+ padding: 8px 16px;
793
+ border: none;
794
+ border-radius: 6px;
795
+ background: ${primaryColor};
796
+ color: white;
797
+ font-size: 13px;
798
+ cursor: pointer;
799
+ }
800
+
801
+ .pp-edit-save:disabled {
802
+ opacity: 0.5;
803
+ cursor: not-allowed;
804
+ }
805
+
806
+ /* Reply Preview */
807
+ .pp-reply-preview {
808
+ display: flex;
809
+ align-items: center;
810
+ gap: 8px;
811
+ padding: 8px 12px;
812
+ background: ${isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.03)"};
813
+ border-top: 1px solid ${colors.border};
814
+ border-left: 3px solid ${primaryColor};
815
+ }
816
+
817
+ .pp-reply-preview-content {
818
+ flex: 1;
819
+ min-width: 0;
820
+ }
821
+
822
+ .pp-reply-label {
823
+ display: block;
824
+ font-size: 11px;
825
+ color: ${primaryColor};
826
+ font-weight: 500;
827
+ margin-bottom: 2px;
828
+ }
829
+
830
+ .pp-reply-text {
831
+ display: block;
832
+ font-size: 12px;
833
+ color: ${colors.textSecondary};
834
+ white-space: nowrap;
835
+ overflow: hidden;
836
+ text-overflow: ellipsis;
837
+ }
838
+
839
+ .pp-reply-cancel {
840
+ background: transparent;
841
+ border: none;
842
+ color: ${colors.textSecondary};
843
+ cursor: pointer;
844
+ padding: 4px;
845
+ flex-shrink: 0;
846
+ }
847
+
848
+ .pp-reply-cancel svg {
849
+ width: 16px;
850
+ height: 16px;
851
+ }
852
+
853
+ /* Reply Quote in Message */
854
+ .pp-reply-quote {
855
+ background: ${isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.05)"};
856
+ border-left: 2px solid ${primaryColor};
857
+ padding: 4px 8px;
858
+ margin-bottom: 6px;
859
+ border-radius: 0 4px 4px 0;
860
+ font-size: 12px;
861
+ }
862
+
863
+ .pp-reply-sender {
864
+ display: block;
865
+ font-weight: 500;
866
+ color: ${primaryColor};
867
+ margin-bottom: 2px;
868
+ }
869
+
870
+ .pp-reply-content {
871
+ display: block;
872
+ color: ${colors.textSecondary};
873
+ white-space: nowrap;
874
+ overflow: hidden;
875
+ text-overflow: ellipsis;
876
+ }
877
+
878
+ /* Deleted Message */
879
+ .pp-message-deleted {
880
+ opacity: 0.6;
881
+ }
882
+
883
+ .pp-deleted-content {
884
+ font-style: italic;
885
+ color: ${colors.textSecondary};
886
+ display: flex;
887
+ align-items: center;
888
+ gap: 4px;
889
+ }
890
+
891
+ .pp-deleted-icon {
892
+ font-size: 12px;
893
+ }
894
+
895
+ /* Edited Badge */
896
+ .pp-edited-badge {
897
+ font-size: 10px;
898
+ color: ${colors.textSecondary};
899
+ margin-left: 4px;
900
+ font-style: italic;
901
+ }
414
902
  `;
415
903
  }
416
904
 
@@ -424,9 +912,18 @@ function ChatWidget({ client: client2, config: initialConfig }) {
424
912
  const [operatorOnline, setOperatorOnline] = (0, import_hooks.useState)(false);
425
913
  const [isConnected, setIsConnected] = (0, import_hooks.useState)(false);
426
914
  const [unreadCount, setUnreadCount] = (0, import_hooks.useState)(0);
915
+ const [pendingAttachments, setPendingAttachments] = (0, import_hooks.useState)([]);
916
+ const [isUploading, setIsUploading] = (0, import_hooks.useState)(false);
917
+ const [replyingTo, setReplyingTo] = (0, import_hooks.useState)(null);
918
+ const [editingMessage, setEditingMessage] = (0, import_hooks.useState)(null);
919
+ const [editContent, setEditContent] = (0, import_hooks.useState)("");
920
+ const [messageMenu, setMessageMenu] = (0, import_hooks.useState)(null);
921
+ const [isDragging, setIsDragging] = (0, import_hooks.useState)(false);
427
922
  const [config, setConfig] = (0, import_hooks.useState)(initialConfig);
428
923
  const messagesEndRef = (0, import_hooks.useRef)(null);
429
924
  const inputRef = (0, import_hooks.useRef)(null);
925
+ const fileInputRef = (0, import_hooks.useRef)(null);
926
+ const messagesContainerRef = (0, import_hooks.useRef)(null);
430
927
  (0, import_hooks.useEffect)(() => {
431
928
  const unsubOpen = client2.on("openChange", setIsOpen);
432
929
  const unsubMessage = client2.on("message", () => {
@@ -523,11 +1020,17 @@ function ChatWidget({ client: client2, config: initialConfig }) {
523
1020
  if (!shouldShow) return null;
524
1021
  const handleSubmit = async (e) => {
525
1022
  e.preventDefault();
526
- if (!inputValue.trim()) return;
1023
+ const hasContent = inputValue.trim().length > 0;
1024
+ const readyAttachments = pendingAttachments.filter((a) => a.status === "ready" && a.attachment);
1025
+ if (!hasContent && readyAttachments.length === 0) return;
527
1026
  const content = inputValue;
1027
+ const attachmentIds = readyAttachments.map((a) => a.attachment.id);
1028
+ const replyToId = replyingTo?.id;
528
1029
  setInputValue("");
1030
+ setPendingAttachments([]);
1031
+ setReplyingTo(null);
529
1032
  try {
530
- await client2.sendMessage(content);
1033
+ await client2.sendMessage(content, attachmentIds, replyToId);
531
1034
  } catch (err) {
532
1035
  console.error("[PocketPing] Failed to send message:", err);
533
1036
  }
@@ -537,6 +1040,190 @@ function ChatWidget({ client: client2, config: initialConfig }) {
537
1040
  setInputValue(target.value);
538
1041
  client2.sendTyping(true);
539
1042
  };
1043
+ const handleFileSelect = async (e) => {
1044
+ const target = e.target;
1045
+ const files = target.files;
1046
+ if (!files || files.length === 0) return;
1047
+ const newPending = [];
1048
+ for (let i = 0; i < files.length; i++) {
1049
+ const file = files[i];
1050
+ const id = `pending-${Date.now()}-${i}`;
1051
+ let preview;
1052
+ if (file.type.startsWith("image/")) {
1053
+ preview = URL.createObjectURL(file);
1054
+ }
1055
+ newPending.push({
1056
+ id,
1057
+ file,
1058
+ preview,
1059
+ progress: 0,
1060
+ status: "pending"
1061
+ });
1062
+ }
1063
+ setPendingAttachments((prev) => [...prev, ...newPending]);
1064
+ target.value = "";
1065
+ setIsUploading(true);
1066
+ for (const pending of newPending) {
1067
+ try {
1068
+ setPendingAttachments(
1069
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, status: "uploading" } : a)
1070
+ );
1071
+ const attachment = await client2.uploadFile(pending.file, (progress) => {
1072
+ setPendingAttachments(
1073
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, progress } : a)
1074
+ );
1075
+ });
1076
+ setPendingAttachments(
1077
+ (prev) => prev.map(
1078
+ (a) => a.id === pending.id ? { ...a, status: "ready", progress: 100, attachment } : a
1079
+ )
1080
+ );
1081
+ } catch (err) {
1082
+ console.error("[PocketPing] Failed to upload file:", err);
1083
+ setPendingAttachments(
1084
+ (prev) => prev.map(
1085
+ (a) => a.id === pending.id ? { ...a, status: "error", error: "Upload failed" } : a
1086
+ )
1087
+ );
1088
+ }
1089
+ }
1090
+ setIsUploading(false);
1091
+ };
1092
+ const handleRemoveAttachment = (id) => {
1093
+ setPendingAttachments((prev) => {
1094
+ const removed = prev.find((a) => a.id === id);
1095
+ if (removed?.preview) {
1096
+ URL.revokeObjectURL(removed.preview);
1097
+ }
1098
+ return prev.filter((a) => a.id !== id);
1099
+ });
1100
+ };
1101
+ const handleReply = (message) => {
1102
+ setReplyingTo(message);
1103
+ setMessageMenu(null);
1104
+ inputRef.current?.focus();
1105
+ };
1106
+ const handleCancelReply = () => {
1107
+ setReplyingTo(null);
1108
+ };
1109
+ const handleStartEdit = (message) => {
1110
+ if (message.sender !== "visitor") return;
1111
+ setEditingMessage(message);
1112
+ setEditContent(message.content);
1113
+ setMessageMenu(null);
1114
+ };
1115
+ const handleCancelEdit = () => {
1116
+ setEditingMessage(null);
1117
+ setEditContent("");
1118
+ };
1119
+ const handleSaveEdit = async () => {
1120
+ if (!editingMessage || !editContent.trim()) return;
1121
+ try {
1122
+ await client2.editMessage(editingMessage.id, editContent.trim());
1123
+ setEditingMessage(null);
1124
+ setEditContent("");
1125
+ } catch (err) {
1126
+ console.error("[PocketPing] Failed to edit message:", err);
1127
+ }
1128
+ };
1129
+ const handleDelete = async (message) => {
1130
+ if (message.sender !== "visitor") return;
1131
+ setMessageMenu(null);
1132
+ if (confirm("Delete this message?")) {
1133
+ try {
1134
+ await client2.deleteMessage(message.id);
1135
+ } catch (err) {
1136
+ console.error("[PocketPing] Failed to delete message:", err);
1137
+ }
1138
+ }
1139
+ };
1140
+ const handleMessageContextMenu = (e, message) => {
1141
+ e.preventDefault();
1142
+ const mouseEvent = e;
1143
+ setMessageMenu({
1144
+ message,
1145
+ x: mouseEvent.clientX,
1146
+ y: mouseEvent.clientY
1147
+ });
1148
+ };
1149
+ (0, import_hooks.useEffect)(() => {
1150
+ if (!messageMenu) return;
1151
+ const handleClickOutside = () => setMessageMenu(null);
1152
+ document.addEventListener("click", handleClickOutside);
1153
+ return () => document.removeEventListener("click", handleClickOutside);
1154
+ }, [messageMenu]);
1155
+ const dragCounterRef = (0, import_hooks.useRef)(0);
1156
+ const handleDragEnter = (e) => {
1157
+ e.preventDefault();
1158
+ e.stopPropagation();
1159
+ dragCounterRef.current++;
1160
+ if (dragCounterRef.current === 1) {
1161
+ setIsDragging(true);
1162
+ }
1163
+ };
1164
+ const handleDragOver = (e) => {
1165
+ e.preventDefault();
1166
+ e.stopPropagation();
1167
+ };
1168
+ const handleDragLeave = (e) => {
1169
+ e.preventDefault();
1170
+ e.stopPropagation();
1171
+ dragCounterRef.current--;
1172
+ if (dragCounterRef.current === 0) {
1173
+ setIsDragging(false);
1174
+ }
1175
+ };
1176
+ const handleDrop = async (e) => {
1177
+ e.preventDefault();
1178
+ e.stopPropagation();
1179
+ dragCounterRef.current = 0;
1180
+ setIsDragging(false);
1181
+ const files = e.dataTransfer?.files;
1182
+ if (!files || files.length === 0) return;
1183
+ const newPending = [];
1184
+ for (let i = 0; i < files.length; i++) {
1185
+ const file = files[i];
1186
+ const id = `pending-${Date.now()}-${i}`;
1187
+ let preview;
1188
+ if (file.type.startsWith("image/")) {
1189
+ preview = URL.createObjectURL(file);
1190
+ }
1191
+ newPending.push({
1192
+ id,
1193
+ file,
1194
+ preview,
1195
+ progress: 0,
1196
+ status: "pending"
1197
+ });
1198
+ }
1199
+ setPendingAttachments((prev) => [...prev, ...newPending]);
1200
+ setIsUploading(true);
1201
+ for (const pending of newPending) {
1202
+ try {
1203
+ setPendingAttachments(
1204
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, status: "uploading" } : a)
1205
+ );
1206
+ const attachment = await client2.uploadFile(pending.file, (progress) => {
1207
+ setPendingAttachments(
1208
+ (prev) => prev.map((a) => a.id === pending.id ? { ...a, progress } : a)
1209
+ );
1210
+ });
1211
+ setPendingAttachments(
1212
+ (prev) => prev.map(
1213
+ (a) => a.id === pending.id ? { ...a, status: "ready", progress: 100, attachment } : a
1214
+ )
1215
+ );
1216
+ } catch (err) {
1217
+ console.error("[PocketPing] Failed to upload dropped file:", err);
1218
+ setPendingAttachments(
1219
+ (prev) => prev.map(
1220
+ (a) => a.id === pending.id ? { ...a, status: "error", error: "Upload failed" } : a
1221
+ )
1222
+ );
1223
+ }
1224
+ }
1225
+ setIsUploading(false);
1226
+ };
540
1227
  const position = config.position ?? "bottom-right";
541
1228
  const theme = getTheme(config.theme ?? "auto");
542
1229
  const primaryColor = config.primaryColor ?? "#6366f1";
@@ -555,84 +1242,211 @@ function ChatWidget({ client: client2, config: initialConfig }) {
555
1242
  ]
556
1243
  }
557
1244
  ),
558
- isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: `pp-window pp-${position} pp-theme-${theme}`, children: [
559
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-header", children: [
560
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-header-info", children: [
561
- config.operatorAvatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: config.operatorAvatar, alt: "", class: "pp-avatar" }),
562
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
563
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-header-title", children: config.operatorName ?? "Support" }),
564
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-header-status", children: operatorOnline ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
565
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-status-dot pp-online" }),
566
- " Online"
567
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
568
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-status-dot" }),
569
- " Away"
570
- ] }) })
571
- ] })
572
- ] }),
573
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
574
- "button",
575
- {
576
- class: "pp-close-btn",
577
- onClick: () => client2.setOpen(false),
578
- "aria-label": "Close chat",
579
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {})
580
- }
581
- )
582
- ] }),
583
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-messages", children: [
584
- config.welcomeMessage && messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-welcome", children: config.welcomeMessage }),
585
- messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
586
- "div",
587
- {
588
- class: `pp-message pp-message-${msg.sender}`,
589
- children: [
590
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-message-content", children: msg.content }),
591
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message-time", children: [
592
- formatTime(msg.timestamp),
593
- msg.sender === "ai" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-ai-badge", children: "AI" }),
594
- 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 }) })
1245
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1246
+ "div",
1247
+ {
1248
+ class: `pp-window pp-${position} pp-theme-${theme} ${isDragging ? "pp-dragging" : ""}`,
1249
+ onDragEnter: handleDragEnter,
1250
+ onDragOver: handleDragOver,
1251
+ onDragLeave: handleDragLeave,
1252
+ onDrop: handleDrop,
1253
+ children: [
1254
+ isDragging && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-drop-overlay", children: [
1255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-drop-icon", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AttachIcon, {}) }),
1256
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-drop-text", children: "Drop files to upload" })
1257
+ ] }),
1258
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-header", children: [
1259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-header-info", children: [
1260
+ config.operatorAvatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: config.operatorAvatar, alt: "", class: "pp-avatar" }),
1261
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-header-title", children: config.operatorName ?? "Support" }),
1263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-header-status", children: operatorOnline ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-status-dot pp-online" }),
1265
+ " Online"
1266
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-status-dot" }),
1268
+ " Away"
1269
+ ] }) })
595
1270
  ] })
596
- ]
597
- },
598
- msg.id
599
- )),
600
- isTyping && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message pp-message-operator pp-typing", children: [
601
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
602
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
603
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {})
604
- ] }),
605
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: messagesEndRef })
606
- ] }),
607
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { class: "pp-input-form", onSubmit: handleSubmit, children: [
608
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
609
- "input",
610
- {
611
- ref: inputRef,
612
- type: "text",
613
- class: "pp-input",
614
- placeholder: config.placeholder ?? "Type a message...",
615
- value: inputValue,
616
- onInput: handleInputChange,
617
- disabled: !isConnected
618
- }
619
- ),
620
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
621
- "button",
622
- {
623
- type: "submit",
624
- class: "pp-send-btn",
625
- disabled: !inputValue.trim() || !isConnected,
626
- "aria-label": "Send message",
627
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SendIcon, {})
628
- }
629
- )
630
- ] }),
631
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-footer", children: [
632
- "Powered by ",
633
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "https://pocketping.io", target: "_blank", rel: "noopener", children: "PocketPing" })
634
- ] })
635
- ] })
1271
+ ] }),
1272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1273
+ "button",
1274
+ {
1275
+ class: "pp-close-btn",
1276
+ onClick: () => client2.setOpen(false),
1277
+ "aria-label": "Close chat",
1278
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {})
1279
+ }
1280
+ )
1281
+ ] }),
1282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-messages", ref: messagesContainerRef, children: [
1283
+ config.welcomeMessage && messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-welcome", children: config.welcomeMessage }),
1284
+ messages.map((msg) => {
1285
+ const isDeleted = !!msg.deletedAt;
1286
+ const isEdited = !!msg.editedAt;
1287
+ const replyToMsg = msg.replyTo ? messages.find((m) => m.id === msg.replyTo) : null;
1288
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1289
+ "div",
1290
+ {
1291
+ class: `pp-message pp-message-${msg.sender} ${isDeleted ? "pp-message-deleted" : ""}`,
1292
+ onContextMenu: (e) => handleMessageContextMenu(e, msg),
1293
+ 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" }),
1296
+ /* @__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 ? "..." : ""
1299
+ ] })
1300
+ ] }),
1301
+ isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message-content pp-deleted-content", children: [
1302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-deleted-icon", children: "\u{1F5D1}\uFE0F" }),
1303
+ " Message deleted"
1304
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1305
+ msg.content && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-message-content", children: msg.content }),
1306
+ msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-message-attachments", children: msg.attachments.map((att) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AttachmentDisplay, { attachment: att }, att.id)) })
1307
+ ] }),
1308
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message-time", children: [
1309
+ formatTime(msg.timestamp),
1310
+ isEdited && !isDeleted && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-edited-badge", children: "edited" }),
1311
+ msg.sender === "ai" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-ai-badge", children: "AI" }),
1312
+ msg.sender === "visitor" && !isDeleted && /* @__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 }) })
1313
+ ] })
1314
+ ]
1315
+ },
1316
+ msg.id
1317
+ );
1318
+ }),
1319
+ isTyping && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-message pp-message-operator pp-typing", children: [
1320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
1321
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {}),
1322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {})
1323
+ ] }),
1324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: messagesEndRef })
1325
+ ] }),
1326
+ messageMenu && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1327
+ "div",
1328
+ {
1329
+ class: "pp-message-menu",
1330
+ style: { top: `${messageMenu.y}px`, left: `${messageMenu.x}px` },
1331
+ children: [
1332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: () => handleReply(messageMenu.message), children: [
1333
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReplyIcon, {}),
1334
+ " Reply"
1335
+ ] }),
1336
+ messageMenu.message.sender === "visitor" && !messageMenu.message.deletedAt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: () => handleStartEdit(messageMenu.message), children: [
1338
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EditIcon, {}),
1339
+ " Edit"
1340
+ ] }),
1341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { class: "pp-menu-delete", onClick: () => handleDelete(messageMenu.message), children: [
1342
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteIcon, {}),
1343
+ " Delete"
1344
+ ] })
1345
+ ] })
1346
+ ]
1347
+ }
1348
+ ),
1349
+ editingMessage && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-edit-modal", children: [
1350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-edit-header", children: [
1351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Edit message" }),
1352
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: handleCancelEdit, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {}) })
1353
+ ] }),
1354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1355
+ "textarea",
1356
+ {
1357
+ class: "pp-edit-input",
1358
+ value: editContent,
1359
+ onInput: (e) => setEditContent(e.target.value),
1360
+ autoFocus: true
1361
+ }
1362
+ ),
1363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-edit-actions", children: [
1364
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { class: "pp-edit-cancel", onClick: handleCancelEdit, children: "Cancel" }),
1365
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { class: "pp-edit-save", onClick: handleSaveEdit, disabled: !editContent.trim(), children: "Save" })
1366
+ ] })
1367
+ ] }),
1368
+ replyingTo && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-reply-preview", children: [
1369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-reply-preview-content", children: [
1370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-reply-label", children: "Replying to" }),
1371
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { class: "pp-reply-text", children: [
1372
+ replyingTo.content.slice(0, 50),
1373
+ replyingTo.content.length > 50 ? "..." : ""
1374
+ ] })
1375
+ ] }),
1376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { class: "pp-reply-cancel", onClick: handleCancelReply, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {}) })
1377
+ ] }),
1378
+ pendingAttachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-attachments-preview", children: pendingAttachments.map((pending) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: `pp-attachment-preview pp-attachment-${pending.status}`, children: [
1379
+ pending.preview ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: pending.preview, alt: pending.file.name, class: "pp-preview-img" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-preview-file", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileIcon, { mimeType: pending.file.type }) }),
1380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1381
+ "button",
1382
+ {
1383
+ class: "pp-remove-attachment",
1384
+ onClick: () => handleRemoveAttachment(pending.id),
1385
+ "aria-label": "Remove attachment",
1386
+ type: "button",
1387
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CloseIcon, {})
1388
+ }
1389
+ ),
1390
+ pending.status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-upload-progress", style: { width: `${pending.progress}%` } }),
1391
+ pending.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-upload-error", title: pending.error, children: "!" })
1392
+ ] }, pending.id)) }),
1393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { class: "pp-input-form", onSubmit: handleSubmit, children: [
1394
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1395
+ "input",
1396
+ {
1397
+ ref: (el) => {
1398
+ fileInputRef.current = el;
1399
+ if (el) {
1400
+ el.onchange = handleFileSelect;
1401
+ }
1402
+ },
1403
+ type: "file",
1404
+ class: "pp-file-input",
1405
+ accept: "image/*,audio/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.txt",
1406
+ multiple: true
1407
+ }
1408
+ ),
1409
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1410
+ "button",
1411
+ {
1412
+ type: "button",
1413
+ class: "pp-attach-btn",
1414
+ onClick: () => fileInputRef.current?.click(),
1415
+ disabled: !isConnected || isUploading,
1416
+ "aria-label": "Attach file",
1417
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AttachIcon, {})
1418
+ }
1419
+ ),
1420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1421
+ "input",
1422
+ {
1423
+ ref: inputRef,
1424
+ type: "text",
1425
+ class: "pp-input",
1426
+ placeholder: config.placeholder ?? "Type a message...",
1427
+ value: inputValue,
1428
+ onInput: handleInputChange,
1429
+ disabled: !isConnected
1430
+ }
1431
+ ),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1433
+ "button",
1434
+ {
1435
+ type: "submit",
1436
+ class: "pp-send-btn",
1437
+ disabled: !inputValue.trim() && pendingAttachments.filter((a) => a.status === "ready").length === 0 || !isConnected || isUploading,
1438
+ "aria-label": "Send message",
1439
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SendIcon, {})
1440
+ }
1441
+ )
1442
+ ] }),
1443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-footer", children: [
1444
+ "Powered by ",
1445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "https://pocketping.io", target: "_blank", rel: "noopener", children: "PocketPing" })
1446
+ ] })
1447
+ ]
1448
+ }
1449
+ )
636
1450
  ] });
637
1451
  }
638
1452
  function checkPageVisibility(config) {
@@ -688,15 +1502,96 @@ function StatusIcon({ status }) {
688
1502
  }
689
1503
  return null;
690
1504
  }
1505
+ function AttachIcon() {
1506
+ 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
+ }
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: [
1510
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "9 17 4 12 9 7" }),
1511
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })
1512
+ ] });
1513
+ }
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" }) });
1516
+ }
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: [
1519
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "3 6 5 6 21 6" }),
1520
+ /* @__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
+ ] });
1522
+ }
1523
+ function FileIcon({ mimeType }) {
1524
+ if (mimeType === "application/pdf") {
1525
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1526
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1527
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "14 2 14 8 20 8" }),
1528
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 15h6" }),
1529
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 11h6" })
1530
+ ] });
1531
+ }
1532
+ if (mimeType.startsWith("audio/")) {
1533
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1534
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 18V5l12-2v13" }),
1535
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "6", cy: "18", r: "3" }),
1536
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "18", cy: "16", r: "3" })
1537
+ ] });
1538
+ }
1539
+ if (mimeType.startsWith("video/")) {
1540
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1541
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "2", width: "20", height: "20", rx: "2.18", ry: "2.18" }),
1542
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "7", y1: "2", x2: "7", y2: "22" }),
1543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "17", y1: "2", x2: "17", y2: "22" }),
1544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
1545
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "2", y1: "7", x2: "7", y2: "7" }),
1546
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "2", y1: "17", x2: "7", y2: "17" }),
1547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "17", y1: "17", x2: "22", y2: "17" }),
1548
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "17", y1: "7", x2: "22", y2: "7" })
1549
+ ] });
1550
+ }
1551
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", children: [
1552
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
1553
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "14 2 14 8 20 8" })
1554
+ ] });
1555
+ }
1556
+ function AttachmentDisplay({ attachment }) {
1557
+ const isImage = attachment.mimeType.startsWith("image/");
1558
+ const isAudio = attachment.mimeType.startsWith("audio/");
1559
+ const isVideo = attachment.mimeType.startsWith("video/");
1560
+ const formatSize = (bytes) => {
1561
+ if (bytes < 1024) return `${bytes} B`;
1562
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1563
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1564
+ };
1565
+ if (isImage) {
1566
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: attachment.url, target: "_blank", rel: "noopener", class: "pp-attachment pp-attachment-image", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: attachment.thumbnailUrl || attachment.url, alt: attachment.filename }) });
1567
+ }
1568
+ if (isAudio) {
1569
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-attachment pp-attachment-audio", children: [
1570
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("audio", { controls: true, preload: "metadata", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("source", { src: attachment.url, type: attachment.mimeType }) }),
1571
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-attachment-name", children: attachment.filename })
1572
+ ] });
1573
+ }
1574
+ if (isVideo) {
1575
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { class: "pp-attachment pp-attachment-video", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { controls: true, preload: "metadata", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("source", { src: attachment.url, type: attachment.mimeType }) }) });
1576
+ }
1577
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: attachment.url, target: "_blank", rel: "noopener", class: "pp-attachment pp-attachment-file", children: [
1578
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileIcon, { mimeType: attachment.mimeType }),
1579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { class: "pp-attachment-info", children: [
1580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-attachment-name", children: attachment.filename }),
1581
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { class: "pp-attachment-size", children: formatSize(attachment.size) })
1582
+ ] })
1583
+ ] });
1584
+ }
691
1585
 
692
1586
  // src/version.ts
693
- var VERSION = "1.0.2";
1587
+ var VERSION = "0.3.6";
694
1588
 
695
1589
  // src/client.ts
696
1590
  var PocketPingClient = class {
697
1591
  constructor(config) {
698
1592
  this.session = null;
699
1593
  this.ws = null;
1594
+ this.sse = null;
700
1595
  this.isOpen = false;
701
1596
  this.listeners = /* @__PURE__ */ new Map();
702
1597
  this.customEventHandlers = /* @__PURE__ */ new Map();
@@ -709,7 +1604,7 @@ var PocketPingClient = class {
709
1604
  this.wsConnectedAt = 0;
710
1605
  this.quickFailureThreshold = 2e3;
711
1606
  // If WS fails within 2s, assume serverless
712
- this.usePollingFallback = false;
1607
+ this.connectionMode = "none";
713
1608
  this.trackedElementCleanups = [];
714
1609
  this.currentTrackedElements = [];
715
1610
  this.inspectorMode = false;
@@ -770,7 +1665,7 @@ var PocketPingClient = class {
770
1665
  welcomeMessage: this.config.welcomeMessage
771
1666
  });
772
1667
  this.storeSessionId(response.sessionId);
773
- this.connectWebSocket();
1668
+ this.connectRealtime();
774
1669
  if (response.inspectorMode) {
775
1670
  this.enableInspectorMode();
776
1671
  } else if (response.trackedElements?.length) {
@@ -781,9 +1676,20 @@ var PocketPingClient = class {
781
1676
  return this.session;
782
1677
  }
783
1678
  disconnect() {
784
- this.ws?.close();
785
- this.ws = null;
1679
+ if (this.ws) {
1680
+ this.ws.onclose = null;
1681
+ this.ws.onmessage = null;
1682
+ this.ws.onerror = null;
1683
+ this.ws.onopen = null;
1684
+ this.ws.close();
1685
+ this.ws = null;
1686
+ }
1687
+ if (this.sse) {
1688
+ this.sse.close();
1689
+ this.sse = null;
1690
+ }
786
1691
  this.session = null;
1692
+ this.connectionMode = "none";
787
1693
  if (this.reconnectTimeout) {
788
1694
  clearTimeout(this.reconnectTimeout);
789
1695
  }
@@ -791,7 +1697,7 @@ var PocketPingClient = class {
791
1697
  this.cleanupTrackedElements();
792
1698
  this.disableInspectorMode();
793
1699
  }
794
- async sendMessage(content) {
1700
+ async sendMessage(content, attachmentIds, replyTo) {
795
1701
  if (!this.session) {
796
1702
  throw new Error("Not connected");
797
1703
  }
@@ -802,7 +1708,8 @@ var PocketPingClient = class {
802
1708
  content,
803
1709
  sender: "visitor",
804
1710
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
805
- status: "sending"
1711
+ status: "sending",
1712
+ replyTo
806
1713
  };
807
1714
  this.session.messages.push(tempMessage);
808
1715
  this.emit("message", tempMessage);
@@ -812,7 +1719,9 @@ var PocketPingClient = class {
812
1719
  body: JSON.stringify({
813
1720
  sessionId: this.session.sessionId,
814
1721
  content,
815
- sender: "visitor"
1722
+ sender: "visitor",
1723
+ attachmentIds: attachmentIds || [],
1724
+ replyTo
816
1725
  })
817
1726
  });
818
1727
  const messageIndex = this.session.messages.findIndex((m) => m.id === tempId);
@@ -820,6 +1729,9 @@ var PocketPingClient = class {
820
1729
  this.session.messages[messageIndex].id = response.messageId;
821
1730
  this.session.messages[messageIndex].timestamp = response.timestamp;
822
1731
  this.session.messages[messageIndex].status = "sent";
1732
+ if (response.attachments && response.attachments.length > 0) {
1733
+ this.session.messages[messageIndex].attachments = response.attachments;
1734
+ }
823
1735
  this.emit("message", this.session.messages[messageIndex]);
824
1736
  }
825
1737
  const message = this.session.messages[messageIndex] || {
@@ -828,7 +1740,8 @@ var PocketPingClient = class {
828
1740
  content,
829
1741
  sender: "visitor",
830
1742
  timestamp: response.timestamp,
831
- status: "sent"
1743
+ status: "sent",
1744
+ attachments: response.attachments
832
1745
  };
833
1746
  this.config.onMessage?.(message);
834
1747
  return message;
@@ -841,6 +1754,110 @@ var PocketPingClient = class {
841
1754
  throw error;
842
1755
  }
843
1756
  }
1757
+ /**
1758
+ * Upload a file attachment
1759
+ * Returns the attachment data after successful upload
1760
+ * @param file - File object to upload
1761
+ * @param onProgress - Optional callback for upload progress (0-100)
1762
+ * @example
1763
+ * const attachment = await PocketPing.uploadFile(file, (progress) => {
1764
+ * console.log(`Upload ${progress}% complete`)
1765
+ * })
1766
+ * await PocketPing.sendMessage('Check this file', [attachment.id])
1767
+ */
1768
+ async uploadFile(file, onProgress) {
1769
+ if (!this.session) {
1770
+ throw new Error("Not connected");
1771
+ }
1772
+ this.emit("uploadStart", { filename: file.name, size: file.size });
1773
+ try {
1774
+ const initResponse = await this.fetch("/upload", {
1775
+ method: "POST",
1776
+ body: JSON.stringify({
1777
+ sessionId: this.session.sessionId,
1778
+ filename: file.name,
1779
+ mimeType: file.type || "application/octet-stream",
1780
+ size: file.size
1781
+ })
1782
+ });
1783
+ onProgress?.(10);
1784
+ this.emit("uploadProgress", { filename: file.name, progress: 10 });
1785
+ await this.uploadToPresignedUrl(initResponse.uploadUrl, file, (progress) => {
1786
+ const mappedProgress = 10 + progress * 0.8;
1787
+ onProgress?.(mappedProgress);
1788
+ this.emit("uploadProgress", { filename: file.name, progress: mappedProgress });
1789
+ });
1790
+ const completeResponse = await this.fetch("/upload/complete", {
1791
+ method: "POST",
1792
+ body: JSON.stringify({
1793
+ sessionId: this.session.sessionId,
1794
+ attachmentId: initResponse.attachmentId
1795
+ })
1796
+ });
1797
+ onProgress?.(100);
1798
+ this.emit("uploadComplete", completeResponse);
1799
+ return {
1800
+ id: completeResponse.id,
1801
+ filename: completeResponse.filename,
1802
+ mimeType: completeResponse.mimeType,
1803
+ size: completeResponse.size,
1804
+ url: completeResponse.url,
1805
+ thumbnailUrl: completeResponse.thumbnailUrl,
1806
+ status: completeResponse.status
1807
+ };
1808
+ } catch (error) {
1809
+ this.emit("uploadError", { filename: file.name, error });
1810
+ throw error;
1811
+ }
1812
+ }
1813
+ /**
1814
+ * Upload multiple files at once
1815
+ * @param files - Array of File objects to upload
1816
+ * @param onProgress - Optional callback for overall progress (0-100)
1817
+ * @returns Array of uploaded attachments
1818
+ */
1819
+ async uploadFiles(files, onProgress) {
1820
+ const attachments = [];
1821
+ const totalFiles = files.length;
1822
+ for (let i = 0; i < totalFiles; i++) {
1823
+ const file = files[i];
1824
+ const baseProgress = i / totalFiles * 100;
1825
+ const fileProgress = 100 / totalFiles;
1826
+ const attachment = await this.uploadFile(file, (progress) => {
1827
+ const totalProgress = baseProgress + progress / 100 * fileProgress;
1828
+ onProgress?.(totalProgress);
1829
+ });
1830
+ attachments.push(attachment);
1831
+ }
1832
+ return attachments;
1833
+ }
1834
+ /**
1835
+ * Upload file to presigned URL with progress tracking
1836
+ */
1837
+ uploadToPresignedUrl(url, file, onProgress) {
1838
+ return new Promise((resolve, reject) => {
1839
+ const xhr = new XMLHttpRequest();
1840
+ xhr.upload.addEventListener("progress", (event) => {
1841
+ if (event.lengthComputable) {
1842
+ const progress = event.loaded / event.total * 100;
1843
+ onProgress?.(progress);
1844
+ }
1845
+ });
1846
+ xhr.addEventListener("load", () => {
1847
+ if (xhr.status >= 200 && xhr.status < 300) {
1848
+ resolve();
1849
+ } else {
1850
+ reject(new Error(`Upload failed with status ${xhr.status}`));
1851
+ }
1852
+ });
1853
+ xhr.addEventListener("error", () => {
1854
+ reject(new Error("Upload failed"));
1855
+ });
1856
+ xhr.open("PUT", url);
1857
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
1858
+ xhr.send(file);
1859
+ });
1860
+ }
844
1861
  async fetchMessages(after) {
845
1862
  if (!this.session) {
846
1863
  throw new Error("Not connected");
@@ -894,6 +1911,54 @@ var PocketPingClient = class {
894
1911
  console.error("[PocketPing] Failed to send read status:", err);
895
1912
  }
896
1913
  }
1914
+ /**
1915
+ * Edit a message (visitor can only edit their own messages)
1916
+ * @param messageId - ID of the message to edit
1917
+ * @param content - New content for the message
1918
+ */
1919
+ async editMessage(messageId, content) {
1920
+ if (!this.session) {
1921
+ throw new Error("Not connected");
1922
+ }
1923
+ const response = await this.fetch(`/message/${messageId}`, {
1924
+ method: "PATCH",
1925
+ body: JSON.stringify({
1926
+ sessionId: this.session.sessionId,
1927
+ content
1928
+ })
1929
+ });
1930
+ const messageIndex = this.session.messages.findIndex((m) => m.id === messageId);
1931
+ if (messageIndex >= 0) {
1932
+ this.session.messages[messageIndex].content = response.message.content;
1933
+ this.session.messages[messageIndex].editedAt = response.message.editedAt;
1934
+ this.emit("messageEdited", this.session.messages[messageIndex]);
1935
+ }
1936
+ return this.session.messages[messageIndex];
1937
+ }
1938
+ /**
1939
+ * Delete a message (soft delete - visitor can only delete their own messages)
1940
+ * @param messageId - ID of the message to delete
1941
+ */
1942
+ async deleteMessage(messageId) {
1943
+ if (!this.session) {
1944
+ throw new Error("Not connected");
1945
+ }
1946
+ const response = await this.fetch(`/message/${messageId}`, {
1947
+ method: "DELETE",
1948
+ body: JSON.stringify({
1949
+ sessionId: this.session.sessionId
1950
+ })
1951
+ });
1952
+ if (response.deleted) {
1953
+ const messageIndex = this.session.messages.findIndex((m) => m.id === messageId);
1954
+ if (messageIndex >= 0) {
1955
+ this.session.messages[messageIndex].deletedAt = (/* @__PURE__ */ new Date()).toISOString();
1956
+ this.session.messages[messageIndex].content = "";
1957
+ this.emit("messageDeleted", this.session.messages[messageIndex]);
1958
+ }
1959
+ }
1960
+ return response.deleted;
1961
+ }
897
1962
  async getPresence() {
898
1963
  return this.fetch("/presence", { method: "GET" });
899
1964
  }
@@ -1387,19 +2452,40 @@ var PocketPingClient = class {
1387
2452
  return this.inspectorMode;
1388
2453
  }
1389
2454
  // ─────────────────────────────────────────────────────────────────
1390
- // WebSocket
2455
+ // Real-time Connection (WebSocket → SSE → Polling)
1391
2456
  // ─────────────────────────────────────────────────────────────────
1392
- connectWebSocket() {
2457
+ connectRealtime() {
1393
2458
  if (!this.session) return;
1394
- if (this.usePollingFallback) {
2459
+ if (this.connectionMode === "polling") {
1395
2460
  this.startPolling();
1396
2461
  return;
1397
2462
  }
2463
+ if (this.connectionMode === "sse") {
2464
+ this.connectSSE();
2465
+ return;
2466
+ }
2467
+ this.connectWebSocket();
2468
+ }
2469
+ connectWebSocket() {
2470
+ if (!this.session) return;
1398
2471
  const wsUrl = this.config.endpoint.replace(/^http/, "ws").replace(/\/$/, "") + `/stream?sessionId=${this.session.sessionId}`;
1399
2472
  try {
1400
2473
  this.ws = new WebSocket(wsUrl);
1401
2474
  this.wsConnectedAt = Date.now();
2475
+ const connectionTimeout = setTimeout(() => {
2476
+ console.warn("[PocketPing] \u23F1\uFE0F WebSocket timeout - trying SSE");
2477
+ if (this.ws && this.ws.readyState !== WebSocket.OPEN) {
2478
+ this.ws.onclose = null;
2479
+ this.ws.onerror = null;
2480
+ this.ws.onopen = null;
2481
+ this.ws.close();
2482
+ this.ws = null;
2483
+ this.connectSSE();
2484
+ }
2485
+ }, 5e3);
1402
2486
  this.ws.onopen = () => {
2487
+ clearTimeout(connectionTimeout);
2488
+ this.connectionMode = "ws";
1403
2489
  this.reconnectAttempts = 0;
1404
2490
  this.wsConnectedAt = Date.now();
1405
2491
  this.emit("wsConnected", null);
@@ -1407,36 +2493,81 @@ var PocketPingClient = class {
1407
2493
  this.ws.onmessage = (event) => {
1408
2494
  try {
1409
2495
  const wsEvent = JSON.parse(event.data);
1410
- this.handleWebSocketEvent(wsEvent);
2496
+ this.handleRealtimeEvent(wsEvent);
1411
2497
  } catch (err) {
1412
2498
  console.error("[PocketPing] Failed to parse WS message:", err);
1413
2499
  }
1414
2500
  };
1415
2501
  this.ws.onclose = () => {
2502
+ clearTimeout(connectionTimeout);
1416
2503
  this.emit("wsDisconnected", null);
1417
2504
  this.handleWsFailure();
1418
2505
  };
1419
2506
  this.ws.onerror = () => {
2507
+ clearTimeout(connectionTimeout);
1420
2508
  };
1421
- } catch (err) {
1422
- console.warn("[PocketPing] WebSocket unavailable, using polling");
1423
- this.usePollingFallback = true;
2509
+ } catch {
2510
+ console.warn("[PocketPing] WebSocket unavailable - trying SSE");
2511
+ this.connectSSE();
2512
+ }
2513
+ }
2514
+ connectSSE() {
2515
+ if (!this.session) return;
2516
+ const sseUrl = this.config.endpoint.replace(/\/$/, "") + `/stream?sessionId=${this.session.sessionId}`;
2517
+ try {
2518
+ this.sse = new EventSource(sseUrl);
2519
+ const connectionTimeout = setTimeout(() => {
2520
+ console.warn("[PocketPing] \u23F1\uFE0F SSE timeout - falling back to polling");
2521
+ if (this.sse && this.sse.readyState !== EventSource.OPEN) {
2522
+ this.sse.close();
2523
+ this.sse = null;
2524
+ this.connectionMode = "polling";
2525
+ this.startPolling();
2526
+ }
2527
+ }, 5e3);
2528
+ this.sse.onopen = () => {
2529
+ clearTimeout(connectionTimeout);
2530
+ this.connectionMode = "sse";
2531
+ this.emit("sseConnected", null);
2532
+ };
2533
+ this.sse.addEventListener("message", (event) => {
2534
+ try {
2535
+ const data = JSON.parse(event.data);
2536
+ this.handleRealtimeEvent(data);
2537
+ } catch (err) {
2538
+ console.error("[PocketPing] Failed to parse SSE message:", err);
2539
+ }
2540
+ });
2541
+ this.sse.addEventListener("connected", () => {
2542
+ });
2543
+ this.sse.onerror = () => {
2544
+ clearTimeout(connectionTimeout);
2545
+ console.warn("[PocketPing] \u274C SSE error - falling back to polling");
2546
+ if (this.sse) {
2547
+ this.sse.close();
2548
+ this.sse = null;
2549
+ }
2550
+ this.connectionMode = "polling";
2551
+ this.startPolling();
2552
+ };
2553
+ } catch {
2554
+ console.warn("[PocketPing] SSE unavailable - falling back to polling");
2555
+ this.connectionMode = "polling";
1424
2556
  this.startPolling();
1425
2557
  }
1426
2558
  }
1427
2559
  handleWsFailure() {
1428
2560
  const timeSinceConnect = Date.now() - this.wsConnectedAt;
1429
2561
  if (timeSinceConnect < this.quickFailureThreshold) {
1430
- this.reconnectAttempts++;
1431
- if (this.reconnectAttempts >= 2) {
1432
- console.info("[PocketPing] WebSocket not available (serverless?), using polling");
1433
- this.usePollingFallback = true;
1434
- this.startPolling();
1435
- return;
1436
- }
2562
+ console.info("[PocketPing] WebSocket failed quickly - trying SSE");
2563
+ this.connectSSE();
2564
+ return;
1437
2565
  }
1438
2566
  this.scheduleReconnect();
1439
2567
  }
2568
+ handleRealtimeEvent(event) {
2569
+ this.handleWebSocketEvent(event);
2570
+ }
1440
2571
  handleWebSocketEvent(event) {
1441
2572
  switch (event.type) {
1442
2573
  case "message":
@@ -1548,8 +2679,8 @@ var PocketPingClient = class {
1548
2679
  }
1549
2680
  scheduleReconnect() {
1550
2681
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1551
- console.warn("[PocketPing] Max reconnect attempts reached, switching to polling");
1552
- this.startPolling();
2682
+ console.warn("[PocketPing] Max reconnect attempts reached, trying SSE");
2683
+ this.connectSSE();
1553
2684
  return;
1554
2685
  }
1555
2686
  const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
@@ -1574,6 +2705,7 @@ var PocketPingClient = class {
1574
2705
  }
1575
2706
  } catch (err) {
1576
2707
  this.pollingFailures++;
2708
+ console.error(`[PocketPing] \u274C Polling error:`, err);
1577
2709
  if (this.pollingFailures <= 3 || this.pollingFailures % 3 === 0) {
1578
2710
  console.warn(`[PocketPing] Polling failed (${this.pollingFailures}/${this.maxPollingFailures})`);
1579
2711
  }
@@ -1584,11 +2716,11 @@ var PocketPingClient = class {
1584
2716
  }
1585
2717
  }
1586
2718
  if (this.session) {
1587
- const delay = this.pollingFailures > 0 ? Math.min(3e3 * Math.pow(2, this.pollingFailures - 1), 3e4) : 3e3;
2719
+ const delay = this.pollingFailures > 0 ? Math.min(2e3 * Math.pow(2, this.pollingFailures - 1), 3e4) : 2e3;
1588
2720
  this.pollingTimeout = setTimeout(poll, delay);
1589
2721
  }
1590
2722
  };
1591
- poll();
2723
+ this.pollingTimeout = setTimeout(poll, 500);
1592
2724
  }
1593
2725
  stopPolling() {
1594
2726
  if (this.pollingTimeout) {
@@ -1730,11 +2862,23 @@ function close() {
1730
2862
  function toggle() {
1731
2863
  client?.toggleOpen();
1732
2864
  }
1733
- function sendMessage(content) {
2865
+ function sendMessage(content, attachmentIds) {
2866
+ if (!client) {
2867
+ throw new Error("[PocketPing] Not initialized");
2868
+ }
2869
+ return client.sendMessage(content, attachmentIds);
2870
+ }
2871
+ async function uploadFile(file, onProgress) {
2872
+ if (!client) {
2873
+ throw new Error("[PocketPing] Not initialized");
2874
+ }
2875
+ return client.uploadFile(file, onProgress);
2876
+ }
2877
+ async function uploadFiles(files, onProgress) {
1734
2878
  if (!client) {
1735
2879
  throw new Error("[PocketPing] Not initialized");
1736
2880
  }
1737
- return client.sendMessage(content);
2881
+ return client.uploadFiles(files, onProgress);
1738
2882
  }
1739
2883
  function trigger(eventName, data, options) {
1740
2884
  if (!client) {
@@ -1803,7 +2947,7 @@ if (typeof document !== "undefined") {
1803
2947
  }
1804
2948
  }
1805
2949
  }
1806
- var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity, setupTrackedElements, getTrackedElements };
2950
+ var index_default = { init, destroy, open, close, toggle, sendMessage, uploadFile, uploadFiles, trigger, onEvent, offEvent, on, identify, reset, getIdentity, setupTrackedElements, getTrackedElements };
1807
2951
  // Annotate the CommonJS export names for ESM import in node:
1808
2952
  0 && (module.exports = {
1809
2953
  close,
@@ -1820,5 +2964,7 @@ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger,
1820
2964
  sendMessage,
1821
2965
  setupTrackedElements,
1822
2966
  toggle,
1823
- trigger
2967
+ trigger,
2968
+ uploadFile,
2969
+ uploadFiles
1824
2970
  });