@weldsuite/helpdesk-widget-sdk 1.0.2

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.
@@ -0,0 +1,2479 @@
1
+ import { useRef, useEffect, useCallback } from 'react';
2
+
3
+ /**
4
+ * Weld SDK - Configuration Types
5
+ * Type definitions for SDK initialization and configuration
6
+ */
7
+ /**
8
+ * Default configuration values
9
+ */
10
+ const DEFAULT_CONFIG = {
11
+ api: {
12
+ baseUrl: 'https://weldsuite-helpdesk-widget.vercel.app',
13
+ widgetId: '',
14
+ timeout: 30000,
15
+ retries: 3,
16
+ },
17
+ iframes: {
18
+ launcher: {
19
+ url: '/widget?mode=launcher',
20
+ name: 'weld-launcher-frame',
21
+ position: { bottom: '24px', right: '24px' },
22
+ size: '60px',
23
+ },
24
+ widget: {
25
+ url: '/widget?mode=widget',
26
+ name: 'weld-widget-frame',
27
+ position: { bottom: '100px', right: '24px' },
28
+ width: '400px',
29
+ height: 'min(680px, calc(100vh - 120px))',
30
+ },
31
+ backdrop: {
32
+ enabled: true,
33
+ closeOnClick: true,
34
+ },
35
+ },
36
+ customization: {
37
+ primaryColor: '#000000',
38
+ accentColor: '#3b82f6',
39
+ backgroundColor: '#ffffff',
40
+ textColor: '#111827',
41
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
42
+ fontSize: '14px',
43
+ borderRadius: '12px',
44
+ },
45
+ features: {
46
+ attachments: true,
47
+ reactions: true,
48
+ typing: true,
49
+ readReceipts: true,
50
+ offlineMode: false,
51
+ fileUpload: true,
52
+ imageUpload: true,
53
+ voiceMessages: false,
54
+ videoMessages: false,
55
+ },
56
+ mobile: {
57
+ fullScreen: true,
58
+ scrollLock: true,
59
+ keyboardHandling: 'auto',
60
+ safeAreaInsets: true,
61
+ },
62
+ auth: {
63
+ enabled: true,
64
+ mode: 'anonymous',
65
+ },
66
+ locale: {
67
+ locale: 'en',
68
+ dateFormat: 'MMM dd, yyyy',
69
+ timeFormat: 'HH:mm',
70
+ },
71
+ logging: {
72
+ enabled: true,
73
+ level: 'warn',
74
+ prefix: '[Weld]',
75
+ includeTimestamp: true,
76
+ },
77
+ performance: {
78
+ lazyLoad: true,
79
+ preload: false,
80
+ caching: true,
81
+ prefetch: false,
82
+ },
83
+ security: {
84
+ allowedOrigins: [],
85
+ validateMessages: true,
86
+ sanitizeInput: true,
87
+ },
88
+ };
89
+ /**
90
+ * Configuration validation
91
+ */
92
+ function validateConfig(config) {
93
+ if (!config.widgetId || typeof config.widgetId !== 'string') {
94
+ throw new Error('WeldConfig: widgetId is required and must be a string');
95
+ }
96
+ return true;
97
+ }
98
+ /**
99
+ * Merge configuration with defaults
100
+ */
101
+ function resolveConfig(config) {
102
+ validateConfig(config);
103
+ return {
104
+ widgetId: config.widgetId,
105
+ api: {
106
+ ...DEFAULT_CONFIG.api,
107
+ widgetId: config.widgetId,
108
+ ...config.api,
109
+ },
110
+ iframes: {
111
+ launcher: {
112
+ ...DEFAULT_CONFIG.iframes.launcher,
113
+ ...config.iframes?.launcher,
114
+ position: {
115
+ ...DEFAULT_CONFIG.iframes.launcher.position,
116
+ ...config.position?.launcher,
117
+ ...config.iframes?.launcher?.position,
118
+ },
119
+ },
120
+ widget: {
121
+ ...DEFAULT_CONFIG.iframes.widget,
122
+ ...config.iframes?.widget,
123
+ position: {
124
+ ...DEFAULT_CONFIG.iframes.widget.position,
125
+ ...config.position?.widget,
126
+ ...config.iframes?.widget?.position,
127
+ },
128
+ },
129
+ backdrop: {
130
+ ...DEFAULT_CONFIG.iframes.backdrop,
131
+ ...config.iframes?.backdrop,
132
+ },
133
+ },
134
+ customization: {
135
+ ...DEFAULT_CONFIG.customization,
136
+ ...config.customization,
137
+ },
138
+ features: {
139
+ ...DEFAULT_CONFIG.features,
140
+ ...config.features,
141
+ },
142
+ mobile: {
143
+ ...DEFAULT_CONFIG.mobile,
144
+ ...config.mobile,
145
+ },
146
+ auth: {
147
+ ...DEFAULT_CONFIG.auth,
148
+ ...config.auth,
149
+ },
150
+ locale: {
151
+ ...DEFAULT_CONFIG.locale,
152
+ ...config.locale,
153
+ },
154
+ logging: {
155
+ ...DEFAULT_CONFIG.logging,
156
+ ...config.logging,
157
+ },
158
+ performance: {
159
+ ...DEFAULT_CONFIG.performance,
160
+ ...config.performance,
161
+ },
162
+ security: {
163
+ ...DEFAULT_CONFIG.security,
164
+ ...config.security,
165
+ },
166
+ // Pass through callbacks
167
+ onReady: config.onReady,
168
+ onError: config.onError,
169
+ onOpen: config.onOpen,
170
+ onClose: config.onClose,
171
+ onMessage: config.onMessage,
172
+ onMinimize: config.onMinimize,
173
+ onMaximize: config.onMaximize,
174
+ onDestroy: config.onDestroy,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Weld SDK - Logger Utility
180
+ * Centralized logging with configurable levels and formatting
181
+ */
182
+ /**
183
+ * Log levels
184
+ */
185
+ var LogLevel;
186
+ (function (LogLevel) {
187
+ LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
188
+ LogLevel[LogLevel["INFO"] = 1] = "INFO";
189
+ LogLevel[LogLevel["WARN"] = 2] = "WARN";
190
+ LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
191
+ LogLevel[LogLevel["SILENT"] = 4] = "SILENT";
192
+ })(LogLevel || (LogLevel = {}));
193
+ /**
194
+ * Logger class
195
+ */
196
+ class Logger {
197
+ constructor(config) {
198
+ this.config = {
199
+ enabled: true,
200
+ level: 'warn',
201
+ prefix: '[Weld]',
202
+ includeTimestamp: true,
203
+ ...config,
204
+ };
205
+ this.level = this.getLevelFromString(this.config.level);
206
+ }
207
+ /**
208
+ * Convert string level to LogLevel enum
209
+ */
210
+ getLevelFromString(level) {
211
+ switch (level.toLowerCase()) {
212
+ case 'debug':
213
+ return LogLevel.DEBUG;
214
+ case 'info':
215
+ return LogLevel.INFO;
216
+ case 'warn':
217
+ return LogLevel.WARN;
218
+ case 'error':
219
+ return LogLevel.ERROR;
220
+ default:
221
+ return LogLevel.WARN;
222
+ }
223
+ }
224
+ /**
225
+ * Check if logging is enabled for level
226
+ */
227
+ shouldLog(level) {
228
+ return (this.config.enabled ?? true) && level >= this.level;
229
+ }
230
+ /**
231
+ * Format log message
232
+ */
233
+ format(level, message, _data) {
234
+ const parts = [];
235
+ if (this.config.prefix) {
236
+ parts.push(this.config.prefix);
237
+ }
238
+ if (this.config.includeTimestamp) {
239
+ const timestamp = new Date().toISOString();
240
+ parts.push(`[${timestamp}]`);
241
+ }
242
+ parts.push(`[${level.toUpperCase()}]`);
243
+ parts.push(message);
244
+ return parts;
245
+ }
246
+ /**
247
+ * Log debug message
248
+ */
249
+ debug(message, data) {
250
+ if (!this.shouldLog(LogLevel.DEBUG))
251
+ return;
252
+ const formatted = this.format('debug', message, data);
253
+ if (data !== undefined) {
254
+ console.debug(...formatted, data);
255
+ }
256
+ else {
257
+ console.debug(...formatted);
258
+ }
259
+ }
260
+ /**
261
+ * Log info message
262
+ */
263
+ info(message, data) {
264
+ if (!this.shouldLog(LogLevel.INFO))
265
+ return;
266
+ const formatted = this.format('info', message, data);
267
+ if (data !== undefined) {
268
+ console.info(...formatted, data);
269
+ }
270
+ else {
271
+ console.info(...formatted);
272
+ }
273
+ }
274
+ /**
275
+ * Log warning message
276
+ */
277
+ warn(message, data) {
278
+ if (!this.shouldLog(LogLevel.WARN))
279
+ return;
280
+ const formatted = this.format('warn', message, data);
281
+ if (data !== undefined) {
282
+ console.warn(...formatted, data);
283
+ }
284
+ else {
285
+ console.warn(...formatted);
286
+ }
287
+ }
288
+ /**
289
+ * Log error message
290
+ */
291
+ error(message, error) {
292
+ if (!this.shouldLog(LogLevel.ERROR))
293
+ return;
294
+ const formatted = this.format('error', message);
295
+ if (error !== undefined) {
296
+ if (error instanceof Error) {
297
+ console.error(...formatted, error.message, error.stack);
298
+ }
299
+ else {
300
+ console.error(...formatted, error);
301
+ }
302
+ }
303
+ else {
304
+ console.error(...formatted);
305
+ }
306
+ }
307
+ /**
308
+ * Create child logger with prefix
309
+ */
310
+ child(prefix) {
311
+ return new Logger({
312
+ ...this.config,
313
+ prefix: `${this.config.prefix} ${prefix}`,
314
+ });
315
+ }
316
+ /**
317
+ * Update log level at runtime
318
+ */
319
+ setLevel(level) {
320
+ this.config.level = level;
321
+ this.level = this.getLevelFromString(level);
322
+ }
323
+ /**
324
+ * Enable/disable logging
325
+ */
326
+ setEnabled(enabled) {
327
+ this.config.enabled = enabled;
328
+ }
329
+ }
330
+ /**
331
+ * Default logger instance
332
+ */
333
+ new Logger({
334
+ enabled: true,
335
+ level: 'warn',
336
+ prefix: '[Weld]',
337
+ includeTimestamp: true,
338
+ });
339
+
340
+ /**
341
+ * Weld SDK - Iframe Manager
342
+ * Manages creation, lifecycle, and communication with multiple iframes
343
+ */
344
+ /**
345
+ * Iframe types
346
+ */
347
+ var IframeType;
348
+ (function (IframeType) {
349
+ IframeType["LAUNCHER"] = "launcher";
350
+ IframeType["WIDGET"] = "widget";
351
+ IframeType["BACKDROP"] = "backdrop";
352
+ })(IframeType || (IframeType = {}));
353
+ /**
354
+ * Device detection result
355
+ */
356
+ function detectDevice() {
357
+ const width = window.innerWidth;
358
+ const height = window.innerHeight;
359
+ const userAgent = navigator.userAgent;
360
+ const isMobile = width < 768;
361
+ const isTablet = width >= 768 && width <= 1024;
362
+ const isDesktop = width > 1024;
363
+ const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
364
+ return {
365
+ type: isMobile ? 'mobile' : isTablet ? 'tablet' : 'desktop',
366
+ isMobile,
367
+ isTablet,
368
+ isDesktop,
369
+ isTouchDevice,
370
+ screenWidth: width,
371
+ screenHeight: height,
372
+ orientation: width > height ? 'landscape' : 'portrait',
373
+ userAgent,
374
+ };
375
+ }
376
+ /**
377
+ * IframeManager class
378
+ * Orchestrates multiple iframes for the widget system
379
+ */
380
+ class IframeManager {
381
+ constructor(config) {
382
+ this.iframes = new Map();
383
+ this.rootContainer = null;
384
+ this.appContainer = null;
385
+ this.modalContainer = null;
386
+ this.styleElement = null;
387
+ this.config = config;
388
+ this.logger = new Logger(config.logging);
389
+ this.deviceInfo = detectDevice();
390
+ this.logger.info('IframeManager initialized', {
391
+ device: this.deviceInfo.type,
392
+ mobile: this.deviceInfo.isMobile,
393
+ });
394
+ }
395
+ /**
396
+ * Initialize all containers and iframes
397
+ */
398
+ async init() {
399
+ this.logger.debug('Initializing iframe manager');
400
+ try {
401
+ // Create root container structure
402
+ this.createRootContainer();
403
+ // Inject CSS
404
+ this.injectCSS();
405
+ // Create iframes
406
+ await this.createLauncherIframe();
407
+ await this.createBackdropIframe();
408
+ await this.createWidgetIframe();
409
+ // Setup event listeners
410
+ this.setupEventListeners();
411
+ this.logger.info('IframeManager initialized successfully');
412
+ }
413
+ catch (error) {
414
+ this.logger.error('Failed to initialize IframeManager', error);
415
+ throw error;
416
+ }
417
+ }
418
+ /**
419
+ * Create root container structure
420
+ */
421
+ createRootContainer() {
422
+ // Check if already exists
423
+ let existingContainer = document.getElementById('weld-container');
424
+ if (existingContainer) {
425
+ this.logger.warn('Weld container already exists, removing old instance');
426
+ existingContainer.remove();
427
+ }
428
+ // Create root container
429
+ this.rootContainer = document.createElement('div');
430
+ this.rootContainer.id = 'weld-container';
431
+ this.rootContainer.className = 'weld-namespace';
432
+ // Create app container
433
+ this.appContainer = document.createElement('div');
434
+ this.appContainer.className = 'weld-app';
435
+ this.appContainer.setAttribute('aria-live', 'polite');
436
+ this.appContainer.setAttribute('role', 'complementary');
437
+ this.appContainer.setAttribute('aria-label', 'Weld Helpdesk Widget');
438
+ // Create modal container
439
+ this.modalContainer = document.createElement('div');
440
+ this.modalContainer.id = 'weld-modal-container';
441
+ // Assemble structure
442
+ this.rootContainer.appendChild(this.appContainer);
443
+ this.rootContainer.appendChild(this.modalContainer);
444
+ document.body.appendChild(this.rootContainer);
445
+ this.logger.debug('Root container created');
446
+ }
447
+ /**
448
+ * Inject CSS into the page
449
+ */
450
+ injectCSS() {
451
+ // Remove existing style if present
452
+ const existingStyle = document.getElementById('weld-styles');
453
+ if (existingStyle) {
454
+ existingStyle.remove();
455
+ }
456
+ this.styleElement = document.createElement('style');
457
+ this.styleElement.id = 'weld-styles';
458
+ this.styleElement.textContent = this.generateCSS();
459
+ document.head.appendChild(this.styleElement);
460
+ this.logger.debug('CSS injected');
461
+ }
462
+ /**
463
+ * Generate CSS for containers
464
+ */
465
+ generateCSS() {
466
+ const { customization } = this.config;
467
+ return `
468
+ /* Weld Container */
469
+ #weld-container {
470
+ --weld-color-primary: ${customization.primaryColor};
471
+ --weld-color-accent: ${customization.accentColor};
472
+ --weld-font-family: ${customization.fontFamily};
473
+ --weld-font-size-base: ${customization.fontSize};
474
+ --weld-radius-xl: ${customization.borderRadius};
475
+ }
476
+
477
+ /* Import main stylesheet */
478
+ @import url('/styles/index.css');
479
+
480
+ /* Prevent page scroll when mobile widget is open */
481
+ body.weld-mobile-open {
482
+ overflow: hidden !important;
483
+ position: fixed !important;
484
+ width: 100% !important;
485
+ height: 100% !important;
486
+ }
487
+
488
+ /* High contrast mode support */
489
+ @media (prefers-contrast: high) {
490
+ .weld-namespace {
491
+ --weld-color-border: currentColor;
492
+ }
493
+ }
494
+ `;
495
+ }
496
+ /**
497
+ * Create launcher iframe
498
+ */
499
+ async createLauncherIframe() {
500
+ const { iframes } = this.config;
501
+ const { launcher } = iframes;
502
+ // Create container
503
+ const container = document.createElement('div');
504
+ container.className = 'weld-launcher-frame';
505
+ container.setAttribute('data-state', 'visible');
506
+ container.style.cssText = `
507
+ position: fixed;
508
+ bottom: ${launcher.position.bottom};
509
+ right: ${launcher.position.right};
510
+ width: ${launcher.size};
511
+ height: ${launcher.size};
512
+ z-index: 2147483003;
513
+ pointer-events: auto;
514
+ display: block;
515
+ `;
516
+ // Create iframe
517
+ const iframe = document.createElement('iframe');
518
+ iframe.name = launcher.name;
519
+ iframe.title = 'Weld Launcher';
520
+ iframe.src = this.buildIframeUrl(launcher.url);
521
+ iframe.style.cssText = `
522
+ width: 100%;
523
+ height: 100%;
524
+ border: none;
525
+ background: transparent;
526
+ display: block;
527
+ `;
528
+ iframe.setAttribute('allow', 'clipboard-write');
529
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
530
+ container.appendChild(iframe);
531
+ this.appContainer?.appendChild(container);
532
+ // Store metadata
533
+ this.iframes.set(IframeType.LAUNCHER, {
534
+ type: IframeType.LAUNCHER,
535
+ element: iframe,
536
+ container,
537
+ ready: false,
538
+ visible: true,
539
+ createdAt: Date.now(),
540
+ });
541
+ // Mark as ready when loaded
542
+ iframe.onload = () => {
543
+ const metadata = this.iframes.get(IframeType.LAUNCHER);
544
+ if (metadata) {
545
+ metadata.ready = true;
546
+ this.logger.debug('Launcher iframe loaded and ready');
547
+ }
548
+ };
549
+ this.logger.debug('Launcher iframe created');
550
+ }
551
+ /**
552
+ * Create widget iframe
553
+ */
554
+ async createWidgetIframe() {
555
+ const { iframes } = this.config;
556
+ const { widget } = iframes;
557
+ // Create container
558
+ const container = document.createElement('div');
559
+ container.className = 'weld-widget-frame';
560
+ container.setAttribute('data-state', 'closed');
561
+ container.style.cssText = `
562
+ position: fixed;
563
+ bottom: ${widget.position.bottom};
564
+ right: ${widget.position.right};
565
+ width: ${widget.width};
566
+ height: ${widget.height};
567
+ max-width: 100vw;
568
+ z-index: 2147483001;
569
+ pointer-events: none;
570
+ display: none;
571
+ border-radius: 16px;
572
+ overflow: hidden;
573
+ background: transparent;
574
+ `;
575
+ // Create iframe
576
+ const iframe = document.createElement('iframe');
577
+ iframe.name = widget.name;
578
+ iframe.title = 'Weld Widget';
579
+ iframe.src = this.buildIframeUrl(widget.url);
580
+ iframe.style.cssText = `
581
+ width: 100%;
582
+ height: 100%;
583
+ border: none;
584
+ background: transparent;
585
+ display: block;
586
+ border-radius: 16px;
587
+ `;
588
+ iframe.setAttribute('allow', 'clipboard-write; camera; microphone');
589
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups allow-downloads');
590
+ container.appendChild(iframe);
591
+ this.appContainer?.appendChild(container);
592
+ // Store metadata
593
+ this.iframes.set(IframeType.WIDGET, {
594
+ type: IframeType.WIDGET,
595
+ element: iframe,
596
+ container,
597
+ ready: false,
598
+ visible: false,
599
+ createdAt: Date.now(),
600
+ });
601
+ // Mark as ready when loaded
602
+ iframe.onload = () => {
603
+ const metadata = this.iframes.get(IframeType.WIDGET);
604
+ if (metadata) {
605
+ metadata.ready = true;
606
+ this.logger.debug('Widget iframe loaded and ready');
607
+ }
608
+ };
609
+ this.logger.debug('Widget iframe created');
610
+ }
611
+ /**
612
+ * Create backdrop iframe
613
+ */
614
+ async createBackdropIframe() {
615
+ if (!this.config.iframes.backdrop?.enabled) {
616
+ this.logger.debug('Backdrop disabled, skipping creation');
617
+ return;
618
+ }
619
+ // Create container
620
+ const container = document.createElement('div');
621
+ container.className = 'weld-backdrop-frame';
622
+ container.setAttribute('data-state', 'hidden');
623
+ container.style.cssText = `
624
+ position: fixed;
625
+ top: 0;
626
+ left: 0;
627
+ right: 0;
628
+ bottom: 0;
629
+ z-index: 2147483000;
630
+ background: transparent;
631
+ pointer-events: none;
632
+ opacity: 0;
633
+ transition: opacity 200ms ease;
634
+ `;
635
+ this.appContainer?.appendChild(container);
636
+ // Store metadata (backdrop doesn't have an iframe, just a div)
637
+ // We'll create a minimal "iframe" reference for consistency
638
+ const dummyIframe = document.createElement('iframe');
639
+ dummyIframe.style.display = 'none';
640
+ this.iframes.set(IframeType.BACKDROP, {
641
+ type: IframeType.BACKDROP,
642
+ element: dummyIframe,
643
+ container,
644
+ ready: true, // Backdrop is always ready
645
+ visible: false,
646
+ createdAt: Date.now(),
647
+ });
648
+ this.logger.debug('Backdrop created');
649
+ }
650
+ /**
651
+ * Build iframe URL with parameters
652
+ */
653
+ buildIframeUrl(path) {
654
+ const { widgetId, api } = this.config;
655
+ const baseUrl = api.baseUrl;
656
+ // Handle paths that may already have query parameters
657
+ const url = new URL(path, baseUrl);
658
+ url.searchParams.set('widgetId', widgetId);
659
+ url.searchParams.set('device', this.deviceInfo.type);
660
+ url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
661
+ url.searchParams.set('parentOrigin', window.location.origin);
662
+ return url.toString();
663
+ }
664
+ /**
665
+ * Setup event listeners
666
+ */
667
+ setupEventListeners() {
668
+ // Window resize
669
+ window.addEventListener('resize', this.handleResize.bind(this));
670
+ // Orientation change
671
+ window.addEventListener('orientationchange', this.handleOrientationChange.bind(this));
672
+ this.logger.debug('Event listeners setup');
673
+ }
674
+ /**
675
+ * Handle window resize
676
+ */
677
+ handleResize() {
678
+ this.deviceInfo = detectDevice();
679
+ this.logger.debug('Window resized', { device: this.deviceInfo.type });
680
+ }
681
+ /**
682
+ * Handle orientation change
683
+ */
684
+ handleOrientationChange() {
685
+ this.deviceInfo = detectDevice();
686
+ this.logger.debug('Orientation changed', { orientation: this.deviceInfo.orientation });
687
+ }
688
+ /**
689
+ * Get iframe by type
690
+ */
691
+ getIframe(type) {
692
+ return this.iframes.get(type);
693
+ }
694
+ /**
695
+ * Get iframe element
696
+ */
697
+ getIframeElement(type) {
698
+ return this.iframes.get(type)?.element;
699
+ }
700
+ /**
701
+ * Get iframe container
702
+ */
703
+ getIframeContainer(type) {
704
+ return this.iframes.get(type)?.container;
705
+ }
706
+ /**
707
+ * Mark iframe as ready
708
+ */
709
+ setIframeReady(type) {
710
+ const iframe = this.iframes.get(type);
711
+ if (iframe) {
712
+ iframe.ready = true;
713
+ this.logger.debug(`Iframe ${type} marked as ready`);
714
+ }
715
+ }
716
+ /**
717
+ * Check if all iframes are ready
718
+ */
719
+ areAllIframesReady() {
720
+ for (const [type, iframe] of this.iframes) {
721
+ if (type !== IframeType.BACKDROP && !iframe.ready) {
722
+ return false;
723
+ }
724
+ }
725
+ return true;
726
+ }
727
+ /**
728
+ * Show iframe
729
+ */
730
+ showIframe(type) {
731
+ const iframe = this.iframes.get(type);
732
+ if (!iframe) {
733
+ console.warn(`[Weld SDK] showIframe: iframe ${type} not found`);
734
+ return;
735
+ }
736
+ console.log(`[Weld SDK] Showing iframe ${type}`, { currentDisplay: iframe.container.style.display });
737
+ iframe.visible = true;
738
+ iframe.container.setAttribute('data-state', type === IframeType.BACKDROP ? 'visible' : 'open');
739
+ iframe.container.style.pointerEvents = 'auto';
740
+ iframe.container.style.display = 'block';
741
+ // Handle mobile scroll lock
742
+ if (this.deviceInfo.isMobile && type === IframeType.WIDGET && this.config.mobile.scrollLock) {
743
+ document.body.classList.add('weld-mobile-open');
744
+ }
745
+ console.log(`[Weld SDK] Iframe ${type} shown`, { newDisplay: iframe.container.style.display });
746
+ }
747
+ /**
748
+ * Hide iframe
749
+ */
750
+ hideIframe(type) {
751
+ const iframe = this.iframes.get(type);
752
+ if (!iframe) {
753
+ console.warn(`[Weld SDK] hideIframe: iframe ${type} not found`);
754
+ return;
755
+ }
756
+ console.log(`[Weld SDK] Hiding iframe ${type}`, { currentDisplay: iframe.container.style.display });
757
+ iframe.visible = false;
758
+ iframe.container.setAttribute('data-state', type === IframeType.BACKDROP ? 'hidden' : 'closed');
759
+ iframe.container.style.pointerEvents = 'none';
760
+ iframe.container.style.display = 'none';
761
+ // Remove mobile scroll lock
762
+ if (this.deviceInfo.isMobile && type === IframeType.WIDGET) {
763
+ document.body.classList.remove('weld-mobile-open');
764
+ }
765
+ console.log(`[Weld SDK] Iframe ${type} hidden`, { newDisplay: iframe.container.style.display });
766
+ }
767
+ /**
768
+ * Get device info
769
+ */
770
+ getDeviceInfo() {
771
+ return this.deviceInfo;
772
+ }
773
+ /**
774
+ * Get modal container
775
+ */
776
+ getModalContainer() {
777
+ return this.modalContainer;
778
+ }
779
+ /**
780
+ * Destroy all iframes and cleanup
781
+ */
782
+ destroy() {
783
+ this.logger.debug('Destroying iframe manager');
784
+ // Remove event listeners
785
+ window.removeEventListener('resize', this.handleResize.bind(this));
786
+ window.removeEventListener('orientationchange', this.handleOrientationChange.bind(this));
787
+ // Remove mobile scroll lock
788
+ document.body.classList.remove('weld-mobile-open');
789
+ // Remove root container
790
+ if (this.rootContainer) {
791
+ this.rootContainer.remove();
792
+ this.rootContainer = null;
793
+ }
794
+ // Remove style element
795
+ if (this.styleElement) {
796
+ this.styleElement.remove();
797
+ this.styleElement = null;
798
+ }
799
+ // Clear iframe references
800
+ this.iframes.clear();
801
+ this.logger.info('IframeManager destroyed');
802
+ }
803
+ }
804
+
805
+ /**
806
+ * Weld SDK - Message Types
807
+ * Type definitions for postMessage communication between parent and iframes
808
+ */
809
+ /**
810
+ * Message origins for validation
811
+ */
812
+ var MessageOrigin;
813
+ (function (MessageOrigin) {
814
+ MessageOrigin["LAUNCHER"] = "launcher";
815
+ MessageOrigin["WIDGET"] = "widget";
816
+ MessageOrigin["PARENT"] = "parent";
817
+ MessageOrigin["BACKDROP"] = "backdrop";
818
+ })(MessageOrigin || (MessageOrigin = {}));
819
+ /**
820
+ * Message types for different communication patterns
821
+ */
822
+ var MessageType;
823
+ (function (MessageType) {
824
+ // Lifecycle
825
+ MessageType["READY"] = "weld:ready";
826
+ MessageType["INIT"] = "weld:init";
827
+ MessageType["DESTROY"] = "weld:destroy";
828
+ // State changes
829
+ MessageType["STATE_UPDATE"] = "weld:state:update";
830
+ MessageType["STATE_REQUEST"] = "weld:state:request";
831
+ MessageType["STATE_RESPONSE"] = "weld:state:response";
832
+ // Widget control
833
+ MessageType["WIDGET_OPEN"] = "weld:widget:open";
834
+ MessageType["WIDGET_CLOSE"] = "weld:widget:close";
835
+ MessageType["WIDGET_TOGGLE"] = "weld:widget:toggle";
836
+ MessageType["WIDGET_MINIMIZE"] = "weld:widget:minimize";
837
+ MessageType["WIDGET_MAXIMIZE"] = "weld:widget:maximize";
838
+ // Launcher control
839
+ MessageType["LAUNCHER_SHOW"] = "weld:launcher:show";
840
+ MessageType["LAUNCHER_HIDE"] = "weld:launcher:hide";
841
+ MessageType["LAUNCHER_UPDATE"] = "weld:launcher:update";
842
+ // Backdrop control
843
+ MessageType["BACKDROP_SHOW"] = "weld:backdrop:show";
844
+ MessageType["BACKDROP_HIDE"] = "weld:backdrop:hide";
845
+ MessageType["BACKDROP_CLICK"] = "weld:backdrop:click";
846
+ // User interactions
847
+ MessageType["MESSAGE_SEND"] = "weld:message:send";
848
+ MessageType["MESSAGE_RECEIVE"] = "weld:message:receive";
849
+ MessageType["TYPING_START"] = "weld:typing:start";
850
+ MessageType["TYPING_STOP"] = "weld:typing:stop";
851
+ // Badge updates
852
+ MessageType["BADGE_UPDATE"] = "weld:badge:update";
853
+ MessageType["BADGE_CLEAR"] = "weld:badge:clear";
854
+ // Mobile handling
855
+ MessageType["MOBILE_SCROLL_LOCK"] = "weld:mobile:scroll:lock";
856
+ MessageType["MOBILE_SCROLL_UNLOCK"] = "weld:mobile:scroll:unlock";
857
+ // Configuration
858
+ MessageType["CONFIG_UPDATE"] = "weld:config:update";
859
+ MessageType["THEME_UPDATE"] = "weld:theme:update";
860
+ MessageType["LOCALE_UPDATE"] = "weld:locale:update";
861
+ // Authentication
862
+ MessageType["AUTH_LOGIN"] = "weld:auth:login";
863
+ MessageType["AUTH_LOGOUT"] = "weld:auth:logout";
864
+ MessageType["AUTH_TOKEN_UPDATE"] = "weld:auth:token:update";
865
+ // Events
866
+ MessageType["EVENT_TRACK"] = "weld:event:track";
867
+ MessageType["ERROR_REPORT"] = "weld:error:report";
868
+ // API responses
869
+ MessageType["API_SUCCESS"] = "weld:api:success";
870
+ MessageType["API_ERROR"] = "weld:api:error";
871
+ })(MessageType || (MessageType = {}));
872
+ /**
873
+ * Type guards
874
+ */
875
+ function isBaseMessage(message) {
876
+ return (typeof message === 'object' &&
877
+ message !== null &&
878
+ 'type' in message &&
879
+ 'origin' in message &&
880
+ 'timestamp' in message &&
881
+ 'id' in message);
882
+ }
883
+ function isPayloadMessage(message) {
884
+ return isBaseMessage(message) && 'payload' in message;
885
+ }
886
+ /**
887
+ * Message creator utilities
888
+ */
889
+ function createMessage(type, origin, payload) {
890
+ return {
891
+ type,
892
+ origin,
893
+ timestamp: Date.now(),
894
+ id: `${type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
895
+ payload,
896
+ };
897
+ }
898
+
899
+ /**
900
+ * Weld SDK - Security Utilities
901
+ * Security validation and origin checking for postMessage communication
902
+ */
903
+ /**
904
+ * SecurityManager class
905
+ */
906
+ class SecurityManager {
907
+ constructor(config, logger) {
908
+ this.config = config;
909
+ this.logger = logger;
910
+ this.allowedOrigins = new Set(config.allowedOrigins || []);
911
+ // Always allow same origin
912
+ this.allowedOrigins.add(window.location.origin);
913
+ }
914
+ /**
915
+ * Validate message origin
916
+ */
917
+ isOriginAllowed(origin) {
918
+ // If no allowed origins specified, only allow same origin
919
+ if (this.config.allowedOrigins?.length === 0) {
920
+ return origin === window.location.origin;
921
+ }
922
+ // Check if origin is in allowed list
923
+ if (this.allowedOrigins.has(origin)) {
924
+ return true;
925
+ }
926
+ // Check for wildcard patterns
927
+ for (const allowed of this.allowedOrigins) {
928
+ if (this.matchesPattern(origin, allowed)) {
929
+ return true;
930
+ }
931
+ }
932
+ this.logger.warn('Origin not allowed', { origin });
933
+ return false;
934
+ }
935
+ /**
936
+ * Match origin against pattern (supports wildcards)
937
+ */
938
+ matchesPattern(origin, pattern) {
939
+ // Exact match
940
+ if (origin === pattern) {
941
+ return true;
942
+ }
943
+ // Wildcard pattern (e.g., "https://*.example.com")
944
+ if (pattern.includes('*')) {
945
+ const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
946
+ return regex.test(origin);
947
+ }
948
+ return false;
949
+ }
950
+ /**
951
+ * Validate postMessage event
952
+ */
953
+ validateMessageEvent(event) {
954
+ // Check origin
955
+ if (!this.isOriginAllowed(event.origin)) {
956
+ this.logger.warn('Invalid message origin', { origin: event.origin });
957
+ return false;
958
+ }
959
+ // Check if message has data
960
+ if (!event.data) {
961
+ this.logger.warn('Message has no data');
962
+ return false;
963
+ }
964
+ // Check if message is an object
965
+ if (typeof event.data !== 'object') {
966
+ this.logger.warn('Message data is not an object');
967
+ return false;
968
+ }
969
+ return true;
970
+ }
971
+ /**
972
+ * Sanitize message data
973
+ */
974
+ sanitizeMessageData(data) {
975
+ if (!this.config.sanitizeInput) {
976
+ return data;
977
+ }
978
+ // Deep clone to avoid modifying original
979
+ const sanitized = JSON.parse(JSON.stringify(data));
980
+ // Recursively sanitize strings
981
+ this.sanitizeObject(sanitized);
982
+ return sanitized;
983
+ }
984
+ /**
985
+ * Recursively sanitize object properties
986
+ */
987
+ sanitizeObject(obj) {
988
+ if (typeof obj !== 'object' || obj === null) {
989
+ return;
990
+ }
991
+ for (const key in obj) {
992
+ if (typeof obj[key] === 'string') {
993
+ obj[key] = this.sanitizeString(obj[key]);
994
+ }
995
+ else if (typeof obj[key] === 'object') {
996
+ this.sanitizeObject(obj[key]);
997
+ }
998
+ }
999
+ }
1000
+ /**
1001
+ * Sanitize string to prevent XSS
1002
+ */
1003
+ sanitizeString(str) {
1004
+ const div = document.createElement('div');
1005
+ div.textContent = str;
1006
+ return div.innerHTML
1007
+ .replace(/javascript:/gi, '')
1008
+ .replace(/on\w+\s*=/gi, '');
1009
+ }
1010
+ /**
1011
+ * Generate secure random ID
1012
+ */
1013
+ generateSecureId() {
1014
+ const array = new Uint8Array(16);
1015
+ crypto.getRandomValues(array);
1016
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
1017
+ }
1018
+ /**
1019
+ * Validate iframe source
1020
+ */
1021
+ isValidIframeSource(src) {
1022
+ try {
1023
+ const url = new URL(src, window.location.origin);
1024
+ // Check if URL is from allowed origin
1025
+ if (!this.isOriginAllowed(url.origin)) {
1026
+ this.logger.warn('Invalid iframe source origin', { src });
1027
+ return false;
1028
+ }
1029
+ // Prevent javascript: protocol
1030
+ if (url.protocol === 'javascript:') {
1031
+ this.logger.warn('Javascript protocol not allowed in iframe source');
1032
+ return false;
1033
+ }
1034
+ return true;
1035
+ }
1036
+ catch (error) {
1037
+ this.logger.warn('Invalid iframe source URL', { src, error });
1038
+ return false;
1039
+ }
1040
+ }
1041
+ /**
1042
+ * Create Content Security Policy
1043
+ */
1044
+ createCSP() {
1045
+ const origins = Array.from(this.allowedOrigins).join(' ');
1046
+ return [
1047
+ `default-src 'self' ${origins}`,
1048
+ `script-src 'self' 'unsafe-inline' ${origins}`,
1049
+ `style-src 'self' 'unsafe-inline' ${origins}`,
1050
+ `img-src 'self' data: https: ${origins}`,
1051
+ `font-src 'self' data: ${origins}`,
1052
+ `connect-src 'self' ${origins}`,
1053
+ `frame-src 'self' ${origins}`,
1054
+ `media-src 'self' ${origins}`,
1055
+ ].join('; ');
1056
+ }
1057
+ /**
1058
+ * Add allowed origin
1059
+ */
1060
+ addAllowedOrigin(origin) {
1061
+ this.allowedOrigins.add(origin);
1062
+ this.logger.debug('Added allowed origin', { origin });
1063
+ }
1064
+ /**
1065
+ * Remove allowed origin
1066
+ */
1067
+ removeAllowedOrigin(origin) {
1068
+ this.allowedOrigins.delete(origin);
1069
+ this.logger.debug('Removed allowed origin', { origin });
1070
+ }
1071
+ /**
1072
+ * Get all allowed origins
1073
+ */
1074
+ getAllowedOrigins() {
1075
+ return Array.from(this.allowedOrigins);
1076
+ }
1077
+ }
1078
+ /**
1079
+ * Rate limiting utility
1080
+ */
1081
+ class RateLimiter {
1082
+ constructor(maxRequests = 100, windowMs = 60000) {
1083
+ this.requests = new Map();
1084
+ this.maxRequests = maxRequests;
1085
+ this.windowMs = windowMs;
1086
+ }
1087
+ /**
1088
+ * Check if request is allowed
1089
+ */
1090
+ isAllowed(key) {
1091
+ const now = Date.now();
1092
+ const requests = this.requests.get(key) || [];
1093
+ // Remove old requests outside window
1094
+ const validRequests = requests.filter((time) => now - time < this.windowMs);
1095
+ // Check if limit exceeded
1096
+ if (validRequests.length >= this.maxRequests) {
1097
+ return false;
1098
+ }
1099
+ // Add current request
1100
+ validRequests.push(now);
1101
+ this.requests.set(key, validRequests);
1102
+ return true;
1103
+ }
1104
+ /**
1105
+ * Reset rate limit for key
1106
+ */
1107
+ reset(key) {
1108
+ this.requests.delete(key);
1109
+ }
1110
+ /**
1111
+ * Clear all rate limits
1112
+ */
1113
+ clearAll() {
1114
+ this.requests.clear();
1115
+ }
1116
+ }
1117
+
1118
+ /**
1119
+ * Weld SDK - Message Broker
1120
+ * Handles secure postMessage communication between parent and iframes
1121
+ */
1122
+ /**
1123
+ * MessageBroker class
1124
+ * Central hub for all postMessage communication
1125
+ */
1126
+ class MessageBroker {
1127
+ constructor(config, iframeManager, logger) {
1128
+ this.subscriptions = new Map();
1129
+ this.messageQueue = [];
1130
+ this.isReady = false;
1131
+ this.responseHandlers = new Map();
1132
+ this.config = config;
1133
+ this.logger = logger.child('[MessageBroker]');
1134
+ this.iframeManager = iframeManager;
1135
+ this.security = new SecurityManager(config.security, this.logger);
1136
+ this.rateLimiter = new RateLimiter(100, 60000); // 100 messages per minute
1137
+ this.setupMessageListener();
1138
+ this.logger.debug('MessageBroker initialized');
1139
+ }
1140
+ /**
1141
+ * Setup global message listener
1142
+ */
1143
+ setupMessageListener() {
1144
+ window.addEventListener('message', this.handleMessage.bind(this));
1145
+ this.logger.debug('Message listener setup');
1146
+ }
1147
+ /**
1148
+ * Handle incoming postMessage
1149
+ */
1150
+ handleMessage(event) {
1151
+ // Validate message event
1152
+ if (!this.security.validateMessageEvent(event)) {
1153
+ return;
1154
+ }
1155
+ // Validate message structure
1156
+ if (!isBaseMessage(event.data)) {
1157
+ this.logger.warn('Invalid message structure', event.data);
1158
+ return;
1159
+ }
1160
+ const message = event.data;
1161
+ // Rate limiting
1162
+ if (!this.rateLimiter.isAllowed(message.origin)) {
1163
+ this.logger.warn('Rate limit exceeded', { origin: message.origin });
1164
+ return;
1165
+ }
1166
+ // Sanitize message if enabled
1167
+ const sanitized = this.config.security.sanitizeInput
1168
+ ? this.security.sanitizeMessageData(message)
1169
+ : message;
1170
+ this.logger.debug('Message received', {
1171
+ type: sanitized.type,
1172
+ origin: sanitized.origin,
1173
+ id: sanitized.id,
1174
+ });
1175
+ // Check for response handlers
1176
+ if (this.responseHandlers.has(sanitized.id)) {
1177
+ const handler = this.responseHandlers.get(sanitized.id);
1178
+ handler?.(sanitized);
1179
+ this.responseHandlers.delete(sanitized.id);
1180
+ return;
1181
+ }
1182
+ // Dispatch to subscribers
1183
+ this.dispatchMessage(sanitized);
1184
+ }
1185
+ /**
1186
+ * Dispatch message to subscribers
1187
+ */
1188
+ dispatchMessage(message) {
1189
+ const subscriptions = Array.from(this.subscriptions.values());
1190
+ for (const subscription of subscriptions) {
1191
+ // Check if type matches
1192
+ const typeMatches = subscription.type === '*' || subscription.type === message.type;
1193
+ // Check if origin matches (if specified)
1194
+ const originMatches = !subscription.origin || subscription.origin === message.origin;
1195
+ if (typeMatches && originMatches) {
1196
+ try {
1197
+ if (isPayloadMessage(message)) {
1198
+ subscription.handler(message.payload, message);
1199
+ }
1200
+ else {
1201
+ subscription.handler(undefined, message);
1202
+ }
1203
+ }
1204
+ catch (error) {
1205
+ this.logger.error('Error in message handler', error);
1206
+ }
1207
+ }
1208
+ }
1209
+ }
1210
+ /**
1211
+ * Subscribe to messages
1212
+ */
1213
+ subscribe(type, handler, origin) {
1214
+ const id = this.security.generateSecureId();
1215
+ this.subscriptions.set(id, {
1216
+ id,
1217
+ type,
1218
+ origin,
1219
+ handler,
1220
+ });
1221
+ this.logger.debug('Subscribed to messages', { type, origin, id });
1222
+ return id;
1223
+ }
1224
+ /**
1225
+ * Unsubscribe from messages
1226
+ */
1227
+ unsubscribe(subscriptionId) {
1228
+ if (this.subscriptions.delete(subscriptionId)) {
1229
+ this.logger.debug('Unsubscribed from messages', { id: subscriptionId });
1230
+ }
1231
+ }
1232
+ /**
1233
+ * Send message to iframe
1234
+ */
1235
+ sendToIframe(iframeType, type, payload) {
1236
+ const iframe = this.iframeManager.getIframe(iframeType);
1237
+ if (!iframe) {
1238
+ this.logger.warn('Iframe not found', { iframeType });
1239
+ return;
1240
+ }
1241
+ if (!iframe.ready && type !== 'weld:init') {
1242
+ // Queue message if iframe not ready
1243
+ const message = createMessage(type, MessageOrigin.PARENT, payload);
1244
+ this.messageQueue.push(message);
1245
+ this.logger.debug('Message queued (iframe not ready)', {
1246
+ iframeType,
1247
+ type,
1248
+ });
1249
+ return;
1250
+ }
1251
+ const message = createMessage(type, MessageOrigin.PARENT, payload);
1252
+ this.postMessage(iframe.element, message);
1253
+ }
1254
+ /**
1255
+ * Send message to all iframes
1256
+ */
1257
+ broadcast(type, payload) {
1258
+ const message = createMessage(type, MessageOrigin.PARENT, payload);
1259
+ const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
1260
+ const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
1261
+ if (launcherIframe && launcherIframe.ready) {
1262
+ this.postMessage(launcherIframe.element, message);
1263
+ }
1264
+ if (widgetIframe && widgetIframe.ready) {
1265
+ this.postMessage(widgetIframe.element, message);
1266
+ }
1267
+ this.logger.debug('Message broadcast', { type });
1268
+ }
1269
+ /**
1270
+ * Send message and wait for response
1271
+ */
1272
+ async sendAndWaitForResponse(iframeType, type, payload, timeout = 5000) {
1273
+ return new Promise((resolve, reject) => {
1274
+ const message = createMessage(type, MessageOrigin.PARENT, payload);
1275
+ // Setup response handler
1276
+ const timeoutId = setTimeout(() => {
1277
+ this.responseHandlers.delete(message.id);
1278
+ reject(new Error('Message response timeout'));
1279
+ }, timeout);
1280
+ this.responseHandlers.set(message.id, (response) => {
1281
+ clearTimeout(timeoutId);
1282
+ if (isPayloadMessage(response)) {
1283
+ resolve(response.payload);
1284
+ }
1285
+ else {
1286
+ resolve(undefined);
1287
+ }
1288
+ });
1289
+ // Send message
1290
+ const iframe = this.iframeManager.getIframe(iframeType);
1291
+ if (!iframe) {
1292
+ reject(new Error('Iframe not found'));
1293
+ return;
1294
+ }
1295
+ this.postMessage(iframe.element, message);
1296
+ });
1297
+ }
1298
+ /**
1299
+ * Post message to iframe
1300
+ */
1301
+ postMessage(iframe, message) {
1302
+ if (!iframe.contentWindow) {
1303
+ this.logger.warn('Iframe contentWindow not available');
1304
+ return;
1305
+ }
1306
+ try {
1307
+ iframe.contentWindow.postMessage(message, '*');
1308
+ this.logger.debug('Message sent', {
1309
+ type: message.type,
1310
+ id: message.id,
1311
+ });
1312
+ }
1313
+ catch (error) {
1314
+ this.logger.error('Failed to post message', error);
1315
+ }
1316
+ }
1317
+ /**
1318
+ * Mark iframe as ready and flush queued messages
1319
+ */
1320
+ setIframeReady(iframeType) {
1321
+ this.iframeManager.setIframeReady(iframeType);
1322
+ // Flush queued messages for this iframe
1323
+ this.flushMessageQueue(iframeType);
1324
+ // Check if all iframes ready
1325
+ if (this.iframeManager.areAllIframesReady()) {
1326
+ this.isReady = true;
1327
+ this.logger.info('All iframes ready');
1328
+ }
1329
+ }
1330
+ /**
1331
+ * Flush queued messages to iframe
1332
+ */
1333
+ flushMessageQueue(iframeType) {
1334
+ const iframe = this.iframeManager.getIframe(iframeType);
1335
+ if (!iframe)
1336
+ return;
1337
+ const queuedMessages = this.messageQueue.splice(0);
1338
+ for (const message of queuedMessages) {
1339
+ this.postMessage(iframe.element, message);
1340
+ }
1341
+ if (queuedMessages.length > 0) {
1342
+ this.logger.debug('Flushed queued messages', {
1343
+ count: queuedMessages.length,
1344
+ iframeType,
1345
+ });
1346
+ }
1347
+ }
1348
+ /**
1349
+ * Check if broker is ready
1350
+ */
1351
+ isMessageBrokerReady() {
1352
+ return this.isReady;
1353
+ }
1354
+ /**
1355
+ * Get queued message count
1356
+ */
1357
+ getQueuedMessageCount() {
1358
+ return this.messageQueue.length;
1359
+ }
1360
+ /**
1361
+ * Clear message queue
1362
+ */
1363
+ clearMessageQueue() {
1364
+ this.messageQueue = [];
1365
+ this.logger.debug('Message queue cleared');
1366
+ }
1367
+ /**
1368
+ * Get active subscription count
1369
+ */
1370
+ getSubscriptionCount() {
1371
+ return this.subscriptions.size;
1372
+ }
1373
+ /**
1374
+ * Destroy broker and cleanup
1375
+ */
1376
+ destroy() {
1377
+ this.logger.debug('Destroying message broker');
1378
+ // Remove event listener
1379
+ window.removeEventListener('message', this.handleMessage.bind(this));
1380
+ // Clear subscriptions
1381
+ this.subscriptions.clear();
1382
+ // Clear message queue
1383
+ this.messageQueue = [];
1384
+ // Clear response handlers
1385
+ this.responseHandlers.clear();
1386
+ // Reset rate limiter
1387
+ this.rateLimiter.clearAll();
1388
+ this.isReady = false;
1389
+ this.logger.info('MessageBroker destroyed');
1390
+ }
1391
+ }
1392
+
1393
+ /**
1394
+ * Weld SDK - State Types
1395
+ * Type definitions for state management across iframes
1396
+ */
1397
+ /**
1398
+ * Widget visibility state
1399
+ */
1400
+ var WidgetVisibility;
1401
+ (function (WidgetVisibility) {
1402
+ WidgetVisibility["HIDDEN"] = "hidden";
1403
+ WidgetVisibility["VISIBLE"] = "visible";
1404
+ WidgetVisibility["MINIMIZED"] = "minimized";
1405
+ })(WidgetVisibility || (WidgetVisibility = {}));
1406
+ /**
1407
+ * Widget view types
1408
+ */
1409
+ var WidgetView;
1410
+ (function (WidgetView) {
1411
+ WidgetView["HOME"] = "home";
1412
+ WidgetView["CONVERSATION"] = "conversation";
1413
+ WidgetView["CONVERSATIONS"] = "conversations";
1414
+ WidgetView["HELP"] = "help";
1415
+ WidgetView["SETTINGS"] = "settings";
1416
+ WidgetView["SEARCH"] = "search";
1417
+ })(WidgetView || (WidgetView = {}));
1418
+ /**
1419
+ * Connection status
1420
+ */
1421
+ var ConnectionStatus;
1422
+ (function (ConnectionStatus) {
1423
+ ConnectionStatus["DISCONNECTED"] = "disconnected";
1424
+ ConnectionStatus["CONNECTING"] = "connecting";
1425
+ ConnectionStatus["CONNECTED"] = "connected";
1426
+ ConnectionStatus["RECONNECTING"] = "reconnecting";
1427
+ ConnectionStatus["ERROR"] = "error";
1428
+ })(ConnectionStatus || (ConnectionStatus = {}));
1429
+ /**
1430
+ * Message status
1431
+ */
1432
+ var MessageStatus;
1433
+ (function (MessageStatus) {
1434
+ MessageStatus["SENDING"] = "sending";
1435
+ MessageStatus["SENT"] = "sent";
1436
+ MessageStatus["DELIVERED"] = "delivered";
1437
+ MessageStatus["READ"] = "read";
1438
+ MessageStatus["FAILED"] = "failed";
1439
+ })(MessageStatus || (MessageStatus = {}));
1440
+ /**
1441
+ * Initial state factory
1442
+ */
1443
+ function createInitialState() {
1444
+ return {
1445
+ user: {
1446
+ isAuthenticated: false,
1447
+ isAnonymous: true,
1448
+ },
1449
+ conversation: {
1450
+ messages: [],
1451
+ participants: [],
1452
+ unreadCount: 0,
1453
+ isTyping: false,
1454
+ typingUsers: [],
1455
+ },
1456
+ widget: {
1457
+ visibility: WidgetVisibility.HIDDEN,
1458
+ view: WidgetView.HOME,
1459
+ isOpen: false,
1460
+ isMinimized: false,
1461
+ dimensions: {
1462
+ width: '400px',
1463
+ height: 'min(680px, 88vh)',
1464
+ },
1465
+ position: {
1466
+ bottom: '24px',
1467
+ right: '24px',
1468
+ },
1469
+ },
1470
+ launcher: {
1471
+ isVisible: true,
1472
+ badge: {
1473
+ count: 0,
1474
+ show: false,
1475
+ },
1476
+ },
1477
+ backdrop: {
1478
+ isVisible: false,
1479
+ closeOnClick: true,
1480
+ opacity: 0,
1481
+ },
1482
+ mobile: {
1483
+ isFullScreen: false,
1484
+ isScrollLocked: false,
1485
+ keyboardHeight: 0,
1486
+ orientation: 'portrait',
1487
+ safeAreaInsets: {
1488
+ top: 0,
1489
+ right: 0,
1490
+ bottom: 0,
1491
+ left: 0,
1492
+ },
1493
+ },
1494
+ network: {
1495
+ status: ConnectionStatus.DISCONNECTED,
1496
+ retryCount: 0,
1497
+ },
1498
+ ui: {
1499
+ theme: 'auto',
1500
+ locale: 'en',
1501
+ isLoading: false,
1502
+ },
1503
+ initialized: false,
1504
+ lastUpdated: Date.now(),
1505
+ };
1506
+ }
1507
+ /**
1508
+ * State path utility
1509
+ */
1510
+ function getStateValue(state, path) {
1511
+ const keys = path.split('.');
1512
+ let value = state;
1513
+ for (const key of keys) {
1514
+ if (value && typeof value === 'object' && key in value) {
1515
+ value = value[key];
1516
+ }
1517
+ else {
1518
+ return undefined;
1519
+ }
1520
+ }
1521
+ return value;
1522
+ }
1523
+ /**
1524
+ * State update utility
1525
+ */
1526
+ function setStateValue(state, path, value, merge = false) {
1527
+ const keys = path.split('.');
1528
+ const newState = JSON.parse(JSON.stringify(state));
1529
+ let current = newState;
1530
+ for (let i = 0; i < keys.length - 1; i++) {
1531
+ const key = keys[i];
1532
+ if (!(key in current)) {
1533
+ current[key] = {};
1534
+ }
1535
+ current = current[key];
1536
+ }
1537
+ const lastKey = keys[keys.length - 1];
1538
+ if (merge && typeof current[lastKey] === 'object' && typeof value === 'object') {
1539
+ current[lastKey] = { ...current[lastKey], ...value };
1540
+ }
1541
+ else {
1542
+ current[lastKey] = value;
1543
+ }
1544
+ newState.lastUpdated = Date.now();
1545
+ return newState;
1546
+ }
1547
+
1548
+ /**
1549
+ * Weld SDK - Validation Utilities
1550
+ * Input validation and sanitization functions
1551
+ */
1552
+ /**
1553
+ * Validate email format
1554
+ */
1555
+ /**
1556
+ * Deep clone object (safe for JSON-serializable objects)
1557
+ */
1558
+ function deepClone(obj) {
1559
+ if (obj === null || typeof obj !== 'object') {
1560
+ return obj;
1561
+ }
1562
+ if (obj instanceof Date) {
1563
+ return new Date(obj.getTime());
1564
+ }
1565
+ if (obj instanceof Array) {
1566
+ return obj.map((item) => deepClone(item));
1567
+ }
1568
+ if (obj instanceof Object) {
1569
+ const cloned = {};
1570
+ for (const key in obj) {
1571
+ if (obj.hasOwnProperty(key)) {
1572
+ cloned[key] = deepClone(obj[key]);
1573
+ }
1574
+ }
1575
+ return cloned;
1576
+ }
1577
+ return obj;
1578
+ }
1579
+
1580
+ /**
1581
+ * Weld SDK - State Coordinator
1582
+ * Manages and synchronizes state across iframes
1583
+ */
1584
+ /**
1585
+ * StateCoordinator class
1586
+ * Central state management for multi-iframe architecture
1587
+ */
1588
+ class StateCoordinator {
1589
+ constructor(messageBroker, logger) {
1590
+ this.subscriptions = new Map();
1591
+ this.stateHistory = [];
1592
+ this.maxHistorySize = 50;
1593
+ this.messageBroker = messageBroker;
1594
+ this.logger = logger.child('[StateCoordinator]');
1595
+ this.state = createInitialState();
1596
+ this.setupMessageHandlers();
1597
+ this.logger.debug('StateCoordinator initialized');
1598
+ }
1599
+ /**
1600
+ * Setup message handlers for state updates
1601
+ */
1602
+ setupMessageHandlers() {
1603
+ // Listen for state update requests from iframes
1604
+ this.messageBroker.subscribe('weld:state:update', this.handleStateUpdate.bind(this));
1605
+ // Listen for state request messages
1606
+ this.messageBroker.subscribe('weld:state:request', this.handleStateRequest.bind(this));
1607
+ this.logger.debug('Message handlers setup');
1608
+ }
1609
+ /**
1610
+ * Handle state update from iframe
1611
+ */
1612
+ handleStateUpdate(payload) {
1613
+ const { path, value, merge } = payload;
1614
+ if (!path) {
1615
+ this.logger.warn('State update missing path', payload);
1616
+ return;
1617
+ }
1618
+ this.updateState(path, value, merge);
1619
+ }
1620
+ /**
1621
+ * Handle state request from iframe
1622
+ */
1623
+ handleStateRequest(payload) {
1624
+ const { path } = payload;
1625
+ if (path) {
1626
+ const value = getStateValue(this.state, path);
1627
+ this.messageBroker.broadcast('weld:state:response', {
1628
+ path,
1629
+ value,
1630
+ });
1631
+ }
1632
+ else {
1633
+ // Send entire state
1634
+ this.messageBroker.broadcast('weld:state:response', {
1635
+ state: this.state,
1636
+ });
1637
+ }
1638
+ }
1639
+ /**
1640
+ * Get current state
1641
+ */
1642
+ getState() {
1643
+ return deepClone(this.state);
1644
+ }
1645
+ /**
1646
+ * Get state value by path
1647
+ */
1648
+ getValue(path) {
1649
+ return getStateValue(this.state, path);
1650
+ }
1651
+ /**
1652
+ * Update state
1653
+ */
1654
+ updateState(path, value, merge = false) {
1655
+ const oldState = deepClone(this.state);
1656
+ this.state = setStateValue(this.state, path, value, merge);
1657
+ // Create state action
1658
+ const action = {
1659
+ type: 'UPDATE',
1660
+ path,
1661
+ payload: value,
1662
+ merge,
1663
+ timestamp: Date.now(),
1664
+ };
1665
+ // Add to history
1666
+ this.addToHistory(action);
1667
+ // Notify subscribers
1668
+ this.notifySubscribers(path, value, getStateValue(oldState, path));
1669
+ // Broadcast to iframes
1670
+ this.messageBroker.broadcast('weld:state:update', {
1671
+ path,
1672
+ value,
1673
+ merge,
1674
+ });
1675
+ this.logger.debug('State updated', { path, merge });
1676
+ }
1677
+ /**
1678
+ * Batch update multiple state paths
1679
+ */
1680
+ batchUpdate(updates) {
1681
+ const oldState = deepClone(this.state);
1682
+ for (const update of updates) {
1683
+ this.state = setStateValue(this.state, update.path, update.value, update.merge);
1684
+ }
1685
+ // Notify subscribers for each update
1686
+ for (const update of updates) {
1687
+ this.notifySubscribers(update.path, getStateValue(this.state, update.path), getStateValue(oldState, update.path));
1688
+ }
1689
+ // Broadcast all updates
1690
+ this.messageBroker.broadcast('weld:state:update', {
1691
+ batch: updates,
1692
+ });
1693
+ this.logger.debug('Batch state update', { count: updates.length });
1694
+ }
1695
+ /**
1696
+ * Reset state to initial
1697
+ */
1698
+ resetState() {
1699
+ const oldState = this.state;
1700
+ this.state = createInitialState();
1701
+ // Notify all subscribers
1702
+ for (const [_id, subscription] of this.subscriptions) {
1703
+ const oldValue = getStateValue(oldState, subscription.path);
1704
+ const newValue = getStateValue(this.state, subscription.path);
1705
+ subscription.listener(newValue, oldValue);
1706
+ }
1707
+ // Broadcast reset
1708
+ this.messageBroker.broadcast('weld:state:update', {
1709
+ reset: true,
1710
+ state: this.state,
1711
+ });
1712
+ this.logger.debug('State reset');
1713
+ }
1714
+ /**
1715
+ * Subscribe to state changes
1716
+ */
1717
+ subscribe(path, listener, immediate = false) {
1718
+ const id = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1719
+ this.subscriptions.set(id, {
1720
+ id,
1721
+ path,
1722
+ listener,
1723
+ immediate,
1724
+ });
1725
+ // Call listener immediately if requested
1726
+ if (immediate) {
1727
+ const value = getStateValue(this.state, path);
1728
+ listener(value, value);
1729
+ }
1730
+ this.logger.debug('State subscription added', { id, path });
1731
+ return id;
1732
+ }
1733
+ /**
1734
+ * Unsubscribe from state changes
1735
+ */
1736
+ unsubscribe(subscriptionId) {
1737
+ if (this.subscriptions.delete(subscriptionId)) {
1738
+ this.logger.debug('State subscription removed', { id: subscriptionId });
1739
+ }
1740
+ }
1741
+ /**
1742
+ * Notify subscribers of state change
1743
+ */
1744
+ notifySubscribers(path, newValue, oldValue) {
1745
+ const subscriptions = Array.from(this.subscriptions.values());
1746
+ for (const subscription of subscriptions) {
1747
+ // Exact path match
1748
+ if (subscription.path === path) {
1749
+ try {
1750
+ subscription.listener(newValue, oldValue);
1751
+ }
1752
+ catch (error) {
1753
+ this.logger.error('Error in state listener', error);
1754
+ }
1755
+ continue;
1756
+ }
1757
+ // Parent path match (e.g., subscriber to "user" gets notified for "user.name")
1758
+ if (path.startsWith(subscription.path + '.')) {
1759
+ const subValue = getStateValue(this.state, subscription.path);
1760
+ try {
1761
+ subscription.listener(subValue, oldValue);
1762
+ }
1763
+ catch (error) {
1764
+ this.logger.error('Error in state listener', error);
1765
+ }
1766
+ continue;
1767
+ }
1768
+ // Child path match (e.g., subscriber to "user.name" gets notified for "user")
1769
+ if (subscription.path.startsWith(path + '.')) {
1770
+ const subValue = getStateValue(this.state, subscription.path);
1771
+ try {
1772
+ subscription.listener(subValue, undefined);
1773
+ }
1774
+ catch (error) {
1775
+ this.logger.error('Error in state listener', error);
1776
+ }
1777
+ }
1778
+ }
1779
+ }
1780
+ /**
1781
+ * Add action to history
1782
+ */
1783
+ addToHistory(action) {
1784
+ this.stateHistory.push(action);
1785
+ // Trim history if too large
1786
+ if (this.stateHistory.length > this.maxHistorySize) {
1787
+ this.stateHistory.shift();
1788
+ }
1789
+ }
1790
+ /**
1791
+ * Get state history
1792
+ */
1793
+ getHistory() {
1794
+ return [...this.stateHistory];
1795
+ }
1796
+ /**
1797
+ * Clear state history
1798
+ */
1799
+ clearHistory() {
1800
+ this.stateHistory = [];
1801
+ this.logger.debug('State history cleared');
1802
+ }
1803
+ /**
1804
+ * Get snapshot of state at specific time
1805
+ */
1806
+ getSnapshot(timestamp) {
1807
+ // Find all actions before timestamp
1808
+ const actions = this.stateHistory.filter((a) => a.timestamp <= timestamp);
1809
+ if (actions.length === 0) {
1810
+ return null;
1811
+ }
1812
+ // Replay actions to reconstruct state
1813
+ let state = createInitialState();
1814
+ for (const action of actions) {
1815
+ state = setStateValue(state, action.path, action.payload, action.merge);
1816
+ }
1817
+ return state;
1818
+ }
1819
+ /**
1820
+ * Widget-specific state helpers
1821
+ */
1822
+ openWidget() {
1823
+ this.batchUpdate([
1824
+ { path: 'widget.isOpen', value: true },
1825
+ { path: 'widget.visibility', value: 'visible' },
1826
+ { path: 'launcher.isVisible', value: false },
1827
+ { path: 'backdrop.isVisible', value: true },
1828
+ ]);
1829
+ }
1830
+ closeWidget() {
1831
+ this.batchUpdate([
1832
+ { path: 'widget.isOpen', value: false },
1833
+ { path: 'widget.visibility', value: 'hidden' },
1834
+ { path: 'launcher.isVisible', value: true },
1835
+ { path: 'backdrop.isVisible', value: false },
1836
+ ]);
1837
+ }
1838
+ minimizeWidget() {
1839
+ this.batchUpdate([
1840
+ { path: 'widget.isMinimized', value: true },
1841
+ { path: 'widget.visibility', value: 'minimized' },
1842
+ ]);
1843
+ }
1844
+ maximizeWidget() {
1845
+ this.batchUpdate([
1846
+ { path: 'widget.isMinimized', value: false },
1847
+ { path: 'widget.visibility', value: 'visible' },
1848
+ ]);
1849
+ }
1850
+ setBadgeCount(count) {
1851
+ this.batchUpdate([
1852
+ { path: 'launcher.badge.count', value: count },
1853
+ { path: 'launcher.badge.show', value: count > 0 },
1854
+ ]);
1855
+ }
1856
+ setUserAuth(userId, email, name) {
1857
+ this.batchUpdate([
1858
+ { path: 'user.isAuthenticated', value: true },
1859
+ { path: 'user.isAnonymous', value: false },
1860
+ { path: 'user.id', value: userId },
1861
+ { path: 'user.email', value: email },
1862
+ { path: 'user.name', value: name },
1863
+ ]);
1864
+ }
1865
+ setConnectionStatus(status) {
1866
+ this.updateState('network.status', status);
1867
+ }
1868
+ setLoading(isLoading) {
1869
+ this.updateState('ui.isLoading', isLoading);
1870
+ }
1871
+ setError(code, message, recoverable = true) {
1872
+ this.updateState('ui.error', { code, message, recoverable });
1873
+ }
1874
+ clearError() {
1875
+ this.updateState('ui.error', undefined);
1876
+ }
1877
+ /**
1878
+ * Get active subscription count
1879
+ */
1880
+ getSubscriptionCount() {
1881
+ return this.subscriptions.size;
1882
+ }
1883
+ /**
1884
+ * Validate state integrity
1885
+ */
1886
+ validateState() {
1887
+ try {
1888
+ // Check required properties exist
1889
+ const requiredPaths = [
1890
+ 'user',
1891
+ 'conversation',
1892
+ 'widget',
1893
+ 'launcher',
1894
+ 'backdrop',
1895
+ 'mobile',
1896
+ 'network',
1897
+ 'ui',
1898
+ ];
1899
+ for (const path of requiredPaths) {
1900
+ if (getStateValue(this.state, path) === undefined) {
1901
+ this.logger.error('Missing required state path', { path });
1902
+ return false;
1903
+ }
1904
+ }
1905
+ return true;
1906
+ }
1907
+ catch (error) {
1908
+ this.logger.error('State validation failed', error);
1909
+ return false;
1910
+ }
1911
+ }
1912
+ /**
1913
+ * Export state as JSON
1914
+ */
1915
+ exportState() {
1916
+ return JSON.stringify(this.state, null, 2);
1917
+ }
1918
+ /**
1919
+ * Import state from JSON
1920
+ */
1921
+ importState(json) {
1922
+ try {
1923
+ const imported = JSON.parse(json);
1924
+ this.state = { ...createInitialState(), ...imported };
1925
+ this.messageBroker.broadcast('weld:state:update', {
1926
+ reset: true,
1927
+ state: this.state,
1928
+ });
1929
+ this.logger.info('State imported');
1930
+ return true;
1931
+ }
1932
+ catch (error) {
1933
+ this.logger.error('Failed to import state', error);
1934
+ return false;
1935
+ }
1936
+ }
1937
+ /**
1938
+ * Destroy coordinator and cleanup
1939
+ */
1940
+ destroy() {
1941
+ this.logger.debug('Destroying state coordinator');
1942
+ // Clear subscriptions
1943
+ this.subscriptions.clear();
1944
+ // Clear history
1945
+ this.stateHistory = [];
1946
+ // Reset state
1947
+ this.state = createInitialState();
1948
+ this.logger.info('StateCoordinator destroyed');
1949
+ }
1950
+ }
1951
+
1952
+ var version = "1.0.2";
1953
+ var packageJson = {
1954
+ version: version};
1955
+
1956
+ /**
1957
+ * Weld SDK - Main Entry Point
1958
+ * Public API for the Weld helpdesk widget
1959
+ */
1960
+ /**
1961
+ * SDK initialization status
1962
+ */
1963
+ var SDKStatus;
1964
+ (function (SDKStatus) {
1965
+ SDKStatus["UNINITIALIZED"] = "uninitialized";
1966
+ SDKStatus["INITIALIZING"] = "initializing";
1967
+ SDKStatus["READY"] = "ready";
1968
+ SDKStatus["ERROR"] = "error";
1969
+ SDKStatus["DESTROYED"] = "destroyed";
1970
+ })(SDKStatus || (SDKStatus = {}));
1971
+ /**
1972
+ * WeldSDK class
1973
+ * Main SDK interface for embedding the widget
1974
+ */
1975
+ class WeldSDK {
1976
+ constructor(config) {
1977
+ this.status = SDKStatus.UNINITIALIZED;
1978
+ this.readyPromise = null;
1979
+ this.readyResolve = null;
1980
+ // Resolve configuration
1981
+ this.config = resolveConfig(config);
1982
+ // Initialize logger
1983
+ this.logger = new Logger(this.config.logging);
1984
+ this.logger.info('WeldSDK created', {
1985
+ widgetId: this.config.widgetId,
1986
+ });
1987
+ // Create ready promise
1988
+ this.readyPromise = new Promise((resolve) => {
1989
+ this.readyResolve = resolve;
1990
+ });
1991
+ // Initialize managers
1992
+ this.iframeManager = new IframeManager(this.config);
1993
+ this.messageBroker = new MessageBroker(this.config, this.iframeManager, this.logger);
1994
+ this.stateCoordinator = new StateCoordinator(this.messageBroker, this.logger);
1995
+ }
1996
+ /**
1997
+ * Initialize the SDK and render widget
1998
+ */
1999
+ async init() {
2000
+ if (this.status !== SDKStatus.UNINITIALIZED) {
2001
+ this.logger.warn('SDK already initialized');
2002
+ return this.readyPromise;
2003
+ }
2004
+ this.status = SDKStatus.INITIALIZING;
2005
+ this.logger.info('Initializing WeldSDK');
2006
+ try {
2007
+ // Initialize iframe manager
2008
+ await this.iframeManager.init();
2009
+ // Setup ready handlers
2010
+ this.setupReadyHandlers();
2011
+ // Wait for all iframes to be ready
2012
+ await this.waitForIframesReady();
2013
+ // Mark as ready
2014
+ this.status = SDKStatus.READY;
2015
+ this.readyResolve?.();
2016
+ this.logger.info('WeldSDK ready');
2017
+ // Call onReady callback
2018
+ this.config.onReady?.();
2019
+ }
2020
+ catch (error) {
2021
+ this.status = SDKStatus.ERROR;
2022
+ this.logger.error('Failed to initialize WeldSDK', error);
2023
+ this.config.onError?.(error);
2024
+ throw error;
2025
+ }
2026
+ return this.readyPromise;
2027
+ }
2028
+ /**
2029
+ * Setup ready message handlers
2030
+ */
2031
+ setupReadyHandlers() {
2032
+ this.messageBroker.subscribe('weld:ready', (payload) => {
2033
+ const { iframe } = payload;
2034
+ this.logger.debug('Iframe ready', { iframe });
2035
+ // Map iframe name to type
2036
+ const iframeType = this.mapIframeNameToType(iframe);
2037
+ if (iframeType) {
2038
+ this.messageBroker.setIframeReady(iframeType);
2039
+ }
2040
+ });
2041
+ // Listen for launcher click events from the iframe
2042
+ window.addEventListener('message', (event) => {
2043
+ // Log all messages from iframes for debugging
2044
+ if (event.data?.type) {
2045
+ console.log('[Weld SDK] Received message:', event.data.type);
2046
+ }
2047
+ if (event.data?.type === 'launcher:clicked') {
2048
+ // Toggle behavior - if widget is open, close it; if closed, open it
2049
+ const state = this.stateCoordinator.getState();
2050
+ if (state.widget.isOpen) {
2051
+ console.log('[Weld SDK] Launcher clicked - closing widget (toggle)');
2052
+ this.close();
2053
+ }
2054
+ else {
2055
+ console.log('[Weld SDK] Launcher clicked - opening widget');
2056
+ this.open();
2057
+ }
2058
+ }
2059
+ if (event.data?.type === 'weld:close') {
2060
+ console.log('[Weld SDK] Widget close requested');
2061
+ this.close();
2062
+ }
2063
+ });
2064
+ }
2065
+ /**
2066
+ * Map iframe name to type
2067
+ */
2068
+ mapIframeNameToType(name) {
2069
+ if (name.includes('launcher'))
2070
+ return IframeType.LAUNCHER;
2071
+ if (name.includes('widget'))
2072
+ return IframeType.WIDGET;
2073
+ return null;
2074
+ }
2075
+ /**
2076
+ * Wait for all iframes to be ready
2077
+ */
2078
+ async waitForIframesReady(timeout = 10000) {
2079
+ const startTime = Date.now();
2080
+ return new Promise((resolve, reject) => {
2081
+ const checkReady = () => {
2082
+ if (this.iframeManager.areAllIframesReady()) {
2083
+ resolve();
2084
+ return;
2085
+ }
2086
+ if (Date.now() - startTime > timeout) {
2087
+ reject(new Error('Timeout waiting for iframes to be ready'));
2088
+ return;
2089
+ }
2090
+ setTimeout(checkReady, 100);
2091
+ };
2092
+ checkReady();
2093
+ });
2094
+ }
2095
+ /**
2096
+ * Wait for SDK to be ready
2097
+ */
2098
+ async ready() {
2099
+ return this.readyPromise;
2100
+ }
2101
+ /**
2102
+ * Check if SDK is ready
2103
+ */
2104
+ isReady() {
2105
+ return this.status === SDKStatus.READY;
2106
+ }
2107
+ /**
2108
+ * Open the widget
2109
+ */
2110
+ open() {
2111
+ this.ensureReady();
2112
+ console.log('[Weld SDK] Opening widget...');
2113
+ this.stateCoordinator.openWidget();
2114
+ this.iframeManager.showIframe(IframeType.WIDGET);
2115
+ this.iframeManager.showIframe(IframeType.BACKDROP);
2116
+ // Keep launcher visible so user can click it to close the widget
2117
+ // Send open message to the widget iframe
2118
+ const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2119
+ if (widgetIframe?.element?.contentWindow) {
2120
+ widgetIframe.element.contentWindow.postMessage({ type: 'weld:open' }, '*');
2121
+ }
2122
+ // Notify launcher that widget is now open (so it can show X icon)
2123
+ const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
2124
+ if (launcherIframe?.element?.contentWindow) {
2125
+ launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
2126
+ }
2127
+ this.config.onOpen?.();
2128
+ }
2129
+ /**
2130
+ * Close the widget
2131
+ */
2132
+ close() {
2133
+ this.ensureReady();
2134
+ console.log('[Weld SDK] Closing widget...');
2135
+ this.stateCoordinator.closeWidget();
2136
+ this.iframeManager.hideIframe(IframeType.WIDGET);
2137
+ this.iframeManager.hideIframe(IframeType.BACKDROP);
2138
+ // Launcher stays visible
2139
+ // Send close message to the widget iframe
2140
+ const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2141
+ if (widgetIframe?.element?.contentWindow) {
2142
+ widgetIframe.element.contentWindow.postMessage({ type: 'weld:close' }, '*');
2143
+ }
2144
+ // Notify launcher that widget is now closed (so it can show chat icon)
2145
+ const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
2146
+ if (launcherIframe?.element?.contentWindow) {
2147
+ launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
2148
+ }
2149
+ this.config.onClose?.();
2150
+ }
2151
+ /**
2152
+ * Toggle widget open/close
2153
+ */
2154
+ toggle() {
2155
+ const state = this.stateCoordinator.getState();
2156
+ if (state.widget.isOpen) {
2157
+ this.close();
2158
+ }
2159
+ else {
2160
+ this.open();
2161
+ }
2162
+ }
2163
+ /**
2164
+ * Minimize the widget
2165
+ */
2166
+ minimize() {
2167
+ this.ensureReady();
2168
+ this.logger.debug('Minimizing widget');
2169
+ this.stateCoordinator.minimizeWidget();
2170
+ this.config.onMinimize?.();
2171
+ }
2172
+ /**
2173
+ * Maximize the widget
2174
+ */
2175
+ maximize() {
2176
+ this.ensureReady();
2177
+ this.logger.debug('Maximizing widget');
2178
+ this.stateCoordinator.maximizeWidget();
2179
+ this.config.onMaximize?.();
2180
+ }
2181
+ /**
2182
+ * Show the launcher
2183
+ */
2184
+ showLauncher() {
2185
+ this.ensureReady();
2186
+ this.logger.debug('Showing launcher');
2187
+ this.iframeManager.showIframe(IframeType.LAUNCHER);
2188
+ this.stateCoordinator.updateState('launcher.isVisible', true);
2189
+ }
2190
+ /**
2191
+ * Hide the launcher
2192
+ */
2193
+ hideLauncher() {
2194
+ this.ensureReady();
2195
+ this.logger.debug('Hiding launcher');
2196
+ this.iframeManager.hideIframe(IframeType.LAUNCHER);
2197
+ this.stateCoordinator.updateState('launcher.isVisible', false);
2198
+ }
2199
+ /**
2200
+ * Set badge count
2201
+ */
2202
+ setBadgeCount(count) {
2203
+ this.ensureReady();
2204
+ this.logger.debug('Setting badge count', { count });
2205
+ this.stateCoordinator.setBadgeCount(count);
2206
+ }
2207
+ /**
2208
+ * Clear badge
2209
+ */
2210
+ clearBadge() {
2211
+ this.setBadgeCount(0);
2212
+ }
2213
+ /**
2214
+ * Send a message
2215
+ */
2216
+ sendMessage(text, metadata) {
2217
+ this.ensureReady();
2218
+ this.logger.debug('Sending message', { text });
2219
+ this.messageBroker.sendToIframe(IframeType.WIDGET, 'weld:message:send', {
2220
+ text,
2221
+ metadata,
2222
+ timestamp: Date.now(),
2223
+ });
2224
+ }
2225
+ /**
2226
+ * Identify user
2227
+ */
2228
+ identify(identity) {
2229
+ this.ensureReady();
2230
+ this.logger.debug('Identifying user', { userId: identity.userId });
2231
+ this.stateCoordinator.setUserAuth(identity.userId, identity.email, identity.name);
2232
+ this.messageBroker.broadcast('weld:auth:login', identity);
2233
+ }
2234
+ /**
2235
+ * Logout user
2236
+ */
2237
+ logout() {
2238
+ this.ensureReady();
2239
+ this.logger.debug('Logging out user');
2240
+ this.stateCoordinator.updateState('user', {
2241
+ isAuthenticated: false,
2242
+ isAnonymous: true,
2243
+ });
2244
+ this.messageBroker.broadcast('weld:auth:logout', {});
2245
+ }
2246
+ /**
2247
+ * Update configuration
2248
+ */
2249
+ updateConfig(updates) {
2250
+ this.ensureReady();
2251
+ this.logger.debug('Updating configuration');
2252
+ // Merge config
2253
+ this.config = resolveConfig({ ...this.config, ...updates });
2254
+ // Broadcast config update
2255
+ this.messageBroker.broadcast('weld:config:update', updates);
2256
+ }
2257
+ /**
2258
+ * Update theme
2259
+ */
2260
+ setTheme(theme) {
2261
+ this.ensureReady();
2262
+ this.logger.debug('Setting theme', { theme });
2263
+ this.stateCoordinator.updateState('ui.theme', theme);
2264
+ this.messageBroker.broadcast('weld:theme:update', { mode: theme });
2265
+ }
2266
+ /**
2267
+ * Update locale
2268
+ */
2269
+ setLocale(locale) {
2270
+ this.ensureReady();
2271
+ this.logger.debug('Setting locale', { locale });
2272
+ this.stateCoordinator.updateState('ui.locale', locale);
2273
+ this.messageBroker.broadcast('weld:locale:update', { locale });
2274
+ }
2275
+ /**
2276
+ * Track custom event
2277
+ */
2278
+ track(eventName, properties) {
2279
+ this.ensureReady();
2280
+ this.logger.debug('Tracking event', { eventName });
2281
+ this.messageBroker.broadcast('weld:event:track', {
2282
+ name: eventName,
2283
+ properties,
2284
+ timestamp: Date.now(),
2285
+ });
2286
+ }
2287
+ /**
2288
+ * Get current state
2289
+ */
2290
+ getState() {
2291
+ return this.stateCoordinator.getState();
2292
+ }
2293
+ /**
2294
+ * Subscribe to state changes
2295
+ */
2296
+ onStateChange(path, listener) {
2297
+ this.ensureReady();
2298
+ const subscriptionId = this.stateCoordinator.subscribe(path, listener);
2299
+ // Return unsubscribe function
2300
+ return () => {
2301
+ this.stateCoordinator.unsubscribe(subscriptionId);
2302
+ };
2303
+ }
2304
+ /**
2305
+ * Get device info
2306
+ */
2307
+ getDeviceInfo() {
2308
+ return this.iframeManager.getDeviceInfo();
2309
+ }
2310
+ /**
2311
+ * Get SDK status
2312
+ */
2313
+ getStatus() {
2314
+ return this.status;
2315
+ }
2316
+ /**
2317
+ * Get SDK version
2318
+ */
2319
+ getVersion() {
2320
+ return packageJson.version;
2321
+ }
2322
+ /**
2323
+ * Enable debug mode
2324
+ */
2325
+ enableDebug() {
2326
+ this.logger.setLevel('debug');
2327
+ this.logger.info('Debug mode enabled');
2328
+ }
2329
+ /**
2330
+ * Disable debug mode
2331
+ */
2332
+ disableDebug() {
2333
+ this.logger.setLevel('warn');
2334
+ this.logger.info('Debug mode disabled');
2335
+ }
2336
+ /**
2337
+ * Ensure SDK is ready before operation
2338
+ */
2339
+ ensureReady() {
2340
+ if (this.status !== SDKStatus.READY) {
2341
+ throw new Error('SDK not ready. Call init() first.');
2342
+ }
2343
+ }
2344
+ /**
2345
+ * Destroy SDK and cleanup
2346
+ */
2347
+ destroy() {
2348
+ this.logger.info('Destroying WeldSDK');
2349
+ // Destroy modules
2350
+ this.stateCoordinator.destroy();
2351
+ this.messageBroker.destroy();
2352
+ this.iframeManager.destroy();
2353
+ // Reset status
2354
+ this.status = SDKStatus.DESTROYED;
2355
+ // Call onDestroy callback
2356
+ this.config.onDestroy?.();
2357
+ this.logger.info('WeldSDK destroyed');
2358
+ }
2359
+ }
2360
+
2361
+ /**
2362
+ * React hook for the Helpdesk Widget
2363
+ *
2364
+ * @example
2365
+ * ```tsx
2366
+ * function App() {
2367
+ * const widget = useHelpdeskWidget({ widgetId: 'your-widget-id' });
2368
+ *
2369
+ * return (
2370
+ * <button onClick={() => widget?.open()}>
2371
+ * Show Support
2372
+ * </button>
2373
+ * );
2374
+ * }
2375
+ * ```
2376
+ */
2377
+ function useHelpdeskWidget(config) {
2378
+ const widgetRef = useRef(null);
2379
+ const configRef = useRef(config);
2380
+ // Update config ref when config changes
2381
+ useEffect(() => {
2382
+ configRef.current = config;
2383
+ }, [config]);
2384
+ useEffect(() => {
2385
+ // Initialize widget
2386
+ try {
2387
+ widgetRef.current = new WeldSDK(configRef.current);
2388
+ widgetRef.current.init();
2389
+ }
2390
+ catch (error) {
2391
+ console.error('Failed to initialize Helpdesk Widget:', error);
2392
+ }
2393
+ // Cleanup on unmount
2394
+ return () => {
2395
+ if (widgetRef.current) {
2396
+ widgetRef.current.destroy();
2397
+ widgetRef.current = null;
2398
+ }
2399
+ };
2400
+ }, []);
2401
+ return widgetRef.current;
2402
+ }
2403
+ /**
2404
+ * Hook that provides widget control methods
2405
+ *
2406
+ * @example
2407
+ * ```tsx
2408
+ * function App() {
2409
+ * const { open, close, toggle } = useHelpdeskWidgetControls({
2410
+ * widgetId: 'your-widget-id'
2411
+ * });
2412
+ *
2413
+ * return (
2414
+ * <button onClick={open}>Show Support</button>
2415
+ * );
2416
+ * }
2417
+ * ```
2418
+ */
2419
+ function useHelpdeskWidgetControls(config) {
2420
+ const widget = useHelpdeskWidget(config);
2421
+ const open = useCallback(() => {
2422
+ widget?.open();
2423
+ }, [widget]);
2424
+ const close = useCallback(() => {
2425
+ widget?.close();
2426
+ }, [widget]);
2427
+ const toggle = useCallback(() => {
2428
+ widget?.toggle();
2429
+ }, [widget]);
2430
+ const sendMessage = useCallback((message) => {
2431
+ widget?.sendMessage(message);
2432
+ }, [widget]);
2433
+ return {
2434
+ widget,
2435
+ open,
2436
+ close,
2437
+ toggle,
2438
+ sendMessage,
2439
+ };
2440
+ }
2441
+
2442
+ /**
2443
+ * React component for the Helpdesk Widget
2444
+ *
2445
+ * This component provides a declarative way to add the widget to your app.
2446
+ * The widget loads in an iframe and doesn't render any visible children.
2447
+ *
2448
+ * @example
2449
+ * ```tsx
2450
+ * import { HelpdeskWidgetReact } from '@weldsuite/helpdesk-widget-sdk/react';
2451
+ *
2452
+ * function App() {
2453
+ * return (
2454
+ * <div>
2455
+ * <h1>My App</h1>
2456
+ * <HelpdeskWidgetReact widgetId="your-widget-id" />
2457
+ * </div>
2458
+ * );
2459
+ * }
2460
+ * ```
2461
+ *
2462
+ * @example With event handlers
2463
+ * ```tsx
2464
+ * <HelpdeskWidgetReact
2465
+ * widgetId="your-widget-id"
2466
+ * onReady={() => console.log('Widget ready')}
2467
+ * onOpen={() => console.log('Widget opened')}
2468
+ * />
2469
+ * ```
2470
+ */
2471
+ const HelpdeskWidgetReact = (config) => {
2472
+ useHelpdeskWidget(config);
2473
+ // Widget loads in iframe, no visible component needed
2474
+ return null;
2475
+ };
2476
+ HelpdeskWidgetReact.displayName = 'HelpdeskWidget';
2477
+
2478
+ export { DEFAULT_CONFIG, WeldSDK as HelpdeskWidget, HelpdeskWidgetReact, WeldSDK, HelpdeskWidgetReact as default, useHelpdeskWidget, useHelpdeskWidgetControls };
2479
+ //# sourceMappingURL=react.esm.js.map