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