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