@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.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var reactQuery = require('@tanstack/react-query');
4
3
  var fs = require('fs');
5
4
  var path = require('path');
6
5
  var React = require('react');
6
+ var reactQuery = require('@tanstack/react-query');
7
7
 
8
8
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
9
9
  /**
@@ -24,18 +24,17 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
24
24
  */
25
25
  class MauticClient {
26
26
  constructor(config) {
27
+ this.baseUrl = '';
27
28
  this.config = config;
28
- if (config.authMode === 'cloudflare_proxy' && config.cloudflareWorkerUrl && config.appId && config.workerSecret) {
29
+ if (config.authMode === 'cloudflare_proxy' && config.cloudflareWorkerUrl) {
29
30
  // Cloudflare Worker proxy (recommended - uses Secrets Store)
30
31
  this.proxyUrl = config.cloudflareWorkerUrl;
31
- this.appId = config.appId;
32
- this.workerSecret = config.workerSecret;
33
32
  this.useProxy = true;
34
33
  this.isConfigured = true;
35
34
  }
36
35
  else if (config.authMode === 'direct' && config.apiUrl) {
37
36
  // Direct Mautic API access (legacy - for local development only)
38
- this.baseUrl = config.apiUrl;
37
+ this.baseUrl = config.apiUrl || '';
39
38
  this.useProxy = false;
40
39
  this.isConfigured = true;
41
40
  }
@@ -50,17 +49,7 @@ class MauticClient {
50
49
  throw new Error('Mautic service not properly configured. Check your configuration.');
51
50
  }
52
51
  }
53
- getAuthHeaders() {
54
- if (this.useProxy) {
55
- return {
56
- 'x-app-id': this.appId || '',
57
- 'x-app-secret': this.workerSecret || '',
58
- };
59
- }
60
- else {
61
- return {};
62
- }
63
- }
52
+ getAuthHeaders() { return {}; }
64
53
  async makeRequest(endpoint, options = {}) {
65
54
  this.validateConfiguration();
66
55
  let url;
@@ -86,7 +75,7 @@ class MauticClient {
86
75
  const isFormSubmission = endpoint.startsWith('/form/submit') ||
87
76
  (endpoint.startsWith('/forms/') && endpoint.includes('/submit'));
88
77
  const headers = {
89
- ...this.getAuthHeaders(),
78
+ // no app headers in proxy mode
90
79
  ...options.headers,
91
80
  };
92
81
  // Default JSON only for non-form submissions when a body exists and no explicit content-type provided
@@ -319,8 +308,6 @@ const createMauticConfig = (overrides = {}) => {
319
308
  authMode: getEnvVar('VITE_AUTH_MODE') || getEnvVar('VITE_MAUTIC_AUTH_MODE') || 'cloudflare_proxy',
320
309
  apiUrl: getEnvVar('VITE_MAUTIC_URL'),
321
310
  cloudflareWorkerUrl: getEnvVar('VITE_MAUTIC_PROXY_URL'),
322
- appId: getEnvVar('VITE_FRONTEND_ID'),
323
- workerSecret: getEnvVar('VITE_FRONTEND_SECRET'),
324
311
  timeout: parseInt(getEnvVar('VITE_MAUTIC_TIMEOUT') || '30000'),
325
312
  retries: parseInt(getEnvVar('VITE_MAUTIC_RETRIES') || '3'),
326
313
  ...overrides,
@@ -330,10 +317,6 @@ const createMauticConfig = (overrides = {}) => {
330
317
  if (merged.authMode === 'cloudflare_proxy') {
331
318
  if (!merged.cloudflareWorkerUrl)
332
319
  errors.push('cloudflareWorkerUrl is required for cloudflare_proxy mode');
333
- if (!merged.appId)
334
- errors.push('appId is required for cloudflare_proxy mode');
335
- if (!merged.workerSecret)
336
- errors.push('workerSecret is required for cloudflare_proxy mode');
337
320
  }
338
321
  if (merged.authMode === 'direct') {
339
322
  if (!merged.apiUrl)
@@ -356,12 +339,6 @@ const validateMauticConfig = (config) => {
356
339
  if (!config.cloudflareWorkerUrl) {
357
340
  errors.push('cloudflareWorkerUrl is required for cloudflare_proxy mode');
358
341
  }
359
- if (!config.appId) {
360
- errors.push('appId is required for cloudflare_proxy mode');
361
- }
362
- if (!config.workerSecret) {
363
- errors.push('workerSecret is required for cloudflare_proxy mode');
364
- }
365
342
  }
366
343
  else if (config.authMode === 'direct') {
367
344
  if (!config.apiUrl) {
@@ -403,7 +380,7 @@ const mergeMauticConfig = (base, overrides) => {
403
380
  */
404
381
  const isMauticEnabled = (config) => {
405
382
  if (config.authMode === 'cloudflare_proxy') {
406
- return !!(config.cloudflareWorkerUrl && config.appId && config.workerSecret);
383
+ return !!config.cloudflareWorkerUrl;
407
384
  }
408
385
  else if (config.authMode === 'direct') {
409
386
  return !!config.apiUrl;
@@ -418,323 +395,12 @@ const getMauticConfigSummary = (config) => {
418
395
  authMode: config.authMode,
419
396
  hasApiUrl: !!config.apiUrl,
420
397
  hasCloudflareWorkerUrl: !!config.cloudflareWorkerUrl,
421
- hasAppId: !!config.appId,
422
- hasWorkerSecret: !!config.workerSecret,
423
398
  timeout: config.timeout,
424
399
  retries: config.retries,
425
400
  isEnabled: isMauticEnabled(config),
426
401
  };
427
402
  };
428
403
 
429
- /**
430
- * @license GPL-3.0-or-later
431
- *
432
- * This file is part of the MarVAlt Open SDK.
433
- * Copyright (c) 2025 Vibune Pty Ltd.
434
- *
435
- * This program is free software: you can redistribute it and/or modify
436
- * it under the terms of the GNU General Public License as published by
437
- * the Free Software Foundation, either version 3 of the License, or
438
- * (at your option) any later version.
439
- *
440
- * This program is distributed in the hope that it will be useful,
441
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
442
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
443
- * See the GNU General Public License for more details.
444
- */
445
- // Create a default client instance (will be overridden by provider)
446
- let defaultClient = null;
447
- /**
448
- * Set the default Mautic client for hooks
449
- */
450
- const setMauticClient = (client) => {
451
- defaultClient = client;
452
- };
453
- /**
454
- * Get the current Mautic client
455
- */
456
- const getMauticClient = () => {
457
- if (!defaultClient) {
458
- // Lazy initialize the client from environment if not set by provider/service
459
- const config = createMauticConfig();
460
- defaultClient = new MauticClient(config);
461
- }
462
- return defaultClient;
463
- };
464
- /**
465
- * Custom hook for submitting forms to Mautic
466
- */
467
- const useMauticFormSubmission = () => {
468
- const queryClient = reactQuery.useQueryClient();
469
- return reactQuery.useMutation({
470
- mutationFn: (submission) => {
471
- const client = getMauticClient();
472
- return client.submitForm(submission.formId, submission);
473
- },
474
- onSuccess: (data, variables) => {
475
- if (undefined?.DEV) {
476
- console.log('Mautic form submission successful:', data);
477
- }
478
- // Invalidate related queries
479
- queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
480
- // Track the submission event
481
- if (variables.contact?.email) {
482
- const client = getMauticClient();
483
- client.trackEvent('form_submission', {
484
- formId: variables.formId,
485
- email: variables.contact.email
486
- }).catch(console.error);
487
- }
488
- },
489
- onError: (error) => {
490
- console.error('Mautic form submission failed:', error);
491
- },
492
- });
493
- };
494
- /**
495
- * Custom hook for creating Mautic contacts
496
- */
497
- const useMauticCreateContact = () => {
498
- const queryClient = reactQuery.useQueryClient();
499
- return reactQuery.useMutation({
500
- mutationFn: (contact) => {
501
- const client = getMauticClient();
502
- return client.createContact(contact);
503
- },
504
- onSuccess: (data, variables) => {
505
- if (undefined?.DEV) {
506
- console.log('Mautic contact created:', data);
507
- }
508
- // Invalidate contacts queries
509
- queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
510
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.email] });
511
- },
512
- onError: (error) => {
513
- console.error('Failed to create Mautic contact:', error);
514
- },
515
- });
516
- };
517
- /**
518
- * Custom hook for updating Mautic contacts
519
- */
520
- const useMauticUpdateContact = () => {
521
- const queryClient = reactQuery.useQueryClient();
522
- return reactQuery.useMutation({
523
- mutationFn: ({ contactId, contact }) => {
524
- const client = getMauticClient();
525
- return client.updateContact(contactId, contact);
526
- },
527
- onSuccess: (data, variables) => {
528
- if (undefined?.DEV) {
529
- console.log('Mautic contact updated:', data);
530
- }
531
- // Invalidate related queries
532
- queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
533
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
534
- },
535
- onError: (error) => {
536
- console.error('Failed to update Mautic contact:', error);
537
- },
538
- });
539
- };
540
- /**
541
- * Custom hook for fetching Mautic contact by email
542
- */
543
- const useMauticContactByEmail = (email) => {
544
- return reactQuery.useQuery({
545
- queryKey: ['mautic-contact', email],
546
- queryFn: () => {
547
- const client = getMauticClient();
548
- return client.getContactByEmail(email);
549
- },
550
- enabled: !!email && email.includes('@'),
551
- staleTime: 5 * 60 * 1000, // 5 minutes
552
- gcTime: 10 * 60 * 1000,
553
- });
554
- };
555
- /**
556
- * Custom hook for fetching Mautic forms
557
- */
558
- const useMauticForms = () => {
559
- return reactQuery.useQuery({
560
- queryKey: ['mautic-forms'],
561
- queryFn: () => {
562
- const client = getMauticClient();
563
- return client.getForms();
564
- },
565
- staleTime: 30 * 60 * 1000, // Forms don't change often
566
- gcTime: 60 * 60 * 1000,
567
- });
568
- };
569
- /**
570
- * Custom hook for fetching a specific Mautic form
571
- */
572
- const useMauticForm = (formId) => {
573
- return reactQuery.useQuery({
574
- queryKey: ['mautic-form', formId],
575
- queryFn: () => {
576
- const client = getMauticClient();
577
- return client.getForm(formId);
578
- },
579
- enabled: !!formId,
580
- staleTime: 30 * 60 * 1000,
581
- gcTime: 60 * 60 * 1000,
582
- });
583
- };
584
- /**
585
- * Custom hook for tracking Mautic events
586
- */
587
- const useMauticEventTracking = () => {
588
- return reactQuery.useMutation({
589
- mutationFn: ({ email, eventName, eventData }) => {
590
- const client = getMauticClient();
591
- return client.trackEvent(eventName, {
592
- email,
593
- ...(eventData || {})
594
- });
595
- },
596
- onSuccess: (data, variables) => {
597
- if (undefined?.DEV) {
598
- console.log('Mautic event tracked successfully:', variables.eventName);
599
- }
600
- },
601
- onError: (error, variables) => {
602
- console.error('Failed to track Mautic event:', variables.eventName, error);
603
- },
604
- });
605
- };
606
- /**
607
- * Custom hook for adding tags to contacts
608
- */
609
- const useMauticAddTags = () => {
610
- const queryClient = reactQuery.useQueryClient();
611
- return reactQuery.useMutation({
612
- mutationFn: ({ contactId, tags }) => {
613
- const client = getMauticClient();
614
- return client.addTagToContact(contactId, tags);
615
- },
616
- onSuccess: (data, variables) => {
617
- if (undefined?.DEV) {
618
- console.log('Tags added to Mautic contact:', variables.tags);
619
- }
620
- // Invalidate contact queries
621
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
622
- },
623
- onError: (error) => {
624
- console.error('Failed to add tags to Mautic contact:', error);
625
- },
626
- });
627
- };
628
- /**
629
- * Custom hook for removing tags from contacts
630
- */
631
- const useMauticRemoveTags = () => {
632
- const queryClient = reactQuery.useQueryClient();
633
- return reactQuery.useMutation({
634
- mutationFn: ({ contactId, tags }) => {
635
- const client = getMauticClient();
636
- return client.removeTagFromContact(contactId, tags);
637
- },
638
- onSuccess: (data, variables) => {
639
- if (undefined?.DEV) {
640
- console.log('Tags removed from Mautic contact:', variables.tags);
641
- }
642
- // Invalidate contact queries
643
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
644
- },
645
- onError: (error) => {
646
- console.error('Failed to remove tags from Mautic contact:', error);
647
- },
648
- });
649
- };
650
- /**
651
- * Custom hook for getting contact tags
652
- */
653
- const useMauticContactTags = (contactId) => {
654
- return reactQuery.useQuery({
655
- queryKey: ['mautic-contact-tags', contactId],
656
- queryFn: () => {
657
- const client = getMauticClient();
658
- return client.getContactTags(contactId);
659
- },
660
- enabled: !!contactId,
661
- staleTime: 5 * 60 * 1000,
662
- gcTime: 10 * 60 * 1000,
663
- });
664
- };
665
- /**
666
- * Custom hook for getting all tags
667
- */
668
- const useMauticTags = () => {
669
- return reactQuery.useQuery({
670
- queryKey: ['mautic-tags'],
671
- queryFn: () => {
672
- const client = getMauticClient();
673
- return client.getTags();
674
- },
675
- staleTime: 30 * 60 * 1000,
676
- gcTime: 60 * 60 * 1000,
677
- });
678
- };
679
- /**
680
- * Custom hook for getting segments
681
- */
682
- const useMauticSegments = () => {
683
- return reactQuery.useQuery({
684
- queryKey: ['mautic-segments'],
685
- queryFn: () => {
686
- const client = getMauticClient();
687
- return client.getSegments();
688
- },
689
- staleTime: 30 * 60 * 1000,
690
- gcTime: 60 * 60 * 1000,
691
- });
692
- };
693
- /**
694
- * Custom hook for adding contact to segment
695
- */
696
- const useMauticAddToSegment = () => {
697
- const queryClient = reactQuery.useQueryClient();
698
- return reactQuery.useMutation({
699
- mutationFn: ({ contactId, segmentId }) => {
700
- const client = getMauticClient();
701
- return client.addContactToSegment(contactId, segmentId);
702
- },
703
- onSuccess: (data, variables) => {
704
- if (undefined?.DEV) {
705
- console.log('Contact added to segment:', variables.segmentId);
706
- }
707
- // Invalidate contact queries
708
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
709
- },
710
- onError: (error) => {
711
- console.error('Failed to add contact to segment:', error);
712
- },
713
- });
714
- };
715
- /**
716
- * Custom hook for removing contact from segment
717
- */
718
- const useMauticRemoveFromSegment = () => {
719
- const queryClient = reactQuery.useQueryClient();
720
- return reactQuery.useMutation({
721
- mutationFn: ({ contactId, segmentId }) => {
722
- const client = getMauticClient();
723
- return client.removeContactFromSegment(contactId, segmentId);
724
- },
725
- onSuccess: (data, variables) => {
726
- if (undefined?.DEV) {
727
- console.log('Contact removed from segment:', variables.segmentId);
728
- }
729
- // Invalidate contact queries
730
- queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
731
- },
732
- onError: (error) => {
733
- console.error('Failed to remove contact from segment:', error);
734
- },
735
- });
736
- };
737
-
738
404
  /**
739
405
  * @license GPL-3.0-or-later
740
406
  *
@@ -836,9 +502,19 @@ class MauticService {
836
502
  return this.client.trackEvent(eventName, eventData);
837
503
  }
838
504
  }
839
- // Export configured instance
840
- const mauticService = new MauticService();
841
- setMauticClient(mauticService['client']);
505
+ // Export configured instance (guarded to avoid throwing at import time)
506
+ exports.mauticService = null;
507
+ try {
508
+ exports.mauticService = new MauticService();
509
+ // Initialize the default client for hooks
510
+ // Lazy import to avoid circular/side-effect issues in some environments
511
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
512
+ const { setMauticClient } = require('../react/hooks/useMautic');
513
+ setMauticClient(exports.mauticService['client']);
514
+ }
515
+ catch {
516
+ // Silently skip initialization when environment is not configured
517
+ }
842
518
 
843
519
  /**
844
520
  * @license GPL-3.0-or-later
@@ -858,17 +534,57 @@ setMauticClient(mauticService['client']);
858
534
  */
859
535
  class MauticGenerator {
860
536
  constructor(config) {
537
+ this.cachedToken = null;
861
538
  this.config = config;
862
539
  this.client = new MauticClient({
863
540
  authMode: config.authMode,
864
541
  apiUrl: config.apiUrl,
865
542
  cloudflareWorkerUrl: config.cloudflareWorkerUrl,
866
- appId: config.appId,
867
- workerSecret: config.workerSecret,
543
+ clientId: config.clientId,
544
+ clientSecret: config.clientSecret,
545
+ cfAccessClientId: config.cfAccessClientId,
546
+ cfAccessClientSecret: config.cfAccessClientSecret,
868
547
  timeout: config.timeout,
869
548
  retries: config.retries,
870
549
  });
871
550
  }
551
+ /**
552
+ * Get OAuth2 token for direct mode API calls
553
+ */
554
+ async getOAuth2Token() {
555
+ if (this.cachedToken && this.cachedToken.expires_at > Date.now() + 300000) {
556
+ console.log('🔑 Using cached OAuth2 token');
557
+ return this.cachedToken.access_token;
558
+ }
559
+ if (!this.config.clientId || !this.config.clientSecret) {
560
+ throw new Error('OAuth2 credentials (clientId, clientSecret) required for direct mode');
561
+ }
562
+ console.log('🔑 Fetching new OAuth2 token...');
563
+ const tokenUrl = `${this.config.apiUrl}/oauth/v2/token`;
564
+ const body = new URLSearchParams({
565
+ grant_type: 'client_credentials',
566
+ client_id: this.config.clientId,
567
+ client_secret: this.config.clientSecret,
568
+ });
569
+ const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
570
+ if (this.config.cfAccessClientId && this.config.cfAccessClientSecret) {
571
+ headers['CF-Access-Client-Id'] = this.config.cfAccessClientId;
572
+ headers['CF-Access-Client-Secret'] = this.config.cfAccessClientSecret;
573
+ console.log('🔐 Added CF Access headers to OAuth2 token request');
574
+ }
575
+ const resp = await fetch(tokenUrl, { method: 'POST', headers, body: body.toString() });
576
+ if (!resp.ok) {
577
+ const errText = await resp.text();
578
+ throw new Error(`OAuth2 token failed: ${resp.status} ${errText}`);
579
+ }
580
+ const data = await resp.json();
581
+ this.cachedToken = {
582
+ access_token: data.access_token,
583
+ expires_at: Date.now() + (data.expires_in * 1000),
584
+ };
585
+ console.log('✅ OAuth2 token cached');
586
+ return this.cachedToken.access_token;
587
+ }
872
588
  /**
873
589
  * Generate static data for Mautic forms
874
590
  */
@@ -902,7 +618,7 @@ class MauticGenerator {
902
618
  // Include other important form properties
903
619
  formType: formDetails.formType || 'standalone',
904
620
  // Extract fields with validation and properties
905
- fields: (formDetails.fields || []).map(field => ({
621
+ fields: (formDetails.fields || []).map((field) => ({
906
622
  id: field.id,
907
623
  label: field.label,
908
624
  alias: field.alias,
@@ -921,7 +637,7 @@ class MauticGenerator {
921
637
  }
922
638
  })),
923
639
  // Extract actions
924
- actions: (formDetails.actions || []).map(action => ({
640
+ actions: (formDetails.actions || []).map((action) => ({
925
641
  id: action.id,
926
642
  name: action.name,
927
643
  type: action.type,
@@ -945,10 +661,10 @@ class MauticGenerator {
945
661
  const staticData = {
946
662
  generated_at: new Date().toISOString(),
947
663
  total_forms: forms.length,
948
- forms: forms.map(form => ({
664
+ forms: forms.map((form) => ({
949
665
  id: parseInt(form.id),
950
666
  name: form.name,
951
- alias: form.alias || form.name.toLowerCase().replace(/\s+/g, '-'),
667
+ alias: String(form.alias ?? (form.name || '').toLowerCase().replace(/\s+/g, '-')),
952
668
  description: form.description,
953
669
  isPublished: form.isPublished,
954
670
  fields: form.fields,
@@ -995,49 +711,44 @@ class MauticGenerator {
995
711
  * Fetch list of forms from Mautic
996
712
  */
997
713
  async fetchMauticFormsList() {
998
- const baseUrl = this.config.cloudflareWorkerUrl;
999
- if (!baseUrl)
1000
- throw new Error('VITE_MAUTIC_PROXY_URL not configured');
1001
- const url = `${baseUrl}?endpoint=/forms`;
1002
- const headers = {
1003
- 'Content-Type': 'application/json',
1004
- };
1005
- if (this.config.appId) {
1006
- headers['x-app-id'] = this.config.appId;
714
+ if (this.config.authMode === 'cloudflare_proxy') {
715
+ const forms = await this.client.getForms();
716
+ return Array.isArray(forms) ? forms : [];
1007
717
  }
1008
- if (this.config.workerSecret) {
1009
- headers['x-app-secret'] = this.config.workerSecret;
718
+ // Direct mode with OAuth2
719
+ const token = await this.getOAuth2Token();
720
+ const url = `${this.config.apiUrl}/api/forms`;
721
+ const headers = { 'Authorization': `Bearer ${token}` };
722
+ if (this.config.cfAccessClientId && this.config.cfAccessClientSecret) {
723
+ headers['CF-Access-Client-Id'] = this.config.cfAccessClientId;
724
+ headers['CF-Access-Client-Secret'] = this.config.cfAccessClientSecret;
1010
725
  }
1011
- const response = await fetch(url, { headers });
1012
- if (!response.ok) {
1013
- throw new Error(`Failed to fetch forms list: ${response.status} ${response.statusText}`);
1014
- }
1015
- const data = await response.json().catch(() => ({}));
1016
- const forms = data.forms;
1017
- return Array.isArray(forms) ? forms : [];
726
+ const resp = await fetch(url, { headers });
727
+ if (!resp.ok)
728
+ throw new Error(`Failed to fetch forms: ${resp.status}`);
729
+ const data = await resp.json();
730
+ return Array.isArray(data.forms) ? data.forms : [];
1018
731
  }
1019
732
  /**
1020
733
  * Fetch individual form details from Mautic
1021
734
  */
1022
735
  async fetchMauticForm(formId) {
1023
- const baseUrl = this.config.cloudflareWorkerUrl;
1024
- if (!baseUrl)
1025
- throw new Error('VITE_MAUTIC_PROXY_URL not configured');
1026
- const url = `${baseUrl}?endpoint=/forms/${formId}`;
1027
- const headers = {
1028
- 'Content-Type': 'application/json',
1029
- };
1030
- if (this.config.appId) {
1031
- headers['x-app-id'] = this.config.appId;
1032
- }
1033
- if (this.config.workerSecret) {
1034
- headers['x-app-secret'] = this.config.workerSecret;
736
+ if (this.config.authMode === 'cloudflare_proxy') {
737
+ return this.client.getForm(parseInt(formId, 10));
1035
738
  }
1036
- const response = await fetch(url, { headers });
1037
- if (!response.ok) {
1038
- throw new Error(`Failed to fetch form ${formId}: ${response.status} ${response.statusText}`);
739
+ // Direct mode with OAuth2
740
+ const token = await this.getOAuth2Token();
741
+ const url = `${this.config.apiUrl}/api/forms/${formId}`;
742
+ const headers = { 'Authorization': `Bearer ${token}` };
743
+ if (this.config.cfAccessClientId && this.config.cfAccessClientSecret) {
744
+ headers['CF-Access-Client-Id'] = this.config.cfAccessClientId;
745
+ headers['CF-Access-Client-Secret'] = this.config.cfAccessClientSecret;
1039
746
  }
1040
- return response.json().catch(() => ({}));
747
+ const resp = await fetch(url, { headers });
748
+ if (!resp.ok)
749
+ throw new Error(`Failed to fetch form ${formId}: ${resp.status}`);
750
+ const data = await resp.json();
751
+ return data.form || data;
1041
752
  }
1042
753
  }
1043
754
  /**
@@ -2403,6 +2114,315 @@ if (process.env.NODE_ENV === 'production') {
2403
2114
 
2404
2115
  var jsxRuntimeExports = jsxRuntime.exports;
2405
2116
 
2117
+ /**
2118
+ * @license GPL-3.0-or-later
2119
+ *
2120
+ * This file is part of the MarVAlt Open SDK.
2121
+ * Copyright (c) 2025 Vibune Pty Ltd.
2122
+ *
2123
+ * This program is free software: you can redistribute it and/or modify
2124
+ * it under the terms of the GNU General Public License as published by
2125
+ * the Free Software Foundation, either version 3 of the License, or
2126
+ * (at your option) any later version.
2127
+ *
2128
+ * This program is distributed in the hope that it will be useful,
2129
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2130
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2131
+ * See the GNU General Public License for more details.
2132
+ */
2133
+ // Create a default client instance (will be overridden by provider)
2134
+ let defaultClient = null;
2135
+ /**
2136
+ * Set the default Mautic client for hooks
2137
+ */
2138
+ const setMauticClient = (client) => {
2139
+ defaultClient = client;
2140
+ };
2141
+ /**
2142
+ * Get the current Mautic client
2143
+ */
2144
+ const getMauticClient = () => {
2145
+ if (!defaultClient) {
2146
+ // Lazy initialize the client from environment if not set by provider/service
2147
+ const config = createMauticConfig();
2148
+ defaultClient = new MauticClient(config);
2149
+ }
2150
+ return defaultClient;
2151
+ };
2152
+ /**
2153
+ * Custom hook for submitting forms to Mautic
2154
+ */
2155
+ const useMauticFormSubmission = () => {
2156
+ const queryClient = reactQuery.useQueryClient();
2157
+ return reactQuery.useMutation({
2158
+ mutationFn: (submission) => {
2159
+ const client = getMauticClient();
2160
+ return client.submitForm(submission.formId, submission);
2161
+ },
2162
+ onSuccess: (data, variables) => {
2163
+ if (process.env.NODE_ENV === 'development') {
2164
+ console.log('Mautic form submission successful:', data);
2165
+ }
2166
+ // Invalidate related queries
2167
+ queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
2168
+ // Track the submission event
2169
+ if (variables.contact?.email) {
2170
+ const client = getMauticClient();
2171
+ client.trackEvent('form_submission', {
2172
+ formId: variables.formId,
2173
+ email: variables.contact.email
2174
+ }).catch(console.error);
2175
+ }
2176
+ },
2177
+ onError: (error) => {
2178
+ console.error('Mautic form submission failed:', error);
2179
+ },
2180
+ });
2181
+ };
2182
+ /**
2183
+ * Custom hook for creating Mautic contacts
2184
+ */
2185
+ const useMauticCreateContact = () => {
2186
+ const queryClient = reactQuery.useQueryClient();
2187
+ return reactQuery.useMutation({
2188
+ mutationFn: (contact) => {
2189
+ const client = getMauticClient();
2190
+ return client.createContact(contact);
2191
+ },
2192
+ onSuccess: (data, variables) => {
2193
+ if (process.env.NODE_ENV === 'development') {
2194
+ console.log('Mautic contact created:', data);
2195
+ }
2196
+ // Invalidate contacts queries
2197
+ queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
2198
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.email] });
2199
+ },
2200
+ onError: (error) => {
2201
+ console.error('Failed to create Mautic contact:', error);
2202
+ },
2203
+ });
2204
+ };
2205
+ /**
2206
+ * Custom hook for updating Mautic contacts
2207
+ */
2208
+ const useMauticUpdateContact = () => {
2209
+ const queryClient = reactQuery.useQueryClient();
2210
+ return reactQuery.useMutation({
2211
+ mutationFn: ({ contactId, contact }) => {
2212
+ const client = getMauticClient();
2213
+ return client.updateContact(contactId, contact);
2214
+ },
2215
+ onSuccess: (data, variables) => {
2216
+ if (process.env.NODE_ENV === 'development') {
2217
+ console.log('Mautic contact updated:', data);
2218
+ }
2219
+ // Invalidate related queries
2220
+ queryClient.invalidateQueries({ queryKey: ['mautic-contacts'] });
2221
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2222
+ },
2223
+ onError: (error) => {
2224
+ console.error('Failed to update Mautic contact:', error);
2225
+ },
2226
+ });
2227
+ };
2228
+ /**
2229
+ * Custom hook for fetching Mautic contact by email
2230
+ */
2231
+ const useMauticContactByEmail = (email) => {
2232
+ return reactQuery.useQuery({
2233
+ queryKey: ['mautic-contact', email],
2234
+ queryFn: () => {
2235
+ const client = getMauticClient();
2236
+ return client.getContactByEmail(email);
2237
+ },
2238
+ enabled: !!email && email.includes('@'),
2239
+ staleTime: 5 * 60 * 1000, // 5 minutes
2240
+ gcTime: 10 * 60 * 1000,
2241
+ });
2242
+ };
2243
+ /**
2244
+ * Custom hook for fetching Mautic forms
2245
+ */
2246
+ const useMauticForms = () => {
2247
+ return reactQuery.useQuery({
2248
+ queryKey: ['mautic-forms'],
2249
+ queryFn: () => {
2250
+ const client = getMauticClient();
2251
+ return client.getForms();
2252
+ },
2253
+ staleTime: 30 * 60 * 1000, // Forms don't change often
2254
+ gcTime: 60 * 60 * 1000,
2255
+ });
2256
+ };
2257
+ /**
2258
+ * Custom hook for fetching a specific Mautic form
2259
+ */
2260
+ const useMauticForm = (formId) => {
2261
+ return reactQuery.useQuery({
2262
+ queryKey: ['mautic-form', formId],
2263
+ queryFn: () => {
2264
+ const client = getMauticClient();
2265
+ return client.getForm(formId);
2266
+ },
2267
+ enabled: !!formId,
2268
+ staleTime: 30 * 60 * 1000,
2269
+ gcTime: 60 * 60 * 1000,
2270
+ });
2271
+ };
2272
+ /**
2273
+ * Custom hook for tracking Mautic events
2274
+ */
2275
+ const useMauticEventTracking = () => {
2276
+ return reactQuery.useMutation({
2277
+ mutationFn: ({ email, eventName, eventData }) => {
2278
+ const client = getMauticClient();
2279
+ return client.trackEvent(eventName, {
2280
+ email,
2281
+ ...(eventData || {})
2282
+ });
2283
+ },
2284
+ onSuccess: (data, variables) => {
2285
+ if (process.env.NODE_ENV === 'development') {
2286
+ console.log('Mautic event tracked successfully:', variables.eventName);
2287
+ }
2288
+ },
2289
+ onError: (error, variables) => {
2290
+ console.error('Failed to track Mautic event:', variables.eventName, error);
2291
+ },
2292
+ });
2293
+ };
2294
+ /**
2295
+ * Custom hook for adding tags to contacts
2296
+ */
2297
+ const useMauticAddTags = () => {
2298
+ const queryClient = reactQuery.useQueryClient();
2299
+ return reactQuery.useMutation({
2300
+ mutationFn: ({ contactId, tags }) => {
2301
+ const client = getMauticClient();
2302
+ return client.addTagToContact(contactId, tags);
2303
+ },
2304
+ onSuccess: (data, variables) => {
2305
+ if (process.env.NODE_ENV === 'development') {
2306
+ console.log('Tags added to Mautic contact:', variables.tags);
2307
+ }
2308
+ // Invalidate contact queries
2309
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2310
+ },
2311
+ onError: (error) => {
2312
+ console.error('Failed to add tags to Mautic contact:', error);
2313
+ },
2314
+ });
2315
+ };
2316
+ /**
2317
+ * Custom hook for removing tags from contacts
2318
+ */
2319
+ const useMauticRemoveTags = () => {
2320
+ const queryClient = reactQuery.useQueryClient();
2321
+ return reactQuery.useMutation({
2322
+ mutationFn: ({ contactId, tags }) => {
2323
+ const client = getMauticClient();
2324
+ return client.removeTagFromContact(contactId, tags);
2325
+ },
2326
+ onSuccess: (data, variables) => {
2327
+ if (process.env.NODE_ENV === 'development') {
2328
+ console.log('Tags removed from Mautic contact:', variables.tags);
2329
+ }
2330
+ // Invalidate contact queries
2331
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2332
+ },
2333
+ onError: (error) => {
2334
+ console.error('Failed to remove tags from Mautic contact:', error);
2335
+ },
2336
+ });
2337
+ };
2338
+ /**
2339
+ * Custom hook for getting contact tags
2340
+ */
2341
+ const useMauticContactTags = (contactId) => {
2342
+ return reactQuery.useQuery({
2343
+ queryKey: ['mautic-contact-tags', contactId],
2344
+ queryFn: () => {
2345
+ const client = getMauticClient();
2346
+ return client.getContactTags(contactId);
2347
+ },
2348
+ enabled: !!contactId,
2349
+ staleTime: 5 * 60 * 1000,
2350
+ gcTime: 10 * 60 * 1000,
2351
+ });
2352
+ };
2353
+ /**
2354
+ * Custom hook for getting all tags
2355
+ */
2356
+ const useMauticTags = () => {
2357
+ return reactQuery.useQuery({
2358
+ queryKey: ['mautic-tags'],
2359
+ queryFn: () => {
2360
+ const client = getMauticClient();
2361
+ return client.getTags();
2362
+ },
2363
+ staleTime: 30 * 60 * 1000,
2364
+ gcTime: 60 * 60 * 1000,
2365
+ });
2366
+ };
2367
+ /**
2368
+ * Custom hook for getting segments
2369
+ */
2370
+ const useMauticSegments = () => {
2371
+ return reactQuery.useQuery({
2372
+ queryKey: ['mautic-segments'],
2373
+ queryFn: () => {
2374
+ const client = getMauticClient();
2375
+ return client.getSegments();
2376
+ },
2377
+ staleTime: 30 * 60 * 1000,
2378
+ gcTime: 60 * 60 * 1000,
2379
+ });
2380
+ };
2381
+ /**
2382
+ * Custom hook for adding contact to segment
2383
+ */
2384
+ const useMauticAddToSegment = () => {
2385
+ const queryClient = reactQuery.useQueryClient();
2386
+ return reactQuery.useMutation({
2387
+ mutationFn: ({ contactId, segmentId }) => {
2388
+ const client = getMauticClient();
2389
+ return client.addContactToSegment(contactId, segmentId);
2390
+ },
2391
+ onSuccess: (data, variables) => {
2392
+ if (process.env.NODE_ENV === 'development') {
2393
+ console.log('Contact added to segment:', variables.segmentId);
2394
+ }
2395
+ // Invalidate contact queries
2396
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2397
+ },
2398
+ onError: (error) => {
2399
+ console.error('Failed to add contact to segment:', error);
2400
+ },
2401
+ });
2402
+ };
2403
+ /**
2404
+ * Custom hook for removing contact from segment
2405
+ */
2406
+ const useMauticRemoveFromSegment = () => {
2407
+ const queryClient = reactQuery.useQueryClient();
2408
+ return reactQuery.useMutation({
2409
+ mutationFn: ({ contactId, segmentId }) => {
2410
+ const client = getMauticClient();
2411
+ return client.removeContactFromSegment(contactId, segmentId);
2412
+ },
2413
+ onSuccess: (data, variables) => {
2414
+ if (process.env.NODE_ENV === 'development') {
2415
+ console.log('Contact removed from segment:', variables.segmentId);
2416
+ }
2417
+ // Invalidate contact queries
2418
+ queryClient.invalidateQueries({ queryKey: ['mautic-contact', variables.contactId] });
2419
+ },
2420
+ onError: (error) => {
2421
+ console.error('Failed to remove contact from segment:', error);
2422
+ },
2423
+ });
2424
+ };
2425
+
2406
2426
  const MauticForm = ({ formId, title, description, className = '', form, onSubmit, onSuccess, onError, }) => {
2407
2427
  const [formData, setFormData] = React.useState({});
2408
2428
  const [errors, setErrors] = React.useState({});
@@ -2575,21 +2595,19 @@ const MauticForm = ({ formId, title, description, className = '', form, onSubmit
2575
2595
  .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." }) }))] }));
2576
2596
  };
2577
2597
 
2578
- const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, children }) => {
2598
+ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, children }) => {
2579
2599
  React.useEffect(() => {
2580
2600
  // Determine if tracking is enabled
2581
- const isEnabled = enabled ?? (!!proxyUrl && !!appId && !!workerSecret);
2601
+ const isEnabled = enabled ?? !!proxyUrl;
2582
2602
  const trackingProxyUrl = proxyUrl;
2583
2603
  // Debug logging to understand configuration
2584
- if (undefined?.DEV) {
2604
+ if (process.env.NODE_ENV === 'development') {
2585
2605
  console.log('🔍 Mautic Tracking Debug:', {
2586
2606
  isEnabled,
2587
2607
  mauticUrl,
2588
2608
  trackingProxyUrl,
2589
- hasAppId: !!appId,
2590
- hasWorkerSecret: !!workerSecret,
2591
- isDev: undefined?.DEV,
2592
- isProd: undefined?.PROD
2609
+ isDev: true,
2610
+ isProd: false
2593
2611
  });
2594
2612
  }
2595
2613
  // Check if tracking is enabled
@@ -2598,7 +2616,7 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2598
2616
  return;
2599
2617
  }
2600
2618
  // Check if we're in development mode - skip tracking in dev
2601
- if (undefined?.DEV) {
2619
+ if (process.env.NODE_ENV === 'development') {
2602
2620
  console.log('Mautic tracking disabled in development mode');
2603
2621
  return;
2604
2622
  }
@@ -2609,6 +2627,8 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2609
2627
  }
2610
2628
  console.log('✅ Mautic tracking enabled for production');
2611
2629
  // Initialize Mautic tracking with error handling
2630
+ // Capture original fetch for cleanup
2631
+ let originalFetch;
2612
2632
  try {
2613
2633
  // Pre-initialize tracking object and queue (standard Mautic snippet behavior)
2614
2634
  window.MauticTrackingObject = 'mt';
@@ -2616,20 +2636,12 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2616
2636
  (window.mt.q = window.mt.q || []).push(arguments);
2617
2637
  };
2618
2638
  // Intercept Mautic tracking API calls to use our proxy (generic /mtc/ matcher)
2619
- const originalFetch = window.fetch.bind(window);
2639
+ originalFetch = window.fetch.bind(window);
2620
2640
  const interceptFetch = function (url, options) {
2621
2641
  if (typeof url === 'string' && /\/mtc\//.test(url)) {
2622
2642
  try {
2623
2643
  const proxyUrl = url.replace(/https?:\/\/[^/]+\/mtc\//, `${trackingProxyUrl}?endpoint=/mtc/`);
2624
- const proxyOptions = {
2625
- ...options,
2626
- headers: {
2627
- ...options?.headers,
2628
- 'Content-Type': 'application/json',
2629
- 'x-app-id': appId || '',
2630
- ...(workerSecret ? { 'x-app-secret': workerSecret } : {})
2631
- }
2632
- };
2644
+ const proxyOptions = { ...options };
2633
2645
  return originalFetch(proxyUrl, proxyOptions);
2634
2646
  }
2635
2647
  catch (e) {
@@ -2639,16 +2651,9 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2639
2651
  return originalFetch(url, options);
2640
2652
  };
2641
2653
  window.fetch = interceptFetch;
2642
- // Fetch the tracking script through the proxy with required headers
2654
+ // Fetch the tracking script through the proxy
2643
2655
  const trackingScriptUrl = `${trackingProxyUrl}?endpoint=/mtc.js`;
2644
- fetch(trackingScriptUrl, {
2645
- method: 'GET',
2646
- headers: {
2647
- 'Content-Type': 'application/javascript',
2648
- 'x-app-id': appId || '',
2649
- ...(workerSecret ? { 'x-app-secret': workerSecret } : {})
2650
- }
2651
- })
2656
+ fetch(trackingScriptUrl, { method: 'GET' })
2652
2657
  .then(response => {
2653
2658
  if (!response.ok) {
2654
2659
  throw new Error(`Failed to load Mautic tracking script: ${response.status}`);
@@ -2676,12 +2681,11 @@ const MauticTracking = ({ enabled, mauticUrl, proxyUrl, appId, workerSecret, chi
2676
2681
  }
2677
2682
  // Cleanup function
2678
2683
  return () => {
2679
- // Restore original fetch if needed
2680
- if (window.fetch !== originalFetch) {
2684
+ if (originalFetch && window.fetch !== originalFetch) {
2681
2685
  window.fetch = originalFetch;
2682
2686
  }
2683
2687
  };
2684
- }, [enabled, mauticUrl, proxyUrl, appId, workerSecret]);
2688
+ }, [enabled, mauticUrl, proxyUrl]);
2685
2689
  // Render children if provided, otherwise render nothing
2686
2690
  return children ? jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: children }) : null;
2687
2691
  };
@@ -3056,30 +3060,33 @@ const validateField = (field, value) => {
3056
3060
  const validateCustomRule = (rule, value, field) => {
3057
3061
  switch (rule) {
3058
3062
  case 'min_length':
3059
- if (typeof value === 'string' && value.length < (field.properties?.minLength || 0)) {
3060
- return `Minimum length is ${field.properties?.minLength} characters`;
3063
+ if (typeof value === 'string' && value.length < Number(field.properties?.minLength ?? 0)) {
3064
+ return `Minimum length is ${Number(field.properties?.minLength ?? 0)} characters`;
3061
3065
  }
3062
3066
  break;
3063
3067
  case 'max_length':
3064
- if (typeof value === 'string' && value.length > (field.properties?.maxLength || 1000)) {
3065
- return `Maximum length is ${field.properties?.maxLength} characters`;
3068
+ if (typeof value === 'string' && value.length > Number(field.properties?.maxLength ?? 1000)) {
3069
+ return `Maximum length is ${Number(field.properties?.maxLength ?? 1000)} characters`;
3066
3070
  }
3067
3071
  break;
3068
3072
  case 'min_value':
3069
- if (typeof value === 'number' && value < (field.properties?.minValue || 0)) {
3070
- return `Minimum value is ${field.properties?.minValue}`;
3073
+ if (typeof value === 'number' && value < Number(field.properties?.minValue ?? 0)) {
3074
+ return `Minimum value is ${Number(field.properties?.minValue ?? 0)}`;
3071
3075
  }
3072
3076
  break;
3073
3077
  case 'max_value':
3074
- if (typeof value === 'number' && value > (field.properties?.maxValue || 1000000)) {
3075
- return `Maximum value is ${field.properties?.maxValue}`;
3078
+ if (typeof value === 'number' && value > Number(field.properties?.maxValue ?? 1000000)) {
3079
+ return `Maximum value is ${Number(field.properties?.maxValue ?? 1000000)}`;
3076
3080
  }
3077
3081
  break;
3078
3082
  case 'pattern':
3079
- if (field.properties?.pattern) {
3080
- const regex = new RegExp(field.properties.pattern);
3081
- if (!regex.test(value)) {
3082
- return 'Invalid format';
3083
+ {
3084
+ const pattern = field.properties?.pattern;
3085
+ if (pattern) {
3086
+ const regex = new RegExp(String(pattern));
3087
+ if (!regex.test(value)) {
3088
+ return 'Invalid format';
3089
+ }
3083
3090
  }
3084
3091
  }
3085
3092
  break;
@@ -3169,7 +3176,6 @@ exports.isValidEmail = isValidEmail;
3169
3176
  exports.isValidPhone = isValidPhone;
3170
3177
  exports.isValidUrl = isValidUrl;
3171
3178
  exports.loadMauticData = loadMauticData;
3172
- exports.mauticService = mauticService;
3173
3179
  exports.mergeMauticConfig = mergeMauticConfig;
3174
3180
  exports.sanitizeFormData = sanitizeFormData;
3175
3181
  exports.searchForms = searchForms;