@wabbit-dashboard/embed 1.0.15 → 1.0.17

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.
@@ -161,99 +161,41 @@ class Wabbit {
161
161
  const mergedConfig = mergeConfig(config);
162
162
  Wabbit.instance = new Wabbit(mergedConfig);
163
163
  // Store email capture widget reference for use in callbacks
164
- // This allows chat widget to trigger email capture without tight coupling
165
164
  let emailCaptureWidgetRef = null;
166
- // Track if email capture is enabled (determined by server config)
167
- let emailCaptureEnabled = false;
168
165
  // Initialize widgets based on merged config
169
166
  if (mergedConfig.chat?.enabled && mergedConfig.chat) {
170
167
  const chat = mergedConfig.chat;
171
168
  const collectionId = chat.collectionId;
169
+ const userOnChatReady = chat.onChatReady || config.onChatReady;
172
170
  // Derive chat service API URL from WebSocket URL
173
- // The email capture config endpoint is on the chat service, not the dashboard
174
171
  const wsUrl = mergedConfig.wsUrl || chat.wsUrl || 'wss://chat.insourcedata.ai/ws/chat';
175
172
  const chatServiceUrl = wsUrl
176
173
  .replace(/^wss:\/\//, 'https://')
177
174
  .replace(/^ws:\/\//, 'http://')
178
- .replace(/\/ws\/chat.*$/, ''); // Remove the path, keep just the base URL
179
- // Fetch email capture config from chat service (in parallel with chat widget import)
180
- const emailCaptureConfigPromise = fetch(`${chatServiceUrl}/api/email-capture/config/${collectionId}`)
181
- .then(res => res.json())
182
- .then(data => {
183
- if (data.success && data.config?.enabled) {
184
- return data.config;
185
- }
186
- return null;
187
- })
188
- .catch(err => {
189
- console.warn('[Wabbit] Failed to fetch email capture config:', err);
190
- return null;
191
- });
192
- // Import ChatWidget dynamically to avoid circular dependencies
193
- Promise.resolve().then(function () { return ChatWidget$1; }).then(async ({ ChatWidget }) => {
194
- // Check if instance was destroyed during async import (e.g., React StrictMode)
175
+ .replace(/\/ws\/chat.*$/, '');
176
+ // Import and initialize ChatWidget immediately (don't wait for email capture config)
177
+ Promise.resolve().then(function () { return ChatWidget$1; }).then(({ ChatWidget }) => {
195
178
  if (!Wabbit.instance) {
196
179
  console.warn('[Wabbit] Instance was destroyed before chat widget could initialize');
197
180
  return;
198
181
  }
199
- // Wait for email capture config to be fetched
200
- const serverEmailConfig = await emailCaptureConfigPromise;
201
- emailCaptureEnabled = !!serverEmailConfig;
202
- // Initialize email capture widget if server config says it's enabled
203
- if (serverEmailConfig) {
204
- const { EmailCaptureWidget } = await Promise.resolve().then(function () { return EmailCaptureWidget$1; });
205
- if (!Wabbit.instance) {
206
- console.warn('[Wabbit] Instance was destroyed before email capture widget could initialize');
207
- return;
208
- }
209
- // Convert server config format to widget config format
210
- const triggerAfterMessages = serverEmailConfig.trigger?.type === 'message_count'
211
- ? serverEmailConfig.trigger.messageCount
212
- : 3;
213
- // Build fields array from server config
214
- const fields = ['email'];
215
- if (serverEmailConfig.fields?.name?.enabled)
216
- fields.push('name');
217
- if (serverEmailConfig.fields?.company?.enabled)
218
- fields.push('company');
219
- const emailCaptureConfig = {
220
- enabled: true,
221
- triggerAfterMessages,
222
- title: serverEmailConfig.modal?.title,
223
- description: serverEmailConfig.modal?.description,
224
- fields,
225
- onCapture: mergedConfig.emailCapture?.onCapture
226
- };
227
- const emailCaptureWidget = new EmailCaptureWidget(emailCaptureConfig);
228
- emailCaptureWidget.init();
229
- Wabbit.instance.emailCaptureWidget = emailCaptureWidget;
230
- emailCaptureWidgetRef = emailCaptureWidget;
231
- console.log('[Wabbit] Email capture initialized from server config');
232
- }
233
- const userOnChatReady = chat.onChatReady || config.onChatReady;
234
182
  const chatConfig = {
235
183
  enabled: chat.enabled,
236
184
  collectionId: chat.collectionId,
237
- // Use chat-specific apiKey/apiUrl if provided, otherwise inherit from WabbitConfig
238
185
  apiKey: chat.apiKey || mergedConfig.apiKey,
239
186
  apiUrl: chat.apiUrl || mergedConfig.apiUrl,
240
- wsUrl: mergedConfig.wsUrl || chat.wsUrl, // Use global wsUrl or chat-specific wsUrl
241
- // Embedding mode
187
+ wsUrl: mergedConfig.wsUrl || chat.wsUrl,
242
188
  mode: chat.mode,
243
189
  container: chat.container,
244
- // Widget mode options
245
190
  position: chat.position,
246
191
  triggerType: chat.triggerType,
247
192
  triggerDelay: chat.triggerDelay,
248
- // Appearance options
249
193
  theme: chat.theme,
250
194
  primaryColor: chat.primaryColor,
251
195
  welcomeMessage: chat.welcomeMessage,
252
196
  placeholder: chat.placeholder,
253
- // Header customization
254
197
  showHeader: chat.showHeader,
255
198
  headerTitle: chat.headerTitle,
256
- // Callbacks
257
199
  onChatReady: () => {
258
200
  // Set WebSocket client on email capture widget when chat is ready
259
201
  if (emailCaptureWidgetRef && Wabbit.instance?.chatWidget) {
@@ -262,24 +204,68 @@ class Wabbit {
262
204
  emailCaptureWidgetRef.setWebSocketClient(chatWidget.wsClient);
263
205
  }
264
206
  }
265
- // Call user's callback
266
207
  if (userOnChatReady) {
267
208
  userOnChatReady();
268
209
  }
269
210
  },
270
- // Trigger email capture when user sends a message (if enabled by server)
271
- onUserMessage: emailCaptureEnabled
272
- ? () => {
273
- if (emailCaptureWidgetRef) {
274
- emailCaptureWidgetRef.handleMessage();
275
- }
211
+ // Always set onUserMessage - it checks if emailCaptureWidgetRef exists at call time
212
+ onUserMessage: () => {
213
+ if (emailCaptureWidgetRef) {
214
+ emailCaptureWidgetRef.handleMessage();
276
215
  }
277
- : undefined
216
+ }
278
217
  };
279
218
  const chatWidget = new ChatWidget(chatConfig);
280
219
  chatWidget.init();
281
220
  Wabbit.instance.chatWidget = chatWidget;
282
221
  });
222
+ // Fetch email capture config from server (in parallel with chat init)
223
+ fetch(`${chatServiceUrl}/api/email-capture/config/${collectionId}`)
224
+ .then(res => res.json())
225
+ .then(async (data) => {
226
+ if (!data.success || !data.config?.enabled) {
227
+ return; // Email capture not enabled
228
+ }
229
+ if (!Wabbit.instance) {
230
+ return; // Instance was destroyed
231
+ }
232
+ const serverConfig = data.config;
233
+ const { EmailCaptureWidget } = await Promise.resolve().then(function () { return EmailCaptureWidget$1; });
234
+ if (!Wabbit.instance) {
235
+ return;
236
+ }
237
+ // Convert server config to widget config
238
+ const triggerAfterMessages = serverConfig.trigger?.type === 'message_count'
239
+ ? serverConfig.trigger.messageCount
240
+ : 3;
241
+ const fields = ['email'];
242
+ if (serverConfig.fields?.name?.enabled)
243
+ fields.push('name');
244
+ if (serverConfig.fields?.company?.enabled)
245
+ fields.push('company');
246
+ const emailCaptureConfig = {
247
+ enabled: true,
248
+ triggerAfterMessages,
249
+ title: serverConfig.modal?.title,
250
+ description: serverConfig.modal?.description,
251
+ fields,
252
+ onCapture: mergedConfig.emailCapture?.onCapture
253
+ };
254
+ const emailCaptureWidget = new EmailCaptureWidget(emailCaptureConfig);
255
+ emailCaptureWidget.init();
256
+ Wabbit.instance.emailCaptureWidget = emailCaptureWidget;
257
+ emailCaptureWidgetRef = emailCaptureWidget;
258
+ // If chat is already ready, set the WebSocket client now
259
+ if (Wabbit.instance.chatWidget) {
260
+ const chatWidget = Wabbit.instance.chatWidget;
261
+ if (chatWidget.wsClient) {
262
+ emailCaptureWidget.setWebSocketClient(chatWidget.wsClient);
263
+ }
264
+ }
265
+ })
266
+ .catch(err => {
267
+ console.warn('[Wabbit] Failed to fetch email capture config:', err);
268
+ });
283
269
  }
284
270
  if (mergedConfig.forms?.enabled && mergedConfig.forms) {
285
271
  // Import FormWidget dynamically to avoid circular dependencies
@@ -985,8 +971,8 @@ class ChatPanel {
985
971
  if (this.options.welcomeMessage) {
986
972
  this.addSystemMessage(this.options.welcomeMessage);
987
973
  }
988
- // Ensure scroll to bottom after DOM settles (for initial load and history)
989
- setTimeout(() => this.scrollToBottom(), 100);
974
+ // Ensure scroll to bottom after DOM settles
975
+ this.scrollToBottom();
990
976
  return this.element;
991
977
  }
992
978
  /**
@@ -1094,8 +1080,7 @@ class ChatPanel {
1094
1080
  if (this.messagesContainer) {
1095
1081
  this.messagesContainer.innerHTML = '';
1096
1082
  messages.forEach((msg) => this.renderMessage(msg));
1097
- // Use setTimeout to ensure DOM has finished layout before scrolling
1098
- setTimeout(() => this.scrollToBottom(), 100);
1083
+ this.scrollToBottom();
1099
1084
  }
1100
1085
  }
1101
1086
  /**
@@ -1129,8 +1114,9 @@ class ChatPanel {
1129
1114
  <div class="wabbit-chat-typing-dot"></div>
1130
1115
  `;
1131
1116
  this.messagesContainer.appendChild(typing);
1132
- this.scrollToBottom();
1133
1117
  }
1118
+ // Always scroll after changing typing indicator state
1119
+ this.scrollToBottom();
1134
1120
  }
1135
1121
  this.updateSendButtonState();
1136
1122
  }
@@ -1212,12 +1198,38 @@ class ChatPanel {
1212
1198
  }
1213
1199
  }
1214
1200
  /**
1215
- * Scroll to bottom of messages
1201
+ * Scroll to bottom of messages - forces scroll with multiple attempts
1216
1202
  */
1217
1203
  scrollToBottom() {
1218
- if (this.messagesContainer) {
1219
- this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
1220
- }
1204
+ if (!this.messagesContainer)
1205
+ return;
1206
+ const container = this.messagesContainer;
1207
+ const forceScroll = () => {
1208
+ // Force the container to recognize its scroll height
1209
+ const scrollHeight = container.scrollHeight;
1210
+ const clientHeight = container.clientHeight;
1211
+ // Only scroll if there's content to scroll
1212
+ if (scrollHeight > clientHeight) {
1213
+ container.scrollTop = scrollHeight;
1214
+ }
1215
+ else {
1216
+ // If heights are equal, set to a large value to force bottom
1217
+ container.scrollTop = 999999;
1218
+ }
1219
+ };
1220
+ // Immediate
1221
+ forceScroll();
1222
+ // After next frame
1223
+ requestAnimationFrame(() => {
1224
+ forceScroll();
1225
+ requestAnimationFrame(forceScroll);
1226
+ });
1227
+ // Multiple delayed attempts
1228
+ setTimeout(forceScroll, 10);
1229
+ setTimeout(forceScroll, 50);
1230
+ setTimeout(forceScroll, 100);
1231
+ setTimeout(forceScroll, 200);
1232
+ setTimeout(forceScroll, 500);
1221
1233
  }
1222
1234
  /**
1223
1235
  * Show the panel
@@ -1225,13 +1237,10 @@ class ChatPanel {
1225
1237
  show() {
1226
1238
  if (this.element) {
1227
1239
  this.element.style.display = 'flex';
1228
- // Focus input and scroll to bottom after a brief delay
1229
- setTimeout(() => {
1230
- this.scrollToBottom();
1231
- if (this.inputElement) {
1232
- this.inputElement.focus();
1233
- }
1234
- }, 100);
1240
+ this.scrollToBottom();
1241
+ if (this.inputElement) {
1242
+ this.inputElement.focus();
1243
+ }
1235
1244
  }
1236
1245
  }
1237
1246
  /**
@@ -1742,8 +1751,9 @@ function injectChatStyles(primaryColor = '#6366f1', theme) {
1742
1751
 
1743
1752
  /* Inline mode messages area fills available space */
1744
1753
  .wabbit-chat-panel-inline .wabbit-chat-messages {
1745
- flex: 1;
1746
- min-height: 200px;
1754
+ flex: 1 1 0; /* flex-grow, flex-shrink, flex-basis: 0 for proper sizing */
1755
+ min-height: 0; /* Critical for nested flex scroll to work */
1756
+ overflow-y: auto !important;
1747
1757
  }
1748
1758
 
1749
1759
  /* Full-height variant for inline mode (used in standalone chat pages) */
@@ -3731,7 +3741,6 @@ class EmailCaptureWidget {
3731
3741
  if (this.emailCaptured) {
3732
3742
  return;
3733
3743
  }
3734
- // Only count user messages
3735
3744
  this.messageCount++;
3736
3745
  const triggerAfter = this.config.triggerAfterMessages || 3;
3737
3746
  if (this.messageCount >= triggerAfter) {