@ihoomanai/chat-widget 3.0.11 → 3.0.13

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/src/types.ts CHANGED
@@ -606,6 +606,7 @@ export type WidgetEvent =
606
606
  | 'connection:change'
607
607
  | 'newConversation'
608
608
  | 'escalation:start'
609
+ | 'escalation:accepted'
609
610
  | 'escalation:end'
610
611
  | 'survey:shown'
611
612
  | 'survey:submitted'
package/src/widget.ts CHANGED
@@ -17,7 +17,7 @@ import type {
17
17
  WidgetView,
18
18
  } from './types';
19
19
 
20
- const VERSION = '3.0.11';
20
+ const VERSION = '3.0.13';
21
21
  const STORAGE_PREFIX = 'ihooman_chat_';
22
22
  const DEFAULT_SERVER_URL = 'https://api.ihooman.ai';
23
23
 
@@ -197,6 +197,32 @@ function emit(event: WidgetEvent, data?: unknown): void {
197
197
  }
198
198
  }
199
199
 
200
+ /**
201
+ * Track an analytics event to the server
202
+ */
203
+ async function trackAnalyticsEvent(
204
+ eventType: string,
205
+ metadata?: Record<string, unknown>
206
+ ): Promise<void> {
207
+ if (!config.widgetId || !config.serverUrl) return;
208
+
209
+ try {
210
+ const url = `${config.serverUrl}/widget-analytics/track?widget_id=${encodeURIComponent(config.widgetId)}`;
211
+ await fetch(url, {
212
+ method: 'POST',
213
+ headers: { 'Content-Type': 'application/json' },
214
+ body: JSON.stringify({
215
+ event_type: eventType,
216
+ session_id: state.sessionId,
217
+ visitor_id: state.visitorId,
218
+ metadata: metadata || {},
219
+ }),
220
+ });
221
+ } catch (e) {
222
+ console.debug('[IhoomanChat] Failed to track event:', eventType, e);
223
+ }
224
+ }
225
+
200
226
  function getCurrentScrollDepth(): number {
201
227
  const scrollTop = window.scrollY || document.documentElement.scrollTop;
202
228
  const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
@@ -557,6 +583,11 @@ function setupEventListeners(): void {
557
583
  const proactiveSecondaryBtn = elements.proactiveToast?.querySelector('.ihooman-proactive-toast-btn.secondary');
558
584
  if (proactivePrimaryBtn) {
559
585
  proactivePrimaryBtn.addEventListener('click', () => {
586
+ // Track click for the most recently shown proactive message
587
+ const lastShownId = shownProactiveIds[shownProactiveIds.length - 1];
588
+ if (lastShownId) {
589
+ trackAnalyticsEvent('proactive_clicked', { message_id: lastShownId });
590
+ }
560
591
  hideProactiveToast();
561
592
  open();
562
593
  emit('proactive:clicked');
@@ -1052,6 +1083,15 @@ async function handleSurveySubmit(): Promise<void> {
1052
1083
  }),
1053
1084
  });
1054
1085
 
1086
+ // Track survey submission analytics
1087
+ trackAnalyticsEvent('survey_submitted', {
1088
+ survey_id: config.surveyConfig?.id,
1089
+ rating,
1090
+ comment: comment || null,
1091
+ session_id: state.sessionId,
1092
+ visitor_id: state.visitorId,
1093
+ });
1094
+
1055
1095
  // Mark survey as completed for this session
1056
1096
  storage('survey_completed_' + state.sessionId, true);
1057
1097
 
@@ -1076,6 +1116,13 @@ function checkAndShowSurvey(): void {
1076
1116
  // Don't show if not enough messages (at least 2 exchanges)
1077
1117
  if (state.messages.length < 4) return;
1078
1118
 
1119
+ // Track survey displayed analytics
1120
+ trackAnalyticsEvent('survey_displayed', {
1121
+ survey_id: config.surveyConfig.id,
1122
+ session_id: state.sessionId,
1123
+ visitor_id: state.visitorId,
1124
+ });
1125
+
1079
1126
  // Show the survey
1080
1127
  showView('survey');
1081
1128
  emit('survey:shown', config.surveyConfig);
@@ -1089,6 +1136,15 @@ function showSurvey(): void {
1089
1136
  console.warn('[IhoomanChat] No survey configured');
1090
1137
  return;
1091
1138
  }
1139
+
1140
+ // Track survey displayed analytics
1141
+ trackAnalyticsEvent('survey_displayed', {
1142
+ survey_id: config.surveyConfig.id,
1143
+ session_id: state.sessionId,
1144
+ visitor_id: state.visitorId,
1145
+ trigger: 'manual',
1146
+ });
1147
+
1092
1148
  showView('survey');
1093
1149
  emit('survey:shown', config.surveyConfig);
1094
1150
  }
@@ -1122,6 +1178,14 @@ function handleSendClick(): void {
1122
1178
  if (elements.sendBtn) elements.sendBtn.disabled = true;
1123
1179
 
1124
1180
  addMessage(content, 'user');
1181
+
1182
+ // Track message sent analytics
1183
+ trackAnalyticsEvent('message_sent', {
1184
+ session_id: state.sessionId,
1185
+ visitor_id: state.visitorId,
1186
+ message_length: content.length,
1187
+ });
1188
+
1125
1189
  showTyping();
1126
1190
  sendMessageToServer(content);
1127
1191
  }
@@ -1163,6 +1227,15 @@ async function sendMessageToServer(content: string): Promise<void> {
1163
1227
  escalation_offered: data.escalation_offered,
1164
1228
  quick_replies: data.quick_replies,
1165
1229
  });
1230
+
1231
+ // Track message received analytics
1232
+ trackAnalyticsEvent('message_received', {
1233
+ session_id: state.sessionId,
1234
+ visitor_id: state.visitorId,
1235
+ message_length: data.response.length,
1236
+ has_sources: !!data.sources,
1237
+ confidence: data.confidence,
1238
+ });
1166
1239
  }
1167
1240
  } catch (error) {
1168
1241
  hideTyping();
@@ -1300,6 +1373,9 @@ function showProactiveMessage(pm: ProactiveMessage): void {
1300
1373
  storage('shown_proactive', shownProactiveIds);
1301
1374
  storage('proactive_cooldowns', proactiveCooldowns);
1302
1375
 
1376
+ // Track analytics event
1377
+ trackAnalyticsEvent('proactive_displayed', { message_id: pm.id });
1378
+
1303
1379
  // Show toast
1304
1380
  if (elements.proactiveToast) {
1305
1381
  const content = elements.proactiveToast.querySelector('.ihooman-proactive-toast-content');
@@ -1422,17 +1498,55 @@ function connectWebSocket(chatEndpoint?: string): void {
1422
1498
  } else if (data.type === 'message') {
1423
1499
  hideTyping();
1424
1500
  const sender = data.sender === 'user' ? 'user' : 'bot';
1501
+
1502
+ // Check if this is an agent-related system message
1503
+ const metadata = data.metadata || {};
1504
+ if (metadata.agent_accepted) {
1505
+ // Agent has accepted the escalation - update status
1506
+ isLiveAgentMode = true;
1507
+ updateStatusBar('connected', '🟢 Connected to live agent');
1508
+ emit('escalation:accepted', { type: 'live_agent' });
1509
+ }
1510
+ if (metadata.closed_by_agent) {
1511
+ // Agent has closed the conversation - return to AI mode
1512
+ isLiveAgentMode = false;
1513
+ stopLiveAgentPolling();
1514
+ updateStatusBar('hidden');
1515
+ emit('escalation:end', { type: 'live_agent' });
1516
+ }
1517
+
1518
+ // Track message received analytics
1519
+ trackAnalyticsEvent('message_received', {
1520
+ session_id: state.sessionId,
1521
+ visitor_id: state.visitorId,
1522
+ sender: data.sender,
1523
+ is_system_message: metadata.is_system_message || false,
1524
+ });
1525
+
1425
1526
  addMessage(data.content, sender, {
1426
1527
  confidence: data.confidence,
1427
1528
  agent_name: data.agent_name,
1428
1529
  escalation_offered: data.escalation_offered,
1429
1530
  escalated: data.escalated,
1430
1531
  quick_replies: data.quick_replies,
1532
+ ...metadata,
1431
1533
  });
1432
1534
  } else if (data.type === 'typing') {
1433
1535
  data.is_typing ? showTyping() : hideTyping();
1434
1536
  } else if (data.type === 'pong') {
1435
1537
  // Heartbeat response
1538
+ } else if (data.type === 'agent_status') {
1539
+ // Handle agent status updates
1540
+ if (data.status === 'connected') {
1541
+ isLiveAgentMode = true;
1542
+ updateStatusBar('connected', '🟢 Connected to live agent');
1543
+ emit('escalation:accepted', { type: 'live_agent' });
1544
+ } else if (data.status === 'disconnected') {
1545
+ isLiveAgentMode = false;
1546
+ stopLiveAgentPolling();
1547
+ updateStatusBar('hidden');
1548
+ emit('escalation:end', { type: 'live_agent' });
1549
+ }
1436
1550
  } else if (data.type === 'error') {
1437
1551
  console.error('WebSocket server error:', data.message);
1438
1552
  emit('error', { message: data.message });
@@ -1504,6 +1618,10 @@ function open(): void {
1504
1618
 
1505
1619
  hideProactiveToast();
1506
1620
  setTimeout(() => elements.input?.focus(), 300);
1621
+
1622
+ // Track widget open event
1623
+ trackAnalyticsEvent('widget_open');
1624
+
1507
1625
  emit('open');
1508
1626
  }
1509
1627
 
@@ -1514,6 +1632,9 @@ function close(): void {
1514
1632
  if (elements.toggle) elements.toggle.classList.remove('open');
1515
1633
  if (elements.window) elements.window.classList.remove('open');
1516
1634
 
1635
+ // Track widget close event
1636
+ trackAnalyticsEvent('widget_close');
1637
+
1517
1638
  // Check if we should show survey after closing (if conversation had enough messages)
1518
1639
  if (config.surveyConfig && state.messages.length >= 4) {
1519
1640
  const surveyCompleted = state.sessionId && storage<boolean>('survey_completed_' + state.sessionId);