@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 +1259 -113
- package/dist/index.d.cts +80 -4
- package/dist/index.d.ts +80 -4
- package/dist/index.js +1256 -112
- package/dist/pocketping.min.global.js +493 -7
- package/package.json +10 -3
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:
|
|
240
|
-
height:
|
|
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
|
-
|
|
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)(
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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 = "
|
|
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.
|
|
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.
|
|
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
|
|
785
|
-
|
|
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
|
-
|
|
2457
|
+
connectRealtime() {
|
|
1393
2458
|
if (!this.session) return;
|
|
1394
|
-
if (this.
|
|
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.
|
|
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
|
|
1422
|
-
console.warn("[PocketPing] WebSocket unavailable
|
|
1423
|
-
this.
|
|
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
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
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,
|
|
1552
|
-
this.
|
|
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(
|
|
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.
|
|
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
|
});
|