@marvalt/madapter 1.0.14 → 2.1.0

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.
package/dist/index.esm.js CHANGED
@@ -1,7 +1,7 @@
1
- import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
2
1
  import fs from 'fs';
3
2
  import path from 'path';
4
3
  import React, { useState, useEffect, createContext, useMemo, useContext } from 'react';
4
+ import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
5
5
 
6
6
  /**
7
7
  * @license GPL-3.0-or-later
@@ -22,68 +22,16 @@ import React, { useState, useEffect, createContext, useMemo, useContext } from '
22
22
  class MauticClient {
23
23
  constructor(config) {
24
24
  this.config = config;
25
- if (config.authMode === 'cloudflare_proxy' && config.cloudflareWorkerUrl && config.appId && config.workerSecret) {
26
- // Cloudflare Worker proxy (recommended - uses Secrets Store)
27
- this.proxyUrl = config.cloudflareWorkerUrl;
28
- this.appId = config.appId;
29
- this.workerSecret = config.workerSecret;
30
- this.useProxy = true;
31
- this.isConfigured = true;
32
- }
33
- else if (config.authMode === 'direct' && config.apiUrl) {
34
- // Direct Mautic API access (legacy - for local development only)
35
- this.baseUrl = config.apiUrl;
36
- this.useProxy = false;
37
- this.isConfigured = true;
38
- }
39
- else {
40
- // Fallback or error state
41
- this.useProxy = false;
42
- this.isConfigured = false;
43
- }
44
- }
45
- validateConfiguration() {
46
- if (!this.isConfigured) {
47
- throw new Error('Mautic service not properly configured. Check your configuration.');
48
- }
49
- }
50
- getAuthHeaders() {
51
- if (this.useProxy) {
52
- return {
53
- 'x-app-id': this.appId || '',
54
- 'x-app-secret': this.workerSecret || '',
55
- };
56
- }
57
- else {
58
- return {};
59
- }
25
+ // Convention-based: Always use /api/mautic-submit unless explicitly overridden
26
+ this.proxyEndpoint = config.proxyEndpoint || '/api/mautic-submit';
60
27
  }
61
28
  async makeRequest(endpoint, options = {}) {
62
- this.validateConfiguration();
63
- let url;
64
- if (this.useProxy) {
65
- // Use Cloudflare Worker proxy
66
- const proxyEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
67
- url = `${this.proxyUrl}?endpoint=${encodeURIComponent(proxyEndpoint)}`;
68
- }
69
- else {
70
- // Use direct Mautic API
71
- // Determine if this is a form submission endpoint (should not use /api prefix)
72
- const isFormSubmission = endpoint.startsWith('/form/submit') ||
73
- (endpoint.startsWith('/forms/') && endpoint.includes('/submit'));
74
- if (isFormSubmission) {
75
- // Form submissions go directly to the form endpoint, not the API
76
- url = `${this.baseUrl}${endpoint}`;
77
- }
78
- else {
79
- // API calls use the /api prefix
80
- url = `${this.baseUrl}/api${endpoint}`;
81
- }
82
- }
29
+ // Always use proxy endpoint (convention-based)
30
+ const proxyEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
31
+ const url = `${this.proxyEndpoint}?endpoint=${encodeURIComponent(proxyEndpoint)}`;
83
32
  const isFormSubmission = endpoint.startsWith('/form/submit') ||
84
33
  (endpoint.startsWith('/forms/') && endpoint.includes('/submit'));
85
34
  const headers = {
86
- ...this.getAuthHeaders(),
87
35
  ...options.headers,
88
36
  };
89
37
  // Default JSON only for non-form submissions when a body exists and no explicit content-type provided
@@ -287,6 +235,7 @@ class MauticClient {
287
235
  */
288
236
  /**
289
237
  * Create Mautic configuration from environment variables
238
+ * Convention-based: Uses /api/mautic-submit by default
290
239
  */
291
240
  const createMauticConfig = (overrides = {}) => {
292
241
  // Use import.meta.env for Vite compatibility in browser
@@ -313,32 +262,12 @@ const createMauticConfig = (overrides = {}) => {
313
262
  return undefined;
314
263
  };
315
264
  const merged = {
316
- authMode: getEnvVar('VITE_AUTH_MODE') || getEnvVar('VITE_MAUTIC_AUTH_MODE') || 'cloudflare_proxy',
317
265
  apiUrl: getEnvVar('VITE_MAUTIC_URL'),
318
- cloudflareWorkerUrl: getEnvVar('VITE_MAUTIC_PROXY_URL'),
319
- appId: getEnvVar('VITE_FRONTEND_ID'),
320
- workerSecret: getEnvVar('VITE_FRONTEND_SECRET'),
266
+ proxyEndpoint: getEnvVar('VITE_MAUTIC_PROXY_ENDPOINT') || '/api/mautic-submit',
321
267
  timeout: parseInt(getEnvVar('VITE_MAUTIC_TIMEOUT') || '30000'),
322
268
  retries: parseInt(getEnvVar('VITE_MAUTIC_RETRIES') || '3'),
323
269
  ...overrides,
324
270
  };
325
- // Enforce required fields depending on authMode
326
- const errors = [];
327
- if (merged.authMode === 'cloudflare_proxy') {
328
- if (!merged.cloudflareWorkerUrl)
329
- errors.push('cloudflareWorkerUrl is required for cloudflare_proxy mode');
330
- if (!merged.appId)
331
- errors.push('appId is required for cloudflare_proxy mode');
332
- if (!merged.workerSecret)
333
- errors.push('workerSecret is required for cloudflare_proxy mode');
334
- }
335
- if (merged.authMode === 'direct') {
336
- if (!merged.apiUrl)
337
- errors.push('apiUrl is required for direct mode');
338
- }
339
- if (errors.length) {
340
- throw new Error(`Invalid Mautic configuration: ${errors.join('; ')}`);
341
- }
342
271
  return merged;
343
272
  };
344
273
  /**
@@ -346,25 +275,6 @@ const createMauticConfig = (overrides = {}) => {
346
275
  */
347
276
  const validateMauticConfig = (config) => {
348
277
  const errors = [];
349
- if (!config.authMode) {
350
- errors.push('authMode is required');
351
- }
352
- if (config.authMode === 'cloudflare_proxy') {
353
- if (!config.cloudflareWorkerUrl) {
354
- errors.push('cloudflareWorkerUrl is required for cloudflare_proxy mode');
355
- }
356
- if (!config.appId) {
357
- errors.push('appId is required for cloudflare_proxy mode');
358
- }
359
- if (!config.workerSecret) {
360
- errors.push('workerSecret is required for cloudflare_proxy mode');
361
- }
362
- }
363
- else if (config.authMode === 'direct') {
364
- if (!config.apiUrl) {
365
- errors.push('apiUrl is required for direct mode');
366
- }
367
- }
368
278
  if (config.timeout && config.timeout < 1000) {
369
279
  errors.push('timeout must be at least 1000ms');
370
280
  }
@@ -381,7 +291,7 @@ const validateMauticConfig = (config) => {
381
291
  */
382
292
  const getDefaultMauticConfig = () => {
383
293
  return {
384
- authMode: 'cloudflare_proxy',
294
+ proxyEndpoint: '/api/mautic-submit',
385
295
  timeout: 30000,
386
296
  retries: 3,
387
297
  };
@@ -399,339 +309,21 @@ const mergeMauticConfig = (base, overrides) => {
399
309
  * Check if Mautic is enabled based on configuration
400
310
  */
401
311
  const isMauticEnabled = (config) => {
402
- if (config.authMode === 'cloudflare_proxy') {
403
- return !!(config.cloudflareWorkerUrl && config.appId && config.workerSecret);
404
- }
405
- else if (config.authMode === 'direct') {
406
- return !!config.apiUrl;
407
- }
408
- return false;
312
+ return !!config.apiUrl;
409
313
  };
410
314
  /**
411
315
  * Get Mautic configuration summary (for debugging)
412
316
  */
413
317
  const getMauticConfigSummary = (config) => {
414
318
  return {
415
- authMode: config.authMode,
416
319
  hasApiUrl: !!config.apiUrl,
417
- hasCloudflareWorkerUrl: !!config.cloudflareWorkerUrl,
418
- hasAppId: !!config.appId,
419
- hasWorkerSecret: !!config.workerSecret,
320
+ proxyEndpoint: config.proxyEndpoint || '/api/mautic-submit',
420
321
  timeout: config.timeout,
421
322
  retries: config.retries,
422
323
  isEnabled: isMauticEnabled(config),
423
324
  };
424
325
  };
425
326
 
426
- /**
427
- * @license GPL-3.0-or-later
428
- *
429
- * This file is part of the MarVAlt Open SDK.
430
- * Copyright (c) 2025 Vibune Pty Ltd.
431
- *
432
- * This program is free software: you can redistribute it and/or modify
433
- * it under the terms of the GNU General Public License as published by
434
- * the Free Software Foundation, either version 3 of the License, or
435
- * (at your option) any later version.
436
- *
437
- * This program is distributed in the hope that it will be useful,
438
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
439
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
440
- * See the GNU General Public License for more details.
441
- */
442
- // Create a default client instance (will be overridden by provider)
443
- let defaultClient = null;
444
- /**
445
- * Set the default Mautic client for hooks
446
- */
447
- const setMauticClient = (client) => {
448
- defaultClient = client;
449
- };
450
- /**
451
- * Get the current Mautic client
452
- */
453
- const getMauticClient = () => {
454
- if (!defaultClient) {
455
- // Lazy initialize the client from environment if not set by provider/service
456
- const config = createMauticConfig();
457
- defaultClient = new MauticClient(config);
458
- }
459
- return defaultClient;
460
- };
461
- /**
462
- * Custom hook for submitting forms to Mautic
463
- */
464
- const useMauticFormSubmission = () => {
465
- const queryClient = useQueryClient();
466
- return useMutation({
467
- mutationFn: (submission) => {
468
- const client = getMauticClient();
469
- return client.submitForm(submission.formId, submission);
470
- },
471
- onSuccess: (data, variables) => {
472
- if (import.meta.env?.DEV) {
473
- console.log('Mautic form submission successful:', data);
474
- }
475
- // Invalidate related queries
476
- queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
477
- // Track the submission event
478
- if (variables.contact?.email) {
479
- const client = getMauticClient();
480
- client.trackEvent('form_submission', {
481
- formId: variables.formId,
482
- email: variables.contact.email
483
- }).catch(console.error);
484
- }
485
- },
486
- onError: (error) => {
487
- console.error('Mautic form submission failed:', error);
488
- },
489
- });
490
- };
491
- /**
492
- * Custom hook for creating Mautic contacts
493
- */
494
- const useMauticCreateContact = () => {
495
- const queryClient = useQueryClient();
496
- return useMutation({
497
- mutationFn: (contact) => {
498
- const client = getMauticClient();
499
- return client.createContact(contact);
500
- },
501
- onSuccess: (data, variables) => {
502
- if (import.meta.env?.DEV) {
503
- console.log('Mautic contact created:', data);
504
- }
505
- // Invalidate contacts queries
506
- queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
507
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.email] });
508
- },
509
- onError: (error) => {
510
- console.error('Failed to create Mautic contact:', error);
511
- },
512
- });
513
- };
514
- /**
515
- * Custom hook for updating Mautic contacts
516
- */
517
- const useMauticUpdateContact = () => {
518
- const queryClient = useQueryClient();
519
- return useMutation({
520
- mutationFn: ({ contactId, contact }) => {
521
- const client = getMauticClient();
522
- return client.updateContact(contactId, contact);
523
- },
524
- onSuccess: (data, variables) => {
525
- if (import.meta.env?.DEV) {
526
- console.log('Mautic contact updated:', data);
527
- }
528
- // Invalidate related queries
529
- queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
530
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
531
- },
532
- onError: (error) => {
533
- console.error('Failed to update Mautic contact:', error);
534
- },
535
- });
536
- };
537
- /**
538
- * Custom hook for fetching Mautic contact by email
539
- */
540
- const useMauticContactByEmail = (email) => {
541
- return useQuery({
542
- queryKey: ['mautic-contact', email],
543
- queryFn: () => {
544
- const client = getMauticClient();
545
- return client.getContactByEmail(email);
546
- },
547
- enabled: !!email && email.includes('@'),
548
- staleTime: 5 * 60 * 1000, // 5 minutes
549
- gcTime: 10 * 60 * 1000,
550
- });
551
- };
552
- /**
553
- * Custom hook for fetching Mautic forms
554
- */
555
- const useMauticForms = () => {
556
- return useQuery({
557
- queryKey: ['mautic-forms'],
558
- queryFn: () => {
559
- const client = getMauticClient();
560
- return client.getForms();
561
- },
562
- staleTime: 30 * 60 * 1000, // Forms don't change often
563
- gcTime: 60 * 60 * 1000,
564
- });
565
- };
566
- /**
567
- * Custom hook for fetching a specific Mautic form
568
- */
569
- const useMauticForm = (formId) => {
570
- return useQuery({
571
- queryKey: ['mautic-form', formId],
572
- queryFn: () => {
573
- const client = getMauticClient();
574
- return client.getForm(formId);
575
- },
576
- enabled: !!formId,
577
- staleTime: 30 * 60 * 1000,
578
- gcTime: 60 * 60 * 1000,
579
- });
580
- };
581
- /**
582
- * Custom hook for tracking Mautic events
583
- */
584
- const useMauticEventTracking = () => {
585
- return useMutation({
586
- mutationFn: ({ email, eventName, eventData }) => {
587
- const client = getMauticClient();
588
- return client.trackEvent(eventName, {
589
- email,
590
- ...(eventData || {})
591
- });
592
- },
593
- onSuccess: (data, variables) => {
594
- if (import.meta.env?.DEV) {
595
- console.log('Mautic event tracked successfully:', variables.eventName);
596
- }
597
- },
598
- onError: (error, variables) => {
599
- console.error('Failed to track Mautic event:', variables.eventName, error);
600
- },
601
- });
602
- };
603
- /**
604
- * Custom hook for adding tags to contacts
605
- */
606
- const useMauticAddTags = () => {
607
- const queryClient = useQueryClient();
608
- return useMutation({
609
- mutationFn: ({ contactId, tags }) => {
610
- const client = getMauticClient();
611
- return client.addTagToContact(contactId, tags);
612
- },
613
- onSuccess: (data, variables) => {
614
- if (import.meta.env?.DEV) {
615
- console.log('Tags added to Mautic contact:', variables.tags);
616
- }
617
- // Invalidate contact queries
618
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
619
- },
620
- onError: (error) => {
621
- console.error('Failed to add tags to Mautic contact:', error);
622
- },
623
- });
624
- };
625
- /**
626
- * Custom hook for removing tags from contacts
627
- */
628
- const useMauticRemoveTags = () => {
629
- const queryClient = useQueryClient();
630
- return useMutation({
631
- mutationFn: ({ contactId, tags }) => {
632
- const client = getMauticClient();
633
- return client.removeTagFromContact(contactId, tags);
634
- },
635
- onSuccess: (data, variables) => {
636
- if (import.meta.env?.DEV) {
637
- console.log('Tags removed from Mautic contact:', variables.tags);
638
- }
639
- // Invalidate contact queries
640
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
641
- },
642
- onError: (error) => {
643
- console.error('Failed to remove tags from Mautic contact:', error);
644
- },
645
- });
646
- };
647
- /**
648
- * Custom hook for getting contact tags
649
- */
650
- const useMauticContactTags = (contactId) => {
651
- return useQuery({
652
- queryKey: ['mautic-contact-tags', contactId],
653
- queryFn: () => {
654
- const client = getMauticClient();
655
- return client.getContactTags(contactId);
656
- },
657
- enabled: !!contactId,
658
- staleTime: 5 * 60 * 1000,
659
- gcTime: 10 * 60 * 1000,
660
- });
661
- };
662
- /**
663
- * Custom hook for getting all tags
664
- */
665
- const useMauticTags = () => {
666
- return useQuery({
667
- queryKey: ['mautic-tags'],
668
- queryFn: () => {
669
- const client = getMauticClient();
670
- return client.getTags();
671
- },
672
- staleTime: 30 * 60 * 1000,
673
- gcTime: 60 * 60 * 1000,
674
- });
675
- };
676
- /**
677
- * Custom hook for getting segments
678
- */
679
- const useMauticSegments = () => {
680
- return useQuery({
681
- queryKey: ['mautic-segments'],
682
- queryFn: () => {
683
- const client = getMauticClient();
684
- return client.getSegments();
685
- },
686
- staleTime: 30 * 60 * 1000,
687
- gcTime: 60 * 60 * 1000,
688
- });
689
- };
690
- /**
691
- * Custom hook for adding contact to segment
692
- */
693
- const useMauticAddToSegment = () => {
694
- const queryClient = useQueryClient();
695
- return useMutation({
696
- mutationFn: ({ contactId, segmentId }) => {
697
- const client = getMauticClient();
698
- return client.addContactToSegment(contactId, segmentId);
699
- },
700
- onSuccess: (data, variables) => {
701
- if (import.meta.env?.DEV) {
702
- console.log('Contact added to segment:', variables.segmentId);
703
- }
704
- // Invalidate contact queries
705
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
706
- },
707
- onError: (error) => {
708
- console.error('Failed to add contact to segment:', error);
709
- },
710
- });
711
- };
712
- /**
713
- * Custom hook for removing contact from segment
714
- */
715
- const useMauticRemoveFromSegment = () => {
716
- const queryClient = useQueryClient();
717
- return useMutation({
718
- mutationFn: ({ contactId, segmentId }) => {
719
- const client = getMauticClient();
720
- return client.removeContactFromSegment(contactId, segmentId);
721
- },
722
- onSuccess: (data, variables) => {
723
- if (import.meta.env?.DEV) {
724
- console.log('Contact removed from segment:', variables.segmentId);
725
- }
726
- // Invalidate contact queries
727
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
728
- },
729
- onError: (error) => {
730
- console.error('Failed to remove contact from segment:', error);
731
- },
732
- });
733
- };
734
-
735
327
  /**
736
328
  * @license GPL-3.0-or-later
737
329
  *
@@ -833,9 +425,19 @@ class MauticService {
833
425
  return this.client.trackEvent(eventName, eventData);
834
426
  }
835
427
  }
836
- // Export configured instance
837
- const mauticService = new MauticService();
838
- setMauticClient(mauticService['client']);
428
+ // Export configured instance (guarded to avoid throwing at import time)
429
+ let mauticService = null;
430
+ try {
431
+ mauticService = new MauticService();
432
+ // Initialize the default client for hooks
433
+ // Lazy import to avoid circular/side-effect issues in some environments
434
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
435
+ const { setMauticClient } = require('../react/hooks/useMautic');
436
+ setMauticClient(mauticService['client']);
437
+ }
438
+ catch {
439
+ // Silently skip initialization when environment is not configured
440
+ }
839
441
 
840
442
  /**
841
443
  * @license GPL-3.0-or-later
@@ -855,17 +457,54 @@ setMauticClient(mauticService['client']);
855
457
  */
856
458
  class MauticGenerator {
857
459
  constructor(config) {
460
+ this.cachedToken = null;
858
461
  this.config = config;
462
+ // Generator uses direct OAuth2 calls, not the client
463
+ // Client is only used for proxy mode (cloudflare_proxy)
859
464
  this.client = new MauticClient({
860
- authMode: config.authMode,
861
465
  apiUrl: config.apiUrl,
862
- cloudflareWorkerUrl: config.cloudflareWorkerUrl,
863
- appId: config.appId,
864
- workerSecret: config.workerSecret,
466
+ proxyEndpoint: config.cloudflareWorkerUrl,
865
467
  timeout: config.timeout,
866
468
  retries: config.retries,
867
469
  });
868
470
  }
471
+ /**
472
+ * Get OAuth2 token for direct mode API calls
473
+ */
474
+ async getOAuth2Token() {
475
+ if (this.cachedToken && this.cachedToken.expires_at > Date.now() + 300000) {
476
+ console.log('🔑 Using cached OAuth2 token');
477
+ return this.cachedToken.access_token;
478
+ }
479
+ if (!this.config.clientId || !this.config.clientSecret) {
480
+ throw new Error('OAuth2 credentials (clientId, clientSecret) required for direct mode');
481
+ }
482
+ console.log('🔑 Fetching new OAuth2 token...');
483
+ const tokenUrl = `${this.config.apiUrl}/oauth/v2/token`;
484
+ const body = new URLSearchParams({
485
+ grant_type: 'client_credentials',
486
+ client_id: this.config.clientId,
487
+ client_secret: this.config.clientSecret,
488
+ });
489
+ const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
490
+ if (this.config.cfAccessClientId && this.config.cfAccessClientSecret) {
491
+ headers['CF-Access-Client-Id'] = this.config.cfAccessClientId;
492
+ headers['CF-Access-Client-Secret'] = this.config.cfAccessClientSecret;
493
+ console.log('🔐 Added CF Access headers to OAuth2 token request');
494
+ }
495
+ const resp = await fetch(tokenUrl, { method: 'POST', headers, body: body.toString() });
496
+ if (!resp.ok) {
497
+ const errText = await resp.text();
498
+ throw new Error(`OAuth2 token failed: ${resp.status} ${errText}`);
499
+ }
500
+ const data = await resp.json();
501
+ this.cachedToken = {
502
+ access_token: data.access_token,
503
+ expires_at: Date.now() + (data.expires_in * 1000),
504
+ };
505
+ console.log('✅ OAuth2 token cached');
506
+ return this.cachedToken.access_token;
507
+ }
869
508
  /**
870
509
  * Generate static data for Mautic forms
871
510
  */
@@ -899,7 +538,7 @@ class MauticGenerator {
899
538
  // Include other important form properties
900
539
  formType: formDetails.formType || 'standalone',
901
540
  // Extract fields with validation and properties
902
- fields: (formDetails.fields || []).map(field => ({
541
+ fields: (formDetails.fields || []).map((field) => ({
903
542
  id: field.id,
904
543
  label: field.label,
905
544
  alias: field.alias,
@@ -918,7 +557,7 @@ class MauticGenerator {
918
557
  }
919
558
  })),
920
559
  // Extract actions
921
- actions: (formDetails.actions || []).map(action => ({
560
+ actions: (formDetails.actions || []).map((action) => ({
922
561
  id: action.id,
923
562
  name: action.name,
924
563
  type: action.type,
@@ -942,10 +581,10 @@ class MauticGenerator {
942
581
  const staticData = {
943
582
  generated_at: new Date().toISOString(),
944
583
  total_forms: forms.length,
945
- forms: forms.map(form => ({
584
+ forms: forms.map((form) => ({
946
585
  id: parseInt(form.id),
947
586
  name: form.name,
948
- alias: form.alias || form.name.toLowerCase().replace(/\s+/g, '-'),
587
+ alias: String(form.alias ?? (form.name || '').toLowerCase().replace(/\s+/g, '-')),
949
588
  description: form.description,
950
589
  isPublished: form.isPublished,
951
590
  fields: form.fields,
@@ -992,49 +631,44 @@ class MauticGenerator {
992
631
  * Fetch list of forms from Mautic
993
632
  */
994
633
  async fetchMauticFormsList() {
995
- const baseUrl = this.config.cloudflareWorkerUrl;
996
- if (!baseUrl)
997
- throw new Error('VITE_MAUTIC_PROXY_URL not configured');
998
- const url = `${baseUrl}?endpoint=/forms`;
999
- const headers = {
1000
- 'Content-Type': 'application/json',
1001
- };
1002
- if (this.config.appId) {
1003
- headers['x-app-id'] = this.config.appId;
1004
- }
1005
- if (this.config.workerSecret) {
1006
- headers['x-app-secret'] = this.config.workerSecret;
634
+ if (this.config.authMode === 'cloudflare_proxy') {
635
+ const forms = await this.client.getForms();
636
+ return Array.isArray(forms) ? forms : [];
1007
637
  }
1008
- const response = await fetch(url, { headers });
1009
- if (!response.ok) {
1010
- throw new Error(`Failed to fetch forms list: ${response.status} ${response.statusText}`);
638
+ // Direct mode with OAuth2
639
+ const token = await this.getOAuth2Token();
640
+ const url = `${this.config.apiUrl}/api/forms`;
641
+ const headers = { 'Authorization': `Bearer ${token}` };
642
+ if (this.config.cfAccessClientId && this.config.cfAccessClientSecret) {
643
+ headers['CF-Access-Client-Id'] = this.config.cfAccessClientId;
644
+ headers['CF-Access-Client-Secret'] = this.config.cfAccessClientSecret;
1011
645
  }
1012
- const data = await response.json().catch(() => ({}));
1013
- const forms = data.forms;
1014
- return Array.isArray(forms) ? forms : [];
646
+ const resp = await fetch(url, { headers });
647
+ if (!resp.ok)
648
+ throw new Error(`Failed to fetch forms: ${resp.status}`);
649
+ const data = await resp.json();
650
+ return Array.isArray(data.forms) ? data.forms : [];
1015
651
  }
1016
652
  /**
1017
653
  * Fetch individual form details from Mautic
1018
654
  */
1019
655
  async fetchMauticForm(formId) {
1020
- const baseUrl = this.config.cloudflareWorkerUrl;
1021
- if (!baseUrl)
1022
- throw new Error('VITE_MAUTIC_PROXY_URL not configured');
1023
- const url = `${baseUrl}?endpoint=/forms/${formId}`;
1024
- const headers = {
1025
- 'Content-Type': 'application/json',
1026
- };
1027
- if (this.config.appId) {
1028
- headers['x-app-id'] = this.config.appId;
1029
- }
1030
- if (this.config.workerSecret) {
1031
- headers['x-app-secret'] = this.config.workerSecret;
656
+ if (this.config.authMode === 'cloudflare_proxy') {
657
+ return this.client.getForm(parseInt(formId, 10));
1032
658
  }
1033
- const response = await fetch(url, { headers });
1034
- if (!response.ok) {
1035
- throw new Error(`Failed to fetch form ${formId}: ${response.status} ${response.statusText}`);
659
+ // Direct mode with OAuth2
660
+ const token = await this.getOAuth2Token();
661
+ const url = `${this.config.apiUrl}/api/forms/${formId}`;
662
+ const headers = { 'Authorization': `Bearer ${token}` };
663
+ if (this.config.cfAccessClientId && this.config.cfAccessClientSecret) {
664
+ headers['CF-Access-Client-Id'] = this.config.cfAccessClientId;
665
+ headers['CF-Access-Client-Secret'] = this.config.cfAccessClientSecret;
1036
666
  }
1037
- return response.json().catch(() => ({}));
667
+ const resp = await fetch(url, { headers });
668
+ if (!resp.ok)
669
+ throw new Error(`Failed to fetch form ${formId}: ${resp.status}`);
670
+ const data = await resp.json();
671
+ return data.form || data;
1038
672
  }
1039
673
  }
1040
674
  /**
@@ -2400,6 +2034,315 @@ if (process.env.NODE_ENV === 'production') {
2400
2034
 
2401
2035
  var jsxRuntimeExports = jsxRuntime.exports;
2402
2036
 
2037
+ /**
2038
+ * @license GPL-3.0-or-later
2039
+ *
2040
+ * This file is part of the MarVAlt Open SDK.
2041
+ * Copyright (c) 2025 Vibune Pty Ltd.
2042
+ *
2043
+ * This program is free software: you can redistribute it and/or modify
2044
+ * it under the terms of the GNU General Public License as published by
2045
+ * the Free Software Foundation, either version 3 of the License, or
2046
+ * (at your option) any later version.
2047
+ *
2048
+ * This program is distributed in the hope that it will be useful,
2049
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2050
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2051
+ * See the GNU General Public License for more details.
2052
+ */
2053
+ // Create a default client instance (will be overridden by provider)
2054
+ let defaultClient = null;
2055
+ /**
2056
+ * Set the default Mautic client for hooks
2057
+ */
2058
+ const setMauticClient = (client) => {
2059
+ defaultClient = client;
2060
+ };
2061
+ /**
2062
+ * Get the current Mautic client
2063
+ */
2064
+ const getMauticClient = () => {
2065
+ if (!defaultClient) {
2066
+ // Lazy initialize the client from environment if not set by provider/service
2067
+ const config = createMauticConfig();
2068
+ defaultClient = new MauticClient(config);
2069
+ }
2070
+ return defaultClient;
2071
+ };
2072
+ /**
2073
+ * Custom hook for submitting forms to Mautic
2074
+ */
2075
+ const useMauticFormSubmission = () => {
2076
+ const queryClient = useQueryClient();
2077
+ return useMutation({
2078
+ mutationFn: (submission) => {
2079
+ const client = getMauticClient();
2080
+ return client.submitForm(submission.formId, submission);
2081
+ },
2082
+ onSuccess: (data, variables) => {
2083
+ if (process.env.NODE_ENV === 'development') {
2084
+ console.log('Mautic form submission successful:', data);
2085
+ }
2086
+ // Invalidate related queries
2087
+ queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
2088
+ // Track the submission event
2089
+ if (variables.contact?.email) {
2090
+ const client = getMauticClient();
2091
+ client.trackEvent('form_submission', {
2092
+ formId: variables.formId,
2093
+ email: variables.contact.email
2094
+ }).catch(console.error);
2095
+ }
2096
+ },
2097
+ onError: (error) => {
2098
+ console.error('Mautic form submission failed:', error);
2099
+ },
2100
+ });
2101
+ };
2102
+ /**
2103
+ * Custom hook for creating Mautic contacts
2104
+ */
2105
+ const useMauticCreateContact = () => {
2106
+ const queryClient = useQueryClient();
2107
+ return useMutation({
2108
+ mutationFn: (contact) => {
2109
+ const client = getMauticClient();
2110
+ return client.createContact(contact);
2111
+ },
2112
+ onSuccess: (data, variables) => {
2113
+ if (process.env.NODE_ENV === 'development') {
2114
+ console.log('Mautic contact created:', data);
2115
+ }
2116
+ // Invalidate contacts queries
2117
+ queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
2118
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.email] });
2119
+ },
2120
+ onError: (error) => {
2121
+ console.error('Failed to create Mautic contact:', error);
2122
+ },
2123
+ });
2124
+ };
2125
+ /**
2126
+ * Custom hook for updating Mautic contacts
2127
+ */
2128
+ const useMauticUpdateContact = () => {
2129
+ const queryClient = useQueryClient();
2130
+ return useMutation({
2131
+ mutationFn: ({ contactId, contact }) => {
2132
+ const client = getMauticClient();
2133
+ return client.updateContact(contactId, contact);
2134
+ },
2135
+ onSuccess: (data, variables) => {
2136
+ if (process.env.NODE_ENV === 'development') {
2137
+ console.log('Mautic contact updated:', data);
2138
+ }
2139
+ // Invalidate related queries
2140
+ queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
2141
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2142
+ },
2143
+ onError: (error) => {
2144
+ console.error('Failed to update Mautic contact:', error);
2145
+ },
2146
+ });
2147
+ };
2148
+ /**
2149
+ * Custom hook for fetching Mautic contact by email
2150
+ */
2151
+ const useMauticContactByEmail = (email) => {
2152
+ return useQuery({
2153
+ queryKey: ['mautic-contact', email],
2154
+ queryFn: () => {
2155
+ const client = getMauticClient();
2156
+ return client.getContactByEmail(email);
2157
+ },
2158
+ enabled: !!email && email.includes('@'),
2159
+ staleTime: 5 * 60 * 1000, // 5 minutes
2160
+ gcTime: 10 * 60 * 1000,
2161
+ });
2162
+ };
2163
+ /**
2164
+ * Custom hook for fetching Mautic forms
2165
+ */
2166
+ const useMauticForms = () => {
2167
+ return useQuery({
2168
+ queryKey: ['mautic-forms'],
2169
+ queryFn: () => {
2170
+ const client = getMauticClient();
2171
+ return client.getForms();
2172
+ },
2173
+ staleTime: 30 * 60 * 1000, // Forms don't change often
2174
+ gcTime: 60 * 60 * 1000,
2175
+ });
2176
+ };
2177
+ /**
2178
+ * Custom hook for fetching a specific Mautic form
2179
+ */
2180
+ const useMauticForm = (formId) => {
2181
+ return useQuery({
2182
+ queryKey: ['mautic-form', formId],
2183
+ queryFn: () => {
2184
+ const client = getMauticClient();
2185
+ return client.getForm(formId);
2186
+ },
2187
+ enabled: !!formId,
2188
+ staleTime: 30 * 60 * 1000,
2189
+ gcTime: 60 * 60 * 1000,
2190
+ });
2191
+ };
2192
+ /**
2193
+ * Custom hook for tracking Mautic events
2194
+ */
2195
+ const useMauticEventTracking = () => {
2196
+ return useMutation({
2197
+ mutationFn: ({ email, eventName, eventData }) => {
2198
+ const client = getMauticClient();
2199
+ return client.trackEvent(eventName, {
2200
+ email,
2201
+ ...(eventData || {})
2202
+ });
2203
+ },
2204
+ onSuccess: (data, variables) => {
2205
+ if (process.env.NODE_ENV === 'development') {
2206
+ console.log('Mautic event tracked successfully:', variables.eventName);
2207
+ }
2208
+ },
2209
+ onError: (error, variables) => {
2210
+ console.error('Failed to track Mautic event:', variables.eventName, error);
2211
+ },
2212
+ });
2213
+ };
2214
+ /**
2215
+ * Custom hook for adding tags to contacts
2216
+ */
2217
+ const useMauticAddTags = () => {
2218
+ const queryClient = useQueryClient();
2219
+ return useMutation({
2220
+ mutationFn: ({ contactId, tags }) => {
2221
+ const client = getMauticClient();
2222
+ return client.addTagToContact(contactId, tags);
2223
+ },
2224
+ onSuccess: (data, variables) => {
2225
+ if (process.env.NODE_ENV === 'development') {
2226
+ console.log('Tags added to Mautic contact:', variables.tags);
2227
+ }
2228
+ // Invalidate contact queries
2229
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2230
+ },
2231
+ onError: (error) => {
2232
+ console.error('Failed to add tags to Mautic contact:', error);
2233
+ },
2234
+ });
2235
+ };
2236
+ /**
2237
+ * Custom hook for removing tags from contacts
2238
+ */
2239
+ const useMauticRemoveTags = () => {
2240
+ const queryClient = useQueryClient();
2241
+ return useMutation({
2242
+ mutationFn: ({ contactId, tags }) => {
2243
+ const client = getMauticClient();
2244
+ return client.removeTagFromContact(contactId, tags);
2245
+ },
2246
+ onSuccess: (data, variables) => {
2247
+ if (process.env.NODE_ENV === 'development') {
2248
+ console.log('Tags removed from Mautic contact:', variables.tags);
2249
+ }
2250
+ // Invalidate contact queries
2251
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2252
+ },
2253
+ onError: (error) => {
2254
+ console.error('Failed to remove tags from Mautic contact:', error);
2255
+ },
2256
+ });
2257
+ };
2258
+ /**
2259
+ * Custom hook for getting contact tags
2260
+ */
2261
+ const useMauticContactTags = (contactId) => {
2262
+ return useQuery({
2263
+ queryKey: ['mautic-contact-tags', contactId],
2264
+ queryFn: () => {
2265
+ const client = getMauticClient();
2266
+ return client.getContactTags(contactId);
2267
+ },
2268
+ enabled: !!contactId,
2269
+ staleTime: 5 * 60 * 1000,
2270
+ gcTime: 10 * 60 * 1000,
2271
+ });
2272
+ };
2273
+ /**
2274
+ * Custom hook for getting all tags
2275
+ */
2276
+ const useMauticTags = () => {
2277
+ return useQuery({
2278
+ queryKey: ['mautic-tags'],
2279
+ queryFn: () => {
2280
+ const client = getMauticClient();
2281
+ return client.getTags();
2282
+ },
2283
+ staleTime: 30 * 60 * 1000,
2284
+ gcTime: 60 * 60 * 1000,
2285
+ });
2286
+ };
2287
+ /**
2288
+ * Custom hook for getting segments
2289
+ */
2290
+ const useMauticSegments = () => {
2291
+ return useQuery({
2292
+ queryKey: ['mautic-segments'],
2293
+ queryFn: () => {
2294
+ const client = getMauticClient();
2295
+ return client.getSegments();
2296
+ },
2297
+ staleTime: 30 * 60 * 1000,
2298
+ gcTime: 60 * 60 * 1000,
2299
+ });
2300
+ };
2301
+ /**
2302
+ * Custom hook for adding contact to segment
2303
+ */
2304
+ const useMauticAddToSegment = () => {
2305
+ const queryClient = useQueryClient();
2306
+ return useMutation({
2307
+ mutationFn: ({ contactId, segmentId }) => {
2308
+ const client = getMauticClient();
2309
+ return client.addContactToSegment(contactId, segmentId);
2310
+ },
2311
+ onSuccess: (data, variables) => {
2312
+ if (process.env.NODE_ENV === 'development') {
2313
+ console.log('Contact added to segment:', variables.segmentId);
2314
+ }
2315
+ // Invalidate contact queries
2316
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2317
+ },
2318
+ onError: (error) => {
2319
+ console.error('Failed to add contact to segment:', error);
2320
+ },
2321
+ });
2322
+ };
2323
+ /**
2324
+ * Custom hook for removing contact from segment
2325
+ */
2326
+ const useMauticRemoveFromSegment = () => {
2327
+ const queryClient = useQueryClient();
2328
+ return useMutation({
2329
+ mutationFn: ({ contactId, segmentId }) => {
2330
+ const client = getMauticClient();
2331
+ return client.removeContactFromSegment(contactId, segmentId);
2332
+ },
2333
+ onSuccess: (data, variables) => {
2334
+ if (process.env.NODE_ENV === 'development') {
2335
+ console.log('Contact removed from segment:', variables.segmentId);
2336
+ }
2337
+ // Invalidate contact queries
2338
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2339
+ },
2340
+ onError: (error) => {
2341
+ console.error('Failed to remove contact from segment:', error);
2342
+ },
2343
+ });
2344
+ };
2345
+
2403
2346
  const MauticForm = ({ formId, title, description, className = '', form, onSubmit, onSuccess, onError, }) => {
2404
2347
  const [formData, setFormData] = useState({});
2405
2348
  const [errors, setErrors] = useState({});
@@ -2572,21 +2515,19 @@ const MauticForm = ({ formId, title, description, className = '', form, onSubmit
2572
2515
  .map((field) => (jsxRuntimeExports.jsxs("div", { className: `form-field form-field-${field.type}`, children: [field.showLabel !== false && (jsxRuntimeExports.jsxs("label", { htmlFor: field.alias, className: "field-label", children: [field.label, field.isRequired && jsxRuntimeExports.jsx("span", { className: "required", children: "*" })] })), renderField(field), field.properties?.helpText && (jsxRuntimeExports.jsx("p", { className: "field-help", children: field.properties.helpText })), errors[field.alias] && (jsxRuntimeExports.jsx("p", { className: "field-error", children: errors[field.alias] }))] }, field.id))) }), jsxRuntimeExports.jsx("div", { className: "form-actions", children: jsxRuntimeExports.jsx("button", { type: "submit", disabled: isSubmitting, className: "submit-button", children: isSubmitting ? 'Submitting...' : 'Submit' }) }), submitMutation.error && (jsxRuntimeExports.jsx("div", { className: "form-error", children: jsxRuntimeExports.jsx("p", { children: "There was an error submitting the form. Please try again." }) }))] }));
2573
2516
  };
2574
2517
 
2575
- const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, children }) => {
2518
+ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, children }) => {
2576
2519
  useEffect(() => {
2577
2520
  // Determine if tracking is enabled
2578
- const isEnabled = enabled ?? (!!proxyUrl && !!appId && !!workerSecret);
2521
+ const isEnabled = enabled ?? !!proxyUrl;
2579
2522
  const trackingProxyUrl = proxyUrl;
2580
2523
  // Debug logging to understand configuration
2581
- if (import.meta.env?.DEV) {
2524
+ if (process.env.NODE_ENV === 'development') {
2582
2525
  console.log('🔍 Mautic Tracking Debug:', {
2583
2526
  isEnabled,
2584
2527
  mauticUrl,
2585
2528
  trackingProxyUrl,
2586
- hasAppId: !!appId,
2587
- hasWorkerSecret: !!workerSecret,
2588
- isDev: import.meta.env?.DEV,
2589
- isProd: import.meta.env?.PROD
2529
+ isDev: true,
2530
+ isProd: false
2590
2531
  });
2591
2532
  }
2592
2533
  // Check if tracking is enabled
@@ -2595,7 +2536,7 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2595
2536
  return;
2596
2537
  }
2597
2538
  // Check if we're in development mode - skip tracking in dev
2598
- if (import.meta.env?.DEV) {
2539
+ if (process.env.NODE_ENV === 'development') {
2599
2540
  console.log('Mautic tracking disabled in development mode');
2600
2541
  return;
2601
2542
  }
@@ -2606,6 +2547,8 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2606
2547
  }
2607
2548
  console.log('✅ Mautic tracking enabled for production');
2608
2549
  // Initialize Mautic tracking with error handling
2550
+ // Capture original fetch for cleanup
2551
+ let originalFetch;
2609
2552
  try {
2610
2553
  // Pre-initialize tracking object and queue (standard Mautic snippet behavior)
2611
2554
  window.MauticTrackingObject = 'mt';
@@ -2613,20 +2556,12 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2613
2556
  (window.mt.q = window.mt.q || []).push(arguments);
2614
2557
  };
2615
2558
  // Intercept Mautic tracking API calls to use our proxy (generic /mtc/ matcher)
2616
- const originalFetch = window.fetch.bind(window);
2559
+ originalFetch = window.fetch.bind(window);
2617
2560
  const interceptFetch = function (url, options) {
2618
2561
  if (typeof url === 'string' && /\/mtc\//.test(url)) {
2619
2562
  try {
2620
2563
  const proxyUrl = url.replace(/https?:\/\/[^/]+\/mtc\//, `${trackingProxyUrl}?endpoint=/mtc/`);
2621
- const proxyOptions = {
2622
- ...options,
2623
- headers: {
2624
- ...options?.headers,
2625
- 'Content-Type': 'application/json',
2626
- 'x-app-id': appId || '',
2627
- ...(workerSecret ? { 'x-app-secret': workerSecret } : {})
2628
- }
2629
- };
2564
+ const proxyOptions = { ...options };
2630
2565
  return originalFetch(proxyUrl, proxyOptions);
2631
2566
  }
2632
2567
  catch (e) {
@@ -2636,16 +2571,9 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2636
2571
  return originalFetch(url, options);
2637
2572
  };
2638
2573
  window.fetch = interceptFetch;
2639
- // Fetch the tracking script through the proxy with required headers
2574
+ // Fetch the tracking script through the proxy
2640
2575
  const trackingScriptUrl = `${trackingProxyUrl}?endpoint=/mtc.js`;
2641
- fetch(trackingScriptUrl, {
2642
- method: 'GET',
2643
- headers: {
2644
- 'Content-Type': 'application/javascript',
2645
- 'x-app-id': appId || '',
2646
- ...(workerSecret ? { 'x-app-secret': workerSecret } : {})
2647
- }
2648
- })
2576
+ fetch(trackingScriptUrl, { method: 'GET' })
2649
2577
  .then(response => {
2650
2578
  if (!response.ok) {
2651
2579
  throw new Error(`Failed to load Mautic tracking script: ${response.status}`);
@@ -2673,12 +2601,11 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2673
2601
  }
2674
2602
  // Cleanup function
2675
2603
  return () => {
2676
- // Restore original fetch if needed
2677
- if (window.fetch !== originalFetch) {
2604
+ if (originalFetch && window.fetch !== originalFetch) {
2678
2605
  window.fetch = originalFetch;
2679
2606
  }
2680
2607
  };
2681
- }, [enabled, mauticUrl, proxyUrl, appId, workerSecret]);
2608
+ }, [enabled, mauticUrl, proxyUrl]);
2682
2609
  // Render children if provided, otherwise render nothing
2683
2610
  return children ? jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: children }) : null;
2684
2611
  };
@@ -3053,30 +2980,33 @@ const validateField = (field, value) => {
3053
2980
  const validateCustomRule = (rule, value, field) => {
3054
2981
  switch (rule) {
3055
2982
  case 'min_length':
3056
- if (typeof value === 'string' && value.length < (field.properties?.minLength || 0)) {
3057
- return `Minimum length is ${field.properties?.minLength} characters`;
2983
+ if (typeof value === 'string' && value.length < Number(field.properties?.minLength ?? 0)) {
2984
+ return `Minimum length is ${Number(field.properties?.minLength ?? 0)} characters`;
3058
2985
  }
3059
2986
  break;
3060
2987
  case 'max_length':
3061
- if (typeof value === 'string' && value.length > (field.properties?.maxLength || 1000)) {
3062
- return `Maximum length is ${field.properties?.maxLength} characters`;
2988
+ if (typeof value === 'string' && value.length > Number(field.properties?.maxLength ?? 1000)) {
2989
+ return `Maximum length is ${Number(field.properties?.maxLength ?? 1000)} characters`;
3063
2990
  }
3064
2991
  break;
3065
2992
  case 'min_value':
3066
- if (typeof value === 'number' && value < (field.properties?.minValue || 0)) {
3067
- return `Minimum value is ${field.properties?.minValue}`;
2993
+ if (typeof value === 'number' && value < Number(field.properties?.minValue ?? 0)) {
2994
+ return `Minimum value is ${Number(field.properties?.minValue ?? 0)}`;
3068
2995
  }
3069
2996
  break;
3070
2997
  case 'max_value':
3071
- if (typeof value === 'number' && value > (field.properties?.maxValue || 1000000)) {
3072
- return `Maximum value is ${field.properties?.maxValue}`;
2998
+ if (typeof value === 'number' && value > Number(field.properties?.maxValue ?? 1000000)) {
2999
+ return `Maximum value is ${Number(field.properties?.maxValue ?? 1000000)}`;
3073
3000
  }
3074
3001
  break;
3075
3002
  case 'pattern':
3076
- if (field.properties?.pattern) {
3077
- const regex = new RegExp(field.properties.pattern);
3078
- if (!regex.test(value)) {
3079
- return 'Invalid format';
3003
+ {
3004
+ const pattern = field.properties?.pattern;
3005
+ if (pattern) {
3006
+ const regex = new RegExp(String(pattern));
3007
+ if (!regex.test(value)) {
3008
+ return 'Invalid format';
3009
+ }
3080
3010
  }
3081
3011
  }
3082
3012
  break;