@marvalt/madapter 1.0.14 → 1.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
@@ -21,18 +21,17 @@ import React, { useState, useEffect, createContext, useMemo, useContext } from '
21
21
  */
22
22
  class MauticClient {
23
23
  constructor(config) {
24
+ this.baseUrl = '';
24
25
  this.config = config;
25
- if (config.authMode === 'cloudflare_proxy' && config.cloudflareWorkerUrl && config.appId && config.workerSecret) {
26
+ if (config.authMode === 'cloudflare_proxy' && config.cloudflareWorkerUrl) {
26
27
  // Cloudflare Worker proxy (recommended - uses Secrets Store)
27
28
  this.proxyUrl = config.cloudflareWorkerUrl;
28
- this.appId = config.appId;
29
- this.workerSecret = config.workerSecret;
30
29
  this.useProxy = true;
31
30
  this.isConfigured = true;
32
31
  }
33
32
  else if (config.authMode === 'direct' && config.apiUrl) {
34
33
  // Direct Mautic API access (legacy - for local development only)
35
- this.baseUrl = config.apiUrl;
34
+ this.baseUrl = config.apiUrl || '';
36
35
  this.useProxy = false;
37
36
  this.isConfigured = true;
38
37
  }
@@ -47,17 +46,7 @@ class MauticClient {
47
46
  throw new Error('Mautic service not properly configured. Check your configuration.');
48
47
  }
49
48
  }
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
- }
60
- }
49
+ getAuthHeaders() { return {}; }
61
50
  async makeRequest(endpoint, options = {}) {
62
51
  this.validateConfiguration();
63
52
  let url;
@@ -83,7 +72,7 @@ class MauticClient {
83
72
  const isFormSubmission = endpoint.startsWith('/form/submit') ||
84
73
  (endpoint.startsWith('/forms/') && endpoint.includes('/submit'));
85
74
  const headers = {
86
- ...this.getAuthHeaders(),
75
+ // no app headers in proxy mode
87
76
  ...options.headers,
88
77
  };
89
78
  // Default JSON only for non-form submissions when a body exists and no explicit content-type provided
@@ -316,8 +305,6 @@ const createMauticConfig = (overrides = {}) => {
316
305
  authMode: getEnvVar('VITE_AUTH_MODE') || getEnvVar('VITE_MAUTIC_AUTH_MODE') || 'cloudflare_proxy',
317
306
  apiUrl: getEnvVar('VITE_MAUTIC_URL'),
318
307
  cloudflareWorkerUrl: getEnvVar('VITE_MAUTIC_PROXY_URL'),
319
- appId: getEnvVar('VITE_FRONTEND_ID'),
320
- workerSecret: getEnvVar('VITE_FRONTEND_SECRET'),
321
308
  timeout: parseInt(getEnvVar('VITE_MAUTIC_TIMEOUT') || '30000'),
322
309
  retries: parseInt(getEnvVar('VITE_MAUTIC_RETRIES') || '3'),
323
310
  ...overrides,
@@ -327,10 +314,6 @@ const createMauticConfig = (overrides = {}) => {
327
314
  if (merged.authMode === 'cloudflare_proxy') {
328
315
  if (!merged.cloudflareWorkerUrl)
329
316
  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
317
  }
335
318
  if (merged.authMode === 'direct') {
336
319
  if (!merged.apiUrl)
@@ -353,12 +336,6 @@ const validateMauticConfig = (config) => {
353
336
  if (!config.cloudflareWorkerUrl) {
354
337
  errors.push('cloudflareWorkerUrl is required for cloudflare_proxy mode');
355
338
  }
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
339
  }
363
340
  else if (config.authMode === 'direct') {
364
341
  if (!config.apiUrl) {
@@ -400,7 +377,7 @@ const mergeMauticConfig = (base, overrides) => {
400
377
  */
401
378
  const isMauticEnabled = (config) => {
402
379
  if (config.authMode === 'cloudflare_proxy') {
403
- return !!(config.cloudflareWorkerUrl && config.appId && config.workerSecret);
380
+ return !!config.cloudflareWorkerUrl;
404
381
  }
405
382
  else if (config.authMode === 'direct') {
406
383
  return !!config.apiUrl;
@@ -415,323 +392,12 @@ const getMauticConfigSummary = (config) => {
415
392
  authMode: config.authMode,
416
393
  hasApiUrl: !!config.apiUrl,
417
394
  hasCloudflareWorkerUrl: !!config.cloudflareWorkerUrl,
418
- hasAppId: !!config.appId,
419
- hasWorkerSecret: !!config.workerSecret,
420
395
  timeout: config.timeout,
421
396
  retries: config.retries,
422
397
  isEnabled: isMauticEnabled(config),
423
398
  };
424
399
  };
425
400
 
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
401
  /**
736
402
  * @license GPL-3.0-or-later
737
403
  *
@@ -833,9 +499,19 @@ class MauticService {
833
499
  return this.client.trackEvent(eventName, eventData);
834
500
  }
835
501
  }
836
- // Export configured instance
837
- const mauticService = new MauticService();
838
- setMauticClient(mauticService['client']);
502
+ // Export configured instance (guarded to avoid throwing at import time)
503
+ let mauticService = null;
504
+ try {
505
+ mauticService = new MauticService();
506
+ // Initialize the default client for hooks
507
+ // Lazy import to avoid circular/side-effect issues in some environments
508
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
509
+ const { setMauticClient } = require('../react/hooks/useMautic');
510
+ setMauticClient(mauticService['client']);
511
+ }
512
+ catch {
513
+ // Silently skip initialization when environment is not configured
514
+ }
839
515
 
840
516
  /**
841
517
  * @license GPL-3.0-or-later
@@ -855,17 +531,57 @@ setMauticClient(mauticService['client']);
855
531
  */
856
532
  class MauticGenerator {
857
533
  constructor(config) {
534
+ this.cachedToken = null;
858
535
  this.config = config;
859
536
  this.client = new MauticClient({
860
537
  authMode: config.authMode,
861
538
  apiUrl: config.apiUrl,
862
539
  cloudflareWorkerUrl: config.cloudflareWorkerUrl,
863
- appId: config.appId,
864
- workerSecret: config.workerSecret,
540
+ clientId: config.clientId,
541
+ clientSecret: config.clientSecret,
542
+ cfAccessClientId: config.cfAccessClientId,
543
+ cfAccessClientSecret: config.cfAccessClientSecret,
865
544
  timeout: config.timeout,
866
545
  retries: config.retries,
867
546
  });
868
547
  }
548
+ /**
549
+ * Get OAuth2 token for direct mode API calls
550
+ */
551
+ async getOAuth2Token() {
552
+ if (this.cachedToken && this.cachedToken.expires_at > Date.now() + 300000) {
553
+ console.log('🔑 Using cached OAuth2 token');
554
+ return this.cachedToken.access_token;
555
+ }
556
+ if (!this.config.clientId || !this.config.clientSecret) {
557
+ throw new Error('OAuth2 credentials (clientId, clientSecret) required for direct mode');
558
+ }
559
+ console.log('🔑 Fetching new OAuth2 token...');
560
+ const tokenUrl = `${this.config.apiUrl}/oauth/v2/token`;
561
+ const body = new URLSearchParams({
562
+ grant_type: 'client_credentials',
563
+ client_id: this.config.clientId,
564
+ client_secret: this.config.clientSecret,
565
+ });
566
+ const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
567
+ if (this.config.cfAccessClientId && this.config.cfAccessClientSecret) {
568
+ headers['CF-Access-Client-Id'] = this.config.cfAccessClientId;
569
+ headers['CF-Access-Client-Secret'] = this.config.cfAccessClientSecret;
570
+ console.log('🔐 Added CF Access headers to OAuth2 token request');
571
+ }
572
+ const resp = await fetch(tokenUrl, { method: 'POST', headers, body: body.toString() });
573
+ if (!resp.ok) {
574
+ const errText = await resp.text();
575
+ throw new Error(`OAuth2 token failed: ${resp.status} ${errText}`);
576
+ }
577
+ const data = await resp.json();
578
+ this.cachedToken = {
579
+ access_token: data.access_token,
580
+ expires_at: Date.now() + (data.expires_in * 1000),
581
+ };
582
+ console.log('✅ OAuth2 token cached');
583
+ return this.cachedToken.access_token;
584
+ }
869
585
  /**
870
586
  * Generate static data for Mautic forms
871
587
  */
@@ -899,7 +615,7 @@ class MauticGenerator {
899
615
  // Include other important form properties
900
616
  formType: formDetails.formType || 'standalone',
901
617
  // Extract fields with validation and properties
902
- fields: (formDetails.fields || []).map(field => ({
618
+ fields: (formDetails.fields || []).map((field) => ({
903
619
  id: field.id,
904
620
  label: field.label,
905
621
  alias: field.alias,
@@ -918,7 +634,7 @@ class MauticGenerator {
918
634
  }
919
635
  })),
920
636
  // Extract actions
921
- actions: (formDetails.actions || []).map(action => ({
637
+ actions: (formDetails.actions || []).map((action) => ({
922
638
  id: action.id,
923
639
  name: action.name,
924
640
  type: action.type,
@@ -942,10 +658,10 @@ class MauticGenerator {
942
658
  const staticData = {
943
659
  generated_at: new Date().toISOString(),
944
660
  total_forms: forms.length,
945
- forms: forms.map(form => ({
661
+ forms: forms.map((form) => ({
946
662
  id: parseInt(form.id),
947
663
  name: form.name,
948
- alias: form.alias || form.name.toLowerCase().replace(/\s+/g, '-'),
664
+ alias: String(form.alias ?? (form.name || '').toLowerCase().replace(/\s+/g, '-')),
949
665
  description: form.description,
950
666
  isPublished: form.isPublished,
951
667
  fields: form.fields,
@@ -992,49 +708,44 @@ class MauticGenerator {
992
708
  * Fetch list of forms from Mautic
993
709
  */
994
710
  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;
711
+ if (this.config.authMode === 'cloudflare_proxy') {
712
+ const forms = await this.client.getForms();
713
+ return Array.isArray(forms) ? forms : [];
1004
714
  }
1005
- if (this.config.workerSecret) {
1006
- headers['x-app-secret'] = this.config.workerSecret;
715
+ // Direct mode with OAuth2
716
+ const token = await this.getOAuth2Token();
717
+ const url = `${this.config.apiUrl}/api/forms`;
718
+ const headers = { 'Authorization': `Bearer ${token}` };
719
+ if (this.config.cfAccessClientId && this.config.cfAccessClientSecret) {
720
+ headers['CF-Access-Client-Id'] = this.config.cfAccessClientId;
721
+ headers['CF-Access-Client-Secret'] = this.config.cfAccessClientSecret;
1007
722
  }
1008
- const response = await fetch(url, { headers });
1009
- if (!response.ok) {
1010
- throw new Error(`Failed to fetch forms list: ${response.status} ${response.statusText}`);
1011
- }
1012
- const data = await response.json().catch(() => ({}));
1013
- const forms = data.forms;
1014
- return Array.isArray(forms) ? forms : [];
723
+ const resp = await fetch(url, { headers });
724
+ if (!resp.ok)
725
+ throw new Error(`Failed to fetch forms: ${resp.status}`);
726
+ const data = await resp.json();
727
+ return Array.isArray(data.forms) ? data.forms : [];
1015
728
  }
1016
729
  /**
1017
730
  * Fetch individual form details from Mautic
1018
731
  */
1019
732
  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;
733
+ if (this.config.authMode === 'cloudflare_proxy') {
734
+ return this.client.getForm(parseInt(formId, 10));
1032
735
  }
1033
- const response = await fetch(url, { headers });
1034
- if (!response.ok) {
1035
- throw new Error(`Failed to fetch form ${formId}: ${response.status} ${response.statusText}`);
736
+ // Direct mode with OAuth2
737
+ const token = await this.getOAuth2Token();
738
+ const url = `${this.config.apiUrl}/api/forms/${formId}`;
739
+ const headers = { 'Authorization': `Bearer ${token}` };
740
+ if (this.config.cfAccessClientId && this.config.cfAccessClientSecret) {
741
+ headers['CF-Access-Client-Id'] = this.config.cfAccessClientId;
742
+ headers['CF-Access-Client-Secret'] = this.config.cfAccessClientSecret;
1036
743
  }
1037
- return response.json().catch(() => ({}));
744
+ const resp = await fetch(url, { headers });
745
+ if (!resp.ok)
746
+ throw new Error(`Failed to fetch form ${formId}: ${resp.status}`);
747
+ const data = await resp.json();
748
+ return data.form || data;
1038
749
  }
1039
750
  }
1040
751
  /**
@@ -2400,6 +2111,315 @@ if (process.env.NODE_ENV === 'production') {
2400
2111
 
2401
2112
  var jsxRuntimeExports = jsxRuntime.exports;
2402
2113
 
2114
+ /**
2115
+ * @license GPL-3.0-or-later
2116
+ *
2117
+ * This file is part of the MarVAlt Open SDK.
2118
+ * Copyright (c) 2025 Vibune Pty Ltd.
2119
+ *
2120
+ * This program is free software: you can redistribute it and/or modify
2121
+ * it under the terms of the GNU General Public License as published by
2122
+ * the Free Software Foundation, either version 3 of the License, or
2123
+ * (at your option) any later version.
2124
+ *
2125
+ * This program is distributed in the hope that it will be useful,
2126
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2127
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2128
+ * See the GNU General Public License for more details.
2129
+ */
2130
+ // Create a default client instance (will be overridden by provider)
2131
+ let defaultClient = null;
2132
+ /**
2133
+ * Set the default Mautic client for hooks
2134
+ */
2135
+ const setMauticClient = (client) => {
2136
+ defaultClient = client;
2137
+ };
2138
+ /**
2139
+ * Get the current Mautic client
2140
+ */
2141
+ const getMauticClient = () => {
2142
+ if (!defaultClient) {
2143
+ // Lazy initialize the client from environment if not set by provider/service
2144
+ const config = createMauticConfig();
2145
+ defaultClient = new MauticClient(config);
2146
+ }
2147
+ return defaultClient;
2148
+ };
2149
+ /**
2150
+ * Custom hook for submitting forms to Mautic
2151
+ */
2152
+ const useMauticFormSubmission = () => {
2153
+ const queryClient = useQueryClient();
2154
+ return useMutation({
2155
+ mutationFn: (submission) => {
2156
+ const client = getMauticClient();
2157
+ return client.submitForm(submission.formId, submission);
2158
+ },
2159
+ onSuccess: (data, variables) => {
2160
+ if (process.env.NODE_ENV === 'development') {
2161
+ console.log('Mautic form submission successful:', data);
2162
+ }
2163
+ // Invalidate related queries
2164
+ queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
2165
+ // Track the submission event
2166
+ if (variables.contact?.email) {
2167
+ const client = getMauticClient();
2168
+ client.trackEvent('form_submission', {
2169
+ formId: variables.formId,
2170
+ email: variables.contact.email
2171
+ }).catch(console.error);
2172
+ }
2173
+ },
2174
+ onError: (error) => {
2175
+ console.error('Mautic form submission failed:', error);
2176
+ },
2177
+ });
2178
+ };
2179
+ /**
2180
+ * Custom hook for creating Mautic contacts
2181
+ */
2182
+ const useMauticCreateContact = () => {
2183
+ const queryClient = useQueryClient();
2184
+ return useMutation({
2185
+ mutationFn: (contact) => {
2186
+ const client = getMauticClient();
2187
+ return client.createContact(contact);
2188
+ },
2189
+ onSuccess: (data, variables) => {
2190
+ if (process.env.NODE_ENV === 'development') {
2191
+ console.log('Mautic contact created:', data);
2192
+ }
2193
+ // Invalidate contacts queries
2194
+ queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
2195
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.email] });
2196
+ },
2197
+ onError: (error) => {
2198
+ console.error('Failed to create Mautic contact:', error);
2199
+ },
2200
+ });
2201
+ };
2202
+ /**
2203
+ * Custom hook for updating Mautic contacts
2204
+ */
2205
+ const useMauticUpdateContact = () => {
2206
+ const queryClient = useQueryClient();
2207
+ return useMutation({
2208
+ mutationFn: ({ contactId, contact }) => {
2209
+ const client = getMauticClient();
2210
+ return client.updateContact(contactId, contact);
2211
+ },
2212
+ onSuccess: (data, variables) => {
2213
+ if (process.env.NODE_ENV === 'development') {
2214
+ console.log('Mautic contact updated:', data);
2215
+ }
2216
+ // Invalidate related queries
2217
+ queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
2218
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2219
+ },
2220
+ onError: (error) => {
2221
+ console.error('Failed to update Mautic contact:', error);
2222
+ },
2223
+ });
2224
+ };
2225
+ /**
2226
+ * Custom hook for fetching Mautic contact by email
2227
+ */
2228
+ const useMauticContactByEmail = (email) => {
2229
+ return useQuery({
2230
+ queryKey: ['mautic-contact', email],
2231
+ queryFn: () => {
2232
+ const client = getMauticClient();
2233
+ return client.getContactByEmail(email);
2234
+ },
2235
+ enabled: !!email && email.includes('@'),
2236
+ staleTime: 5 * 60 * 1000, // 5 minutes
2237
+ gcTime: 10 * 60 * 1000,
2238
+ });
2239
+ };
2240
+ /**
2241
+ * Custom hook for fetching Mautic forms
2242
+ */
2243
+ const useMauticForms = () => {
2244
+ return useQuery({
2245
+ queryKey: ['mautic-forms'],
2246
+ queryFn: () => {
2247
+ const client = getMauticClient();
2248
+ return client.getForms();
2249
+ },
2250
+ staleTime: 30 * 60 * 1000, // Forms don't change often
2251
+ gcTime: 60 * 60 * 1000,
2252
+ });
2253
+ };
2254
+ /**
2255
+ * Custom hook for fetching a specific Mautic form
2256
+ */
2257
+ const useMauticForm = (formId) => {
2258
+ return useQuery({
2259
+ queryKey: ['mautic-form', formId],
2260
+ queryFn: () => {
2261
+ const client = getMauticClient();
2262
+ return client.getForm(formId);
2263
+ },
2264
+ enabled: !!formId,
2265
+ staleTime: 30 * 60 * 1000,
2266
+ gcTime: 60 * 60 * 1000,
2267
+ });
2268
+ };
2269
+ /**
2270
+ * Custom hook for tracking Mautic events
2271
+ */
2272
+ const useMauticEventTracking = () => {
2273
+ return useMutation({
2274
+ mutationFn: ({ email, eventName, eventData }) => {
2275
+ const client = getMauticClient();
2276
+ return client.trackEvent(eventName, {
2277
+ email,
2278
+ ...(eventData || {})
2279
+ });
2280
+ },
2281
+ onSuccess: (data, variables) => {
2282
+ if (process.env.NODE_ENV === 'development') {
2283
+ console.log('Mautic event tracked successfully:', variables.eventName);
2284
+ }
2285
+ },
2286
+ onError: (error, variables) => {
2287
+ console.error('Failed to track Mautic event:', variables.eventName, error);
2288
+ },
2289
+ });
2290
+ };
2291
+ /**
2292
+ * Custom hook for adding tags to contacts
2293
+ */
2294
+ const useMauticAddTags = () => {
2295
+ const queryClient = useQueryClient();
2296
+ return useMutation({
2297
+ mutationFn: ({ contactId, tags }) => {
2298
+ const client = getMauticClient();
2299
+ return client.addTagToContact(contactId, tags);
2300
+ },
2301
+ onSuccess: (data, variables) => {
2302
+ if (process.env.NODE_ENV === 'development') {
2303
+ console.log('Tags added to Mautic contact:', variables.tags);
2304
+ }
2305
+ // Invalidate contact queries
2306
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2307
+ },
2308
+ onError: (error) => {
2309
+ console.error('Failed to add tags to Mautic contact:', error);
2310
+ },
2311
+ });
2312
+ };
2313
+ /**
2314
+ * Custom hook for removing tags from contacts
2315
+ */
2316
+ const useMauticRemoveTags = () => {
2317
+ const queryClient = useQueryClient();
2318
+ return useMutation({
2319
+ mutationFn: ({ contactId, tags }) => {
2320
+ const client = getMauticClient();
2321
+ return client.removeTagFromContact(contactId, tags);
2322
+ },
2323
+ onSuccess: (data, variables) => {
2324
+ if (process.env.NODE_ENV === 'development') {
2325
+ console.log('Tags removed from Mautic contact:', variables.tags);
2326
+ }
2327
+ // Invalidate contact queries
2328
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2329
+ },
2330
+ onError: (error) => {
2331
+ console.error('Failed to remove tags from Mautic contact:', error);
2332
+ },
2333
+ });
2334
+ };
2335
+ /**
2336
+ * Custom hook for getting contact tags
2337
+ */
2338
+ const useMauticContactTags = (contactId) => {
2339
+ return useQuery({
2340
+ queryKey: ['mautic-contact-tags', contactId],
2341
+ queryFn: () => {
2342
+ const client = getMauticClient();
2343
+ return client.getContactTags(contactId);
2344
+ },
2345
+ enabled: !!contactId,
2346
+ staleTime: 5 * 60 * 1000,
2347
+ gcTime: 10 * 60 * 1000,
2348
+ });
2349
+ };
2350
+ /**
2351
+ * Custom hook for getting all tags
2352
+ */
2353
+ const useMauticTags = () => {
2354
+ return useQuery({
2355
+ queryKey: ['mautic-tags'],
2356
+ queryFn: () => {
2357
+ const client = getMauticClient();
2358
+ return client.getTags();
2359
+ },
2360
+ staleTime: 30 * 60 * 1000,
2361
+ gcTime: 60 * 60 * 1000,
2362
+ });
2363
+ };
2364
+ /**
2365
+ * Custom hook for getting segments
2366
+ */
2367
+ const useMauticSegments = () => {
2368
+ return useQuery({
2369
+ queryKey: ['mautic-segments'],
2370
+ queryFn: () => {
2371
+ const client = getMauticClient();
2372
+ return client.getSegments();
2373
+ },
2374
+ staleTime: 30 * 60 * 1000,
2375
+ gcTime: 60 * 60 * 1000,
2376
+ });
2377
+ };
2378
+ /**
2379
+ * Custom hook for adding contact to segment
2380
+ */
2381
+ const useMauticAddToSegment = () => {
2382
+ const queryClient = useQueryClient();
2383
+ return useMutation({
2384
+ mutationFn: ({ contactId, segmentId }) => {
2385
+ const client = getMauticClient();
2386
+ return client.addContactToSegment(contactId, segmentId);
2387
+ },
2388
+ onSuccess: (data, variables) => {
2389
+ if (process.env.NODE_ENV === 'development') {
2390
+ console.log('Contact added to segment:', variables.segmentId);
2391
+ }
2392
+ // Invalidate contact queries
2393
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2394
+ },
2395
+ onError: (error) => {
2396
+ console.error('Failed to add contact to segment:', error);
2397
+ },
2398
+ });
2399
+ };
2400
+ /**
2401
+ * Custom hook for removing contact from segment
2402
+ */
2403
+ const useMauticRemoveFromSegment = () => {
2404
+ const queryClient = useQueryClient();
2405
+ return useMutation({
2406
+ mutationFn: ({ contactId, segmentId }) => {
2407
+ const client = getMauticClient();
2408
+ return client.removeContactFromSegment(contactId, segmentId);
2409
+ },
2410
+ onSuccess: (data, variables) => {
2411
+ if (process.env.NODE_ENV === 'development') {
2412
+ console.log('Contact removed from segment:', variables.segmentId);
2413
+ }
2414
+ // Invalidate contact queries
2415
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2416
+ },
2417
+ onError: (error) => {
2418
+ console.error('Failed to remove contact from segment:', error);
2419
+ },
2420
+ });
2421
+ };
2422
+
2403
2423
  const MauticForm = ({ formId, title, description, className = '', form, onSubmit, onSuccess, onError, }) => {
2404
2424
  const [formData, setFormData] = useState({});
2405
2425
  const [errors, setErrors] = useState({});
@@ -2572,21 +2592,19 @@ const MauticForm = ({ formId, title, description, className = '', form, onSubmit
2572
2592
  .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
2593
  };
2574
2594
 
2575
- const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, children }) => {
2595
+ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, children }) => {
2576
2596
  useEffect(() => {
2577
2597
  // Determine if tracking is enabled
2578
- const isEnabled = enabled ?? (!!proxyUrl && !!appId && !!workerSecret);
2598
+ const isEnabled = enabled ?? !!proxyUrl;
2579
2599
  const trackingProxyUrl = proxyUrl;
2580
2600
  // Debug logging to understand configuration
2581
- if (import.meta.env?.DEV) {
2601
+ if (process.env.NODE_ENV === 'development') {
2582
2602
  console.log('🔍 Mautic Tracking Debug:', {
2583
2603
  isEnabled,
2584
2604
  mauticUrl,
2585
2605
  trackingProxyUrl,
2586
- hasAppId: !!appId,
2587
- hasWorkerSecret: !!workerSecret,
2588
- isDev: import.meta.env?.DEV,
2589
- isProd: import.meta.env?.PROD
2606
+ isDev: true,
2607
+ isProd: false
2590
2608
  });
2591
2609
  }
2592
2610
  // Check if tracking is enabled
@@ -2595,7 +2613,7 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2595
2613
  return;
2596
2614
  }
2597
2615
  // Check if we're in development mode - skip tracking in dev
2598
- if (import.meta.env?.DEV) {
2616
+ if (process.env.NODE_ENV === 'development') {
2599
2617
  console.log('Mautic tracking disabled in development mode');
2600
2618
  return;
2601
2619
  }
@@ -2606,6 +2624,8 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2606
2624
  }
2607
2625
  console.log('✅ Mautic tracking enabled for production');
2608
2626
  // Initialize Mautic tracking with error handling
2627
+ // Capture original fetch for cleanup
2628
+ let originalFetch;
2609
2629
  try {
2610
2630
  // Pre-initialize tracking object and queue (standard Mautic snippet behavior)
2611
2631
  window.MauticTrackingObject = 'mt';
@@ -2613,20 +2633,12 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2613
2633
  (window.mt.q = window.mt.q || []).push(arguments);
2614
2634
  };
2615
2635
  // Intercept Mautic tracking API calls to use our proxy (generic /mtc/ matcher)
2616
- const originalFetch = window.fetch.bind(window);
2636
+ originalFetch = window.fetch.bind(window);
2617
2637
  const interceptFetch = function (url, options) {
2618
2638
  if (typeof url === 'string' && /\/mtc\//.test(url)) {
2619
2639
  try {
2620
2640
  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
- };
2641
+ const proxyOptions = { ...options };
2630
2642
  return originalFetch(proxyUrl, proxyOptions);
2631
2643
  }
2632
2644
  catch (e) {
@@ -2636,16 +2648,9 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2636
2648
  return originalFetch(url, options);
2637
2649
  };
2638
2650
  window.fetch = interceptFetch;
2639
- // Fetch the tracking script through the proxy with required headers
2651
+ // Fetch the tracking script through the proxy
2640
2652
  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
- })
2653
+ fetch(trackingScriptUrl, { method: 'GET' })
2649
2654
  .then(response => {
2650
2655
  if (!response.ok) {
2651
2656
  throw new Error(`Failed to load Mautic tracking script: ${response.status}`);
@@ -2673,12 +2678,11 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2673
2678
  }
2674
2679
  // Cleanup function
2675
2680
  return () => {
2676
- // Restore original fetch if needed
2677
- if (window.fetch !== originalFetch) {
2681
+ if (originalFetch && window.fetch !== originalFetch) {
2678
2682
  window.fetch = originalFetch;
2679
2683
  }
2680
2684
  };
2681
- }, [enabled, mauticUrl, proxyUrl, appId, workerSecret]);
2685
+ }, [enabled, mauticUrl, proxyUrl]);
2682
2686
  // Render children if provided, otherwise render nothing
2683
2687
  return children ? jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: children }) : null;
2684
2688
  };
@@ -3053,30 +3057,33 @@ const validateField = (field, value) => {
3053
3057
  const validateCustomRule = (rule, value, field) => {
3054
3058
  switch (rule) {
3055
3059
  case 'min_length':
3056
- if (typeof value === 'string' && value.length < (field.properties?.minLength || 0)) {
3057
- return `Minimum length is ${field.properties?.minLength} characters`;
3060
+ if (typeof value === 'string' && value.length < Number(field.properties?.minLength ?? 0)) {
3061
+ return `Minimum length is ${Number(field.properties?.minLength ?? 0)} characters`;
3058
3062
  }
3059
3063
  break;
3060
3064
  case 'max_length':
3061
- if (typeof value === 'string' && value.length > (field.properties?.maxLength || 1000)) {
3062
- return `Maximum length is ${field.properties?.maxLength} characters`;
3065
+ if (typeof value === 'string' && value.length > Number(field.properties?.maxLength ?? 1000)) {
3066
+ return `Maximum length is ${Number(field.properties?.maxLength ?? 1000)} characters`;
3063
3067
  }
3064
3068
  break;
3065
3069
  case 'min_value':
3066
- if (typeof value === 'number' && value < (field.properties?.minValue || 0)) {
3067
- return `Minimum value is ${field.properties?.minValue}`;
3070
+ if (typeof value === 'number' && value < Number(field.properties?.minValue ?? 0)) {
3071
+ return `Minimum value is ${Number(field.properties?.minValue ?? 0)}`;
3068
3072
  }
3069
3073
  break;
3070
3074
  case 'max_value':
3071
- if (typeof value === 'number' && value > (field.properties?.maxValue || 1000000)) {
3072
- return `Maximum value is ${field.properties?.maxValue}`;
3075
+ if (typeof value === 'number' && value > Number(field.properties?.maxValue ?? 1000000)) {
3076
+ return `Maximum value is ${Number(field.properties?.maxValue ?? 1000000)}`;
3073
3077
  }
3074
3078
  break;
3075
3079
  case 'pattern':
3076
- if (field.properties?.pattern) {
3077
- const regex = new RegExp(field.properties.pattern);
3078
- if (!regex.test(value)) {
3079
- return 'Invalid format';
3080
+ {
3081
+ const pattern = field.properties?.pattern;
3082
+ if (pattern) {
3083
+ const regex = new RegExp(String(pattern));
3084
+ if (!regex.test(value)) {
3085
+ return 'Invalid format';
3086
+ }
3080
3087
  }
3081
3088
  }
3082
3089
  break;