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