@product7/feedback-sdk 1.2.6 → 1.2.7
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 +35 -35
- package/dist/README.md +35 -35
- package/dist/feedback-sdk.js +719 -660
- 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 +66 -41
- package/src/core/FeedbackSDK.js +5 -3
- package/src/core/WebSocketService.js +3 -1
- package/src/widgets/BaseWidget.js +6 -4
- package/src/widgets/MessengerWidget.js +22 -7
- package/src/widgets/messenger/views/ChatView.js +12 -4
- package/src/widgets/messenger/views/HomeView.js +7 -2
- package/types/index.d.ts +49 -0
package/package.json
CHANGED
package/src/core/APIService.js
CHANGED
|
@@ -79,7 +79,9 @@ const MOCK_CONVERSATIONS = [
|
|
|
79
79
|
id: 'conv_2',
|
|
80
80
|
subject: 'Feature request',
|
|
81
81
|
status: 'open',
|
|
82
|
-
last_message_at: new Date(
|
|
82
|
+
last_message_at: new Date(
|
|
83
|
+
Date.now() - 6 * 24 * 60 * 60 * 1000
|
|
84
|
+
).toISOString(),
|
|
83
85
|
created_at: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
|
|
84
86
|
unread: 0,
|
|
85
87
|
assigned_user: {
|
|
@@ -121,11 +123,14 @@ const MOCK_MESSAGES = {
|
|
|
121
123
|
id: 'msg_4',
|
|
122
124
|
content: 'I would love to see a dark mode feature!',
|
|
123
125
|
sender_type: 'customer',
|
|
124
|
-
created_at: new Date(
|
|
126
|
+
created_at: new Date(
|
|
127
|
+
Date.now() - 6 * 24 * 60 * 60 * 1000 - 30 * 60 * 1000
|
|
128
|
+
).toISOString(),
|
|
125
129
|
},
|
|
126
130
|
{
|
|
127
131
|
id: 'msg_5',
|
|
128
|
-
content:
|
|
132
|
+
content:
|
|
133
|
+
"Great suggestion! That feature will be available next week. I'll let you know when it's ready.",
|
|
129
134
|
sender_type: 'agent',
|
|
130
135
|
sender_name: 'Tom',
|
|
131
136
|
sender_avatar: null,
|
|
@@ -155,7 +160,8 @@ const MOCK_HELP_COLLECTIONS = [
|
|
|
155
160
|
{
|
|
156
161
|
id: 'collection_3',
|
|
157
162
|
title: 'AI Agent',
|
|
158
|
-
description:
|
|
163
|
+
description:
|
|
164
|
+
'Resolving customer questions instantly and accurately—from live chat to email.',
|
|
159
165
|
articleCount: 82,
|
|
160
166
|
icon: 'ph-robot',
|
|
161
167
|
url: '#',
|
|
@@ -163,7 +169,8 @@ const MOCK_HELP_COLLECTIONS = [
|
|
|
163
169
|
{
|
|
164
170
|
id: 'collection_4',
|
|
165
171
|
title: 'Channels',
|
|
166
|
-
description:
|
|
172
|
+
description:
|
|
173
|
+
'Enabling the channels you use to communicate with customers, all from the Inbox.',
|
|
167
174
|
articleCount: 45,
|
|
168
175
|
icon: 'ph-chat-circle',
|
|
169
176
|
url: '#',
|
|
@@ -710,10 +717,13 @@ export class APIService {
|
|
|
710
717
|
};
|
|
711
718
|
}
|
|
712
719
|
|
|
713
|
-
return this._makeRequest(
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
720
|
+
return this._makeRequest(
|
|
721
|
+
`/widget/messenger/conversations/${conversationId}`,
|
|
722
|
+
{
|
|
723
|
+
method: 'GET',
|
|
724
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
725
|
+
}
|
|
726
|
+
);
|
|
717
727
|
}
|
|
718
728
|
|
|
719
729
|
/**
|
|
@@ -820,14 +830,17 @@ export class APIService {
|
|
|
820
830
|
return { status: true, data: newMessage };
|
|
821
831
|
}
|
|
822
832
|
|
|
823
|
-
return this._makeRequest(
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
833
|
+
return this._makeRequest(
|
|
834
|
+
`/widget/messenger/conversations/${conversationId}/messages`,
|
|
835
|
+
{
|
|
836
|
+
method: 'POST',
|
|
837
|
+
headers: {
|
|
838
|
+
'Content-Type': 'application/json',
|
|
839
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
840
|
+
},
|
|
841
|
+
body: JSON.stringify({ content: data.content }),
|
|
842
|
+
}
|
|
843
|
+
);
|
|
831
844
|
}
|
|
832
845
|
|
|
833
846
|
/**
|
|
@@ -845,14 +858,17 @@ export class APIService {
|
|
|
845
858
|
return { status: true };
|
|
846
859
|
}
|
|
847
860
|
|
|
848
|
-
return this._makeRequest(
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
861
|
+
return this._makeRequest(
|
|
862
|
+
`/widget/messenger/conversations/${conversationId}/typing`,
|
|
863
|
+
{
|
|
864
|
+
method: 'POST',
|
|
865
|
+
headers: {
|
|
866
|
+
'Content-Type': 'application/json',
|
|
867
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
868
|
+
},
|
|
869
|
+
body: JSON.stringify({ is_typing: isTyping }),
|
|
870
|
+
}
|
|
871
|
+
);
|
|
856
872
|
}
|
|
857
873
|
|
|
858
874
|
/**
|
|
@@ -869,10 +885,13 @@ export class APIService {
|
|
|
869
885
|
return { status: true };
|
|
870
886
|
}
|
|
871
887
|
|
|
872
|
-
return this._makeRequest(
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
888
|
+
return this._makeRequest(
|
|
889
|
+
`/widget/messenger/conversations/${conversationId}/read`,
|
|
890
|
+
{
|
|
891
|
+
method: 'POST',
|
|
892
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
893
|
+
}
|
|
894
|
+
);
|
|
876
895
|
}
|
|
877
896
|
|
|
878
897
|
/**
|
|
@@ -885,7 +904,10 @@ export class APIService {
|
|
|
885
904
|
}
|
|
886
905
|
|
|
887
906
|
if (this.mock) {
|
|
888
|
-
const count = MOCK_CONVERSATIONS.reduce(
|
|
907
|
+
const count = MOCK_CONVERSATIONS.reduce(
|
|
908
|
+
(sum, c) => sum + (c.unread || 0),
|
|
909
|
+
0
|
|
910
|
+
);
|
|
889
911
|
return {
|
|
890
912
|
status: true,
|
|
891
913
|
data: { unread_count: count, unread_conversations: count > 0 ? 1 : 0 },
|
|
@@ -915,17 +937,20 @@ export class APIService {
|
|
|
915
937
|
return { status: true, message: 'Thank you for your feedback!' };
|
|
916
938
|
}
|
|
917
939
|
|
|
918
|
-
return this._makeRequest(
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
940
|
+
return this._makeRequest(
|
|
941
|
+
`/widget/messenger/conversations/${conversationId}/rate`,
|
|
942
|
+
{
|
|
943
|
+
method: 'POST',
|
|
944
|
+
headers: {
|
|
945
|
+
'Content-Type': 'application/json',
|
|
946
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
947
|
+
},
|
|
948
|
+
body: JSON.stringify({
|
|
949
|
+
rating: data.rating,
|
|
950
|
+
comment: data.comment || '',
|
|
951
|
+
}),
|
|
952
|
+
}
|
|
953
|
+
);
|
|
929
954
|
}
|
|
930
955
|
|
|
931
956
|
/**
|
package/src/core/FeedbackSDK.js
CHANGED
|
@@ -358,8 +358,10 @@ export class FeedbackSDK {
|
|
|
358
358
|
// Check backend tracking first (from init response)
|
|
359
359
|
if (this.config.last_feedback_at) {
|
|
360
360
|
try {
|
|
361
|
-
const backendTimestamp = new Date(
|
|
362
|
-
|
|
361
|
+
const backendTimestamp = new Date(
|
|
362
|
+
this.config.last_feedback_at
|
|
363
|
+
).getTime();
|
|
364
|
+
if (now - backendTimestamp < cooldownMs) {
|
|
363
365
|
return true;
|
|
364
366
|
}
|
|
365
367
|
} catch (e) {
|
|
@@ -374,7 +376,7 @@ export class FeedbackSDK {
|
|
|
374
376
|
if (!stored) return false;
|
|
375
377
|
|
|
376
378
|
const data = JSON.parse(stored);
|
|
377
|
-
return
|
|
379
|
+
return now - data.submittedAt < cooldownMs;
|
|
378
380
|
} catch (e) {
|
|
379
381
|
return false;
|
|
380
382
|
}
|
|
@@ -208,7 +208,9 @@ export class WebSocketService {
|
|
|
208
208
|
|
|
209
209
|
this.reconnectAttempts++;
|
|
210
210
|
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
211
|
-
console.log(
|
|
211
|
+
console.log(
|
|
212
|
+
`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
|
|
213
|
+
);
|
|
212
214
|
|
|
213
215
|
setTimeout(() => {
|
|
214
216
|
this.connect();
|
|
@@ -52,7 +52,7 @@ export class BaseWidget {
|
|
|
52
52
|
if (this.options.suppressAfterSubmission && this._hasRecentlySubmitted()) {
|
|
53
53
|
this.sdk.eventBus.emit('widget:suppressed', {
|
|
54
54
|
widget: this,
|
|
55
|
-
reason: 'recently_submitted'
|
|
55
|
+
reason: 'recently_submitted',
|
|
56
56
|
});
|
|
57
57
|
return this;
|
|
58
58
|
}
|
|
@@ -276,8 +276,10 @@ export class BaseWidget {
|
|
|
276
276
|
// Check backend tracking first (from init response)
|
|
277
277
|
if (this.sdk.config.last_feedback_at) {
|
|
278
278
|
try {
|
|
279
|
-
const backendTimestamp = new Date(
|
|
280
|
-
|
|
279
|
+
const backendTimestamp = new Date(
|
|
280
|
+
this.sdk.config.last_feedback_at
|
|
281
|
+
).getTime();
|
|
282
|
+
if (now - backendTimestamp < cooldownMs) {
|
|
281
283
|
return true;
|
|
282
284
|
}
|
|
283
285
|
} catch (e) {
|
|
@@ -296,7 +298,7 @@ export class BaseWidget {
|
|
|
296
298
|
const data = JSON.parse(stored);
|
|
297
299
|
const submittedAt = data.submittedAt;
|
|
298
300
|
|
|
299
|
-
return
|
|
301
|
+
return now - submittedAt < cooldownMs;
|
|
300
302
|
} catch (e) {
|
|
301
303
|
// localStorage may not be available or data is corrupted
|
|
302
304
|
return false;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MessengerWidget - Full-featured Messenger/Chat widget
|
|
3
3
|
*/
|
|
4
|
+
import { WebSocketService } from '../core/WebSocketService.js';
|
|
4
5
|
import { BaseWidget } from './BaseWidget.js';
|
|
5
6
|
import { MessengerState } from './messenger/MessengerState.js';
|
|
6
7
|
import { MessengerLauncher } from './messenger/components/MessengerLauncher.js';
|
|
@@ -10,7 +11,6 @@ import { ChatView } from './messenger/views/ChatView.js';
|
|
|
10
11
|
import { ConversationsView } from './messenger/views/ConversationsView.js';
|
|
11
12
|
import { HelpView } from './messenger/views/HelpView.js';
|
|
12
13
|
import { HomeView } from './messenger/views/HomeView.js';
|
|
13
|
-
import { WebSocketService } from '../core/WebSocketService.js';
|
|
14
14
|
|
|
15
15
|
export class MessengerWidget extends BaseWidget {
|
|
16
16
|
constructor(options) {
|
|
@@ -216,7 +216,10 @@ export class MessengerWidget extends BaseWidget {
|
|
|
216
216
|
this.messengerState.addMessage(conversation_id, localMessage);
|
|
217
217
|
|
|
218
218
|
// Update unread count if panel is closed or viewing different conversation
|
|
219
|
-
if (
|
|
219
|
+
if (
|
|
220
|
+
!this.messengerState.isOpen ||
|
|
221
|
+
this.messengerState.activeConversationId !== conversation_id
|
|
222
|
+
) {
|
|
220
223
|
this._updateUnreadCount();
|
|
221
224
|
}
|
|
222
225
|
}
|
|
@@ -250,7 +253,9 @@ export class MessengerWidget extends BaseWidget {
|
|
|
250
253
|
const response = await this.apiService.getUnreadCount();
|
|
251
254
|
if (response.status && response.data) {
|
|
252
255
|
this.messengerState.unreadCount = response.data.unread_count || 0;
|
|
253
|
-
this.messengerState._notify('unreadCountChange', {
|
|
256
|
+
this.messengerState._notify('unreadCountChange', {
|
|
257
|
+
count: this.messengerState.unreadCount,
|
|
258
|
+
});
|
|
254
259
|
}
|
|
255
260
|
} catch (error) {
|
|
256
261
|
console.error('[MessengerWidget] Failed to get unread count:', error);
|
|
@@ -424,9 +429,16 @@ export class MessengerWidget extends BaseWidget {
|
|
|
424
429
|
// Transform API response to local format
|
|
425
430
|
return response.data.map((conv) => ({
|
|
426
431
|
id: conv.id,
|
|
427
|
-
title:
|
|
432
|
+
title:
|
|
433
|
+
conv.subject ||
|
|
434
|
+
`Chat with ${conv.assigned_user?.name || 'Support'}`,
|
|
428
435
|
participants: conv.assigned_user
|
|
429
|
-
? [
|
|
436
|
+
? [
|
|
437
|
+
{
|
|
438
|
+
name: conv.assigned_user.name,
|
|
439
|
+
avatarUrl: conv.assigned_user.avatar,
|
|
440
|
+
},
|
|
441
|
+
]
|
|
430
442
|
: [{ name: 'Support', avatarUrl: null }],
|
|
431
443
|
lastMessage: conv.preview || conv.snippet || '',
|
|
432
444
|
lastMessageTime: conv.last_message_at,
|
|
@@ -450,7 +462,8 @@ export class MessengerWidget extends BaseWidget {
|
|
|
450
462
|
id: collection.id,
|
|
451
463
|
title: collection.title || collection.name,
|
|
452
464
|
description: collection.description || '',
|
|
453
|
-
articleCount:
|
|
465
|
+
articleCount:
|
|
466
|
+
collection.article_count || collection.articleCount || 0,
|
|
454
467
|
icon: collection.icon || 'ph-book-open',
|
|
455
468
|
url: collection.url || `#/help/${collection.slug || collection.id}`,
|
|
456
469
|
}));
|
|
@@ -475,7 +488,9 @@ export class MessengerWidget extends BaseWidget {
|
|
|
475
488
|
isOwn: msg.sender_type === 'customer',
|
|
476
489
|
timestamp: msg.created_at,
|
|
477
490
|
sender: {
|
|
478
|
-
name:
|
|
491
|
+
name:
|
|
492
|
+
msg.sender_name ||
|
|
493
|
+
(msg.sender_type === 'customer' ? 'You' : 'Support'),
|
|
479
494
|
avatarUrl: msg.sender_avatar || null,
|
|
480
495
|
},
|
|
481
496
|
}));
|
|
@@ -37,7 +37,10 @@ export class ChatView {
|
|
|
37
37
|
data.conversationId === this.state.activeConversationId
|
|
38
38
|
) {
|
|
39
39
|
this._hideTypingIndicator();
|
|
40
|
-
} else if (
|
|
40
|
+
} else if (
|
|
41
|
+
type === 'messagesUpdate' &&
|
|
42
|
+
data.conversationId === this.state.activeConversationId
|
|
43
|
+
) {
|
|
41
44
|
this._updateContent();
|
|
42
45
|
}
|
|
43
46
|
});
|
|
@@ -97,14 +100,17 @@ export class ChatView {
|
|
|
97
100
|
</div>
|
|
98
101
|
`;
|
|
99
102
|
|
|
100
|
-
this._typingIndicator = this.element.querySelector(
|
|
103
|
+
this._typingIndicator = this.element.querySelector(
|
|
104
|
+
'.messenger-typing-indicator'
|
|
105
|
+
);
|
|
101
106
|
this._attachEvents();
|
|
102
107
|
this._scrollToBottom();
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
_renderEmptyState(isNewConversation = false) {
|
|
106
111
|
const avatarHtml = this._renderTeamAvatars();
|
|
107
|
-
const responseTime =
|
|
112
|
+
const responseTime =
|
|
113
|
+
this.state.responseTime || 'We typically reply within a few minutes';
|
|
108
114
|
const isOnline = this.state.agentsOnline;
|
|
109
115
|
|
|
110
116
|
return `
|
|
@@ -360,7 +366,9 @@ export class ChatView {
|
|
|
360
366
|
_showTypingIndicator(userName) {
|
|
361
367
|
if (this._typingIndicator) {
|
|
362
368
|
this._typingIndicator.style.display = 'flex';
|
|
363
|
-
const textEl = this._typingIndicator.querySelector(
|
|
369
|
+
const textEl = this._typingIndicator.querySelector(
|
|
370
|
+
'.messenger-typing-text'
|
|
371
|
+
);
|
|
364
372
|
if (textEl) {
|
|
365
373
|
textEl.textContent = `${userName || 'Support'} is typing...`;
|
|
366
374
|
}
|
|
@@ -17,7 +17,11 @@ export class HomeView {
|
|
|
17
17
|
|
|
18
18
|
// Subscribe to state changes to re-render when data loads
|
|
19
19
|
this._unsubscribe = this.state.subscribe((type) => {
|
|
20
|
-
if (
|
|
20
|
+
if (
|
|
21
|
+
type === 'homeChangelogUpdate' ||
|
|
22
|
+
type === 'conversationsUpdate' ||
|
|
23
|
+
type === 'availabilityUpdate'
|
|
24
|
+
) {
|
|
21
25
|
this._updateContent();
|
|
22
26
|
}
|
|
23
27
|
});
|
|
@@ -94,7 +98,8 @@ export class HomeView {
|
|
|
94
98
|
|
|
95
99
|
_renderAvailabilityStatus() {
|
|
96
100
|
const isOnline = this.state.agentsOnline;
|
|
97
|
-
const responseTime =
|
|
101
|
+
const responseTime =
|
|
102
|
+
this.state.responseTime || 'We typically reply within a few minutes';
|
|
98
103
|
|
|
99
104
|
if (isOnline) {
|
|
100
105
|
return `
|
package/types/index.d.ts
CHANGED
|
@@ -159,6 +159,55 @@ declare module '@product7/feedback-sdk' {
|
|
|
159
159
|
refresh(): Promise<void>;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
export interface MessengerWidgetOptions {
|
|
163
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
164
|
+
theme?: 'light' | 'dark';
|
|
165
|
+
teamName?: string;
|
|
166
|
+
teamAvatars?: string[];
|
|
167
|
+
welcomeMessage?: string;
|
|
168
|
+
enableHelp?: boolean;
|
|
169
|
+
enableChangelog?: boolean;
|
|
170
|
+
logoUrl?: string;
|
|
171
|
+
primaryColor?: string;
|
|
172
|
+
featuredContent?: {
|
|
173
|
+
title: string;
|
|
174
|
+
description: string;
|
|
175
|
+
imageUrl?: string;
|
|
176
|
+
action?: {
|
|
177
|
+
type: 'url' | 'view';
|
|
178
|
+
value: string;
|
|
179
|
+
label: string;
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
onSendMessage?: (conversationId: string, message: any) => void;
|
|
183
|
+
onArticleClick?: (article: any) => void;
|
|
184
|
+
onChangelogClick?: (changelog: any) => void;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface MessengerWidget {
|
|
188
|
+
id: string;
|
|
189
|
+
type: 'messenger';
|
|
190
|
+
mount(container?: string | HTMLElement): this;
|
|
191
|
+
destroy(): void;
|
|
192
|
+
open(): void;
|
|
193
|
+
close(): void;
|
|
194
|
+
toggle(): void;
|
|
195
|
+
navigateTo(view: 'home' | 'messages' | 'chat' | 'help' | 'changelog'): void;
|
|
196
|
+
setConversations(conversations: any[]): void;
|
|
197
|
+
setHelpArticles(articles: any[]): void;
|
|
198
|
+
setChangelogItems(items: any[]): void;
|
|
199
|
+
setHomeChangelogItems(items: any[]): void;
|
|
200
|
+
setUnreadCount(count: number): void;
|
|
201
|
+
loadInitialData(): Promise<void>;
|
|
202
|
+
checkAgentAvailability(): Promise<any>;
|
|
203
|
+
getState(): {
|
|
204
|
+
isOpen: boolean;
|
|
205
|
+
currentView: string;
|
|
206
|
+
unreadCount: number;
|
|
207
|
+
conversations: any[];
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
162
211
|
const FeedbackSDKDefault: FeedbackSDKExport;
|
|
163
212
|
export default FeedbackSDKDefault;
|
|
164
213
|
}
|