@weldsuite/helpdesk-widget-sdk 1.0.16 → 1.0.17
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/angular.d.ts +32 -41
- package/dist/angular.esm.js +237 -58
- package/dist/angular.esm.js.map +1 -1
- package/dist/angular.js +237 -58
- package/dist/angular.js.map +1 -1
- package/dist/index.d.ts +55 -55
- package/dist/index.esm.js +259 -60
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +260 -59
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +260 -59
- package/dist/index.umd.js.map +1 -1
- package/dist/react.d.ts +32 -41
- package/dist/react.esm.js +237 -58
- package/dist/react.esm.js.map +1 -1
- package/dist/react.js +237 -58
- package/dist/react.js.map +1 -1
- package/dist/vue-composables.esm.js +237 -58
- package/dist/vue-composables.esm.js.map +1 -1
- package/dist/vue-composables.js +237 -58
- package/dist/vue-composables.js.map +1 -1
- package/package.json +4 -4
package/dist/index.esm.js
CHANGED
|
@@ -31,32 +31,6 @@ const DEFAULT_CONFIG = {
|
|
|
31
31
|
closeOnClick: true,
|
|
32
32
|
},
|
|
33
33
|
},
|
|
34
|
-
customization: {
|
|
35
|
-
primaryColor: '#000000',
|
|
36
|
-
accentColor: '#3b82f6',
|
|
37
|
-
backgroundColor: '#ffffff',
|
|
38
|
-
textColor: '#111827',
|
|
39
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
40
|
-
fontSize: '14px',
|
|
41
|
-
borderRadius: '12px',
|
|
42
|
-
},
|
|
43
|
-
features: {
|
|
44
|
-
attachments: true,
|
|
45
|
-
reactions: true,
|
|
46
|
-
typing: true,
|
|
47
|
-
readReceipts: true,
|
|
48
|
-
offlineMode: false,
|
|
49
|
-
fileUpload: true,
|
|
50
|
-
imageUpload: true,
|
|
51
|
-
voiceMessages: false,
|
|
52
|
-
videoMessages: false,
|
|
53
|
-
},
|
|
54
|
-
mobile: {
|
|
55
|
-
fullScreen: true,
|
|
56
|
-
scrollLock: true,
|
|
57
|
-
keyboardHandling: 'auto',
|
|
58
|
-
safeAreaInsets: true,
|
|
59
|
-
},
|
|
60
34
|
auth: {
|
|
61
35
|
enabled: true,
|
|
62
36
|
mode: 'anonymous',
|
|
@@ -100,6 +74,7 @@ function resolveConfig(config) {
|
|
|
100
74
|
validateConfig(config);
|
|
101
75
|
return {
|
|
102
76
|
widgetId: config.widgetId,
|
|
77
|
+
testMode: config.testMode,
|
|
103
78
|
api: {
|
|
104
79
|
...DEFAULT_CONFIG.api,
|
|
105
80
|
widgetId: config.widgetId,
|
|
@@ -129,18 +104,6 @@ function resolveConfig(config) {
|
|
|
129
104
|
...config.iframes?.backdrop,
|
|
130
105
|
},
|
|
131
106
|
},
|
|
132
|
-
customization: {
|
|
133
|
-
...DEFAULT_CONFIG.customization,
|
|
134
|
-
...config.customization,
|
|
135
|
-
},
|
|
136
|
-
features: {
|
|
137
|
-
...DEFAULT_CONFIG.features,
|
|
138
|
-
...config.features,
|
|
139
|
-
},
|
|
140
|
-
mobile: {
|
|
141
|
-
...DEFAULT_CONFIG.mobile,
|
|
142
|
-
...config.mobile,
|
|
143
|
-
},
|
|
144
107
|
auth: {
|
|
145
108
|
...DEFAULT_CONFIG.auth,
|
|
146
109
|
...config.auth,
|
|
@@ -383,6 +346,8 @@ class IframeManager {
|
|
|
383
346
|
this.modalContainer = null;
|
|
384
347
|
this.styleElement = null;
|
|
385
348
|
this.messageBroker = null;
|
|
349
|
+
// Guard flag to prevent double-binding event listeners
|
|
350
|
+
this.eventListenersBound = false;
|
|
386
351
|
this.config = config;
|
|
387
352
|
this.logger = new Logger(config.logging);
|
|
388
353
|
this.deviceInfo = detectDevice();
|
|
@@ -422,18 +387,27 @@ class IframeManager {
|
|
|
422
387
|
}
|
|
423
388
|
/**
|
|
424
389
|
* Create root container structure
|
|
390
|
+
* Reuses existing container if it has the same widgetId (singleton behavior)
|
|
425
391
|
*/
|
|
426
392
|
createRootContainer() {
|
|
427
|
-
|
|
428
|
-
let existingContainer = document.getElementById('weld-container');
|
|
393
|
+
const existingContainer = document.getElementById('weld-container');
|
|
429
394
|
if (existingContainer) {
|
|
430
|
-
|
|
395
|
+
// Reuse if same widgetId
|
|
396
|
+
if (existingContainer.getAttribute('data-widget-id') === this.config.widgetId) {
|
|
397
|
+
this.logger.debug('Reusing existing root container');
|
|
398
|
+
this.rootContainer = existingContainer;
|
|
399
|
+
this.appContainer = existingContainer.querySelector('.weld-app');
|
|
400
|
+
this.modalContainer = document.getElementById('weld-modal-container');
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.logger.warn('Weld container already exists with different widgetId, removing old instance');
|
|
431
404
|
existingContainer.remove();
|
|
432
405
|
}
|
|
433
406
|
// Create root container
|
|
434
407
|
this.rootContainer = document.createElement('div');
|
|
435
408
|
this.rootContainer.id = 'weld-container';
|
|
436
409
|
this.rootContainer.className = 'weld-namespace';
|
|
410
|
+
this.rootContainer.setAttribute('data-widget-id', this.config.widgetId);
|
|
437
411
|
// Create app container
|
|
438
412
|
this.appContainer = document.createElement('div');
|
|
439
413
|
this.appContainer.className = 'weld-app';
|
|
@@ -468,17 +442,7 @@ class IframeManager {
|
|
|
468
442
|
* Generate CSS for containers
|
|
469
443
|
*/
|
|
470
444
|
generateCSS() {
|
|
471
|
-
const { customization } = this.config;
|
|
472
445
|
return `
|
|
473
|
-
/* Weld Container */
|
|
474
|
-
#weld-container {
|
|
475
|
-
--weld-color-primary: ${customization.primaryColor};
|
|
476
|
-
--weld-color-accent: ${customization.accentColor};
|
|
477
|
-
--weld-font-family: ${customization.fontFamily};
|
|
478
|
-
--weld-font-size-base: ${customization.fontSize};
|
|
479
|
-
--weld-radius-xl: ${customization.borderRadius};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
446
|
/* Import main stylesheet */
|
|
483
447
|
@import url('/styles/index.css');
|
|
484
448
|
|
|
@@ -502,6 +466,11 @@ class IframeManager {
|
|
|
502
466
|
* Create launcher iframe
|
|
503
467
|
*/
|
|
504
468
|
async createLauncherIframe() {
|
|
469
|
+
// Guard: skip if launcher iframe already exists
|
|
470
|
+
if (this.iframes.has(IframeType.LAUNCHER)) {
|
|
471
|
+
this.logger.debug('Launcher iframe already exists, skipping creation');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
505
474
|
const { iframes } = this.config;
|
|
506
475
|
const { launcher } = iframes;
|
|
507
476
|
// Create container
|
|
@@ -529,9 +498,12 @@ class IframeManager {
|
|
|
529
498
|
width: 100%;
|
|
530
499
|
height: 100%;
|
|
531
500
|
border: none;
|
|
532
|
-
background:
|
|
501
|
+
background: none;
|
|
502
|
+
color-scheme: none;
|
|
533
503
|
display: block;
|
|
534
504
|
pointer-events: auto;
|
|
505
|
+
border-radius: 50%;
|
|
506
|
+
filter: drop-shadow(rgba(9, 14, 21, 0.54) 0px 1px 6px) drop-shadow(rgba(9, 14, 21, 0.9) 0px 2px 32px);
|
|
535
507
|
`;
|
|
536
508
|
iframe.setAttribute('allow', 'clipboard-write');
|
|
537
509
|
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
|
|
@@ -547,20 +519,36 @@ class IframeManager {
|
|
|
547
519
|
createdAt: Date.now(),
|
|
548
520
|
});
|
|
549
521
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
522
|
+
let launcherRetried = false;
|
|
550
523
|
iframe.onload = () => {
|
|
551
524
|
const metadata = this.iframes.get(IframeType.LAUNCHER);
|
|
552
525
|
if (metadata) {
|
|
553
526
|
this.logger.debug('Launcher iframe DOM loaded');
|
|
554
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
555
527
|
this.messageBroker?.setIframeDomLoaded(IframeType.LAUNCHER);
|
|
556
528
|
}
|
|
557
529
|
};
|
|
530
|
+
iframe.onerror = () => {
|
|
531
|
+
this.logger.error('Launcher iframe failed to load');
|
|
532
|
+
if (!launcherRetried) {
|
|
533
|
+
launcherRetried = true;
|
|
534
|
+
this.logger.info('Retrying launcher iframe load...');
|
|
535
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(launcher.url); }, 3000);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
this.config.onError?.(new Error('Failed to load widget launcher'));
|
|
539
|
+
}
|
|
540
|
+
};
|
|
558
541
|
this.logger.debug('Launcher iframe created');
|
|
559
542
|
}
|
|
560
543
|
/**
|
|
561
544
|
* Create widget iframe
|
|
562
545
|
*/
|
|
563
546
|
async createWidgetIframe() {
|
|
547
|
+
// Guard: skip if widget iframe already exists
|
|
548
|
+
if (this.iframes.has(IframeType.WIDGET)) {
|
|
549
|
+
this.logger.debug('Widget iframe already exists, skipping creation');
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
564
552
|
const { iframes } = this.config;
|
|
565
553
|
const { widget } = iframes;
|
|
566
554
|
// Create container
|
|
@@ -641,14 +629,25 @@ class IframeManager {
|
|
|
641
629
|
createdAt: Date.now(),
|
|
642
630
|
});
|
|
643
631
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
632
|
+
let widgetRetried = false;
|
|
644
633
|
iframe.onload = () => {
|
|
645
634
|
const metadata = this.iframes.get(IframeType.WIDGET);
|
|
646
635
|
if (metadata) {
|
|
647
636
|
this.logger.debug('Widget iframe DOM loaded');
|
|
648
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
649
637
|
this.messageBroker?.setIframeDomLoaded(IframeType.WIDGET);
|
|
650
638
|
}
|
|
651
639
|
};
|
|
640
|
+
iframe.onerror = () => {
|
|
641
|
+
this.logger.error('Widget iframe failed to load');
|
|
642
|
+
if (!widgetRetried) {
|
|
643
|
+
widgetRetried = true;
|
|
644
|
+
this.logger.info('Retrying widget iframe load...');
|
|
645
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(widget.url); }, 3000);
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
this.config.onError?.(new Error('Failed to load widget'));
|
|
649
|
+
}
|
|
650
|
+
};
|
|
652
651
|
this.logger.debug('Widget iframe created');
|
|
653
652
|
}
|
|
654
653
|
/**
|
|
@@ -669,12 +668,21 @@ class IframeManager {
|
|
|
669
668
|
url.searchParams.set('device', this.deviceInfo.type);
|
|
670
669
|
url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
|
|
671
670
|
url.searchParams.set('parentOrigin', window.location.origin);
|
|
671
|
+
if (this.config.testMode) {
|
|
672
|
+
url.searchParams.set('testMode', 'true');
|
|
673
|
+
}
|
|
672
674
|
return url.toString();
|
|
673
675
|
}
|
|
674
676
|
/**
|
|
675
677
|
* Setup event listeners
|
|
676
678
|
*/
|
|
677
679
|
setupEventListeners() {
|
|
680
|
+
// Guard: prevent double-binding
|
|
681
|
+
if (this.eventListenersBound) {
|
|
682
|
+
this.logger.debug('Event listeners already bound, skipping');
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
this.eventListenersBound = true;
|
|
678
686
|
// Window resize - use bound handler for proper cleanup
|
|
679
687
|
window.addEventListener('resize', this.boundHandleResize);
|
|
680
688
|
// Orientation change - use bound handler for proper cleanup
|
|
@@ -777,7 +785,7 @@ class IframeManager {
|
|
|
777
785
|
iframe.container.style.transform = 'scale(1) translateY(0)';
|
|
778
786
|
}
|
|
779
787
|
// Handle mobile scroll lock
|
|
780
|
-
if (this.deviceInfo.isMobile && type === IframeType.WIDGET
|
|
788
|
+
if (this.deviceInfo.isMobile && type === IframeType.WIDGET) {
|
|
781
789
|
document.body.classList.add('weld-mobile-open');
|
|
782
790
|
}
|
|
783
791
|
// Hide launcher on mobile when widget is open (full-screen mode)
|
|
@@ -876,6 +884,8 @@ class IframeManager {
|
|
|
876
884
|
this.iframes.clear();
|
|
877
885
|
// Clear messageBroker reference
|
|
878
886
|
this.messageBroker = null;
|
|
887
|
+
// Reset guard flag
|
|
888
|
+
this.eventListenersBound = false;
|
|
879
889
|
this.logger.info('IframeManager destroyed');
|
|
880
890
|
}
|
|
881
891
|
}
|
|
@@ -943,6 +953,8 @@ var MessageType;
|
|
|
943
953
|
// Events
|
|
944
954
|
MessageType["EVENT_TRACK"] = "weld:event:track";
|
|
945
955
|
MessageType["ERROR_REPORT"] = "weld:error:report";
|
|
956
|
+
// Page tracking
|
|
957
|
+
MessageType["PAGE_CHANGE"] = "weld:page:change";
|
|
946
958
|
// API responses
|
|
947
959
|
MessageType["API_SUCCESS"] = "weld:api:success";
|
|
948
960
|
MessageType["API_ERROR"] = "weld:api:error";
|
|
@@ -1521,8 +1533,6 @@ class MessageBroker {
|
|
|
1521
1533
|
iframeType,
|
|
1522
1534
|
config: {
|
|
1523
1535
|
api: this.config.api,
|
|
1524
|
-
customization: this.config.customization,
|
|
1525
|
-
features: this.config.features,
|
|
1526
1536
|
},
|
|
1527
1537
|
};
|
|
1528
1538
|
const message = createMessage('weld:init', MessageOrigin.PARENT, initPayload);
|
|
@@ -2365,7 +2375,7 @@ class StateCoordinator {
|
|
|
2365
2375
|
}
|
|
2366
2376
|
}
|
|
2367
2377
|
|
|
2368
|
-
var version = "1.0.
|
|
2378
|
+
var version = "1.0.17";
|
|
2369
2379
|
var packageJson = {
|
|
2370
2380
|
version: version};
|
|
2371
2381
|
|
|
@@ -2373,6 +2383,16 @@ var packageJson = {
|
|
|
2373
2383
|
* Weld SDK - Main Entry Point
|
|
2374
2384
|
* Public API for the Weld helpdesk widget
|
|
2375
2385
|
*/
|
|
2386
|
+
/**
|
|
2387
|
+
* Module-level singleton registry keyed by widgetId
|
|
2388
|
+
*/
|
|
2389
|
+
const sdkRegistry = new Map();
|
|
2390
|
+
/**
|
|
2391
|
+
* SessionStorage key helpers
|
|
2392
|
+
*/
|
|
2393
|
+
function openStateKey(widgetId) {
|
|
2394
|
+
return `weld-widget-open-${widgetId}`;
|
|
2395
|
+
}
|
|
2376
2396
|
/**
|
|
2377
2397
|
* SDK initialization status
|
|
2378
2398
|
*/
|
|
@@ -2395,6 +2415,8 @@ class WeldSDK {
|
|
|
2395
2415
|
this.readyResolve = null;
|
|
2396
2416
|
// Subscription IDs for cleanup
|
|
2397
2417
|
this.subscriptionIds = [];
|
|
2418
|
+
// Page tracking cleanup
|
|
2419
|
+
this.pageTrackingCleanup = null;
|
|
2398
2420
|
/**
|
|
2399
2421
|
* Update user attributes (Intercom-style, with rate limiting)
|
|
2400
2422
|
* Limited to 20 calls per page load to prevent abuse
|
|
@@ -2433,6 +2455,13 @@ class WeldSDK {
|
|
|
2433
2455
|
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2434
2456
|
}
|
|
2435
2457
|
if (event.data?.type === 'launcher:clicked') {
|
|
2458
|
+
if (this.status !== SDKStatus.READY) {
|
|
2459
|
+
console.log('[Weld SDK] Launcher clicked but SDK not ready yet — waiting...');
|
|
2460
|
+
this.readyPromise?.then(() => {
|
|
2461
|
+
this.handleLauncherClickMessage(event);
|
|
2462
|
+
});
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2436
2465
|
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2437
2466
|
const state = this.stateCoordinator.getState();
|
|
2438
2467
|
if (state.widget.isOpen) {
|
|
@@ -2445,9 +2474,24 @@ class WeldSDK {
|
|
|
2445
2474
|
}
|
|
2446
2475
|
}
|
|
2447
2476
|
if (event.data?.type === 'weld:close') {
|
|
2477
|
+
if (this.status !== SDKStatus.READY)
|
|
2478
|
+
return;
|
|
2448
2479
|
console.log('[Weld SDK] Widget close requested');
|
|
2449
2480
|
this.close();
|
|
2450
2481
|
}
|
|
2482
|
+
if (event.data?.type === 'weld:unread-count') {
|
|
2483
|
+
const count = event.data.count ?? 0;
|
|
2484
|
+
// Forward to launcher iframe
|
|
2485
|
+
const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
|
|
2486
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2487
|
+
launcherIframe.element.contentWindow.postMessage({
|
|
2488
|
+
type: 'weld:unread-count',
|
|
2489
|
+
count
|
|
2490
|
+
}, '*');
|
|
2491
|
+
}
|
|
2492
|
+
// Update state coordinator for external API consumers
|
|
2493
|
+
this.stateCoordinator.setBadgeCount(count);
|
|
2494
|
+
}
|
|
2451
2495
|
if (event.data?.type === 'weld:image:open' && event.data?.url) {
|
|
2452
2496
|
this.showImageLightbox(event.data.url);
|
|
2453
2497
|
}
|
|
@@ -2709,6 +2753,13 @@ class WeldSDK {
|
|
|
2709
2753
|
this.logger.info('WeldSDK ready');
|
|
2710
2754
|
// Call onReady callback
|
|
2711
2755
|
this.config.onReady?.();
|
|
2756
|
+
// Start tracking page URL changes
|
|
2757
|
+
this.startPageTracking();
|
|
2758
|
+
// Auto-open if widget was previously open (persisted in sessionStorage)
|
|
2759
|
+
if (this.wasOpen()) {
|
|
2760
|
+
this.logger.info('Restoring previously open widget from sessionStorage');
|
|
2761
|
+
this.open();
|
|
2762
|
+
}
|
|
2712
2763
|
}
|
|
2713
2764
|
catch (error) {
|
|
2714
2765
|
this.status = SDKStatus.ERROR;
|
|
@@ -2787,6 +2838,58 @@ class WeldSDK {
|
|
|
2787
2838
|
isReady() {
|
|
2788
2839
|
return this.status === SDKStatus.READY;
|
|
2789
2840
|
}
|
|
2841
|
+
/**
|
|
2842
|
+
* Update callbacks on an existing instance (used by singleton reuse)
|
|
2843
|
+
*/
|
|
2844
|
+
updateCallbacks(config) {
|
|
2845
|
+
if (config.onReady !== undefined)
|
|
2846
|
+
this.config.onReady = config.onReady;
|
|
2847
|
+
if (config.onOpen !== undefined)
|
|
2848
|
+
this.config.onOpen = config.onOpen;
|
|
2849
|
+
if (config.onClose !== undefined)
|
|
2850
|
+
this.config.onClose = config.onClose;
|
|
2851
|
+
if (config.onError !== undefined)
|
|
2852
|
+
this.config.onError = config.onError;
|
|
2853
|
+
if (config.onDestroy !== undefined)
|
|
2854
|
+
this.config.onDestroy = config.onDestroy;
|
|
2855
|
+
if (config.onMinimize !== undefined)
|
|
2856
|
+
this.config.onMinimize = config.onMinimize;
|
|
2857
|
+
if (config.onMaximize !== undefined)
|
|
2858
|
+
this.config.onMaximize = config.onMaximize;
|
|
2859
|
+
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Persist open/closed state to sessionStorage
|
|
2862
|
+
*/
|
|
2863
|
+
persistOpenState(isOpen) {
|
|
2864
|
+
try {
|
|
2865
|
+
sessionStorage.setItem(openStateKey(this.config.widgetId), isOpen ? 'true' : 'false');
|
|
2866
|
+
}
|
|
2867
|
+
catch {
|
|
2868
|
+
// sessionStorage might not be available
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
/**
|
|
2872
|
+
* Clear persisted state from sessionStorage
|
|
2873
|
+
*/
|
|
2874
|
+
clearPersistedState() {
|
|
2875
|
+
try {
|
|
2876
|
+
sessionStorage.removeItem(openStateKey(this.config.widgetId));
|
|
2877
|
+
}
|
|
2878
|
+
catch {
|
|
2879
|
+
// sessionStorage might not be available
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Check if widget was previously open (from sessionStorage)
|
|
2884
|
+
*/
|
|
2885
|
+
wasOpen() {
|
|
2886
|
+
try {
|
|
2887
|
+
return sessionStorage.getItem(openStateKey(this.config.widgetId)) === 'true';
|
|
2888
|
+
}
|
|
2889
|
+
catch {
|
|
2890
|
+
return false;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2790
2893
|
/**
|
|
2791
2894
|
* Open the widget
|
|
2792
2895
|
*/
|
|
@@ -2805,6 +2908,7 @@ class WeldSDK {
|
|
|
2805
2908
|
if (launcherIframe?.element?.contentWindow) {
|
|
2806
2909
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2807
2910
|
}
|
|
2911
|
+
this.persistOpenState(true);
|
|
2808
2912
|
this.config.onOpen?.();
|
|
2809
2913
|
}
|
|
2810
2914
|
/**
|
|
@@ -2825,6 +2929,7 @@ class WeldSDK {
|
|
|
2825
2929
|
if (launcherIframe?.element?.contentWindow) {
|
|
2826
2930
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2827
2931
|
}
|
|
2932
|
+
this.persistOpenState(false);
|
|
2828
2933
|
this.config.onClose?.();
|
|
2829
2934
|
}
|
|
2830
2935
|
/**
|
|
@@ -3013,6 +3118,8 @@ class WeldSDK {
|
|
|
3013
3118
|
});
|
|
3014
3119
|
// Broadcast logout to iframes
|
|
3015
3120
|
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
3121
|
+
// Clear persisted widget state
|
|
3122
|
+
this.clearPersistedState();
|
|
3016
3123
|
// Clear any stored session data
|
|
3017
3124
|
try {
|
|
3018
3125
|
const prefix = 'weld-';
|
|
@@ -3154,6 +3261,63 @@ class WeldSDK {
|
|
|
3154
3261
|
this.logger.setLevel('warn');
|
|
3155
3262
|
this.logger.info('Debug mode disabled');
|
|
3156
3263
|
}
|
|
3264
|
+
/**
|
|
3265
|
+
* Send a page change message to the widget iframe
|
|
3266
|
+
*/
|
|
3267
|
+
sendPageChange(url, title) {
|
|
3268
|
+
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
3269
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
3270
|
+
widgetIframe.element.contentWindow.postMessage({
|
|
3271
|
+
type: 'weld:page:change',
|
|
3272
|
+
url,
|
|
3273
|
+
title,
|
|
3274
|
+
timestamp: Date.now(),
|
|
3275
|
+
}, '*');
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
/**
|
|
3279
|
+
* Start tracking page URL changes (SPA navigations + popstate)
|
|
3280
|
+
*/
|
|
3281
|
+
startPageTracking() {
|
|
3282
|
+
let lastUrl = window.location.href;
|
|
3283
|
+
let debounceTimer = null;
|
|
3284
|
+
const notifyChange = () => {
|
|
3285
|
+
const currentUrl = window.location.href;
|
|
3286
|
+
if (currentUrl !== lastUrl) {
|
|
3287
|
+
lastUrl = currentUrl;
|
|
3288
|
+
this.sendPageChange(currentUrl, document.title);
|
|
3289
|
+
}
|
|
3290
|
+
};
|
|
3291
|
+
const debouncedNotify = () => {
|
|
3292
|
+
if (debounceTimer)
|
|
3293
|
+
clearTimeout(debounceTimer);
|
|
3294
|
+
debounceTimer = setTimeout(notifyChange, 300);
|
|
3295
|
+
};
|
|
3296
|
+
// Send initial page
|
|
3297
|
+
this.sendPageChange(window.location.href, document.title);
|
|
3298
|
+
// Monkey-patch history.pushState and history.replaceState
|
|
3299
|
+
const origPushState = history.pushState.bind(history);
|
|
3300
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
3301
|
+
history.pushState = function (...args) {
|
|
3302
|
+
origPushState(...args);
|
|
3303
|
+
debouncedNotify();
|
|
3304
|
+
};
|
|
3305
|
+
history.replaceState = function (...args) {
|
|
3306
|
+
origReplaceState(...args);
|
|
3307
|
+
debouncedNotify();
|
|
3308
|
+
};
|
|
3309
|
+
// Listen for popstate (browser back/forward)
|
|
3310
|
+
const handlePopstate = () => debouncedNotify();
|
|
3311
|
+
window.addEventListener('popstate', handlePopstate);
|
|
3312
|
+
// Store cleanup
|
|
3313
|
+
this.pageTrackingCleanup = () => {
|
|
3314
|
+
if (debounceTimer)
|
|
3315
|
+
clearTimeout(debounceTimer);
|
|
3316
|
+
window.removeEventListener('popstate', handlePopstate);
|
|
3317
|
+
history.pushState = origPushState;
|
|
3318
|
+
history.replaceState = origReplaceState;
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
3157
3321
|
/**
|
|
3158
3322
|
* Ensure SDK is ready before operation
|
|
3159
3323
|
*/
|
|
@@ -3162,11 +3326,26 @@ class WeldSDK {
|
|
|
3162
3326
|
throw new Error('SDK not ready. Call init() first.');
|
|
3163
3327
|
}
|
|
3164
3328
|
}
|
|
3329
|
+
/**
|
|
3330
|
+
* Detach from the current component lifecycle without destroying the widget.
|
|
3331
|
+
* Use this as a React useEffect cleanup — the widget stays alive across navigations.
|
|
3332
|
+
*/
|
|
3333
|
+
detach() {
|
|
3334
|
+
// No-op: widget stays alive in the singleton registry
|
|
3335
|
+
this.logger.debug('WeldSDK detached (no-op, widget stays alive)');
|
|
3336
|
+
}
|
|
3165
3337
|
/**
|
|
3166
3338
|
* Destroy SDK and cleanup
|
|
3167
3339
|
*/
|
|
3168
3340
|
destroy() {
|
|
3169
3341
|
this.logger.info('Destroying WeldSDK');
|
|
3342
|
+
// Remove from singleton registry
|
|
3343
|
+
sdkRegistry.delete(this.config.widgetId);
|
|
3344
|
+
// Clear persisted state
|
|
3345
|
+
this.clearPersistedState();
|
|
3346
|
+
// Stop page tracking
|
|
3347
|
+
this.pageTrackingCleanup?.();
|
|
3348
|
+
this.pageTrackingCleanup = null;
|
|
3170
3349
|
// Remove event listener using bound handler
|
|
3171
3350
|
window.removeEventListener('message', this.boundHandleLauncherClick);
|
|
3172
3351
|
// Unsubscribe from all message broker subscriptions
|
|
@@ -3186,13 +3365,33 @@ class WeldSDK {
|
|
|
3186
3365
|
}
|
|
3187
3366
|
}
|
|
3188
3367
|
/**
|
|
3189
|
-
* Create and initialize WeldSDK instance
|
|
3368
|
+
* Create and initialize WeldSDK instance.
|
|
3369
|
+
* Uses singleton pattern — if an instance for the same widgetId already exists
|
|
3370
|
+
* and is not destroyed, updates callbacks and returns the existing instance.
|
|
3190
3371
|
*/
|
|
3191
3372
|
async function createWeldSDK(config) {
|
|
3373
|
+
const widgetId = config.widgetId;
|
|
3374
|
+
// Check for existing, non-destroyed instance
|
|
3375
|
+
const existing = sdkRegistry.get(widgetId);
|
|
3376
|
+
if (existing && existing.getStatus() !== 'destroyed') {
|
|
3377
|
+
existing.updateCallbacks(config);
|
|
3378
|
+
return existing;
|
|
3379
|
+
}
|
|
3192
3380
|
const sdk = new WeldSDK(config);
|
|
3381
|
+
sdkRegistry.set(widgetId, sdk);
|
|
3193
3382
|
await sdk.init();
|
|
3194
3383
|
return sdk;
|
|
3195
3384
|
}
|
|
3385
|
+
/**
|
|
3386
|
+
* Explicitly destroy a WeldSDK instance by widgetId.
|
|
3387
|
+
* Use this for logout or when you need to fully remove the widget.
|
|
3388
|
+
*/
|
|
3389
|
+
function destroyWeldSDK(widgetId) {
|
|
3390
|
+
const sdk = sdkRegistry.get(widgetId);
|
|
3391
|
+
if (sdk) {
|
|
3392
|
+
sdk.destroy();
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3196
3395
|
|
|
3197
|
-
export { DEFAULT_CONFIG, WeldSDK as HelpdeskWidget, IframeManager, IframeType, LogLevel, Logger, MessageBroker, RateLimiter, SecurityManager, StateCoordinator, TokenValidator, WeldSDK, createInitialState, createMessage, createWeldSDK, deepClone, deepMerge, WeldSDK as default, defaultLogger, formatFileSize, getStateValue, hasRequiredProperties, createWeldSDK as initHelpdeskWidget, isBaseMessage, isInRange, isPayloadMessage, isPlainObject, isValidApiKey, isValidArrayLength, isValidColor, isValidEmail, isValidFileSize, isValidFileType, isValidLength, isValidMessageText, isValidUrl, isValidWorkspaceId, resolveConfig, sanitizeHtml, sanitizeInput, setStateValue, validateConfig };
|
|
3396
|
+
export { DEFAULT_CONFIG, WeldSDK as HelpdeskWidget, IframeManager, IframeType, LogLevel, Logger, MessageBroker, RateLimiter, SecurityManager, StateCoordinator, TokenValidator, WeldSDK, createInitialState, createMessage, createWeldSDK, deepClone, deepMerge, WeldSDK as default, defaultLogger, destroyWeldSDK as destroyHelpdeskWidget, destroyWeldSDK, formatFileSize, getStateValue, hasRequiredProperties, createWeldSDK as initHelpdeskWidget, isBaseMessage, isInRange, isPayloadMessage, isPlainObject, isValidApiKey, isValidArrayLength, isValidColor, isValidEmail, isValidFileSize, isValidFileType, isValidLength, isValidMessageText, isValidUrl, isValidWorkspaceId, resolveConfig, sanitizeHtml, sanitizeInput, setStateValue, validateConfig };
|
|
3198
3397
|
//# sourceMappingURL=index.esm.js.map
|