@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,2445 @@
1
+ 'use strict';
2
+
3
+ var vue = require('vue');
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
+ var LogLevel;
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
+ })(LogLevel || (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 LogLevel.DEBUG;
216
+ case 'info':
217
+ return LogLevel.INFO;
218
+ case 'warn':
219
+ return LogLevel.WARN;
220
+ case 'error':
221
+ return LogLevel.ERROR;
222
+ default:
223
+ return 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(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(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(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(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
+ 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
+ var IframeType;
350
+ (function (IframeType) {
351
+ IframeType["LAUNCHER"] = "launcher";
352
+ IframeType["WIDGET"] = "widget";
353
+ IframeType["BACKDROP"] = "backdrop";
354
+ })(IframeType || (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(IframeType.LAUNCHER, {
536
+ type: 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(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(IframeType.WIDGET, {
596
+ type: 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(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(IframeType.BACKDROP, {
643
+ type: 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 !== 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 === 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 === 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 === 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 === 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
+ /**
1121
+ * Weld SDK - Message Broker
1122
+ * Handles secure postMessage communication between parent and iframes
1123
+ */
1124
+ /**
1125
+ * MessageBroker class
1126
+ * Central hub for all postMessage communication
1127
+ */
1128
+ class MessageBroker {
1129
+ constructor(config, iframeManager, logger) {
1130
+ this.subscriptions = new Map();
1131
+ this.messageQueue = [];
1132
+ this.isReady = false;
1133
+ this.responseHandlers = new Map();
1134
+ this.config = config;
1135
+ this.logger = logger.child('[MessageBroker]');
1136
+ this.iframeManager = iframeManager;
1137
+ this.security = new SecurityManager(config.security, this.logger);
1138
+ this.rateLimiter = new RateLimiter(100, 60000); // 100 messages per minute
1139
+ this.setupMessageListener();
1140
+ this.logger.debug('MessageBroker initialized');
1141
+ }
1142
+ /**
1143
+ * Setup global message listener
1144
+ */
1145
+ setupMessageListener() {
1146
+ window.addEventListener('message', this.handleMessage.bind(this));
1147
+ this.logger.debug('Message listener setup');
1148
+ }
1149
+ /**
1150
+ * Handle incoming postMessage
1151
+ */
1152
+ handleMessage(event) {
1153
+ // Validate message event
1154
+ if (!this.security.validateMessageEvent(event)) {
1155
+ return;
1156
+ }
1157
+ // Validate message structure
1158
+ if (!isBaseMessage(event.data)) {
1159
+ this.logger.warn('Invalid message structure', event.data);
1160
+ return;
1161
+ }
1162
+ const message = event.data;
1163
+ // Rate limiting
1164
+ if (!this.rateLimiter.isAllowed(message.origin)) {
1165
+ this.logger.warn('Rate limit exceeded', { origin: message.origin });
1166
+ return;
1167
+ }
1168
+ // Sanitize message if enabled
1169
+ const sanitized = this.config.security.sanitizeInput
1170
+ ? this.security.sanitizeMessageData(message)
1171
+ : message;
1172
+ this.logger.debug('Message received', {
1173
+ type: sanitized.type,
1174
+ origin: sanitized.origin,
1175
+ id: sanitized.id,
1176
+ });
1177
+ // Check for response handlers
1178
+ if (this.responseHandlers.has(sanitized.id)) {
1179
+ const handler = this.responseHandlers.get(sanitized.id);
1180
+ handler?.(sanitized);
1181
+ this.responseHandlers.delete(sanitized.id);
1182
+ return;
1183
+ }
1184
+ // Dispatch to subscribers
1185
+ this.dispatchMessage(sanitized);
1186
+ }
1187
+ /**
1188
+ * Dispatch message to subscribers
1189
+ */
1190
+ dispatchMessage(message) {
1191
+ const subscriptions = Array.from(this.subscriptions.values());
1192
+ for (const subscription of subscriptions) {
1193
+ // Check if type matches
1194
+ const typeMatches = subscription.type === '*' || subscription.type === message.type;
1195
+ // Check if origin matches (if specified)
1196
+ const originMatches = !subscription.origin || subscription.origin === message.origin;
1197
+ if (typeMatches && originMatches) {
1198
+ try {
1199
+ if (isPayloadMessage(message)) {
1200
+ subscription.handler(message.payload, message);
1201
+ }
1202
+ else {
1203
+ subscription.handler(undefined, message);
1204
+ }
1205
+ }
1206
+ catch (error) {
1207
+ this.logger.error('Error in message handler', error);
1208
+ }
1209
+ }
1210
+ }
1211
+ }
1212
+ /**
1213
+ * Subscribe to messages
1214
+ */
1215
+ subscribe(type, handler, origin) {
1216
+ const id = this.security.generateSecureId();
1217
+ this.subscriptions.set(id, {
1218
+ id,
1219
+ type,
1220
+ origin,
1221
+ handler,
1222
+ });
1223
+ this.logger.debug('Subscribed to messages', { type, origin, id });
1224
+ return id;
1225
+ }
1226
+ /**
1227
+ * Unsubscribe from messages
1228
+ */
1229
+ unsubscribe(subscriptionId) {
1230
+ if (this.subscriptions.delete(subscriptionId)) {
1231
+ this.logger.debug('Unsubscribed from messages', { id: subscriptionId });
1232
+ }
1233
+ }
1234
+ /**
1235
+ * Send message to iframe
1236
+ */
1237
+ sendToIframe(iframeType, type, payload) {
1238
+ const iframe = this.iframeManager.getIframe(iframeType);
1239
+ if (!iframe) {
1240
+ this.logger.warn('Iframe not found', { iframeType });
1241
+ return;
1242
+ }
1243
+ if (!iframe.ready && type !== 'weld:init') {
1244
+ // Queue message if iframe not ready
1245
+ const message = createMessage(type, MessageOrigin.PARENT, payload);
1246
+ this.messageQueue.push(message);
1247
+ this.logger.debug('Message queued (iframe not ready)', {
1248
+ iframeType,
1249
+ type,
1250
+ });
1251
+ return;
1252
+ }
1253
+ const message = createMessage(type, MessageOrigin.PARENT, payload);
1254
+ this.postMessage(iframe.element, message);
1255
+ }
1256
+ /**
1257
+ * Send message to all iframes
1258
+ */
1259
+ broadcast(type, payload) {
1260
+ const message = createMessage(type, MessageOrigin.PARENT, payload);
1261
+ const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
1262
+ const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
1263
+ if (launcherIframe && launcherIframe.ready) {
1264
+ this.postMessage(launcherIframe.element, message);
1265
+ }
1266
+ if (widgetIframe && widgetIframe.ready) {
1267
+ this.postMessage(widgetIframe.element, message);
1268
+ }
1269
+ this.logger.debug('Message broadcast', { type });
1270
+ }
1271
+ /**
1272
+ * Send message and wait for response
1273
+ */
1274
+ async sendAndWaitForResponse(iframeType, type, payload, timeout = 5000) {
1275
+ return new Promise((resolve, reject) => {
1276
+ const message = createMessage(type, MessageOrigin.PARENT, payload);
1277
+ // Setup response handler
1278
+ const timeoutId = setTimeout(() => {
1279
+ this.responseHandlers.delete(message.id);
1280
+ reject(new Error('Message response timeout'));
1281
+ }, timeout);
1282
+ this.responseHandlers.set(message.id, (response) => {
1283
+ clearTimeout(timeoutId);
1284
+ if (isPayloadMessage(response)) {
1285
+ resolve(response.payload);
1286
+ }
1287
+ else {
1288
+ resolve(undefined);
1289
+ }
1290
+ });
1291
+ // Send message
1292
+ const iframe = this.iframeManager.getIframe(iframeType);
1293
+ if (!iframe) {
1294
+ reject(new Error('Iframe not found'));
1295
+ return;
1296
+ }
1297
+ this.postMessage(iframe.element, message);
1298
+ });
1299
+ }
1300
+ /**
1301
+ * Post message to iframe
1302
+ */
1303
+ postMessage(iframe, message) {
1304
+ if (!iframe.contentWindow) {
1305
+ this.logger.warn('Iframe contentWindow not available');
1306
+ return;
1307
+ }
1308
+ try {
1309
+ iframe.contentWindow.postMessage(message, '*');
1310
+ this.logger.debug('Message sent', {
1311
+ type: message.type,
1312
+ id: message.id,
1313
+ });
1314
+ }
1315
+ catch (error) {
1316
+ this.logger.error('Failed to post message', error);
1317
+ }
1318
+ }
1319
+ /**
1320
+ * Mark iframe as ready and flush queued messages
1321
+ */
1322
+ setIframeReady(iframeType) {
1323
+ this.iframeManager.setIframeReady(iframeType);
1324
+ // Flush queued messages for this iframe
1325
+ this.flushMessageQueue(iframeType);
1326
+ // Check if all iframes ready
1327
+ if (this.iframeManager.areAllIframesReady()) {
1328
+ this.isReady = true;
1329
+ this.logger.info('All iframes ready');
1330
+ }
1331
+ }
1332
+ /**
1333
+ * Flush queued messages to iframe
1334
+ */
1335
+ flushMessageQueue(iframeType) {
1336
+ const iframe = this.iframeManager.getIframe(iframeType);
1337
+ if (!iframe)
1338
+ return;
1339
+ const queuedMessages = this.messageQueue.splice(0);
1340
+ for (const message of queuedMessages) {
1341
+ this.postMessage(iframe.element, message);
1342
+ }
1343
+ if (queuedMessages.length > 0) {
1344
+ this.logger.debug('Flushed queued messages', {
1345
+ count: queuedMessages.length,
1346
+ iframeType,
1347
+ });
1348
+ }
1349
+ }
1350
+ /**
1351
+ * Check if broker is ready
1352
+ */
1353
+ isMessageBrokerReady() {
1354
+ return this.isReady;
1355
+ }
1356
+ /**
1357
+ * Get queued message count
1358
+ */
1359
+ getQueuedMessageCount() {
1360
+ return this.messageQueue.length;
1361
+ }
1362
+ /**
1363
+ * Clear message queue
1364
+ */
1365
+ clearMessageQueue() {
1366
+ this.messageQueue = [];
1367
+ this.logger.debug('Message queue cleared');
1368
+ }
1369
+ /**
1370
+ * Get active subscription count
1371
+ */
1372
+ getSubscriptionCount() {
1373
+ return this.subscriptions.size;
1374
+ }
1375
+ /**
1376
+ * Destroy broker and cleanup
1377
+ */
1378
+ destroy() {
1379
+ this.logger.debug('Destroying message broker');
1380
+ // Remove event listener
1381
+ window.removeEventListener('message', this.handleMessage.bind(this));
1382
+ // Clear subscriptions
1383
+ this.subscriptions.clear();
1384
+ // Clear message queue
1385
+ this.messageQueue = [];
1386
+ // Clear response handlers
1387
+ this.responseHandlers.clear();
1388
+ // Reset rate limiter
1389
+ this.rateLimiter.clearAll();
1390
+ this.isReady = false;
1391
+ this.logger.info('MessageBroker destroyed');
1392
+ }
1393
+ }
1394
+
1395
+ /**
1396
+ * Weld SDK - State Types
1397
+ * Type definitions for state management across iframes
1398
+ */
1399
+ /**
1400
+ * Widget visibility state
1401
+ */
1402
+ var WidgetVisibility;
1403
+ (function (WidgetVisibility) {
1404
+ WidgetVisibility["HIDDEN"] = "hidden";
1405
+ WidgetVisibility["VISIBLE"] = "visible";
1406
+ WidgetVisibility["MINIMIZED"] = "minimized";
1407
+ })(WidgetVisibility || (WidgetVisibility = {}));
1408
+ /**
1409
+ * Widget view types
1410
+ */
1411
+ var WidgetView;
1412
+ (function (WidgetView) {
1413
+ WidgetView["HOME"] = "home";
1414
+ WidgetView["CONVERSATION"] = "conversation";
1415
+ WidgetView["CONVERSATIONS"] = "conversations";
1416
+ WidgetView["HELP"] = "help";
1417
+ WidgetView["SETTINGS"] = "settings";
1418
+ WidgetView["SEARCH"] = "search";
1419
+ })(WidgetView || (WidgetView = {}));
1420
+ /**
1421
+ * Connection status
1422
+ */
1423
+ var ConnectionStatus;
1424
+ (function (ConnectionStatus) {
1425
+ ConnectionStatus["DISCONNECTED"] = "disconnected";
1426
+ ConnectionStatus["CONNECTING"] = "connecting";
1427
+ ConnectionStatus["CONNECTED"] = "connected";
1428
+ ConnectionStatus["RECONNECTING"] = "reconnecting";
1429
+ ConnectionStatus["ERROR"] = "error";
1430
+ })(ConnectionStatus || (ConnectionStatus = {}));
1431
+ /**
1432
+ * Message status
1433
+ */
1434
+ var MessageStatus;
1435
+ (function (MessageStatus) {
1436
+ MessageStatus["SENDING"] = "sending";
1437
+ MessageStatus["SENT"] = "sent";
1438
+ MessageStatus["DELIVERED"] = "delivered";
1439
+ MessageStatus["READ"] = "read";
1440
+ MessageStatus["FAILED"] = "failed";
1441
+ })(MessageStatus || (MessageStatus = {}));
1442
+ /**
1443
+ * Initial state factory
1444
+ */
1445
+ function createInitialState() {
1446
+ return {
1447
+ user: {
1448
+ isAuthenticated: false,
1449
+ isAnonymous: true,
1450
+ },
1451
+ conversation: {
1452
+ messages: [],
1453
+ participants: [],
1454
+ unreadCount: 0,
1455
+ isTyping: false,
1456
+ typingUsers: [],
1457
+ },
1458
+ widget: {
1459
+ visibility: WidgetVisibility.HIDDEN,
1460
+ view: WidgetView.HOME,
1461
+ isOpen: false,
1462
+ isMinimized: false,
1463
+ dimensions: {
1464
+ width: '400px',
1465
+ height: 'min(680px, 88vh)',
1466
+ },
1467
+ position: {
1468
+ bottom: '24px',
1469
+ right: '24px',
1470
+ },
1471
+ },
1472
+ launcher: {
1473
+ isVisible: true,
1474
+ badge: {
1475
+ count: 0,
1476
+ show: false,
1477
+ },
1478
+ },
1479
+ backdrop: {
1480
+ isVisible: false,
1481
+ closeOnClick: true,
1482
+ opacity: 0,
1483
+ },
1484
+ mobile: {
1485
+ isFullScreen: false,
1486
+ isScrollLocked: false,
1487
+ keyboardHeight: 0,
1488
+ orientation: 'portrait',
1489
+ safeAreaInsets: {
1490
+ top: 0,
1491
+ right: 0,
1492
+ bottom: 0,
1493
+ left: 0,
1494
+ },
1495
+ },
1496
+ network: {
1497
+ status: ConnectionStatus.DISCONNECTED,
1498
+ retryCount: 0,
1499
+ },
1500
+ ui: {
1501
+ theme: 'auto',
1502
+ locale: 'en',
1503
+ isLoading: false,
1504
+ },
1505
+ initialized: false,
1506
+ lastUpdated: Date.now(),
1507
+ };
1508
+ }
1509
+ /**
1510
+ * State path utility
1511
+ */
1512
+ function getStateValue(state, path) {
1513
+ const keys = path.split('.');
1514
+ let value = state;
1515
+ for (const key of keys) {
1516
+ if (value && typeof value === 'object' && key in value) {
1517
+ value = value[key];
1518
+ }
1519
+ else {
1520
+ return undefined;
1521
+ }
1522
+ }
1523
+ return value;
1524
+ }
1525
+ /**
1526
+ * State update utility
1527
+ */
1528
+ function setStateValue(state, path, value, merge = false) {
1529
+ const keys = path.split('.');
1530
+ const newState = JSON.parse(JSON.stringify(state));
1531
+ let current = newState;
1532
+ for (let i = 0; i < keys.length - 1; i++) {
1533
+ const key = keys[i];
1534
+ if (!(key in current)) {
1535
+ current[key] = {};
1536
+ }
1537
+ current = current[key];
1538
+ }
1539
+ const lastKey = keys[keys.length - 1];
1540
+ if (merge && typeof current[lastKey] === 'object' && typeof value === 'object') {
1541
+ current[lastKey] = { ...current[lastKey], ...value };
1542
+ }
1543
+ else {
1544
+ current[lastKey] = value;
1545
+ }
1546
+ newState.lastUpdated = Date.now();
1547
+ return newState;
1548
+ }
1549
+
1550
+ /**
1551
+ * Weld SDK - Validation Utilities
1552
+ * Input validation and sanitization functions
1553
+ */
1554
+ /**
1555
+ * Validate email format
1556
+ */
1557
+ /**
1558
+ * Deep clone object (safe for JSON-serializable objects)
1559
+ */
1560
+ function deepClone(obj) {
1561
+ if (obj === null || typeof obj !== 'object') {
1562
+ return obj;
1563
+ }
1564
+ if (obj instanceof Date) {
1565
+ return new Date(obj.getTime());
1566
+ }
1567
+ if (obj instanceof Array) {
1568
+ return obj.map((item) => deepClone(item));
1569
+ }
1570
+ if (obj instanceof Object) {
1571
+ const cloned = {};
1572
+ for (const key in obj) {
1573
+ if (obj.hasOwnProperty(key)) {
1574
+ cloned[key] = deepClone(obj[key]);
1575
+ }
1576
+ }
1577
+ return cloned;
1578
+ }
1579
+ return obj;
1580
+ }
1581
+
1582
+ /**
1583
+ * Weld SDK - State Coordinator
1584
+ * Manages and synchronizes state across iframes
1585
+ */
1586
+ /**
1587
+ * StateCoordinator class
1588
+ * Central state management for multi-iframe architecture
1589
+ */
1590
+ class StateCoordinator {
1591
+ constructor(messageBroker, logger) {
1592
+ this.subscriptions = new Map();
1593
+ this.stateHistory = [];
1594
+ this.maxHistorySize = 50;
1595
+ this.messageBroker = messageBroker;
1596
+ this.logger = logger.child('[StateCoordinator]');
1597
+ this.state = createInitialState();
1598
+ this.setupMessageHandlers();
1599
+ this.logger.debug('StateCoordinator initialized');
1600
+ }
1601
+ /**
1602
+ * Setup message handlers for state updates
1603
+ */
1604
+ setupMessageHandlers() {
1605
+ // Listen for state update requests from iframes
1606
+ this.messageBroker.subscribe('weld:state:update', this.handleStateUpdate.bind(this));
1607
+ // Listen for state request messages
1608
+ this.messageBroker.subscribe('weld:state:request', this.handleStateRequest.bind(this));
1609
+ this.logger.debug('Message handlers setup');
1610
+ }
1611
+ /**
1612
+ * Handle state update from iframe
1613
+ */
1614
+ handleStateUpdate(payload) {
1615
+ const { path, value, merge } = payload;
1616
+ if (!path) {
1617
+ this.logger.warn('State update missing path', payload);
1618
+ return;
1619
+ }
1620
+ this.updateState(path, value, merge);
1621
+ }
1622
+ /**
1623
+ * Handle state request from iframe
1624
+ */
1625
+ handleStateRequest(payload) {
1626
+ const { path } = payload;
1627
+ if (path) {
1628
+ const value = getStateValue(this.state, path);
1629
+ this.messageBroker.broadcast('weld:state:response', {
1630
+ path,
1631
+ value,
1632
+ });
1633
+ }
1634
+ else {
1635
+ // Send entire state
1636
+ this.messageBroker.broadcast('weld:state:response', {
1637
+ state: this.state,
1638
+ });
1639
+ }
1640
+ }
1641
+ /**
1642
+ * Get current state
1643
+ */
1644
+ getState() {
1645
+ return deepClone(this.state);
1646
+ }
1647
+ /**
1648
+ * Get state value by path
1649
+ */
1650
+ getValue(path) {
1651
+ return getStateValue(this.state, path);
1652
+ }
1653
+ /**
1654
+ * Update state
1655
+ */
1656
+ updateState(path, value, merge = false) {
1657
+ const oldState = deepClone(this.state);
1658
+ this.state = setStateValue(this.state, path, value, merge);
1659
+ // Create state action
1660
+ const action = {
1661
+ type: 'UPDATE',
1662
+ path,
1663
+ payload: value,
1664
+ merge,
1665
+ timestamp: Date.now(),
1666
+ };
1667
+ // Add to history
1668
+ this.addToHistory(action);
1669
+ // Notify subscribers
1670
+ this.notifySubscribers(path, value, getStateValue(oldState, path));
1671
+ // Broadcast to iframes
1672
+ this.messageBroker.broadcast('weld:state:update', {
1673
+ path,
1674
+ value,
1675
+ merge,
1676
+ });
1677
+ this.logger.debug('State updated', { path, merge });
1678
+ }
1679
+ /**
1680
+ * Batch update multiple state paths
1681
+ */
1682
+ batchUpdate(updates) {
1683
+ const oldState = deepClone(this.state);
1684
+ for (const update of updates) {
1685
+ this.state = setStateValue(this.state, update.path, update.value, update.merge);
1686
+ }
1687
+ // Notify subscribers for each update
1688
+ for (const update of updates) {
1689
+ this.notifySubscribers(update.path, getStateValue(this.state, update.path), getStateValue(oldState, update.path));
1690
+ }
1691
+ // Broadcast all updates
1692
+ this.messageBroker.broadcast('weld:state:update', {
1693
+ batch: updates,
1694
+ });
1695
+ this.logger.debug('Batch state update', { count: updates.length });
1696
+ }
1697
+ /**
1698
+ * Reset state to initial
1699
+ */
1700
+ resetState() {
1701
+ const oldState = this.state;
1702
+ this.state = createInitialState();
1703
+ // Notify all subscribers
1704
+ for (const [_id, subscription] of this.subscriptions) {
1705
+ const oldValue = getStateValue(oldState, subscription.path);
1706
+ const newValue = getStateValue(this.state, subscription.path);
1707
+ subscription.listener(newValue, oldValue);
1708
+ }
1709
+ // Broadcast reset
1710
+ this.messageBroker.broadcast('weld:state:update', {
1711
+ reset: true,
1712
+ state: this.state,
1713
+ });
1714
+ this.logger.debug('State reset');
1715
+ }
1716
+ /**
1717
+ * Subscribe to state changes
1718
+ */
1719
+ subscribe(path, listener, immediate = false) {
1720
+ const id = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1721
+ this.subscriptions.set(id, {
1722
+ id,
1723
+ path,
1724
+ listener,
1725
+ immediate,
1726
+ });
1727
+ // Call listener immediately if requested
1728
+ if (immediate) {
1729
+ const value = getStateValue(this.state, path);
1730
+ listener(value, value);
1731
+ }
1732
+ this.logger.debug('State subscription added', { id, path });
1733
+ return id;
1734
+ }
1735
+ /**
1736
+ * Unsubscribe from state changes
1737
+ */
1738
+ unsubscribe(subscriptionId) {
1739
+ if (this.subscriptions.delete(subscriptionId)) {
1740
+ this.logger.debug('State subscription removed', { id: subscriptionId });
1741
+ }
1742
+ }
1743
+ /**
1744
+ * Notify subscribers of state change
1745
+ */
1746
+ notifySubscribers(path, newValue, oldValue) {
1747
+ const subscriptions = Array.from(this.subscriptions.values());
1748
+ for (const subscription of subscriptions) {
1749
+ // Exact path match
1750
+ if (subscription.path === path) {
1751
+ try {
1752
+ subscription.listener(newValue, oldValue);
1753
+ }
1754
+ catch (error) {
1755
+ this.logger.error('Error in state listener', error);
1756
+ }
1757
+ continue;
1758
+ }
1759
+ // Parent path match (e.g., subscriber to "user" gets notified for "user.name")
1760
+ if (path.startsWith(subscription.path + '.')) {
1761
+ const subValue = getStateValue(this.state, subscription.path);
1762
+ try {
1763
+ subscription.listener(subValue, oldValue);
1764
+ }
1765
+ catch (error) {
1766
+ this.logger.error('Error in state listener', error);
1767
+ }
1768
+ continue;
1769
+ }
1770
+ // Child path match (e.g., subscriber to "user.name" gets notified for "user")
1771
+ if (subscription.path.startsWith(path + '.')) {
1772
+ const subValue = getStateValue(this.state, subscription.path);
1773
+ try {
1774
+ subscription.listener(subValue, undefined);
1775
+ }
1776
+ catch (error) {
1777
+ this.logger.error('Error in state listener', error);
1778
+ }
1779
+ }
1780
+ }
1781
+ }
1782
+ /**
1783
+ * Add action to history
1784
+ */
1785
+ addToHistory(action) {
1786
+ this.stateHistory.push(action);
1787
+ // Trim history if too large
1788
+ if (this.stateHistory.length > this.maxHistorySize) {
1789
+ this.stateHistory.shift();
1790
+ }
1791
+ }
1792
+ /**
1793
+ * Get state history
1794
+ */
1795
+ getHistory() {
1796
+ return [...this.stateHistory];
1797
+ }
1798
+ /**
1799
+ * Clear state history
1800
+ */
1801
+ clearHistory() {
1802
+ this.stateHistory = [];
1803
+ this.logger.debug('State history cleared');
1804
+ }
1805
+ /**
1806
+ * Get snapshot of state at specific time
1807
+ */
1808
+ getSnapshot(timestamp) {
1809
+ // Find all actions before timestamp
1810
+ const actions = this.stateHistory.filter((a) => a.timestamp <= timestamp);
1811
+ if (actions.length === 0) {
1812
+ return null;
1813
+ }
1814
+ // Replay actions to reconstruct state
1815
+ let state = createInitialState();
1816
+ for (const action of actions) {
1817
+ state = setStateValue(state, action.path, action.payload, action.merge);
1818
+ }
1819
+ return state;
1820
+ }
1821
+ /**
1822
+ * Widget-specific state helpers
1823
+ */
1824
+ openWidget() {
1825
+ this.batchUpdate([
1826
+ { path: 'widget.isOpen', value: true },
1827
+ { path: 'widget.visibility', value: 'visible' },
1828
+ { path: 'launcher.isVisible', value: false },
1829
+ { path: 'backdrop.isVisible', value: true },
1830
+ ]);
1831
+ }
1832
+ closeWidget() {
1833
+ this.batchUpdate([
1834
+ { path: 'widget.isOpen', value: false },
1835
+ { path: 'widget.visibility', value: 'hidden' },
1836
+ { path: 'launcher.isVisible', value: true },
1837
+ { path: 'backdrop.isVisible', value: false },
1838
+ ]);
1839
+ }
1840
+ minimizeWidget() {
1841
+ this.batchUpdate([
1842
+ { path: 'widget.isMinimized', value: true },
1843
+ { path: 'widget.visibility', value: 'minimized' },
1844
+ ]);
1845
+ }
1846
+ maximizeWidget() {
1847
+ this.batchUpdate([
1848
+ { path: 'widget.isMinimized', value: false },
1849
+ { path: 'widget.visibility', value: 'visible' },
1850
+ ]);
1851
+ }
1852
+ setBadgeCount(count) {
1853
+ this.batchUpdate([
1854
+ { path: 'launcher.badge.count', value: count },
1855
+ { path: 'launcher.badge.show', value: count > 0 },
1856
+ ]);
1857
+ }
1858
+ setUserAuth(userId, email, name) {
1859
+ this.batchUpdate([
1860
+ { path: 'user.isAuthenticated', value: true },
1861
+ { path: 'user.isAnonymous', value: false },
1862
+ { path: 'user.id', value: userId },
1863
+ { path: 'user.email', value: email },
1864
+ { path: 'user.name', value: name },
1865
+ ]);
1866
+ }
1867
+ setConnectionStatus(status) {
1868
+ this.updateState('network.status', status);
1869
+ }
1870
+ setLoading(isLoading) {
1871
+ this.updateState('ui.isLoading', isLoading);
1872
+ }
1873
+ setError(code, message, recoverable = true) {
1874
+ this.updateState('ui.error', { code, message, recoverable });
1875
+ }
1876
+ clearError() {
1877
+ this.updateState('ui.error', undefined);
1878
+ }
1879
+ /**
1880
+ * Get active subscription count
1881
+ */
1882
+ getSubscriptionCount() {
1883
+ return this.subscriptions.size;
1884
+ }
1885
+ /**
1886
+ * Validate state integrity
1887
+ */
1888
+ validateState() {
1889
+ try {
1890
+ // Check required properties exist
1891
+ const requiredPaths = [
1892
+ 'user',
1893
+ 'conversation',
1894
+ 'widget',
1895
+ 'launcher',
1896
+ 'backdrop',
1897
+ 'mobile',
1898
+ 'network',
1899
+ 'ui',
1900
+ ];
1901
+ for (const path of requiredPaths) {
1902
+ if (getStateValue(this.state, path) === undefined) {
1903
+ this.logger.error('Missing required state path', { path });
1904
+ return false;
1905
+ }
1906
+ }
1907
+ return true;
1908
+ }
1909
+ catch (error) {
1910
+ this.logger.error('State validation failed', error);
1911
+ return false;
1912
+ }
1913
+ }
1914
+ /**
1915
+ * Export state as JSON
1916
+ */
1917
+ exportState() {
1918
+ return JSON.stringify(this.state, null, 2);
1919
+ }
1920
+ /**
1921
+ * Import state from JSON
1922
+ */
1923
+ importState(json) {
1924
+ try {
1925
+ const imported = JSON.parse(json);
1926
+ this.state = { ...createInitialState(), ...imported };
1927
+ this.messageBroker.broadcast('weld:state:update', {
1928
+ reset: true,
1929
+ state: this.state,
1930
+ });
1931
+ this.logger.info('State imported');
1932
+ return true;
1933
+ }
1934
+ catch (error) {
1935
+ this.logger.error('Failed to import state', error);
1936
+ return false;
1937
+ }
1938
+ }
1939
+ /**
1940
+ * Destroy coordinator and cleanup
1941
+ */
1942
+ destroy() {
1943
+ this.logger.debug('Destroying state coordinator');
1944
+ // Clear subscriptions
1945
+ this.subscriptions.clear();
1946
+ // Clear history
1947
+ this.stateHistory = [];
1948
+ // Reset state
1949
+ this.state = createInitialState();
1950
+ this.logger.info('StateCoordinator destroyed');
1951
+ }
1952
+ }
1953
+
1954
+ var version = "1.0.2";
1955
+ var packageJson = {
1956
+ version: version};
1957
+
1958
+ /**
1959
+ * Weld SDK - Main Entry Point
1960
+ * Public API for the Weld helpdesk widget
1961
+ */
1962
+ /**
1963
+ * SDK initialization status
1964
+ */
1965
+ var SDKStatus;
1966
+ (function (SDKStatus) {
1967
+ SDKStatus["UNINITIALIZED"] = "uninitialized";
1968
+ SDKStatus["INITIALIZING"] = "initializing";
1969
+ SDKStatus["READY"] = "ready";
1970
+ SDKStatus["ERROR"] = "error";
1971
+ SDKStatus["DESTROYED"] = "destroyed";
1972
+ })(SDKStatus || (SDKStatus = {}));
1973
+ /**
1974
+ * WeldSDK class
1975
+ * Main SDK interface for embedding the widget
1976
+ */
1977
+ class WeldSDK {
1978
+ constructor(config) {
1979
+ this.status = SDKStatus.UNINITIALIZED;
1980
+ this.readyPromise = null;
1981
+ this.readyResolve = null;
1982
+ // Resolve configuration
1983
+ this.config = resolveConfig(config);
1984
+ // Initialize logger
1985
+ this.logger = new Logger(this.config.logging);
1986
+ this.logger.info('WeldSDK created', {
1987
+ widgetId: this.config.widgetId,
1988
+ });
1989
+ // Create ready promise
1990
+ this.readyPromise = new Promise((resolve) => {
1991
+ this.readyResolve = resolve;
1992
+ });
1993
+ // Initialize managers
1994
+ this.iframeManager = new IframeManager(this.config);
1995
+ this.messageBroker = new MessageBroker(this.config, this.iframeManager, this.logger);
1996
+ this.stateCoordinator = new StateCoordinator(this.messageBroker, this.logger);
1997
+ }
1998
+ /**
1999
+ * Initialize the SDK and render widget
2000
+ */
2001
+ async init() {
2002
+ if (this.status !== SDKStatus.UNINITIALIZED) {
2003
+ this.logger.warn('SDK already initialized');
2004
+ return this.readyPromise;
2005
+ }
2006
+ this.status = SDKStatus.INITIALIZING;
2007
+ this.logger.info('Initializing WeldSDK');
2008
+ try {
2009
+ // Initialize iframe manager
2010
+ await this.iframeManager.init();
2011
+ // Setup ready handlers
2012
+ this.setupReadyHandlers();
2013
+ // Wait for all iframes to be ready
2014
+ await this.waitForIframesReady();
2015
+ // Mark as ready
2016
+ this.status = SDKStatus.READY;
2017
+ this.readyResolve?.();
2018
+ this.logger.info('WeldSDK ready');
2019
+ // Call onReady callback
2020
+ this.config.onReady?.();
2021
+ }
2022
+ catch (error) {
2023
+ this.status = SDKStatus.ERROR;
2024
+ this.logger.error('Failed to initialize WeldSDK', error);
2025
+ this.config.onError?.(error);
2026
+ throw error;
2027
+ }
2028
+ return this.readyPromise;
2029
+ }
2030
+ /**
2031
+ * Setup ready message handlers
2032
+ */
2033
+ setupReadyHandlers() {
2034
+ this.messageBroker.subscribe('weld:ready', (payload) => {
2035
+ const { iframe } = payload;
2036
+ this.logger.debug('Iframe ready', { iframe });
2037
+ // Map iframe name to type
2038
+ const iframeType = this.mapIframeNameToType(iframe);
2039
+ if (iframeType) {
2040
+ this.messageBroker.setIframeReady(iframeType);
2041
+ }
2042
+ });
2043
+ // Listen for launcher click events from the iframe
2044
+ window.addEventListener('message', (event) => {
2045
+ // Log all messages from iframes for debugging
2046
+ if (event.data?.type) {
2047
+ console.log('[Weld SDK] Received message:', event.data.type);
2048
+ }
2049
+ if (event.data?.type === 'launcher:clicked') {
2050
+ // Toggle behavior - if widget is open, close it; if closed, open it
2051
+ const state = this.stateCoordinator.getState();
2052
+ if (state.widget.isOpen) {
2053
+ console.log('[Weld SDK] Launcher clicked - closing widget (toggle)');
2054
+ this.close();
2055
+ }
2056
+ else {
2057
+ console.log('[Weld SDK] Launcher clicked - opening widget');
2058
+ this.open();
2059
+ }
2060
+ }
2061
+ if (event.data?.type === 'weld:close') {
2062
+ console.log('[Weld SDK] Widget close requested');
2063
+ this.close();
2064
+ }
2065
+ });
2066
+ }
2067
+ /**
2068
+ * Map iframe name to type
2069
+ */
2070
+ mapIframeNameToType(name) {
2071
+ if (name.includes('launcher'))
2072
+ return IframeType.LAUNCHER;
2073
+ if (name.includes('widget'))
2074
+ return IframeType.WIDGET;
2075
+ return null;
2076
+ }
2077
+ /**
2078
+ * Wait for all iframes to be ready
2079
+ */
2080
+ async waitForIframesReady(timeout = 10000) {
2081
+ const startTime = Date.now();
2082
+ return new Promise((resolve, reject) => {
2083
+ const checkReady = () => {
2084
+ if (this.iframeManager.areAllIframesReady()) {
2085
+ resolve();
2086
+ return;
2087
+ }
2088
+ if (Date.now() - startTime > timeout) {
2089
+ reject(new Error('Timeout waiting for iframes to be ready'));
2090
+ return;
2091
+ }
2092
+ setTimeout(checkReady, 100);
2093
+ };
2094
+ checkReady();
2095
+ });
2096
+ }
2097
+ /**
2098
+ * Wait for SDK to be ready
2099
+ */
2100
+ async ready() {
2101
+ return this.readyPromise;
2102
+ }
2103
+ /**
2104
+ * Check if SDK is ready
2105
+ */
2106
+ isReady() {
2107
+ return this.status === SDKStatus.READY;
2108
+ }
2109
+ /**
2110
+ * Open the widget
2111
+ */
2112
+ open() {
2113
+ this.ensureReady();
2114
+ console.log('[Weld SDK] Opening widget...');
2115
+ this.stateCoordinator.openWidget();
2116
+ this.iframeManager.showIframe(IframeType.WIDGET);
2117
+ this.iframeManager.showIframe(IframeType.BACKDROP);
2118
+ // Keep launcher visible so user can click it to close the widget
2119
+ // Send open message to the widget iframe
2120
+ const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2121
+ if (widgetIframe?.element?.contentWindow) {
2122
+ widgetIframe.element.contentWindow.postMessage({ type: 'weld:open' }, '*');
2123
+ }
2124
+ // Notify launcher that widget is now open (so it can show X icon)
2125
+ const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
2126
+ if (launcherIframe?.element?.contentWindow) {
2127
+ launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
2128
+ }
2129
+ this.config.onOpen?.();
2130
+ }
2131
+ /**
2132
+ * Close the widget
2133
+ */
2134
+ close() {
2135
+ this.ensureReady();
2136
+ console.log('[Weld SDK] Closing widget...');
2137
+ this.stateCoordinator.closeWidget();
2138
+ this.iframeManager.hideIframe(IframeType.WIDGET);
2139
+ this.iframeManager.hideIframe(IframeType.BACKDROP);
2140
+ // Launcher stays visible
2141
+ // Send close message to the widget iframe
2142
+ const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
2143
+ if (widgetIframe?.element?.contentWindow) {
2144
+ widgetIframe.element.contentWindow.postMessage({ type: 'weld:close' }, '*');
2145
+ }
2146
+ // Notify launcher that widget is now closed (so it can show chat icon)
2147
+ const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
2148
+ if (launcherIframe?.element?.contentWindow) {
2149
+ launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
2150
+ }
2151
+ this.config.onClose?.();
2152
+ }
2153
+ /**
2154
+ * Toggle widget open/close
2155
+ */
2156
+ toggle() {
2157
+ const state = this.stateCoordinator.getState();
2158
+ if (state.widget.isOpen) {
2159
+ this.close();
2160
+ }
2161
+ else {
2162
+ this.open();
2163
+ }
2164
+ }
2165
+ /**
2166
+ * Minimize the widget
2167
+ */
2168
+ minimize() {
2169
+ this.ensureReady();
2170
+ this.logger.debug('Minimizing widget');
2171
+ this.stateCoordinator.minimizeWidget();
2172
+ this.config.onMinimize?.();
2173
+ }
2174
+ /**
2175
+ * Maximize the widget
2176
+ */
2177
+ maximize() {
2178
+ this.ensureReady();
2179
+ this.logger.debug('Maximizing widget');
2180
+ this.stateCoordinator.maximizeWidget();
2181
+ this.config.onMaximize?.();
2182
+ }
2183
+ /**
2184
+ * Show the launcher
2185
+ */
2186
+ showLauncher() {
2187
+ this.ensureReady();
2188
+ this.logger.debug('Showing launcher');
2189
+ this.iframeManager.showIframe(IframeType.LAUNCHER);
2190
+ this.stateCoordinator.updateState('launcher.isVisible', true);
2191
+ }
2192
+ /**
2193
+ * Hide the launcher
2194
+ */
2195
+ hideLauncher() {
2196
+ this.ensureReady();
2197
+ this.logger.debug('Hiding launcher');
2198
+ this.iframeManager.hideIframe(IframeType.LAUNCHER);
2199
+ this.stateCoordinator.updateState('launcher.isVisible', false);
2200
+ }
2201
+ /**
2202
+ * Set badge count
2203
+ */
2204
+ setBadgeCount(count) {
2205
+ this.ensureReady();
2206
+ this.logger.debug('Setting badge count', { count });
2207
+ this.stateCoordinator.setBadgeCount(count);
2208
+ }
2209
+ /**
2210
+ * Clear badge
2211
+ */
2212
+ clearBadge() {
2213
+ this.setBadgeCount(0);
2214
+ }
2215
+ /**
2216
+ * Send a message
2217
+ */
2218
+ sendMessage(text, metadata) {
2219
+ this.ensureReady();
2220
+ this.logger.debug('Sending message', { text });
2221
+ this.messageBroker.sendToIframe(IframeType.WIDGET, 'weld:message:send', {
2222
+ text,
2223
+ metadata,
2224
+ timestamp: Date.now(),
2225
+ });
2226
+ }
2227
+ /**
2228
+ * Identify user
2229
+ */
2230
+ identify(identity) {
2231
+ this.ensureReady();
2232
+ this.logger.debug('Identifying user', { userId: identity.userId });
2233
+ this.stateCoordinator.setUserAuth(identity.userId, identity.email, identity.name);
2234
+ this.messageBroker.broadcast('weld:auth:login', identity);
2235
+ }
2236
+ /**
2237
+ * Logout user
2238
+ */
2239
+ logout() {
2240
+ this.ensureReady();
2241
+ this.logger.debug('Logging out user');
2242
+ this.stateCoordinator.updateState('user', {
2243
+ isAuthenticated: false,
2244
+ isAnonymous: true,
2245
+ });
2246
+ this.messageBroker.broadcast('weld:auth:logout', {});
2247
+ }
2248
+ /**
2249
+ * Update configuration
2250
+ */
2251
+ updateConfig(updates) {
2252
+ this.ensureReady();
2253
+ this.logger.debug('Updating configuration');
2254
+ // Merge config
2255
+ this.config = resolveConfig({ ...this.config, ...updates });
2256
+ // Broadcast config update
2257
+ this.messageBroker.broadcast('weld:config:update', updates);
2258
+ }
2259
+ /**
2260
+ * Update theme
2261
+ */
2262
+ setTheme(theme) {
2263
+ this.ensureReady();
2264
+ this.logger.debug('Setting theme', { theme });
2265
+ this.stateCoordinator.updateState('ui.theme', theme);
2266
+ this.messageBroker.broadcast('weld:theme:update', { mode: theme });
2267
+ }
2268
+ /**
2269
+ * Update locale
2270
+ */
2271
+ setLocale(locale) {
2272
+ this.ensureReady();
2273
+ this.logger.debug('Setting locale', { locale });
2274
+ this.stateCoordinator.updateState('ui.locale', locale);
2275
+ this.messageBroker.broadcast('weld:locale:update', { locale });
2276
+ }
2277
+ /**
2278
+ * Track custom event
2279
+ */
2280
+ track(eventName, properties) {
2281
+ this.ensureReady();
2282
+ this.logger.debug('Tracking event', { eventName });
2283
+ this.messageBroker.broadcast('weld:event:track', {
2284
+ name: eventName,
2285
+ properties,
2286
+ timestamp: Date.now(),
2287
+ });
2288
+ }
2289
+ /**
2290
+ * Get current state
2291
+ */
2292
+ getState() {
2293
+ return this.stateCoordinator.getState();
2294
+ }
2295
+ /**
2296
+ * Subscribe to state changes
2297
+ */
2298
+ onStateChange(path, listener) {
2299
+ this.ensureReady();
2300
+ const subscriptionId = this.stateCoordinator.subscribe(path, listener);
2301
+ // Return unsubscribe function
2302
+ return () => {
2303
+ this.stateCoordinator.unsubscribe(subscriptionId);
2304
+ };
2305
+ }
2306
+ /**
2307
+ * Get device info
2308
+ */
2309
+ getDeviceInfo() {
2310
+ return this.iframeManager.getDeviceInfo();
2311
+ }
2312
+ /**
2313
+ * Get SDK status
2314
+ */
2315
+ getStatus() {
2316
+ return this.status;
2317
+ }
2318
+ /**
2319
+ * Get SDK version
2320
+ */
2321
+ getVersion() {
2322
+ return packageJson.version;
2323
+ }
2324
+ /**
2325
+ * Enable debug mode
2326
+ */
2327
+ enableDebug() {
2328
+ this.logger.setLevel('debug');
2329
+ this.logger.info('Debug mode enabled');
2330
+ }
2331
+ /**
2332
+ * Disable debug mode
2333
+ */
2334
+ disableDebug() {
2335
+ this.logger.setLevel('warn');
2336
+ this.logger.info('Debug mode disabled');
2337
+ }
2338
+ /**
2339
+ * Ensure SDK is ready before operation
2340
+ */
2341
+ ensureReady() {
2342
+ if (this.status !== SDKStatus.READY) {
2343
+ throw new Error('SDK not ready. Call init() first.');
2344
+ }
2345
+ }
2346
+ /**
2347
+ * Destroy SDK and cleanup
2348
+ */
2349
+ destroy() {
2350
+ this.logger.info('Destroying WeldSDK');
2351
+ // Destroy modules
2352
+ this.stateCoordinator.destroy();
2353
+ this.messageBroker.destroy();
2354
+ this.iframeManager.destroy();
2355
+ // Reset status
2356
+ this.status = SDKStatus.DESTROYED;
2357
+ // Call onDestroy callback
2358
+ this.config.onDestroy?.();
2359
+ this.logger.info('WeldSDK destroyed');
2360
+ }
2361
+ }
2362
+
2363
+ /**
2364
+ * Vue 3 composable for the Helpdesk Widget
2365
+ *
2366
+ * @example
2367
+ * ```vue
2368
+ * <script setup lang="ts">
2369
+ * import { useHelpdeskWidget } from '@weldsuite/helpdesk-widget-sdk/vue';
2370
+ *
2371
+ * const widget = useHelpdeskWidget({ widgetId: 'your-widget-id' });
2372
+ * </script>
2373
+ *
2374
+ * <template>
2375
+ * <button @click="widget?.open()">Show Support</button>
2376
+ * </template>
2377
+ * ```
2378
+ */
2379
+ function useHelpdeskWidget(config) {
2380
+ const widget = vue.ref(null);
2381
+ vue.onMounted(() => {
2382
+ try {
2383
+ widget.value = new WeldSDK(config);
2384
+ widget.value.init();
2385
+ }
2386
+ catch (error) {
2387
+ console.error('Failed to initialize Helpdesk Widget:', error);
2388
+ }
2389
+ });
2390
+ vue.onUnmounted(() => {
2391
+ if (widget.value) {
2392
+ widget.value.destroy();
2393
+ widget.value = null;
2394
+ }
2395
+ });
2396
+ return widget;
2397
+ }
2398
+ /**
2399
+ * Composable that provides widget control methods
2400
+ *
2401
+ * @example
2402
+ * ```vue
2403
+ * <script setup lang="ts">
2404
+ * import { useHelpdeskWidgetControls } from '@weldsuite/helpdesk-widget-sdk/vue';
2405
+ *
2406
+ * const { open, close, toggle } = useHelpdeskWidgetControls({
2407
+ * widgetId: 'your-widget-id'
2408
+ * });
2409
+ * </script>
2410
+ *
2411
+ * <template>
2412
+ * <div>
2413
+ * <button @click="open">Show Support</button>
2414
+ * <button @click="close">Hide Support</button>
2415
+ * <button @click="toggle">Toggle Support</button>
2416
+ * </div>
2417
+ * </template>
2418
+ * ```
2419
+ */
2420
+ function useHelpdeskWidgetControls(config) {
2421
+ const widget = useHelpdeskWidget(config);
2422
+ const open = () => {
2423
+ widget.value?.open();
2424
+ };
2425
+ const close = () => {
2426
+ widget.value?.close();
2427
+ };
2428
+ const toggle = () => {
2429
+ widget.value?.toggle();
2430
+ };
2431
+ const sendMessage = (message) => {
2432
+ widget.value?.sendMessage(message);
2433
+ };
2434
+ return {
2435
+ widget,
2436
+ open,
2437
+ close,
2438
+ toggle,
2439
+ sendMessage,
2440
+ };
2441
+ }
2442
+
2443
+ exports.useHelpdeskWidget = useHelpdeskWidget;
2444
+ exports.useHelpdeskWidgetControls = useHelpdeskWidgetControls;
2445
+ //# sourceMappingURL=vue-composables.js.map