@wabbit-dashboard/embed 1.0.16 → 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
@@ -1128,8 +1114,9 @@ class ChatPanel {
1128
1114
  <div class="wabbit-chat-typing-dot"></div>
1129
1115
  `;
1130
1116
  this.messagesContainer.appendChild(typing);
1131
- this.scrollToBottom();
1132
1117
  }
1118
+ // Always scroll after changing typing indicator state
1119
+ this.scrollToBottom();
1133
1120
  }
1134
1121
  this.updateSendButtonState();
1135
1122
  }
@@ -1211,29 +1198,38 @@ class ChatPanel {
1211
1198
  }
1212
1199
  }
1213
1200
  /**
1214
- * Scroll to bottom of messages - uses multiple techniques to ensure it works
1201
+ * Scroll to bottom of messages - forces scroll with multiple attempts
1215
1202
  */
1216
1203
  scrollToBottom() {
1217
1204
  if (!this.messagesContainer)
1218
1205
  return;
1219
1206
  const container = this.messagesContainer;
1220
- // Immediate scroll
1221
- container.scrollTop = container.scrollHeight;
1222
- // Use requestAnimationFrame to scroll after next paint
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
1223
  requestAnimationFrame(() => {
1224
- container.scrollTop = container.scrollHeight;
1225
- // And again after another frame to be extra sure
1226
- requestAnimationFrame(() => {
1227
- container.scrollTop = container.scrollHeight;
1228
- });
1224
+ forceScroll();
1225
+ requestAnimationFrame(forceScroll);
1229
1226
  });
1230
- // Also use setTimeout as a fallback for slower renders
1231
- setTimeout(() => {
1232
- container.scrollTop = container.scrollHeight;
1233
- }, 50);
1234
- setTimeout(() => {
1235
- container.scrollTop = container.scrollHeight;
1236
- }, 150);
1227
+ // Multiple delayed attempts
1228
+ setTimeout(forceScroll, 10);
1229
+ setTimeout(forceScroll, 50);
1230
+ setTimeout(forceScroll, 100);
1231
+ setTimeout(forceScroll, 200);
1232
+ setTimeout(forceScroll, 500);
1237
1233
  }
1238
1234
  /**
1239
1235
  * Show the panel
@@ -1755,8 +1751,9 @@ function injectChatStyles(primaryColor = '#6366f1', theme) {
1755
1751
 
1756
1752
  /* Inline mode messages area fills available space */
1757
1753
  .wabbit-chat-panel-inline .wabbit-chat-messages {
1758
- flex: 1;
1759
- 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;
1760
1757
  }
1761
1758
 
1762
1759
  /* Full-height variant for inline mode (used in standalone chat pages) */
@@ -3744,7 +3741,6 @@ class EmailCaptureWidget {
3744
3741
  if (this.emailCaptured) {
3745
3742
  return;
3746
3743
  }
3747
- // Only count user messages
3748
3744
  this.messageCount++;
3749
3745
  const triggerAfter = this.config.triggerAfterMessages || 3;
3750
3746
  if (this.messageCount >= triggerAfter) {