@marvalt/digivalt-core 0.1.7 → 0.2.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +32 -4
  3. package/bin/init.cjs +42 -7
  4. package/dist/config.cjs +520 -0
  5. package/dist/config.cjs.map +1 -0
  6. package/dist/config.d.ts +307 -0
  7. package/dist/config.esm.js +502 -0
  8. package/dist/config.esm.js.map +1 -0
  9. package/dist/generators.cjs +2481 -0
  10. package/dist/generators.cjs.map +1 -0
  11. package/dist/generators.d.ts +19 -0
  12. package/dist/generators.esm.js +2475 -0
  13. package/dist/generators.esm.js.map +1 -0
  14. package/dist/index.cjs +18 -7955
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.ts +18 -1196
  17. package/dist/index.esm.js +13 -7929
  18. package/dist/index.esm.js.map +1 -1
  19. package/dist/runtime/env.d.ts +4 -0
  20. package/dist/runtime/lazy.d.ts +1 -0
  21. package/dist/services/cf-wp-webhook.d.ts +2 -0
  22. package/dist/services/gravityForms.d.ts +3 -0
  23. package/dist/services/mautic.d.ts +5 -1
  24. package/dist/services/suitecrm.d.ts +2 -0
  25. package/dist/services.cjs +1339 -0
  26. package/dist/services.cjs.map +1 -0
  27. package/dist/services.d.ts +432 -0
  28. package/dist/services.esm.js +1322 -0
  29. package/dist/services.esm.js.map +1 -0
  30. package/dist/static/index.d.ts +4 -0
  31. package/dist/static.cjs +997 -0
  32. package/dist/static.cjs.map +1 -0
  33. package/dist/static.d.ts +410 -0
  34. package/dist/static.esm.js +962 -0
  35. package/dist/static.esm.js.map +1 -0
  36. package/dist/types.cjs +3 -0
  37. package/dist/types.cjs.map +1 -0
  38. package/dist/types.d.ts +134 -0
  39. package/dist/types.esm.js +2 -0
  40. package/dist/types.esm.js.map +1 -0
  41. package/package.json +30 -2
  42. package/template/DIGIVALT_SETUP.md +62 -0
  43. package/template/scripts/deploy-secrets.js +55 -23
  44. package/template/scripts/generate.ts +3 -17
@@ -0,0 +1,1339 @@
1
+ 'use strict';
2
+
3
+ var wadapter = require('@marvalt/wadapter');
4
+ var madapter = require('@marvalt/madapter');
5
+
6
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
7
+ const getImportMetaEnv = () => {
8
+ try {
9
+ return typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('services.cjs', document.baseURI).href)) }) !== 'undefined' ? undefined : undefined;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ };
15
+ const getEnv = (key) => {
16
+ const viteEnv = getImportMetaEnv();
17
+ if (viteEnv && key in viteEnv) {
18
+ const value = viteEnv[key];
19
+ return value == null ? undefined : String(value);
20
+ }
21
+ if (typeof process !== 'undefined' && process.env && key in process.env) {
22
+ const value = process.env[key];
23
+ return value == null ? undefined : String(value);
24
+ }
25
+ return undefined;
26
+ };
27
+ const isDevEnvironment = () => {
28
+ const viteEnv = getImportMetaEnv();
29
+ if (viteEnv && 'DEV' in viteEnv) {
30
+ return Boolean(viteEnv.DEV);
31
+ }
32
+ return typeof process !== 'undefined' && process.env?.NODE_ENV === 'development';
33
+ };
34
+ const isLocalDevelopmentEnabled = () => {
35
+ return getEnv('VITE_LOCAL_DEVELOPMENT') === 'true';
36
+ };
37
+
38
+ const createLazyProxy = (factory) => new Proxy({}, {
39
+ get(_target, prop, receiver) {
40
+ const instance = factory();
41
+ const value = Reflect.get(instance, prop, receiver);
42
+ return typeof value === 'function' ? value.bind(instance) : value;
43
+ },
44
+ set(_target, prop, value, receiver) {
45
+ const instance = factory();
46
+ return Reflect.set(instance, prop, value, receiver);
47
+ },
48
+ has(_target, prop) {
49
+ const instance = factory();
50
+ return prop in instance;
51
+ },
52
+ ownKeys() {
53
+ const instance = factory();
54
+ return Reflect.ownKeys(instance);
55
+ },
56
+ getOwnPropertyDescriptor(_target, prop) {
57
+ const instance = factory();
58
+ return Object.getOwnPropertyDescriptor(instance, prop);
59
+ },
60
+ });
61
+
62
+ /**
63
+ * Gravity Forms Integration Service
64
+ * Uses @marvalt/wadapter package for secure form submissions
65
+ */
66
+ // Convention-based: Uses /api/gravity-forms-submit by default.
67
+ let gravityFormsClientInstance = null;
68
+ const createGravityFormsClient = () => {
69
+ const config = {
70
+ apiUrl: getEnv('VITE_WORDPRESS_API_URL'),
71
+ authMode: 'direct',
72
+ timeout: 30000,
73
+ retries: 3,
74
+ };
75
+ return new wadapter.GravityFormsClient(config);
76
+ };
77
+ const getGravityFormsClient = () => {
78
+ if (!gravityFormsClientInstance) {
79
+ gravityFormsClientInstance = createGravityFormsClient();
80
+ }
81
+ return gravityFormsClientInstance;
82
+ };
83
+ const resetGravityFormsClient = () => {
84
+ gravityFormsClientInstance = null;
85
+ };
86
+ const gravityFormsClient = createLazyProxy(getGravityFormsClient);
87
+ // Legacy service wrapper for backwards compatibility
88
+ const gravityFormsService = {
89
+ /**
90
+ * Submit a Gravity Form (legacy interface)
91
+ * @param formId - Form ID as string or number
92
+ * @param entry - Entry data with field IDs as keys
93
+ * @param files - Optional files (not yet supported)
94
+ * @param useBasicApi - Ignored (always uses proxy)
95
+ */
96
+ async submitForm(formId, entry, files, useBasicApi) {
97
+ console.log('🔍 gravityFormsService.submitForm (legacy wrapper) called:', {
98
+ formId,
99
+ entry,
100
+ hasFiles: !!files,
101
+ useBasicApi,
102
+ });
103
+ if (files && Object.keys(files).length > 0) {
104
+ console.warn('⚠️ File uploads not yet supported in new Gravity Forms client');
105
+ }
106
+ // Convert to new API format
107
+ const formIdNumber = typeof formId === 'string' ? parseInt(formId, 10) : formId;
108
+ return getGravityFormsClient().submitForm(formIdNumber, {
109
+ form_id: formIdNumber,
110
+ field_values: entry,
111
+ });
112
+ },
113
+ // Pass through other methods
114
+ getForm: (id) => getGravityFormsClient().getForm(id),
115
+ getForms: () => getGravityFormsClient().getForms(),
116
+ getFormConfig: (id) => getGravityFormsClient().getFormConfig(id),
117
+ getHealth: () => getGravityFormsClient().getHealth(),
118
+ };
119
+
120
+ /**
121
+ * Mautic Integration Service (via @marvalt/madapter)
122
+ */
123
+ // @ts-ignore - internal workspace type resolution
124
+ // Convention-based: Uses /api/mautic-submit Pages Function for server-side OAuth2
125
+ // The Pages Function handles authentication securely without exposing credentials to the browser.
126
+ let mauticServiceInstance = null;
127
+ const createMauticService = () => {
128
+ const useWranglerProxy = isDevEnvironment() && isLocalDevelopmentEnabled();
129
+ return new madapter.MauticClient({
130
+ apiUrl: getEnv('VITE_MAUTIC_URL'),
131
+ proxyEndpoint: useWranglerProxy
132
+ ? 'http://127.0.0.1:8788/api/mautic-submit'
133
+ : '/api/mautic-submit',
134
+ });
135
+ };
136
+ const getMauticService = () => {
137
+ if (!mauticServiceInstance) {
138
+ mauticServiceInstance = createMauticService();
139
+ }
140
+ return mauticServiceInstance;
141
+ };
142
+ const resetMauticService = () => {
143
+ mauticServiceInstance = null;
144
+ };
145
+ const mauticService = createLazyProxy(getMauticService);
146
+
147
+ // Detect if we're running in a local development environment
148
+ const isLocalDevelopment = () => {
149
+ // Check if we're in a browser environment
150
+ if (typeof window !== 'undefined') {
151
+ // Check for local development indicators
152
+ const isLocalhost = window.location.hostname === 'localhost' ||
153
+ window.location.hostname === '127.0.0.1' ||
154
+ window.location.hostname.includes('192.168.') ||
155
+ window.location.hostname.includes('10.0.');
156
+ const isDevPort = window.location.port === '3000' ||
157
+ window.location.port === '5173' ||
158
+ window.location.port === '8080';
159
+ const hasLocalEnv = Boolean(getEnv('DEV')) || getEnv('VITE_LOCAL_DEVELOPMENT') === 'true';
160
+ return Boolean(isLocalhost && (isDevPort || hasLocalEnv));
161
+ }
162
+ else {
163
+ // Node.js environment - check environment variables
164
+ return getEnv('NODE_ENV') === 'development' ||
165
+ getEnv('VITE_LOCAL_DEVELOPMENT') === 'true';
166
+ }
167
+ };
168
+ // Get the appropriate environment configuration
169
+ const getEnvironmentConfig = () => {
170
+ const isLocal = isLocalDevelopment();
171
+ // Default to cloudflare_proxy (via Cloudflare Pages Functions)
172
+ // Use 'direct' only for local development with whitelisted IPs
173
+ const authModeRaw = getEnv('VITE_AUTH_MODE');
174
+ const authMode = authModeRaw || 'cloudflare_proxy';
175
+ // Reduced debug logging - only in development and only once
176
+ if (isDevEnvironment() && !authModeRaw) {
177
+ console.log('ℹ️ VITE_AUTH_MODE not set, defaulting to cloudflare_proxy (Cloudflare Pages Functions)');
178
+ }
179
+ // Validate auth mode value
180
+ if (authMode !== 'direct' && authMode !== 'cloudflare_proxy') {
181
+ console.error('❌ Invalid VITE_AUTH_MODE value. Must be "direct" or "cloudflare_proxy".');
182
+ return {
183
+ mode: 'error',
184
+ authMode: 'error',
185
+ dataSource: 'static',
186
+ baseUrl: '',
187
+ headers: {},
188
+ description: `Invalid configuration - VITE_AUTH_MODE="${authModeRaw}"`,
189
+ suiteCRM: {
190
+ url: undefined,
191
+ proxyUrl: undefined,
192
+ appId: undefined,
193
+ workerSecret: undefined,
194
+ oauth2: {
195
+ clientId: undefined,
196
+ clientSecret: undefined,
197
+ tokenUrl: undefined,
198
+ username: undefined,
199
+ password: undefined,
200
+ },
201
+ }
202
+ };
203
+ }
204
+ // Cloudflare proxy mode (uses Cloudflare Pages Functions by default)
205
+ // Worker URL is optional - Pages Functions handle routing automatically
206
+ if (authMode === 'cloudflare_proxy') {
207
+ const workerUrl = getEnv('VITE_CLOUDFLARE_WORKER_URL');
208
+ // Cloudflare Pages Functions work without explicit worker URL
209
+ // The worker URL is only needed for separate Cloudflare Workers
210
+ // Pages Functions automatically handle /api/* routes
211
+ const baseUrl = workerUrl || '/api';
212
+ return {
213
+ mode: isLocal ? 'local' : 'remote',
214
+ authMode: 'cloudflare_proxy',
215
+ dataSource: 'static',
216
+ baseUrl,
217
+ headers: {
218
+ 'Content-Type': 'application/json'
219
+ },
220
+ description: workerUrl
221
+ ? (isLocal ? 'Local development with Cloudflare Worker proxy' : 'Remote development with Cloudflare Worker proxy')
222
+ : (isLocal ? 'Local development with Cloudflare Pages Functions' : 'Production with Cloudflare Pages Functions'),
223
+ mautic: {
224
+ proxyUrl: getEnv('VITE_MAUTIC_PROXY_URL')
225
+ },
226
+ suiteCRM: {
227
+ url: getEnv('VITE_SUITECRM_URL'),
228
+ proxyUrl: getEnv('VITE_SUITECRM_PROXY_URL'),
229
+ appId: undefined,
230
+ workerSecret: undefined,
231
+ oauth2: {
232
+ clientId: getEnv('VITE_SUITECRM_CLIENT_ID'),
233
+ clientSecret: getEnv('VITE_SUITECRM_CLIENT_SECRET'),
234
+ tokenUrl: getEnv('VITE_SUITECRM_TOKEN_URL'),
235
+ username: getEnv('VITE_SUITECRM_USERNAME'),
236
+ password: getEnv('VITE_SUITECRM_PASSWORD'),
237
+ },
238
+ }
239
+ };
240
+ }
241
+ // Direct WordPress mode (legacy - for local development only)
242
+ if (authMode === 'direct') {
243
+ const wpApiUrl = getEnv('VITE_WORDPRESS_API_URL');
244
+ if (!wpApiUrl) {
245
+ console.warn('⚠️ Direct mode requires: VITE_WORDPRESS_API_URL');
246
+ return {
247
+ mode: 'error',
248
+ authMode: 'error',
249
+ dataSource: 'static',
250
+ baseUrl: '',
251
+ headers: {},
252
+ description: 'Invalid configuration - missing WordPress API URL',
253
+ suiteCRM: {
254
+ url: undefined,
255
+ proxyUrl: undefined,
256
+ appId: undefined,
257
+ workerSecret: undefined,
258
+ oauth2: {
259
+ clientId: undefined,
260
+ clientSecret: undefined,
261
+ tokenUrl: undefined,
262
+ username: undefined,
263
+ password: undefined,
264
+ },
265
+ }
266
+ };
267
+ }
268
+ if (isDevEnvironment()) {
269
+ console.log('🔍 Using direct WordPress mode (legacy - for local development only)');
270
+ }
271
+ const localDev = getEnv('VITE_LOCAL_DEVELOPMENT') === 'true';
272
+ return {
273
+ mode: localDev ? 'local' : 'remote',
274
+ authMode: 'direct',
275
+ dataSource: 'hybrid',
276
+ baseUrl: wpApiUrl,
277
+ headers: { 'Content-Type': 'application/json' },
278
+ description: localDev ? 'Direct (local dev) without Access' : 'Direct mode',
279
+ mautic: { url: getEnv('VITE_MAUTIC_URL') },
280
+ suiteCRM: {
281
+ url: getEnv('VITE_SUITECRM_URL'),
282
+ proxyUrl: getEnv('VITE_SUITECRM_PROXY_URL'),
283
+ appId: undefined,
284
+ workerSecret: undefined,
285
+ oauth2: {
286
+ clientId: getEnv('VITE_SUITECRM_CLIENT_ID'),
287
+ clientSecret: getEnv('VITE_SUITECRM_CLIENT_SECRET'),
288
+ tokenUrl: getEnv('VITE_SUITECRM_TOKEN_URL'),
289
+ username: getEnv('VITE_SUITECRM_USERNAME'),
290
+ password: getEnv('VITE_SUITECRM_PASSWORD'),
291
+ },
292
+ }
293
+ };
294
+ }
295
+ // This should never be reached, but just in case
296
+ return {
297
+ mode: 'error',
298
+ authMode: 'error',
299
+ dataSource: 'static',
300
+ baseUrl: '',
301
+ headers: {},
302
+ description: 'Invalid configuration - unknown error',
303
+ suiteCRM: {
304
+ url: undefined,
305
+ proxyUrl: undefined,
306
+ appId: undefined,
307
+ workerSecret: undefined,
308
+ oauth2: {
309
+ clientId: undefined,
310
+ clientSecret: undefined,
311
+ tokenUrl: undefined,
312
+ username: undefined,
313
+ password: undefined,
314
+ },
315
+ }
316
+ };
317
+ };
318
+ // Environment status and diagnostics
319
+ const getEnvironmentStatus = () => {
320
+ const config = getEnvironmentConfig();
321
+ const isLocal = isLocalDevelopment();
322
+ const authMode = getEnv('VITE_AUTH_MODE');
323
+ // Check if required credentials are available for each mode
324
+ const hasCloudflareCredentials = !!getEnv('VITE_CLOUDFLARE_WORKER_URL');
325
+ const hasWordPressApiUrl = !!getEnv('VITE_WORDPRESS_API_URL');
326
+ return {
327
+ currentConfig: config,
328
+ diagnostics: {
329
+ isLocalDevelopment: isLocal,
330
+ authMode: authMode,
331
+ hasCloudflareCredentials,
332
+ hasWordpressApiUrl: hasWordPressApiUrl,
333
+ cloudflareWorkerUrl: !!getEnv('VITE_CLOUDFLARE_WORKER_URL'),
334
+ cfAccessId: !!getEnv('VITE_CF_ACCESS_CLIENT_ID'),
335
+ cfAccessSecret: !!getEnv('VITE_CF_ACCESS_CLIENT_SECRET'),
336
+ mauticProxyUrl: !!getEnv('VITE_MAUTIC_PROXY_URL')
337
+ },
338
+ recommendations: {
339
+ // Only show recommendation if authMode is explicitly set to an invalid value
340
+ // Default is cloudflare_proxy, so no recommendation needed if not set
341
+ authMode: null, // Defaults to cloudflare_proxy, no warning needed
342
+ cloudflare: null // Pages Functions work without explicit worker URL
343
+ }
344
+ };
345
+ };
346
+ // Export current environment config as default
347
+ getEnvironmentConfig();
348
+ getEnvironmentStatus();
349
+
350
+ /**
351
+ * Integration Configuration
352
+ * Centralized configuration for all third-party integrations
353
+ */
354
+ /**
355
+ * Get integration configuration with environment variable fallbacks
356
+ */
357
+ function getIntegrationConfig() {
358
+ isDevEnvironment();
359
+ return {
360
+ wordpress: {
361
+ enabled: true,
362
+ apiUrl: getEnv('VITE_WORDPRESS_API_URL') || '',
363
+ authMode: getEnv('VITE_AUTH_MODE') || 'direct',
364
+ },
365
+ supabase: {
366
+ enabled: getEnv('VITE_AUTH_MODE') === 'supabase_proxy',
367
+ url: getEnv('VITE_SUPABASE_URL') || '',
368
+ anonKey: getEnv('VITE_SUPABASE_ANON_KEY') || '',
369
+ serviceRoleKey: getEnv('VITE_SUPABASE_SERVICE_ROLE_KEY'),
370
+ },
371
+ gravityForms: {
372
+ enabled: !!(getEnv('VITE_CLOUDFLARE_WORKER_URL') || getEnv('VITE_WORDPRESS_API_URL')),
373
+ url: getEnv('VITE_WORDPRESS_API_URL'),
374
+ proxyUrl: getEnv('VITE_CLOUDFLARE_WORKER_URL'),
375
+ consumerKey: getEnv('VITE_GF_CONSUMER_KEY'),
376
+ consumerSecret: getEnv('VITE_GF_CONSUMER_SECRET'),
377
+ forms: {
378
+ 'contact': {
379
+ name: 'Contact Form',
380
+ type: 'contact',
381
+ successMessage: 'Thank you for your message. We\'ll get back to you soon!',
382
+ },
383
+ 'newsletter': {
384
+ name: 'Newsletter Signup',
385
+ type: 'newsletter',
386
+ successMessage: 'Thank you for subscribing to our newsletter!',
387
+ },
388
+ 'support': {
389
+ name: 'Support Request',
390
+ type: 'support',
391
+ successMessage: 'Your support request has been submitted successfully.',
392
+ },
393
+ },
394
+ },
395
+ mautic: {
396
+ enabled: !!(getEnv('VITE_MAUTIC_PROXY_URL') || getEnv('VITE_MAUTIC_URL')),
397
+ url: getEnv('VITE_MAUTIC_URL') || '',
398
+ proxyUrl: getEnv('VITE_MAUTIC_PROXY_URL'),
399
+ forms: {
400
+ '1': {
401
+ name: 'Newsletter Signup',
402
+ type: 'newsletter',
403
+ successMessage: 'Thank you for subscribing to our newsletter!',
404
+ postAction: 'message',
405
+ postActionProperty: 'Thank you for your submission!'
406
+ },
407
+ '2': {
408
+ name: 'Contact Form',
409
+ type: 'contact',
410
+ successMessage: 'Thank you for contacting us! We\'ll get back to you soon.',
411
+ postAction: 'message',
412
+ postActionProperty: 'Thank you for your message!'
413
+ },
414
+ '3': {
415
+ name: 'Lead Capture',
416
+ type: 'lead',
417
+ successMessage: 'Thank you for your interest! We\'ll be in touch.',
418
+ postAction: 'message',
419
+ postActionProperty: 'Thank you for your submission!'
420
+ }
421
+ }
422
+ },
423
+ suiteCRM: {
424
+ enabled: !!(getEnv('VITE_SUITECRM_URL') && getEnv('VITE_SUITECRM_CLIENT_ID') && getEnv('VITE_SUITECRM_CLIENT_SECRET')),
425
+ url: getEnv('VITE_SUITECRM_URL'),
426
+ proxyUrl: getEnv('VITE_SUITECRM_PROXY_URL'),
427
+ appId: undefined,
428
+ workerSecret: undefined,
429
+ oauth2: {
430
+ clientId: getEnv('VITE_SUITECRM_CLIENT_ID'),
431
+ clientSecret: getEnv('VITE_SUITECRM_CLIENT_SECRET'),
432
+ username: getEnv('VITE_SUITECRM_USERNAME'),
433
+ password: getEnv('VITE_SUITECRM_PASSWORD'),
434
+ },
435
+ },
436
+ chatwoot: {
437
+ enabled: !!(getEnv('VITE_CHATWOOT_WEBSITE_TOKEN') && getEnv('VITE_CHATWOOT_BASE_URL')),
438
+ websiteToken: getEnv('VITE_CHATWOOT_WEBSITE_TOKEN') || '',
439
+ baseUrl: getEnv('VITE_CHATWOOT_BASE_URL') || '',
440
+ locale: 'en',
441
+ type: 'standard',
442
+ launcherTitle: 'Chat',
443
+ hideMessageBubble: false,
444
+ position: 'right',
445
+ showPopoutButton: false,
446
+ },
447
+ analytics: {
448
+ googleAnalytics: { enabled: false },
449
+ googleTagManager: { enabled: false },
450
+ facebookPixel: { enabled: false },
451
+ linkedinInsight: { enabled: false },
452
+ },
453
+ seo: {
454
+ defaultTitle: '',
455
+ defaultDescription: '',
456
+ defaultImage: '',
457
+ siteName: '',
458
+ siteUrl: '',
459
+ },
460
+ performance: {
461
+ enableLazyLoading: true,
462
+ enableImageOptimization: true,
463
+ enableCodeSplitting: true,
464
+ enableServiceWorker: false,
465
+ enableCaching: true,
466
+ }
467
+ };
468
+ }
469
+ function getSuiteCRMConfig() {
470
+ return getIntegrationConfig().suiteCRM;
471
+ }
472
+
473
+ // @ts-nocheck
474
+ /**
475
+ * Password utility functions for handling special characters
476
+ */
477
+ /**
478
+ * Safely encode a password for API requests
479
+ * Handles special characters like $, @, #, %, etc.
480
+ */
481
+ function encodePassword(password) {
482
+ if (!password)
483
+ return '';
484
+ // For JSON requests, we don't need to encode the password
485
+ // The JSON.stringify() will handle special characters properly
486
+ return password;
487
+ }
488
+ /**
489
+ * Validate password for common issues
490
+ */
491
+ function validatePassword(password) {
492
+ const issues = [];
493
+ if (!password) {
494
+ issues.push('Password is empty');
495
+ }
496
+ if (password.length < 1) {
497
+ issues.push('Password is too short');
498
+ }
499
+ // Check for potentially problematic characters
500
+ const problematicChars = ['$', '@', '#', '%', '&', '*', '(', ')', '[', ']', '{', '}', '|', '\\', '`', '~'];
501
+ const foundChars = problematicChars.filter(char => password.includes(char));
502
+ if (foundChars.length > 0) {
503
+ issues.push(`Password contains potentially problematic characters: ${foundChars.join(', ')}`);
504
+ }
505
+ return {
506
+ isValid: issues.length === 0,
507
+ issues
508
+ };
509
+ }
510
+ /**
511
+ * Get password encoding recommendations
512
+ */
513
+ function getPasswordRecommendations(password) {
514
+ const recommendations = [];
515
+ if (password.includes('$')) {
516
+ recommendations.push('The $ character in passwords can cause issues in some contexts. Consider using a different character or ensure proper encoding.');
517
+ }
518
+ if (password.includes('@')) {
519
+ recommendations.push('The @ character might be interpreted as email syntax in some contexts.');
520
+ }
521
+ if (password.includes('#')) {
522
+ recommendations.push('The # character might be interpreted as a URL fragment in some contexts.');
523
+ }
524
+ if (password.includes('%')) {
525
+ recommendations.push('The % character might be interpreted as URL encoding in some contexts.');
526
+ }
527
+ if (password.length < 8) {
528
+ recommendations.push('Consider using a longer password for better security.');
529
+ }
530
+ return recommendations;
531
+ }
532
+
533
+ class SuiteCRMService {
534
+ baseUrl;
535
+ proxyUrl;
536
+ appId;
537
+ workerSecret;
538
+ useProxy;
539
+ isConfigured;
540
+ oauth2Config;
541
+ accessToken;
542
+ tokenExpiresAt;
543
+ sessionId;
544
+ csrfToken;
545
+ constructor() {
546
+ // Get environment configuration
547
+ const envConfig = getEnvironmentConfig();
548
+ const suitecrm = getSuiteCRMConfig();
549
+ if (undefined.DEV) {
550
+ console.log('🔍 SuiteCRM Service Auth Mode Debug:', {
551
+ authMode: envConfig.authMode,
552
+ hasSuiteCRMConfig: !!suitecrm,
553
+ hasEnvSuiteCRM: !!envConfig.suiteCRM,
554
+ suitecrmConfig: suitecrm ? {
555
+ hasUrl: !!suitecrm.url,
556
+ hasProxyUrl: !!suitecrm.proxyUrl,
557
+ hasAppId: !!suitecrm.appId,
558
+ hasWorkerSecret: !!suitecrm.workerSecret,
559
+ hasOAuth2: !!(suitecrm.oauth2?.clientId && suitecrm.oauth2?.clientSecret)
560
+ } : null,
561
+ envSuiteCRM: envConfig.suiteCRM ? {
562
+ hasUrl: !!envConfig.suiteCRM.url,
563
+ hasProxyUrl: !!envConfig.suiteCRM.proxyUrl,
564
+ hasAppId: !!envConfig.suiteCRM.appId,
565
+ hasWorkerSecret: !!envConfig.suiteCRM.workerSecret,
566
+ hasOAuth2: !!(envConfig.suiteCRM.oauth2?.clientId && envConfig.suiteCRM.oauth2?.clientSecret)
567
+ } : null
568
+ });
569
+ }
570
+ if (envConfig.authMode === 'cloudflare_proxy' && (suitecrm || envConfig.suiteCRM)) {
571
+ // Cloudflare Worker proxy (recommended - uses Secrets Store)
572
+ const config = suitecrm || envConfig.suiteCRM;
573
+ this.proxyUrl = config?.proxyUrl;
574
+ this.appId = undefined;
575
+ this.workerSecret = undefined;
576
+ this.useProxy = true;
577
+ this.isConfigured = !!this.proxyUrl;
578
+ if (undefined.DEV) {
579
+ console.log('🔍 SuiteCRM Service: Using Cloudflare Worker proxy (Secrets Store)');
580
+ console.log('🔍 Constructor Debug:', {
581
+ proxyUrl: this.proxyUrl,
582
+ appId: this.appId,
583
+ hasWorkerSecret: !!this.workerSecret,
584
+ workerSecretLength: this.workerSecret?.length,
585
+ isConfigured: this.isConfigured
586
+ });
587
+ }
588
+ }
589
+ else if (envConfig.authMode === 'direct' && (suitecrm || envConfig.suiteCRM)) {
590
+ // Direct SuiteCRM API access with OAuth2
591
+ const config = suitecrm || envConfig.suiteCRM;
592
+ this.baseUrl = config?.url || '';
593
+ this.useProxy = false;
594
+ this.oauth2Config = config?.oauth2;
595
+ this.isConfigured = !!(this.baseUrl && config?.oauth2?.clientId && config?.oauth2?.clientSecret && config?.oauth2?.username && config?.oauth2?.password);
596
+ if (undefined.DEV) {
597
+ console.log('🔍 SuiteCRM Service: Using direct SuiteCRM API access with OAuth2');
598
+ console.log('🔍 OAuth2 Config:', {
599
+ hasClientId: !!this.oauth2Config?.clientId,
600
+ hasClientSecret: !!this.oauth2Config?.clientSecret,
601
+ hasUsername: !!this.oauth2Config?.username,
602
+ hasPassword: !!this.oauth2Config?.password
603
+ });
604
+ }
605
+ }
606
+ else {
607
+ // Fallback or error state
608
+ this.useProxy = false;
609
+ this.isConfigured = false;
610
+ if (undefined.DEV) {
611
+ console.warn('⚠️ SuiteCRM Service: No valid configuration found');
612
+ }
613
+ }
614
+ }
615
+ validateConfiguration() {
616
+ if (!this.isConfigured) {
617
+ throw new Error('SuiteCRM service is not properly configured');
618
+ }
619
+ }
620
+ async getSessionAndCSRFToken() {
621
+ // Check if we have valid session and CSRF token
622
+ if (this.sessionId && this.csrfToken) {
623
+ return { sessionId: this.sessionId, csrfToken: this.csrfToken };
624
+ }
625
+ if (undefined.DEV) {
626
+ console.log('🔍 Starting session and CSRF token extraction...');
627
+ }
628
+ try {
629
+ // Step 1: Get initial session by accessing the login page
630
+ const loginResponse = await fetch(`${this.baseUrl}/`, {
631
+ method: 'GET',
632
+ headers: {
633
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
634
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
635
+ }
636
+ });
637
+ if (undefined.DEV) {
638
+ console.log('📊 Login response status:', loginResponse.status);
639
+ console.log('📊 Login response ok:', loginResponse.ok);
640
+ }
641
+ if (!loginResponse.ok) {
642
+ throw new Error(`Failed to access SuiteCRM login page: ${loginResponse.status}`);
643
+ }
644
+ // Extract session ID from cookies
645
+ const cookies = loginResponse.headers.get('set-cookie');
646
+ if (undefined.DEV) {
647
+ console.log('🍪 Set-Cookie header:', cookies);
648
+ }
649
+ if (cookies) {
650
+ // Try SCRMSESSID first (SuiteCRM specific)
651
+ const sessionMatch = cookies.match(/SCRMSESSID=([^;]+)/);
652
+ if (sessionMatch) {
653
+ this.sessionId = sessionMatch[1];
654
+ if (undefined.DEV) {
655
+ console.log('✅ Found SCRMSESSID:', this.sessionId);
656
+ }
657
+ }
658
+ else {
659
+ if (undefined.DEV) {
660
+ console.log('❌ SCRMSESSID not found in cookies');
661
+ }
662
+ // Fallback to PHPSESSID
663
+ const phpSessionMatch = cookies.match(/PHPSESSID=([^;]+)/);
664
+ if (phpSessionMatch) {
665
+ this.sessionId = phpSessionMatch[1];
666
+ if (undefined.DEV) {
667
+ console.log('✅ Found PHPSESSID:', this.sessionId);
668
+ }
669
+ }
670
+ else {
671
+ if (undefined.DEV) {
672
+ console.log('❌ PHPSESSID not found in cookies');
673
+ }
674
+ }
675
+ }
676
+ }
677
+ else {
678
+ if (undefined.DEV) {
679
+ console.log('❌ No set-cookie header found');
680
+ }
681
+ }
682
+ // Step 2: Extract CSRF token from cookies (XSRF-TOKEN)
683
+ if (cookies) {
684
+ const xsrfMatch = cookies.match(/XSRF-TOKEN=([^;]+)/);
685
+ if (xsrfMatch) {
686
+ this.csrfToken = xsrfMatch[1];
687
+ if (undefined.DEV) {
688
+ console.log('✅ Found XSRF-TOKEN:', this.csrfToken);
689
+ }
690
+ }
691
+ else {
692
+ if (undefined.DEV) {
693
+ console.log('❌ XSRF-TOKEN not found in cookies');
694
+ }
695
+ }
696
+ }
697
+ // If not found in cookies, try HTML
698
+ if (!this.csrfToken) {
699
+ if (undefined.DEV) {
700
+ console.log('🔍 Trying to extract CSRF token from HTML...');
701
+ }
702
+ const loginHtml = await loginResponse.text();
703
+ const csrfMatch = loginHtml.match(/name="csrf_token" value="([^"]+)"/);
704
+ if (csrfMatch) {
705
+ this.csrfToken = csrfMatch[1];
706
+ if (undefined.DEV) {
707
+ console.log('✅ Found csrf_token in HTML:', this.csrfToken);
708
+ }
709
+ }
710
+ else {
711
+ if (undefined.DEV) {
712
+ console.log('❌ csrf_token not found in HTML');
713
+ }
714
+ }
715
+ }
716
+ if (undefined.DEV) {
717
+ console.log('📋 Final Summary:');
718
+ console.log('Session ID:', this.sessionId ? '✅ Found' : '❌ Not found');
719
+ console.log('CSRF Token:', this.csrfToken ? '✅ Found' : '❌ Not found');
720
+ }
721
+ if (!this.sessionId || !this.csrfToken) {
722
+ throw new Error('Failed to obtain session ID or CSRF token from SuiteCRM');
723
+ }
724
+ if (undefined.DEV) {
725
+ console.log('🔑 Session and CSRF token obtained:', {
726
+ sessionId: this.sessionId ? 'SET' : 'NOT SET',
727
+ csrfToken: this.csrfToken ? 'SET' : 'NOT SET'
728
+ });
729
+ }
730
+ return { sessionId: this.sessionId, csrfToken: this.csrfToken };
731
+ }
732
+ catch (error) {
733
+ if (undefined.DEV) {
734
+ console.error('❌ Error in getSessionAndCSRFToken:', error);
735
+ console.error('Error details:', {
736
+ message: error.message,
737
+ stack: error.stack,
738
+ name: error.name
739
+ });
740
+ }
741
+ throw error;
742
+ }
743
+ }
744
+ async getAccessToken() {
745
+ if (!this.oauth2Config) {
746
+ throw new Error('OAuth2 configuration not available');
747
+ }
748
+ // Check if we have a valid cached token
749
+ if (this.accessToken && this.tokenExpiresAt && Date.now() < this.tokenExpiresAt - 300000) {
750
+ return this.accessToken;
751
+ }
752
+ // Request new OAuth2 token using password grant
753
+ const tokenUrl = `${this.baseUrl}/Api/access_token`;
754
+ // Ensure password is properly handled for special characters
755
+ const rawPassword = this.oauth2Config.password || '';
756
+ const password = encodePassword(rawPassword);
757
+ // Validate password and log warnings
758
+ const passwordValidation = validatePassword(rawPassword);
759
+ if (!passwordValidation.isValid && undefined.DEV) {
760
+ console.warn('⚠️ Password validation issues:', passwordValidation.issues);
761
+ }
762
+ const recommendations = getPasswordRecommendations(rawPassword);
763
+ if (recommendations.length > 0 && undefined.DEV) {
764
+ console.warn('💡 Password recommendations:', recommendations);
765
+ }
766
+ const tokenData = {
767
+ grant_type: 'password',
768
+ client_id: this.oauth2Config.clientId,
769
+ client_secret: this.oauth2Config.clientSecret,
770
+ username: this.oauth2Config.username,
771
+ password: password,
772
+ };
773
+ if (undefined.DEV) {
774
+ console.log('🔑 Requesting OAuth2 token with password grant:', {
775
+ tokenUrl,
776
+ clientId: tokenData.client_id,
777
+ username: tokenData.username,
778
+ hasPassword: !!tokenData.password
779
+ });
780
+ }
781
+ const response = await fetch(tokenUrl, {
782
+ method: 'POST',
783
+ headers: {
784
+ 'Content-Type': 'application/json',
785
+ },
786
+ body: JSON.stringify(tokenData),
787
+ });
788
+ if (!response.ok) {
789
+ const errorText = await response.text();
790
+ throw new Error(`OAuth2 token request failed: ${response.status} - ${errorText}`);
791
+ }
792
+ const tokenResponse = await response.json();
793
+ if (undefined.DEV) {
794
+ console.log('🔑 OAuth2 token obtained:', {
795
+ tokenType: tokenResponse.token_type,
796
+ expiresIn: tokenResponse.expires_in
797
+ });
798
+ }
799
+ this.accessToken = tokenResponse.access_token || '';
800
+ this.tokenExpiresAt = Date.now() + (tokenResponse.expires_in * 1000);
801
+ return this.accessToken || '';
802
+ }
803
+ async getAuthHeaders() {
804
+ if (this.useProxy) {
805
+ return {
806
+ 'Content-Type': 'application/json'
807
+ };
808
+ }
809
+ else {
810
+ // Direct SuiteCRM API access with OAuth2 Bearer token
811
+ if (this.oauth2Config) {
812
+ const accessToken = await this.getAccessToken();
813
+ return {
814
+ 'Content-Type': 'application/json',
815
+ 'Accept': 'application/json',
816
+ 'Authorization': `Bearer ${accessToken}`
817
+ };
818
+ }
819
+ else {
820
+ // Fallback to session-based authentication
821
+ const { sessionId, csrfToken } = await this.getSessionAndCSRFToken();
822
+ return {
823
+ 'Content-Type': 'application/json',
824
+ 'Accept': 'application/json',
825
+ 'X-CSRF-Token': csrfToken,
826
+ 'Cookie': `SCRMSESSID=${sessionId}`
827
+ };
828
+ }
829
+ }
830
+ }
831
+ async makeRequest(endpoint, options = {}) {
832
+ this.validateConfiguration();
833
+ const headers = {
834
+ ...(await this.getAuthHeaders()),
835
+ ...options.headers
836
+ };
837
+ let url;
838
+ if (this.useProxy) {
839
+ // Use Cloudflare Worker proxy - pass endpoint as query parameter
840
+ const queryParams = new URLSearchParams({ endpoint });
841
+ url = `${this.proxyUrl}?${queryParams.toString()}`;
842
+ }
843
+ else {
844
+ // Direct SuiteCRM API access
845
+ const directUrl = `${this.baseUrl}/api${endpoint}`;
846
+ const localDev = undefined.VITE_LOCAL_DEVELOPMENT === 'true';
847
+ const shouldUseAccess = !localDev && undefined.VITE_AUTH_MODE === 'direct'
848
+ && !!undefined.VITE_CF_ACCESS_CLIENT_ID && !!undefined.VITE_CF_ACCESS_CLIENT_SECRET;
849
+ url = shouldUseAccess ? `/api/fetch-with-access?target=${encodeURIComponent(directUrl)}` : directUrl;
850
+ }
851
+ if (undefined.DEV) {
852
+ console.log(`🔗 SuiteCRM API Request: ${options.method || 'GET'} ${url}`);
853
+ console.log('🔑 Headers:', Object.keys(headers).map(k => `${k}: ${headers[k] ? '***' : 'undefined'}`).join(', '));
854
+ }
855
+ const response = await fetch(url, {
856
+ ...options,
857
+ headers
858
+ });
859
+ if (!response.ok) {
860
+ const errorText = await response.text();
861
+ console.error('❌ SuiteCRM API Error:', response.status, errorText);
862
+ throw new Error(`SuiteCRM API error: ${response.status} - ${errorText}`);
863
+ }
864
+ const data = await response.json();
865
+ if (undefined.DEV) {
866
+ console.log('✅ SuiteCRM API Response:', data);
867
+ }
868
+ return data;
869
+ }
870
+ // Check if SuiteCRM is configured
871
+ isServiceConfigured() {
872
+ return this.isConfigured;
873
+ }
874
+ // Lead Management
875
+ async createLead(lead) {
876
+ if (undefined.DEV) {
877
+ console.log('🚀 Starting lead creation for:', lead.first_name, lead.last_name);
878
+ }
879
+ // Use REST API by default since GraphQL has permission issues
880
+ // GraphQL can be used later when permissions are resolved
881
+ return this.createLeadREST(lead);
882
+ }
883
+ async createLeadREST(lead) {
884
+ if (undefined.DEV) {
885
+ console.log('🔍 Attempting REST API lead creation...');
886
+ }
887
+ const leadData = {
888
+ data: {
889
+ type: 'Leads',
890
+ attributes: {
891
+ first_name: lead.first_name,
892
+ last_name: lead.last_name,
893
+ email1: lead.email1,
894
+ phone_work: lead.phone_work,
895
+ lead_source: lead.lead_source || 'Website',
896
+ status: lead.status || 'New',
897
+ description: lead.description,
898
+ campaign_id: lead.campaign_id
899
+ }
900
+ }
901
+ };
902
+ const result = await this.makeRequest('/legacy/Api/V8/module', {
903
+ method: 'POST',
904
+ body: JSON.stringify(leadData),
905
+ });
906
+ if (undefined.DEV) {
907
+ console.log('✅ REST API lead creation successful:', result);
908
+ }
909
+ return result;
910
+ }
911
+ async verifyLeadCreation(email, firstName, lastName) {
912
+ try {
913
+ if (undefined.DEV) {
914
+ console.log('🔍 Verifying lead creation for:', email);
915
+ }
916
+ // Try to get all leads and filter client-side since SuiteCRM filter syntax is complex
917
+ const result = await this.makeRequest('/legacy/Api/V8/module/Leads');
918
+ if (result.data && Array.isArray(result.data)) {
919
+ // Check if any lead matches the email and name
920
+ const matchingLead = result.data.find((lead) => lead.attributes?.email1 === email &&
921
+ lead.attributes?.first_name === firstName &&
922
+ lead.attributes?.last_name === lastName);
923
+ if (undefined.DEV) {
924
+ console.log('✅ Lead verification result:', !!matchingLead);
925
+ console.log('🔍 Found leads:', result.data.length);
926
+ console.log('🔍 Matching lead:', matchingLead);
927
+ }
928
+ return !!matchingLead;
929
+ }
930
+ return false;
931
+ }
932
+ catch (error) {
933
+ if (undefined.DEV) {
934
+ console.log('❌ Lead verification failed:', error);
935
+ }
936
+ return false;
937
+ }
938
+ }
939
+ async getLead(leadId) {
940
+ return this.makeRequest(`/legacy/Api/V8/module/Leads/${leadId}`);
941
+ }
942
+ async updateLead(leadId, lead) {
943
+ const leadData = {
944
+ data: {
945
+ type: 'Leads',
946
+ id: leadId,
947
+ attributes: lead
948
+ }
949
+ };
950
+ return this.makeRequest(`/legacy/Api/V8/module/Leads/${leadId}`, {
951
+ method: 'PATCH',
952
+ body: JSON.stringify(leadData),
953
+ });
954
+ }
955
+ async getLeads(params = {}) {
956
+ const queryParams = new URLSearchParams();
957
+ if (params.page)
958
+ queryParams.append('page[number]', params.page.toString());
959
+ if (params.per_page)
960
+ queryParams.append('page[size]', params.per_page.toString());
961
+ if (params.filter)
962
+ queryParams.append('filter', params.filter);
963
+ if (params.sort)
964
+ queryParams.append('sort', params.sort);
965
+ const queryString = queryParams.toString();
966
+ const endpoint = queryString ? `/legacy/Api/V8/module/Leads?${queryString}` : '/legacy/Api/V8/module/Leads';
967
+ return this.makeRequest(endpoint);
968
+ }
969
+ // Contact Management
970
+ async createContact(contact) {
971
+ const contactData = {
972
+ data: {
973
+ type: 'Contacts',
974
+ attributes: contact
975
+ }
976
+ };
977
+ return this.makeRequest('/contacts', {
978
+ method: 'POST',
979
+ body: JSON.stringify(contactData),
980
+ });
981
+ }
982
+ async getContact(contactId) {
983
+ return this.makeRequest(`/contacts/${contactId}`);
984
+ }
985
+ async updateContact(contactId, contact) {
986
+ const contactData = {
987
+ data: {
988
+ type: 'Contacts',
989
+ id: contactId,
990
+ attributes: contact
991
+ }
992
+ };
993
+ return this.makeRequest(`/contacts/${contactId}`, {
994
+ method: 'PATCH',
995
+ body: JSON.stringify(contactData),
996
+ });
997
+ }
998
+ // Account Management
999
+ async createAccount(account) {
1000
+ const accountData = {
1001
+ data: {
1002
+ type: 'Accounts',
1003
+ attributes: account
1004
+ }
1005
+ };
1006
+ return this.makeRequest('/accounts', {
1007
+ method: 'POST',
1008
+ body: JSON.stringify(accountData),
1009
+ });
1010
+ }
1011
+ async getAccount(accountId) {
1012
+ return this.makeRequest(`/accounts/${accountId}`);
1013
+ }
1014
+ // Opportunity Management
1015
+ async createOpportunity(opportunity) {
1016
+ const opportunityData = {
1017
+ data: {
1018
+ type: 'Opportunities',
1019
+ attributes: opportunity
1020
+ }
1021
+ };
1022
+ return this.makeRequest('/opportunities', {
1023
+ method: 'POST',
1024
+ body: JSON.stringify(opportunityData),
1025
+ });
1026
+ }
1027
+ async getOpportunity(opportunityId) {
1028
+ return this.makeRequest(`/opportunities/${opportunityId}`);
1029
+ }
1030
+ // Search and Filter
1031
+ async searchLeads(query) {
1032
+ const searchParams = new URLSearchParams({
1033
+ filter: `contains(first_name,"${query}") or contains(last_name,"${query}") or contains(email1,"${query}")`
1034
+ });
1035
+ return this.makeRequest(`/legacy/Api/V8/module/Leads?${searchParams.toString()}`);
1036
+ }
1037
+ // Utility Methods
1038
+ async getLeadSources() {
1039
+ return this.makeRequest('/enum/lead_sources');
1040
+ }
1041
+ async getLeadStatuses() {
1042
+ return this.makeRequest('/enum/lead_statuses');
1043
+ }
1044
+ // GraphQL support for SuiteCRM 8.8.0+
1045
+ async graphqlRequest(query, variables) {
1046
+ this.validateConfiguration();
1047
+ const headers = await this.getAuthHeaders();
1048
+ // Use the correct endpoint based on mode
1049
+ let url;
1050
+ if (this.useProxy) {
1051
+ // Use Cloudflare Worker proxy for GraphQL - pass endpoint as query parameter
1052
+ const queryParams = new URLSearchParams({ endpoint: '/Api/graphql' });
1053
+ url = `${this.proxyUrl}?${queryParams.toString()}`;
1054
+ }
1055
+ else {
1056
+ // Direct SuiteCRM API access
1057
+ url = `${this.baseUrl}/Api/graphql`;
1058
+ }
1059
+ if (undefined.DEV) {
1060
+ console.log(`🔗 SuiteCRM GraphQL Request: POST ${url}`);
1061
+ console.log('📝 Query:', query);
1062
+ console.log('🔧 Variables:', variables);
1063
+ console.log('🔑 Headers:', Object.keys(headers).map(k => `${k}: ${headers[k] ? '***' : 'undefined'}`).join(', '));
1064
+ }
1065
+ const response = await fetch(url, {
1066
+ method: 'POST',
1067
+ headers,
1068
+ body: JSON.stringify({
1069
+ query,
1070
+ variables
1071
+ })
1072
+ });
1073
+ if (!response.ok) {
1074
+ const errorText = await response.text();
1075
+ console.error('❌ SuiteCRM GraphQL Error:', response.status, errorText);
1076
+ throw new Error(`SuiteCRM GraphQL error: ${response.status} - ${errorText}`);
1077
+ }
1078
+ const data = await response.json();
1079
+ if (undefined.DEV) {
1080
+ console.log('✅ SuiteCRM GraphQL Response:', data);
1081
+ }
1082
+ return data;
1083
+ }
1084
+ // Test connection with GraphQL
1085
+ async testConnection() {
1086
+ try {
1087
+ // Try GraphQL first
1088
+ const graphqlQuery = `
1089
+ query TestConnection {
1090
+ __schema {
1091
+ types {
1092
+ name
1093
+ }
1094
+ }
1095
+ }
1096
+ `;
1097
+ await this.graphqlRequest(graphqlQuery);
1098
+ return true;
1099
+ }
1100
+ catch (graphqlError) {
1101
+ console.log('GraphQL test failed, trying REST API:', graphqlError);
1102
+ try {
1103
+ // Fallback to REST API
1104
+ await this.makeRequest('/leads?page[size]=1');
1105
+ return true;
1106
+ }
1107
+ catch (restError) {
1108
+ console.error('❌ SuiteCRM connection test failed:', { graphqlError, restError });
1109
+ return false;
1110
+ }
1111
+ }
1112
+ }
1113
+ // Simple Lead Creation - Create new lead directly
1114
+ async createLeadSimple(leadData) {
1115
+ if (undefined.DEV) {
1116
+ console.log('🚀 Creating new lead for:', leadData.first_name, leadData.last_name, leadData.email1);
1117
+ }
1118
+ try {
1119
+ const result = await this.createLeadREST(leadData);
1120
+ if (undefined.DEV) {
1121
+ console.log('✅ Lead created successfully');
1122
+ }
1123
+ return {
1124
+ data: result.data,
1125
+ meta: { action: 'lead_created' }
1126
+ };
1127
+ }
1128
+ catch (error) {
1129
+ if (undefined.DEV) {
1130
+ console.error('❌ Lead creation failed:', error);
1131
+ }
1132
+ throw error;
1133
+ }
1134
+ }
1135
+ }
1136
+ let suitecrmServiceInstance = null;
1137
+ const getSuiteCRMService = () => {
1138
+ if (!suitecrmServiceInstance) {
1139
+ suitecrmServiceInstance = new SuiteCRMService();
1140
+ }
1141
+ return suitecrmServiceInstance;
1142
+ };
1143
+ const resetSuiteCRMService = () => {
1144
+ suitecrmServiceInstance = null;
1145
+ };
1146
+ const suitecrmService = createLazyProxy(getSuiteCRMService);
1147
+
1148
+ /**
1149
+ * Cloudflare WordPress Webhook Service
1150
+ * Handles webhook status and data regeneration monitoring
1151
+ */
1152
+ class CfWpWebhookService {
1153
+ baseUrl;
1154
+ refreshSecret;
1155
+ constructor() {
1156
+ this.baseUrl = getEnv('VITE_WORDPRESS_API_URL') || '';
1157
+ this.refreshSecret = getEnv('VITE_WP_REFRESH_SECRET') || '';
1158
+ if (!this.refreshSecret) {
1159
+ console.warn('⚠️ VITE_WP_REFRESH_SECRET not configured - webhook authentication will fail');
1160
+ }
1161
+ }
1162
+ /**
1163
+ * Get webhook status from WordPress
1164
+ */
1165
+ async getWebhookStatus() {
1166
+ try {
1167
+ const response = await fetch(`${this.baseUrl}/wp-json/cf-wp/v1/webhook-status`, {
1168
+ method: 'GET',
1169
+ headers: {
1170
+ 'Content-Type': 'application/json',
1171
+ 'Authorization': `Bearer ${this.refreshSecret}`
1172
+ }
1173
+ });
1174
+ if (!response.ok) {
1175
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1176
+ }
1177
+ return await response.json();
1178
+ }
1179
+ catch (error) {
1180
+ console.error('Failed to fetch webhook status:', error);
1181
+ return {
1182
+ last_triggered: null,
1183
+ last_success: null,
1184
+ last_error: error instanceof Error ? error.message : 'Unknown error',
1185
+ total_triggers: 0,
1186
+ success_rate: 0,
1187
+ status: 'error'
1188
+ };
1189
+ }
1190
+ }
1191
+ /**
1192
+ * Get webhook configuration from WordPress
1193
+ */
1194
+ async getWebhookConfig() {
1195
+ try {
1196
+ const response = await fetch(`${this.baseUrl}/wp-json/cf-wp/v1/webhook-config`, {
1197
+ method: 'GET',
1198
+ headers: {
1199
+ 'Content-Type': 'application/json',
1200
+ 'Authorization': `Bearer ${this.refreshSecret}`
1201
+ }
1202
+ });
1203
+ if (!response.ok) {
1204
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1205
+ }
1206
+ return await response.json();
1207
+ }
1208
+ catch (error) {
1209
+ console.error('Failed to fetch webhook config:', error);
1210
+ return null;
1211
+ }
1212
+ }
1213
+ /**
1214
+ * Test webhook endpoint
1215
+ */
1216
+ async testWebhook() {
1217
+ try {
1218
+ const testPayload = {
1219
+ post_id: 0,
1220
+ post_type: 'test',
1221
+ action: 'test',
1222
+ timestamp: new Date().toISOString(),
1223
+ frontend_id: 'test'
1224
+ };
1225
+ const response = await fetch('/api/webhook-test', {
1226
+ method: 'POST',
1227
+ headers: {
1228
+ 'Content-Type': 'application/json',
1229
+ 'Authorization': `Bearer ${this.refreshSecret}`
1230
+ },
1231
+ body: JSON.stringify(testPayload)
1232
+ });
1233
+ if (!response.ok) {
1234
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1235
+ }
1236
+ const result = await response.json();
1237
+ return {
1238
+ success: result.success,
1239
+ message: result.message || 'Webhook test completed'
1240
+ };
1241
+ }
1242
+ catch (error) {
1243
+ console.error('Webhook test failed:', error);
1244
+ return {
1245
+ success: false,
1246
+ message: error instanceof Error ? error.message : 'Unknown error'
1247
+ };
1248
+ }
1249
+ }
1250
+ /**
1251
+ * Get last webhook response
1252
+ */
1253
+ async getLastWebhookResponse() {
1254
+ try {
1255
+ const response = await fetch('/webhook-response.json');
1256
+ if (!response.ok) {
1257
+ return null;
1258
+ }
1259
+ return await response.json();
1260
+ }
1261
+ catch (error) {
1262
+ console.error('Failed to fetch last webhook response:', error);
1263
+ return null;
1264
+ }
1265
+ }
1266
+ /**
1267
+ * Manually trigger data regeneration
1268
+ */
1269
+ async triggerDataRegeneration() {
1270
+ try {
1271
+ const response = await fetch('/api/trigger-regeneration', {
1272
+ method: 'POST',
1273
+ headers: {
1274
+ 'Content-Type': 'application/json',
1275
+ 'Authorization': `Bearer ${this.refreshSecret}`
1276
+ }
1277
+ });
1278
+ if (!response.ok) {
1279
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1280
+ }
1281
+ const result = await response.json();
1282
+ return {
1283
+ success: result.success,
1284
+ message: result.message || 'Data regeneration triggered'
1285
+ };
1286
+ }
1287
+ catch (error) {
1288
+ console.error('Failed to trigger data regeneration:', error);
1289
+ return {
1290
+ success: false,
1291
+ message: error instanceof Error ? error.message : 'Unknown error'
1292
+ };
1293
+ }
1294
+ }
1295
+ /**
1296
+ * Check if webhook is properly configured
1297
+ */
1298
+ isConfigured() {
1299
+ return !!(this.baseUrl && this.refreshSecret);
1300
+ }
1301
+ /**
1302
+ * Get configuration status
1303
+ */
1304
+ getConfigurationStatus() {
1305
+ return {
1306
+ wordpress_url: !!this.baseUrl,
1307
+ refresh_secret: !!this.refreshSecret,
1308
+ fully_configured: !!(this.baseUrl && this.refreshSecret)
1309
+ };
1310
+ }
1311
+ }
1312
+ let cfWpWebhookServiceInstance = null;
1313
+ const getCfWpWebhookService = () => {
1314
+ if (!cfWpWebhookServiceInstance) {
1315
+ cfWpWebhookServiceInstance = new CfWpWebhookService();
1316
+ }
1317
+ return cfWpWebhookServiceInstance;
1318
+ };
1319
+ const resetCfWpWebhookService = () => {
1320
+ cfWpWebhookServiceInstance = null;
1321
+ };
1322
+ const cfWpWebhookService = createLazyProxy(getCfWpWebhookService);
1323
+
1324
+ exports.cfWpWebhookService = cfWpWebhookService;
1325
+ exports.createGravityFormsClient = createGravityFormsClient;
1326
+ exports.createMauticService = createMauticService;
1327
+ exports.getCfWpWebhookService = getCfWpWebhookService;
1328
+ exports.getGravityFormsClient = getGravityFormsClient;
1329
+ exports.getMauticService = getMauticService;
1330
+ exports.getSuiteCRMService = getSuiteCRMService;
1331
+ exports.gravityFormsClient = gravityFormsClient;
1332
+ exports.gravityFormsService = gravityFormsService;
1333
+ exports.mauticService = mauticService;
1334
+ exports.resetCfWpWebhookService = resetCfWpWebhookService;
1335
+ exports.resetGravityFormsClient = resetGravityFormsClient;
1336
+ exports.resetMauticService = resetMauticService;
1337
+ exports.resetSuiteCRMService = resetSuiteCRMService;
1338
+ exports.suitecrmService = suitecrmService;
1339
+ //# sourceMappingURL=services.cjs.map