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