@weldsuite/helpdesk-widget-sdk 1.0.2

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