@product7/feedback-sdk 1.2.5 → 1.2.6
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/README.md +176 -7
- package/dist/README.md +176 -7
- package/dist/feedback-sdk.js +1603 -125
- package/dist/feedback-sdk.js.map +1 -1
- package/dist/feedback-sdk.min.js +1 -1
- package/dist/feedback-sdk.min.js.map +1 -1
- package/package.json +1 -1
- package/src/core/APIService.js +536 -0
- package/src/core/FeedbackSDK.js +48 -0
- package/src/core/WebSocketService.js +273 -0
- package/src/styles/messengerStyles.js +100 -0
- package/src/styles/styles.js +2 -2
- package/src/widgets/BaseWidget.js +96 -0
- package/src/widgets/MessengerWidget.js +374 -89
- package/src/widgets/messenger/MessengerState.js +12 -0
- package/src/widgets/messenger/views/ChatView.js +121 -16
- package/src/widgets/messenger/views/ConversationsView.js +12 -11
- package/src/widgets/messenger/views/HomeView.js +23 -1
package/package.json
CHANGED
package/src/core/APIService.js
CHANGED
|
@@ -60,6 +60,124 @@ const MOCK_CHANGELOGS = [
|
|
|
60
60
|
},
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
+
// Mock conversations for development
|
|
64
|
+
const MOCK_CONVERSATIONS = [
|
|
65
|
+
{
|
|
66
|
+
id: 'conv_1',
|
|
67
|
+
subject: 'Question about pricing',
|
|
68
|
+
status: 'open',
|
|
69
|
+
last_message_at: new Date(Date.now() - 49 * 60 * 1000).toISOString(),
|
|
70
|
+
created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
71
|
+
unread: 1,
|
|
72
|
+
assigned_user: {
|
|
73
|
+
id: 'user_1',
|
|
74
|
+
name: 'Sarah',
|
|
75
|
+
avatar: null,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'conv_2',
|
|
80
|
+
subject: 'Feature request',
|
|
81
|
+
status: 'open',
|
|
82
|
+
last_message_at: new Date(Date.now() - 6 * 24 * 60 * 60 * 1000).toISOString(),
|
|
83
|
+
created_at: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
|
|
84
|
+
unread: 0,
|
|
85
|
+
assigned_user: {
|
|
86
|
+
id: 'user_2',
|
|
87
|
+
name: 'Tom',
|
|
88
|
+
avatar: null,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
// Mock messages for development
|
|
94
|
+
const MOCK_MESSAGES = {
|
|
95
|
+
conv_1: [
|
|
96
|
+
{
|
|
97
|
+
id: 'msg_1',
|
|
98
|
+
content: "Hi there! 👋 I'm Sarah. How can I help you today?",
|
|
99
|
+
sender_type: 'agent',
|
|
100
|
+
sender_name: 'Sarah',
|
|
101
|
+
sender_avatar: null,
|
|
102
|
+
created_at: new Date(Date.now() - 50 * 60 * 1000).toISOString(),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'msg_2',
|
|
106
|
+
content: 'Hi! I have a question about your enterprise pricing.',
|
|
107
|
+
sender_type: 'customer',
|
|
108
|
+
created_at: new Date(Date.now() - 49 * 60 * 1000).toISOString(),
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
conv_2: [
|
|
112
|
+
{
|
|
113
|
+
id: 'msg_3',
|
|
114
|
+
content: "Hello! I'm Tom from the product team.",
|
|
115
|
+
sender_type: 'agent',
|
|
116
|
+
sender_name: 'Tom',
|
|
117
|
+
sender_avatar: null,
|
|
118
|
+
created_at: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'msg_4',
|
|
122
|
+
content: 'I would love to see a dark mode feature!',
|
|
123
|
+
sender_type: 'customer',
|
|
124
|
+
created_at: new Date(Date.now() - 6 * 24 * 60 * 60 * 1000 - 30 * 60 * 1000).toISOString(),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'msg_5',
|
|
128
|
+
content: "Great suggestion! That feature will be available next week. I'll let you know when it's ready.",
|
|
129
|
+
sender_type: 'agent',
|
|
130
|
+
sender_name: 'Tom',
|
|
131
|
+
sender_avatar: null,
|
|
132
|
+
created_at: new Date(Date.now() - 6 * 24 * 60 * 60 * 1000).toISOString(),
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Mock help collections for development
|
|
138
|
+
const MOCK_HELP_COLLECTIONS = [
|
|
139
|
+
{
|
|
140
|
+
id: 'collection_1',
|
|
141
|
+
title: 'Product Overview',
|
|
142
|
+
description: 'See how your AI-first customer service solution works.',
|
|
143
|
+
articleCount: 24,
|
|
144
|
+
icon: 'ph-book-open',
|
|
145
|
+
url: '#',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: 'collection_2',
|
|
149
|
+
title: 'Getting Started',
|
|
150
|
+
description: 'Everything you need to know to get started with Product7.',
|
|
151
|
+
articleCount: 30,
|
|
152
|
+
icon: 'ph-rocket',
|
|
153
|
+
url: '#',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'collection_3',
|
|
157
|
+
title: 'AI Agent',
|
|
158
|
+
description: 'Resolving customer questions instantly and accurately—from live chat to email.',
|
|
159
|
+
articleCount: 82,
|
|
160
|
+
icon: 'ph-robot',
|
|
161
|
+
url: '#',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'collection_4',
|
|
165
|
+
title: 'Channels',
|
|
166
|
+
description: 'Enabling the channels you use to communicate with customers, all from the Inbox.',
|
|
167
|
+
articleCount: 45,
|
|
168
|
+
icon: 'ph-chat-circle',
|
|
169
|
+
url: '#',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'collection_5',
|
|
173
|
+
title: 'Billing & Payments',
|
|
174
|
+
description: 'Manage your subscription, invoices, and payment methods.',
|
|
175
|
+
articleCount: 12,
|
|
176
|
+
icon: 'ph-credit-card',
|
|
177
|
+
url: '#',
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
|
|
63
181
|
// Mock surveys for development
|
|
64
182
|
const MOCK_SURVEYS = [
|
|
65
183
|
{
|
|
@@ -481,6 +599,424 @@ export class APIService {
|
|
|
481
599
|
}
|
|
482
600
|
}
|
|
483
601
|
|
|
602
|
+
// ==========================================
|
|
603
|
+
// MESSENGER / CHAT ENDPOINTS
|
|
604
|
+
// ==========================================
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Get messenger settings
|
|
608
|
+
* @returns {Promise<Object>} Messenger settings
|
|
609
|
+
*/
|
|
610
|
+
async getMessengerSettings() {
|
|
611
|
+
if (!this.isSessionValid()) {
|
|
612
|
+
await this.init();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (this.mock) {
|
|
616
|
+
return {
|
|
617
|
+
status: true,
|
|
618
|
+
data: {
|
|
619
|
+
enabled: true,
|
|
620
|
+
greeting_message: 'Hi there! How can we help you today?',
|
|
621
|
+
team_name: 'Support Team',
|
|
622
|
+
response_time: 'Usually replies within a few minutes',
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return this._makeRequest('/widget/messenger/settings', {
|
|
628
|
+
method: 'GET',
|
|
629
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Check if agents are online
|
|
635
|
+
* @returns {Promise<Object>} Agent availability status
|
|
636
|
+
*/
|
|
637
|
+
async checkAgentsOnline() {
|
|
638
|
+
if (!this.isSessionValid()) {
|
|
639
|
+
await this.init();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (this.mock) {
|
|
643
|
+
return {
|
|
644
|
+
status: true,
|
|
645
|
+
data: {
|
|
646
|
+
agents_online: true,
|
|
647
|
+
online_count: 2,
|
|
648
|
+
response_time: 'Usually replies within a few minutes',
|
|
649
|
+
},
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return this._makeRequest('/widget/messenger/agents/online', {
|
|
654
|
+
method: 'GET',
|
|
655
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Get all conversations for the current contact
|
|
661
|
+
* @param {Object} options - Query options
|
|
662
|
+
* @param {number} options.page - Page number
|
|
663
|
+
* @param {number} options.limit - Items per page
|
|
664
|
+
* @returns {Promise<Object>} Conversations list
|
|
665
|
+
*/
|
|
666
|
+
async getConversations(options = {}) {
|
|
667
|
+
if (!this.isSessionValid()) {
|
|
668
|
+
await this.init();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (this.mock) {
|
|
672
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
673
|
+
return {
|
|
674
|
+
status: true,
|
|
675
|
+
data: MOCK_CONVERSATIONS,
|
|
676
|
+
meta: { total: MOCK_CONVERSATIONS.length, page: 1, limit: 20 },
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const params = new URLSearchParams();
|
|
681
|
+
if (options.page) params.append('page', options.page);
|
|
682
|
+
if (options.limit) params.append('limit', options.limit);
|
|
683
|
+
|
|
684
|
+
const endpoint = `/widget/messenger/conversations${params.toString() ? '?' + params.toString() : ''}`;
|
|
685
|
+
return this._makeRequest(endpoint, {
|
|
686
|
+
method: 'GET',
|
|
687
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Get a single conversation with messages
|
|
693
|
+
* @param {string} conversationId - Conversation ID
|
|
694
|
+
* @returns {Promise<Object>} Conversation with messages
|
|
695
|
+
*/
|
|
696
|
+
async getConversation(conversationId) {
|
|
697
|
+
if (!this.isSessionValid()) {
|
|
698
|
+
await this.init();
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (this.mock) {
|
|
702
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
703
|
+
const conv = MOCK_CONVERSATIONS.find((c) => c.id === conversationId);
|
|
704
|
+
return {
|
|
705
|
+
status: true,
|
|
706
|
+
data: {
|
|
707
|
+
...conv,
|
|
708
|
+
messages: MOCK_MESSAGES[conversationId] || [],
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return this._makeRequest(`/widget/messenger/conversations/${conversationId}`, {
|
|
714
|
+
method: 'GET',
|
|
715
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Get messages for a conversation
|
|
721
|
+
* @param {string} conversationId - Conversation ID
|
|
722
|
+
* @param {Object} options - Query options
|
|
723
|
+
* @returns {Promise<Object>} Messages list
|
|
724
|
+
*/
|
|
725
|
+
async getMessages(conversationId, options = {}) {
|
|
726
|
+
if (!this.isSessionValid()) {
|
|
727
|
+
await this.init();
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (this.mock) {
|
|
731
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
732
|
+
return {
|
|
733
|
+
status: true,
|
|
734
|
+
data: MOCK_MESSAGES[conversationId] || [],
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const params = new URLSearchParams();
|
|
739
|
+
if (options.page) params.append('page', options.page);
|
|
740
|
+
if (options.limit) params.append('limit', options.limit);
|
|
741
|
+
|
|
742
|
+
const endpoint = `/widget/messenger/conversations/${conversationId}/messages${params.toString() ? '?' + params.toString() : ''}`;
|
|
743
|
+
return this._makeRequest(endpoint, {
|
|
744
|
+
method: 'GET',
|
|
745
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Start a new conversation
|
|
751
|
+
* @param {Object} data - Conversation data
|
|
752
|
+
* @param {string} data.message - Initial message content
|
|
753
|
+
* @param {string} data.subject - Optional subject
|
|
754
|
+
* @returns {Promise<Object>} Created conversation
|
|
755
|
+
*/
|
|
756
|
+
async startConversation(data) {
|
|
757
|
+
if (!this.isSessionValid()) {
|
|
758
|
+
await this.init();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (this.mock) {
|
|
762
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
763
|
+
const newConv = {
|
|
764
|
+
id: 'conv_' + Date.now(),
|
|
765
|
+
subject: data.subject || 'New conversation',
|
|
766
|
+
status: 'open',
|
|
767
|
+
last_message_at: new Date().toISOString(),
|
|
768
|
+
created_at: new Date().toISOString(),
|
|
769
|
+
messages: [
|
|
770
|
+
{
|
|
771
|
+
id: 'msg_' + Date.now(),
|
|
772
|
+
content: data.message,
|
|
773
|
+
sender_type: 'customer',
|
|
774
|
+
created_at: new Date().toISOString(),
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
};
|
|
778
|
+
MOCK_CONVERSATIONS.unshift(newConv);
|
|
779
|
+
MOCK_MESSAGES[newConv.id] = newConv.messages;
|
|
780
|
+
return { status: true, data: newConv };
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return this._makeRequest('/widget/messenger/conversations', {
|
|
784
|
+
method: 'POST',
|
|
785
|
+
headers: {
|
|
786
|
+
'Content-Type': 'application/json',
|
|
787
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
788
|
+
},
|
|
789
|
+
body: JSON.stringify({
|
|
790
|
+
message: data.message,
|
|
791
|
+
subject: data.subject || '',
|
|
792
|
+
}),
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Send a message in a conversation
|
|
798
|
+
* @param {string} conversationId - Conversation ID
|
|
799
|
+
* @param {Object} data - Message data
|
|
800
|
+
* @param {string} data.content - Message content
|
|
801
|
+
* @returns {Promise<Object>} Sent message
|
|
802
|
+
*/
|
|
803
|
+
async sendMessage(conversationId, data) {
|
|
804
|
+
if (!this.isSessionValid()) {
|
|
805
|
+
await this.init();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (this.mock) {
|
|
809
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
810
|
+
const newMessage = {
|
|
811
|
+
id: 'msg_' + Date.now(),
|
|
812
|
+
content: data.content,
|
|
813
|
+
sender_type: 'customer',
|
|
814
|
+
created_at: new Date().toISOString(),
|
|
815
|
+
};
|
|
816
|
+
if (!MOCK_MESSAGES[conversationId]) {
|
|
817
|
+
MOCK_MESSAGES[conversationId] = [];
|
|
818
|
+
}
|
|
819
|
+
MOCK_MESSAGES[conversationId].push(newMessage);
|
|
820
|
+
return { status: true, data: newMessage };
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
return this._makeRequest(`/widget/messenger/conversations/${conversationId}/messages`, {
|
|
824
|
+
method: 'POST',
|
|
825
|
+
headers: {
|
|
826
|
+
'Content-Type': 'application/json',
|
|
827
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
828
|
+
},
|
|
829
|
+
body: JSON.stringify({ content: data.content }),
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Send typing indicator
|
|
835
|
+
* @param {string} conversationId - Conversation ID
|
|
836
|
+
* @param {boolean} isTyping - Whether user is typing
|
|
837
|
+
* @returns {Promise<Object>} Response
|
|
838
|
+
*/
|
|
839
|
+
async sendTypingIndicator(conversationId, isTyping) {
|
|
840
|
+
if (!this.isSessionValid()) {
|
|
841
|
+
await this.init();
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (this.mock) {
|
|
845
|
+
return { status: true };
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return this._makeRequest(`/widget/messenger/conversations/${conversationId}/typing`, {
|
|
849
|
+
method: 'POST',
|
|
850
|
+
headers: {
|
|
851
|
+
'Content-Type': 'application/json',
|
|
852
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
853
|
+
},
|
|
854
|
+
body: JSON.stringify({ is_typing: isTyping }),
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Mark conversation as read
|
|
860
|
+
* @param {string} conversationId - Conversation ID
|
|
861
|
+
* @returns {Promise<Object>} Response
|
|
862
|
+
*/
|
|
863
|
+
async markConversationAsRead(conversationId) {
|
|
864
|
+
if (!this.isSessionValid()) {
|
|
865
|
+
await this.init();
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (this.mock) {
|
|
869
|
+
return { status: true };
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return this._makeRequest(`/widget/messenger/conversations/${conversationId}/read`, {
|
|
873
|
+
method: 'POST',
|
|
874
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Get unread count
|
|
880
|
+
* @returns {Promise<Object>} Unread count data
|
|
881
|
+
*/
|
|
882
|
+
async getUnreadCount() {
|
|
883
|
+
if (!this.isSessionValid()) {
|
|
884
|
+
await this.init();
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (this.mock) {
|
|
888
|
+
const count = MOCK_CONVERSATIONS.reduce((sum, c) => sum + (c.unread || 0), 0);
|
|
889
|
+
return {
|
|
890
|
+
status: true,
|
|
891
|
+
data: { unread_count: count, unread_conversations: count > 0 ? 1 : 0 },
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return this._makeRequest('/widget/messenger/unread', {
|
|
896
|
+
method: 'GET',
|
|
897
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Submit conversation rating
|
|
903
|
+
* @param {string} conversationId - Conversation ID
|
|
904
|
+
* @param {Object} data - Rating data
|
|
905
|
+
* @param {number} data.rating - Rating (1-5 or thumbs up/down)
|
|
906
|
+
* @param {string} data.comment - Optional comment
|
|
907
|
+
* @returns {Promise<Object>} Response
|
|
908
|
+
*/
|
|
909
|
+
async submitRating(conversationId, data) {
|
|
910
|
+
if (!this.isSessionValid()) {
|
|
911
|
+
await this.init();
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (this.mock) {
|
|
915
|
+
return { status: true, message: 'Thank you for your feedback!' };
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return this._makeRequest(`/widget/messenger/conversations/${conversationId}/rate`, {
|
|
919
|
+
method: 'POST',
|
|
920
|
+
headers: {
|
|
921
|
+
'Content-Type': 'application/json',
|
|
922
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
923
|
+
},
|
|
924
|
+
body: JSON.stringify({
|
|
925
|
+
rating: data.rating,
|
|
926
|
+
comment: data.comment || '',
|
|
927
|
+
}),
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Identify contact (for logged-in users)
|
|
933
|
+
* @param {Object} data - Contact data
|
|
934
|
+
* @param {string} data.email - Email address
|
|
935
|
+
* @param {string} data.name - Name
|
|
936
|
+
* @returns {Promise<Object>} Response
|
|
937
|
+
*/
|
|
938
|
+
async identifyContact(data) {
|
|
939
|
+
if (!this.isSessionValid()) {
|
|
940
|
+
await this.init();
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (this.mock) {
|
|
944
|
+
return { status: true, message: 'Contact identified' };
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
return this._makeRequest('/widget/messenger/identify', {
|
|
948
|
+
method: 'POST',
|
|
949
|
+
headers: {
|
|
950
|
+
'Content-Type': 'application/json',
|
|
951
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
952
|
+
},
|
|
953
|
+
body: JSON.stringify(data),
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// ==========================================
|
|
958
|
+
// HELP ARTICLES ENDPOINTS
|
|
959
|
+
// ==========================================
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Get help collections
|
|
963
|
+
* @param {Object} options - Query options
|
|
964
|
+
* @returns {Promise<Object>} Collections list
|
|
965
|
+
*/
|
|
966
|
+
async getHelpCollections(options = {}) {
|
|
967
|
+
if (!this.isSessionValid()) {
|
|
968
|
+
await this.init();
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
if (this.mock) {
|
|
972
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
973
|
+
return { status: true, data: MOCK_HELP_COLLECTIONS };
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const params = new URLSearchParams();
|
|
977
|
+
if (options.limit) params.append('limit', options.limit);
|
|
978
|
+
|
|
979
|
+
const endpoint = `/widget/help/collections${params.toString() ? '?' + params.toString() : ''}`;
|
|
980
|
+
return this._makeRequest(endpoint, {
|
|
981
|
+
method: 'GET',
|
|
982
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Search help articles
|
|
988
|
+
* @param {string} query - Search query
|
|
989
|
+
* @param {Object} options - Query options
|
|
990
|
+
* @returns {Promise<Object>} Search results
|
|
991
|
+
*/
|
|
992
|
+
async searchHelpArticles(query, options = {}) {
|
|
993
|
+
if (!this.isSessionValid()) {
|
|
994
|
+
await this.init();
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (this.mock) {
|
|
998
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
999
|
+
const filtered = MOCK_HELP_COLLECTIONS.filter(
|
|
1000
|
+
(c) =>
|
|
1001
|
+
c.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
1002
|
+
c.description.toLowerCase().includes(query.toLowerCase())
|
|
1003
|
+
);
|
|
1004
|
+
return { status: true, data: filtered };
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const params = new URLSearchParams({ q: query });
|
|
1008
|
+
if (options.limit) params.append('limit', options.limit);
|
|
1009
|
+
|
|
1010
|
+
return this._makeRequest(`/widget/help/search?${params.toString()}`, {
|
|
1011
|
+
method: 'GET',
|
|
1012
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// ==========================================
|
|
1017
|
+
// CHANGELOG ENDPOINTS
|
|
1018
|
+
// ==========================================
|
|
1019
|
+
|
|
484
1020
|
/**
|
|
485
1021
|
* Get published changelogs
|
|
486
1022
|
* @param {Object} options - Optional query parameters
|
package/src/core/FeedbackSDK.js
CHANGED
|
@@ -345,6 +345,54 @@ export class FeedbackSDK {
|
|
|
345
345
|
this.eventBus.emit('sdk:destroyed');
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Check if feedback was recently submitted for this workspace
|
|
350
|
+
* Uses backend tracking (preferred) with localStorage as fallback
|
|
351
|
+
* @param {number} cooldownDays - Days to consider as "recently" (default: 30)
|
|
352
|
+
* @returns {boolean} true if feedback was submitted within the cooldown period
|
|
353
|
+
*/
|
|
354
|
+
hasFeedbackBeenSubmitted(cooldownDays = 30) {
|
|
355
|
+
const cooldownMs = cooldownDays * 24 * 60 * 60 * 1000;
|
|
356
|
+
const now = Date.now();
|
|
357
|
+
|
|
358
|
+
// Check backend tracking first (from init response)
|
|
359
|
+
if (this.config.last_feedback_at) {
|
|
360
|
+
try {
|
|
361
|
+
const backendTimestamp = new Date(this.config.last_feedback_at).getTime();
|
|
362
|
+
if ((now - backendTimestamp) < cooldownMs) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
} catch (e) {
|
|
366
|
+
// Invalid date format, continue to localStorage check
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Fallback to localStorage
|
|
371
|
+
try {
|
|
372
|
+
const storageKey = `feedback_submitted_${this.config.workspace}`;
|
|
373
|
+
const stored = localStorage.getItem(storageKey);
|
|
374
|
+
if (!stored) return false;
|
|
375
|
+
|
|
376
|
+
const data = JSON.parse(stored);
|
|
377
|
+
return (now - data.submittedAt) < cooldownMs;
|
|
378
|
+
} catch (e) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Clear the feedback submission tracking (allow showing widgets again)
|
|
385
|
+
*/
|
|
386
|
+
clearFeedbackSubmissionTracking() {
|
|
387
|
+
try {
|
|
388
|
+
const storageKey = `feedback_submitted_${this.config.workspace}`;
|
|
389
|
+
localStorage.removeItem(storageKey);
|
|
390
|
+
this.eventBus.emit('feedback:trackingCleared');
|
|
391
|
+
} catch (e) {
|
|
392
|
+
console.warn('Failed to clear feedback tracking:', e);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
348
396
|
_detectEnvironment() {
|
|
349
397
|
if (typeof window === 'undefined') {
|
|
350
398
|
return 'production';
|