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