@wabbit-dashboard/embed 1.0.12 → 1.0.14

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.
@@ -162,16 +162,71 @@
162
162
  validateConfig(config);
163
163
  const mergedConfig = mergeConfig(config);
164
164
  Wabbit.instance = new Wabbit(mergedConfig);
165
+ // Store email capture widget reference for use in callbacks
166
+ // This allows chat widget to trigger email capture without tight coupling
167
+ let emailCaptureWidgetRef = null;
168
+ // Track if email capture is enabled (determined by server config)
169
+ let emailCaptureEnabled = false;
165
170
  // Initialize widgets based on merged config
166
171
  if (mergedConfig.chat?.enabled && mergedConfig.chat) {
172
+ const chat = mergedConfig.chat;
173
+ const apiUrl = chat.apiUrl || mergedConfig.apiUrl || 'https://platform.insourcedata.ai';
174
+ const collectionId = chat.collectionId;
175
+ // Fetch email capture config from server (in parallel with chat widget import)
176
+ const emailCaptureConfigPromise = fetch(`${apiUrl}/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
+ });
167
188
  // Import ChatWidget dynamically to avoid circular dependencies
168
- Promise.resolve().then(function () { return ChatWidget$1; }).then(({ ChatWidget }) => {
189
+ Promise.resolve().then(function () { return ChatWidget$1; }).then(async ({ ChatWidget }) => {
169
190
  // Check if instance was destroyed during async import (e.g., React StrictMode)
170
191
  if (!Wabbit.instance) {
171
192
  console.warn('[Wabbit] Instance was destroyed before chat widget could initialize');
172
193
  return;
173
194
  }
174
- const chat = mergedConfig.chat;
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;
175
230
  const chatConfig = {
176
231
  enabled: chat.enabled,
177
232
  collectionId: chat.collectionId,
@@ -194,8 +249,28 @@
194
249
  // Header customization
195
250
  showHeader: chat.showHeader,
196
251
  headerTitle: chat.headerTitle,
197
- // Callbacks - prefer chat-specific, fallback to global
198
- onChatReady: chat.onChatReady || config.onChatReady
252
+ // Callbacks
253
+ onChatReady: () => {
254
+ // Set WebSocket client on email capture widget when chat is ready
255
+ if (emailCaptureWidgetRef && Wabbit.instance?.chatWidget) {
256
+ const chatWidget = Wabbit.instance.chatWidget;
257
+ if (chatWidget.wsClient) {
258
+ emailCaptureWidgetRef.setWebSocketClient(chatWidget.wsClient);
259
+ }
260
+ }
261
+ // Call user's callback
262
+ if (userOnChatReady) {
263
+ userOnChatReady();
264
+ }
265
+ },
266
+ // Trigger email capture when user sends a message (if enabled by server)
267
+ onUserMessage: emailCaptureEnabled
268
+ ? () => {
269
+ if (emailCaptureWidgetRef) {
270
+ emailCaptureWidgetRef.handleMessage();
271
+ }
272
+ }
273
+ : undefined
199
274
  };
200
275
  const chatWidget = new ChatWidget(chatConfig);
201
276
  chatWidget.init();
@@ -229,49 +304,8 @@
229
304
  Wabbit.instance.formsWidget = formWidget;
230
305
  });
231
306
  }
232
- // Initialize email capture widget if enabled
233
- // Note: We need to wait for chat widget to be initialized first
234
- if (mergedConfig.emailCapture?.enabled) {
235
- const emailCapture = mergedConfig.emailCapture;
236
- Promise.resolve().then(function () { return EmailCaptureWidget$1; }).then(({ EmailCaptureWidget }) => {
237
- // Check if instance was destroyed during async import
238
- if (!Wabbit.instance) {
239
- console.warn('[Wabbit] Instance was destroyed before email capture widget could initialize');
240
- return;
241
- }
242
- const emailCaptureConfig = {
243
- enabled: emailCapture.enabled,
244
- triggerAfterMessages: emailCapture.triggerAfterMessages,
245
- title: emailCapture.title,
246
- description: emailCapture.description,
247
- fields: emailCapture.fields,
248
- onCapture: emailCapture.onCapture
249
- };
250
- const emailCaptureWidget = new EmailCaptureWidget(emailCaptureConfig);
251
- emailCaptureWidget.init();
252
- Wabbit.instance.emailCaptureWidget = emailCaptureWidget;
253
- // Connect to chat widget after it's initialized
254
- // Use a small delay to ensure chat widget is ready
255
- setTimeout(() => {
256
- if (Wabbit.instance?.chatWidget) {
257
- const chatWidget = Wabbit.instance.chatWidget;
258
- // Set WebSocket client
259
- if (chatWidget.wsClient) {
260
- emailCaptureWidget.setWebSocketClient(chatWidget.wsClient);
261
- }
262
- // Hook into message sending by intercepting ChatWidget's handleSendMessage
263
- const originalHandleSendMessage = chatWidget.handleSendMessage;
264
- if (originalHandleSendMessage) {
265
- chatWidget.handleSendMessage = function (content) {
266
- originalHandleSendMessage.call(this, content);
267
- // Notify email capture widget when user sends a message
268
- emailCaptureWidget.handleMessage();
269
- };
270
- }
271
- }
272
- }, 100);
273
- });
274
- }
307
+ // Note: Email capture is now automatically initialized from server config
308
+ // when chat widget is enabled. The config is fetched from /api/email-capture/config/{collectionId}
275
309
  // Auto-initialize forms with data-wabbit-form-id (backward compatibility)
276
310
  if (Wabbit.instance) {
277
311
  Wabbit.instance.initLegacyForms(config);
@@ -1967,6 +2001,10 @@
1967
2001
  }
1968
2002
  // Send via WebSocket
1969
2003
  this.wsClient.sendMessage(content);
2004
+ // Notify external listeners (for email capture, analytics, etc.)
2005
+ if (this.config.onUserMessage) {
2006
+ this.config.onUserMessage(content);
2007
+ }
1970
2008
  }
1971
2009
  /**
1972
2010
  * Open the chat panel (no-op in inline mode)
@@ -2561,7 +2599,7 @@
2561
2599
  class ApiClient {
2562
2600
  constructor(options) {
2563
2601
  this.baseUrl = options.baseUrl.replace(/\/$/, '');
2564
- this.apiKey = options.apiKey;
2602
+ this.apiKey = options.apiKey || '';
2565
2603
  this.timeout = options.timeout || 30000;
2566
2604
  }
2567
2605
  /**
@@ -2693,15 +2731,12 @@
2693
2731
  this.themeCleanup = null;
2694
2732
  this.formObserver = null;
2695
2733
  this.config = config;
2696
- // Validate apiKey (required for API client)
2697
- if (!config.apiKey) {
2698
- throw new Error('FormWidget requires apiKey. Provide it in FormConfig or WabbitConfig.');
2699
- }
2700
2734
  // Initialize API client
2735
+ // Note: apiKey is optional for forms since form endpoints are public
2701
2736
  const apiUrl = config.apiUrl || this.getApiUrlFromScript();
2702
2737
  this.apiClient = new ApiClient({
2703
2738
  baseUrl: apiUrl,
2704
- apiKey: config.apiKey,
2739
+ apiKey: config.apiKey, // Optional - forms don't require auth
2705
2740
  });
2706
2741
  // Initialize styles and renderer
2707
2742
  // Pass containerId to FormStyles to generate unique style ID
@@ -3852,6 +3887,107 @@
3852
3887
  getChatPageUrl,
3853
3888
  openChatPage
3854
3889
  };
3890
+ /**
3891
+ * Auto-initialization for forms with data-wabbit-form-id attribute
3892
+ *
3893
+ * This provides backward compatibility with the legacy embed-form.js script.
3894
+ * Forms will auto-initialize without requiring Wabbit.init() to be called.
3895
+ */
3896
+ function autoInitForms() {
3897
+ // Skip if Wabbit.init() was already called
3898
+ if (Wabbit.getInstance()) {
3899
+ return;
3900
+ }
3901
+ // Find all containers with data-wabbit-form-id
3902
+ const containers = document.querySelectorAll('[data-wabbit-form-id]');
3903
+ if (containers.length === 0) {
3904
+ return;
3905
+ }
3906
+ console.log('[Wabbit] Auto-initializing', containers.length, 'form(s)');
3907
+ // Get API URL from script src
3908
+ const getApiUrl = () => {
3909
+ const scriptTag = document.currentScript ||
3910
+ document.querySelector('script[src*="wabbit"]') ||
3911
+ document.querySelector('script[src*="embed"]');
3912
+ if (scriptTag && scriptTag instanceof HTMLScriptElement && scriptTag.src) {
3913
+ try {
3914
+ return new URL(scriptTag.src).origin;
3915
+ }
3916
+ catch {
3917
+ // Invalid URL
3918
+ }
3919
+ }
3920
+ return 'https://platform.insourcedata.ai';
3921
+ };
3922
+ const apiUrl = getApiUrl();
3923
+ // Import and initialize each form
3924
+ containers.forEach((container) => {
3925
+ const formId = container.getAttribute('data-wabbit-form-id');
3926
+ if (!formId)
3927
+ return;
3928
+ // Skip if already initialized
3929
+ if (container.__wabbitFormWidget) {
3930
+ return;
3931
+ }
3932
+ // Dynamically import FormWidget to avoid issues
3933
+ Promise.resolve().then(function () { return FormWidget$1; }).then(({ FormWidget }) => {
3934
+ try {
3935
+ const formWidget = new FormWidget({
3936
+ enabled: true,
3937
+ formId: formId,
3938
+ apiUrl: apiUrl,
3939
+ theme: 'auto',
3940
+ });
3941
+ // Override findContainer to use the specific container element
3942
+ formWidget.findContainer = () => container;
3943
+ formWidget.init();
3944
+ container.__wabbitFormWidget = formWidget;
3945
+ console.log('[Wabbit] Form auto-initialized:', formId);
3946
+ }
3947
+ catch (error) {
3948
+ console.error('[Wabbit] Failed to auto-initialize form:', formId, error);
3949
+ }
3950
+ });
3951
+ });
3952
+ }
3953
+ // Run auto-init when DOM is ready
3954
+ if (typeof document !== 'undefined') {
3955
+ if (document.readyState === 'loading') {
3956
+ document.addEventListener('DOMContentLoaded', autoInitForms);
3957
+ }
3958
+ else {
3959
+ // DOM already loaded, run immediately
3960
+ autoInitForms();
3961
+ }
3962
+ // Also watch for dynamically added forms (for SPAs)
3963
+ const formObserver = new MutationObserver((mutations) => {
3964
+ for (const mutation of mutations) {
3965
+ if (mutation.addedNodes.length) {
3966
+ const hasFormContainer = Array.from(mutation.addedNodes).some((node) => {
3967
+ if (node.nodeType !== 1)
3968
+ return false;
3969
+ const element = node;
3970
+ return (element.hasAttribute?.('data-wabbit-form-id') ||
3971
+ element.querySelector?.('[data-wabbit-form-id]'));
3972
+ });
3973
+ if (hasFormContainer) {
3974
+ console.log('[Wabbit] Detected dynamically added form container');
3975
+ autoInitForms();
3976
+ break;
3977
+ }
3978
+ }
3979
+ }
3980
+ });
3981
+ // Start observing once DOM is ready
3982
+ if (document.readyState === 'loading') {
3983
+ document.addEventListener('DOMContentLoaded', () => {
3984
+ formObserver.observe(document.body, { childList: true, subtree: true });
3985
+ });
3986
+ }
3987
+ else {
3988
+ formObserver.observe(document.body, { childList: true, subtree: true });
3989
+ }
3990
+ }
3855
3991
 
3856
3992
  exports.ApiClient = ApiClient;
3857
3993
  exports.ChatBubble = ChatBubble;