@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.js
CHANGED
|
@@ -35,32 +35,6 @@ const DEFAULT_CONFIG = {
|
|
|
35
35
|
closeOnClick: true,
|
|
36
36
|
},
|
|
37
37
|
},
|
|
38
|
-
customization: {
|
|
39
|
-
primaryColor: '#000000',
|
|
40
|
-
accentColor: '#3b82f6',
|
|
41
|
-
backgroundColor: '#ffffff',
|
|
42
|
-
textColor: '#111827',
|
|
43
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
44
|
-
fontSize: '14px',
|
|
45
|
-
borderRadius: '12px',
|
|
46
|
-
},
|
|
47
|
-
features: {
|
|
48
|
-
attachments: true,
|
|
49
|
-
reactions: true,
|
|
50
|
-
typing: true,
|
|
51
|
-
readReceipts: true,
|
|
52
|
-
offlineMode: false,
|
|
53
|
-
fileUpload: true,
|
|
54
|
-
imageUpload: true,
|
|
55
|
-
voiceMessages: false,
|
|
56
|
-
videoMessages: false,
|
|
57
|
-
},
|
|
58
|
-
mobile: {
|
|
59
|
-
fullScreen: true,
|
|
60
|
-
scrollLock: true,
|
|
61
|
-
keyboardHandling: 'auto',
|
|
62
|
-
safeAreaInsets: true,
|
|
63
|
-
},
|
|
64
38
|
auth: {
|
|
65
39
|
enabled: true,
|
|
66
40
|
mode: 'anonymous',
|
|
@@ -104,6 +78,7 @@ function resolveConfig(config) {
|
|
|
104
78
|
validateConfig(config);
|
|
105
79
|
return {
|
|
106
80
|
widgetId: config.widgetId,
|
|
81
|
+
testMode: config.testMode,
|
|
107
82
|
api: {
|
|
108
83
|
...DEFAULT_CONFIG.api,
|
|
109
84
|
widgetId: config.widgetId,
|
|
@@ -133,18 +108,6 @@ function resolveConfig(config) {
|
|
|
133
108
|
...config.iframes?.backdrop,
|
|
134
109
|
},
|
|
135
110
|
},
|
|
136
|
-
customization: {
|
|
137
|
-
...DEFAULT_CONFIG.customization,
|
|
138
|
-
...config.customization,
|
|
139
|
-
},
|
|
140
|
-
features: {
|
|
141
|
-
...DEFAULT_CONFIG.features,
|
|
142
|
-
...config.features,
|
|
143
|
-
},
|
|
144
|
-
mobile: {
|
|
145
|
-
...DEFAULT_CONFIG.mobile,
|
|
146
|
-
...config.mobile,
|
|
147
|
-
},
|
|
148
111
|
auth: {
|
|
149
112
|
...DEFAULT_CONFIG.auth,
|
|
150
113
|
...config.auth,
|
|
@@ -387,6 +350,8 @@ class IframeManager {
|
|
|
387
350
|
this.modalContainer = null;
|
|
388
351
|
this.styleElement = null;
|
|
389
352
|
this.messageBroker = null;
|
|
353
|
+
// Guard flag to prevent double-binding event listeners
|
|
354
|
+
this.eventListenersBound = false;
|
|
390
355
|
this.config = config;
|
|
391
356
|
this.logger = new Logger(config.logging);
|
|
392
357
|
this.deviceInfo = detectDevice();
|
|
@@ -426,18 +391,27 @@ class IframeManager {
|
|
|
426
391
|
}
|
|
427
392
|
/**
|
|
428
393
|
* Create root container structure
|
|
394
|
+
* Reuses existing container if it has the same widgetId (singleton behavior)
|
|
429
395
|
*/
|
|
430
396
|
createRootContainer() {
|
|
431
|
-
|
|
432
|
-
let existingContainer = document.getElementById('weld-container');
|
|
397
|
+
const existingContainer = document.getElementById('weld-container');
|
|
433
398
|
if (existingContainer) {
|
|
434
|
-
|
|
399
|
+
// Reuse if same widgetId
|
|
400
|
+
if (existingContainer.getAttribute('data-widget-id') === this.config.widgetId) {
|
|
401
|
+
this.logger.debug('Reusing existing root container');
|
|
402
|
+
this.rootContainer = existingContainer;
|
|
403
|
+
this.appContainer = existingContainer.querySelector('.weld-app');
|
|
404
|
+
this.modalContainer = document.getElementById('weld-modal-container');
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
this.logger.warn('Weld container already exists with different widgetId, removing old instance');
|
|
435
408
|
existingContainer.remove();
|
|
436
409
|
}
|
|
437
410
|
// Create root container
|
|
438
411
|
this.rootContainer = document.createElement('div');
|
|
439
412
|
this.rootContainer.id = 'weld-container';
|
|
440
413
|
this.rootContainer.className = 'weld-namespace';
|
|
414
|
+
this.rootContainer.setAttribute('data-widget-id', this.config.widgetId);
|
|
441
415
|
// Create app container
|
|
442
416
|
this.appContainer = document.createElement('div');
|
|
443
417
|
this.appContainer.className = 'weld-app';
|
|
@@ -472,17 +446,7 @@ class IframeManager {
|
|
|
472
446
|
* Generate CSS for containers
|
|
473
447
|
*/
|
|
474
448
|
generateCSS() {
|
|
475
|
-
const { customization } = this.config;
|
|
476
449
|
return `
|
|
477
|
-
/* Weld Container */
|
|
478
|
-
#weld-container {
|
|
479
|
-
--weld-color-primary: ${customization.primaryColor};
|
|
480
|
-
--weld-color-accent: ${customization.accentColor};
|
|
481
|
-
--weld-font-family: ${customization.fontFamily};
|
|
482
|
-
--weld-font-size-base: ${customization.fontSize};
|
|
483
|
-
--weld-radius-xl: ${customization.borderRadius};
|
|
484
|
-
}
|
|
485
|
-
|
|
486
450
|
/* Import main stylesheet */
|
|
487
451
|
@import url('/styles/index.css');
|
|
488
452
|
|
|
@@ -506,6 +470,11 @@ class IframeManager {
|
|
|
506
470
|
* Create launcher iframe
|
|
507
471
|
*/
|
|
508
472
|
async createLauncherIframe() {
|
|
473
|
+
// Guard: skip if launcher iframe already exists
|
|
474
|
+
if (this.iframes.has(exports.IframeType.LAUNCHER)) {
|
|
475
|
+
this.logger.debug('Launcher iframe already exists, skipping creation');
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
509
478
|
const { iframes } = this.config;
|
|
510
479
|
const { launcher } = iframes;
|
|
511
480
|
// Create container
|
|
@@ -533,9 +502,12 @@ class IframeManager {
|
|
|
533
502
|
width: 100%;
|
|
534
503
|
height: 100%;
|
|
535
504
|
border: none;
|
|
536
|
-
background:
|
|
505
|
+
background: none;
|
|
506
|
+
color-scheme: none;
|
|
537
507
|
display: block;
|
|
538
508
|
pointer-events: auto;
|
|
509
|
+
border-radius: 50%;
|
|
510
|
+
filter: drop-shadow(rgba(9, 14, 21, 0.54) 0px 1px 6px) drop-shadow(rgba(9, 14, 21, 0.9) 0px 2px 32px);
|
|
539
511
|
`;
|
|
540
512
|
iframe.setAttribute('allow', 'clipboard-write');
|
|
541
513
|
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
|
|
@@ -551,20 +523,36 @@ class IframeManager {
|
|
|
551
523
|
createdAt: Date.now(),
|
|
552
524
|
});
|
|
553
525
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
526
|
+
let launcherRetried = false;
|
|
554
527
|
iframe.onload = () => {
|
|
555
528
|
const metadata = this.iframes.get(exports.IframeType.LAUNCHER);
|
|
556
529
|
if (metadata) {
|
|
557
530
|
this.logger.debug('Launcher iframe DOM loaded');
|
|
558
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
559
531
|
this.messageBroker?.setIframeDomLoaded(exports.IframeType.LAUNCHER);
|
|
560
532
|
}
|
|
561
533
|
};
|
|
534
|
+
iframe.onerror = () => {
|
|
535
|
+
this.logger.error('Launcher iframe failed to load');
|
|
536
|
+
if (!launcherRetried) {
|
|
537
|
+
launcherRetried = true;
|
|
538
|
+
this.logger.info('Retrying launcher iframe load...');
|
|
539
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(launcher.url); }, 3000);
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
this.config.onError?.(new Error('Failed to load widget launcher'));
|
|
543
|
+
}
|
|
544
|
+
};
|
|
562
545
|
this.logger.debug('Launcher iframe created');
|
|
563
546
|
}
|
|
564
547
|
/**
|
|
565
548
|
* Create widget iframe
|
|
566
549
|
*/
|
|
567
550
|
async createWidgetIframe() {
|
|
551
|
+
// Guard: skip if widget iframe already exists
|
|
552
|
+
if (this.iframes.has(exports.IframeType.WIDGET)) {
|
|
553
|
+
this.logger.debug('Widget iframe already exists, skipping creation');
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
568
556
|
const { iframes } = this.config;
|
|
569
557
|
const { widget } = iframes;
|
|
570
558
|
// Create container
|
|
@@ -645,14 +633,25 @@ class IframeManager {
|
|
|
645
633
|
createdAt: Date.now(),
|
|
646
634
|
});
|
|
647
635
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
636
|
+
let widgetRetried = false;
|
|
648
637
|
iframe.onload = () => {
|
|
649
638
|
const metadata = this.iframes.get(exports.IframeType.WIDGET);
|
|
650
639
|
if (metadata) {
|
|
651
640
|
this.logger.debug('Widget iframe DOM loaded');
|
|
652
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
653
641
|
this.messageBroker?.setIframeDomLoaded(exports.IframeType.WIDGET);
|
|
654
642
|
}
|
|
655
643
|
};
|
|
644
|
+
iframe.onerror = () => {
|
|
645
|
+
this.logger.error('Widget iframe failed to load');
|
|
646
|
+
if (!widgetRetried) {
|
|
647
|
+
widgetRetried = true;
|
|
648
|
+
this.logger.info('Retrying widget iframe load...');
|
|
649
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(widget.url); }, 3000);
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
this.config.onError?.(new Error('Failed to load widget'));
|
|
653
|
+
}
|
|
654
|
+
};
|
|
656
655
|
this.logger.debug('Widget iframe created');
|
|
657
656
|
}
|
|
658
657
|
/**
|
|
@@ -673,12 +672,21 @@ class IframeManager {
|
|
|
673
672
|
url.searchParams.set('device', this.deviceInfo.type);
|
|
674
673
|
url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
|
|
675
674
|
url.searchParams.set('parentOrigin', window.location.origin);
|
|
675
|
+
if (this.config.testMode) {
|
|
676
|
+
url.searchParams.set('testMode', 'true');
|
|
677
|
+
}
|
|
676
678
|
return url.toString();
|
|
677
679
|
}
|
|
678
680
|
/**
|
|
679
681
|
* Setup event listeners
|
|
680
682
|
*/
|
|
681
683
|
setupEventListeners() {
|
|
684
|
+
// Guard: prevent double-binding
|
|
685
|
+
if (this.eventListenersBound) {
|
|
686
|
+
this.logger.debug('Event listeners already bound, skipping');
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
this.eventListenersBound = true;
|
|
682
690
|
// Window resize - use bound handler for proper cleanup
|
|
683
691
|
window.addEventListener('resize', this.boundHandleResize);
|
|
684
692
|
// Orientation change - use bound handler for proper cleanup
|
|
@@ -781,7 +789,7 @@ class IframeManager {
|
|
|
781
789
|
iframe.container.style.transform = 'scale(1) translateY(0)';
|
|
782
790
|
}
|
|
783
791
|
// Handle mobile scroll lock
|
|
784
|
-
if (this.deviceInfo.isMobile && type === exports.IframeType.WIDGET
|
|
792
|
+
if (this.deviceInfo.isMobile && type === exports.IframeType.WIDGET) {
|
|
785
793
|
document.body.classList.add('weld-mobile-open');
|
|
786
794
|
}
|
|
787
795
|
// Hide launcher on mobile when widget is open (full-screen mode)
|
|
@@ -880,6 +888,8 @@ class IframeManager {
|
|
|
880
888
|
this.iframes.clear();
|
|
881
889
|
// Clear messageBroker reference
|
|
882
890
|
this.messageBroker = null;
|
|
891
|
+
// Reset guard flag
|
|
892
|
+
this.eventListenersBound = false;
|
|
883
893
|
this.logger.info('IframeManager destroyed');
|
|
884
894
|
}
|
|
885
895
|
}
|
|
@@ -947,6 +957,8 @@ var MessageType;
|
|
|
947
957
|
// Events
|
|
948
958
|
MessageType["EVENT_TRACK"] = "weld:event:track";
|
|
949
959
|
MessageType["ERROR_REPORT"] = "weld:error:report";
|
|
960
|
+
// Page tracking
|
|
961
|
+
MessageType["PAGE_CHANGE"] = "weld:page:change";
|
|
950
962
|
// API responses
|
|
951
963
|
MessageType["API_SUCCESS"] = "weld:api:success";
|
|
952
964
|
MessageType["API_ERROR"] = "weld:api:error";
|
|
@@ -1525,8 +1537,6 @@ class MessageBroker {
|
|
|
1525
1537
|
iframeType,
|
|
1526
1538
|
config: {
|
|
1527
1539
|
api: this.config.api,
|
|
1528
|
-
customization: this.config.customization,
|
|
1529
|
-
features: this.config.features,
|
|
1530
1540
|
},
|
|
1531
1541
|
};
|
|
1532
1542
|
const message = createMessage('weld:init', MessageOrigin.PARENT, initPayload);
|
|
@@ -2369,7 +2379,7 @@ class StateCoordinator {
|
|
|
2369
2379
|
}
|
|
2370
2380
|
}
|
|
2371
2381
|
|
|
2372
|
-
var version = "1.0.
|
|
2382
|
+
var version = "1.0.17";
|
|
2373
2383
|
var packageJson = {
|
|
2374
2384
|
version: version};
|
|
2375
2385
|
|
|
@@ -2377,6 +2387,16 @@ var packageJson = {
|
|
|
2377
2387
|
* Weld SDK - Main Entry Point
|
|
2378
2388
|
* Public API for the Weld helpdesk widget
|
|
2379
2389
|
*/
|
|
2390
|
+
/**
|
|
2391
|
+
* Module-level singleton registry keyed by widgetId
|
|
2392
|
+
*/
|
|
2393
|
+
const sdkRegistry = new Map();
|
|
2394
|
+
/**
|
|
2395
|
+
* SessionStorage key helpers
|
|
2396
|
+
*/
|
|
2397
|
+
function openStateKey(widgetId) {
|
|
2398
|
+
return `weld-widget-open-${widgetId}`;
|
|
2399
|
+
}
|
|
2380
2400
|
/**
|
|
2381
2401
|
* SDK initialization status
|
|
2382
2402
|
*/
|
|
@@ -2399,6 +2419,8 @@ class WeldSDK {
|
|
|
2399
2419
|
this.readyResolve = null;
|
|
2400
2420
|
// Subscription IDs for cleanup
|
|
2401
2421
|
this.subscriptionIds = [];
|
|
2422
|
+
// Page tracking cleanup
|
|
2423
|
+
this.pageTrackingCleanup = null;
|
|
2402
2424
|
/**
|
|
2403
2425
|
* Update user attributes (Intercom-style, with rate limiting)
|
|
2404
2426
|
* Limited to 20 calls per page load to prevent abuse
|
|
@@ -2437,6 +2459,13 @@ class WeldSDK {
|
|
|
2437
2459
|
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2438
2460
|
}
|
|
2439
2461
|
if (event.data?.type === 'launcher:clicked') {
|
|
2462
|
+
if (this.status !== SDKStatus.READY) {
|
|
2463
|
+
console.log('[Weld SDK] Launcher clicked but SDK not ready yet — waiting...');
|
|
2464
|
+
this.readyPromise?.then(() => {
|
|
2465
|
+
this.handleLauncherClickMessage(event);
|
|
2466
|
+
});
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2440
2469
|
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2441
2470
|
const state = this.stateCoordinator.getState();
|
|
2442
2471
|
if (state.widget.isOpen) {
|
|
@@ -2449,9 +2478,24 @@ class WeldSDK {
|
|
|
2449
2478
|
}
|
|
2450
2479
|
}
|
|
2451
2480
|
if (event.data?.type === 'weld:close') {
|
|
2481
|
+
if (this.status !== SDKStatus.READY)
|
|
2482
|
+
return;
|
|
2452
2483
|
console.log('[Weld SDK] Widget close requested');
|
|
2453
2484
|
this.close();
|
|
2454
2485
|
}
|
|
2486
|
+
if (event.data?.type === 'weld:unread-count') {
|
|
2487
|
+
const count = event.data.count ?? 0;
|
|
2488
|
+
// Forward to launcher iframe
|
|
2489
|
+
const launcherIframe = this.iframeManager.getIframe(exports.IframeType.LAUNCHER);
|
|
2490
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2491
|
+
launcherIframe.element.contentWindow.postMessage({
|
|
2492
|
+
type: 'weld:unread-count',
|
|
2493
|
+
count
|
|
2494
|
+
}, '*');
|
|
2495
|
+
}
|
|
2496
|
+
// Update state coordinator for external API consumers
|
|
2497
|
+
this.stateCoordinator.setBadgeCount(count);
|
|
2498
|
+
}
|
|
2455
2499
|
if (event.data?.type === 'weld:image:open' && event.data?.url) {
|
|
2456
2500
|
this.showImageLightbox(event.data.url);
|
|
2457
2501
|
}
|
|
@@ -2713,6 +2757,13 @@ class WeldSDK {
|
|
|
2713
2757
|
this.logger.info('WeldSDK ready');
|
|
2714
2758
|
// Call onReady callback
|
|
2715
2759
|
this.config.onReady?.();
|
|
2760
|
+
// Start tracking page URL changes
|
|
2761
|
+
this.startPageTracking();
|
|
2762
|
+
// Auto-open if widget was previously open (persisted in sessionStorage)
|
|
2763
|
+
if (this.wasOpen()) {
|
|
2764
|
+
this.logger.info('Restoring previously open widget from sessionStorage');
|
|
2765
|
+
this.open();
|
|
2766
|
+
}
|
|
2716
2767
|
}
|
|
2717
2768
|
catch (error) {
|
|
2718
2769
|
this.status = SDKStatus.ERROR;
|
|
@@ -2791,6 +2842,58 @@ class WeldSDK {
|
|
|
2791
2842
|
isReady() {
|
|
2792
2843
|
return this.status === SDKStatus.READY;
|
|
2793
2844
|
}
|
|
2845
|
+
/**
|
|
2846
|
+
* Update callbacks on an existing instance (used by singleton reuse)
|
|
2847
|
+
*/
|
|
2848
|
+
updateCallbacks(config) {
|
|
2849
|
+
if (config.onReady !== undefined)
|
|
2850
|
+
this.config.onReady = config.onReady;
|
|
2851
|
+
if (config.onOpen !== undefined)
|
|
2852
|
+
this.config.onOpen = config.onOpen;
|
|
2853
|
+
if (config.onClose !== undefined)
|
|
2854
|
+
this.config.onClose = config.onClose;
|
|
2855
|
+
if (config.onError !== undefined)
|
|
2856
|
+
this.config.onError = config.onError;
|
|
2857
|
+
if (config.onDestroy !== undefined)
|
|
2858
|
+
this.config.onDestroy = config.onDestroy;
|
|
2859
|
+
if (config.onMinimize !== undefined)
|
|
2860
|
+
this.config.onMinimize = config.onMinimize;
|
|
2861
|
+
if (config.onMaximize !== undefined)
|
|
2862
|
+
this.config.onMaximize = config.onMaximize;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Persist open/closed state to sessionStorage
|
|
2866
|
+
*/
|
|
2867
|
+
persistOpenState(isOpen) {
|
|
2868
|
+
try {
|
|
2869
|
+
sessionStorage.setItem(openStateKey(this.config.widgetId), isOpen ? 'true' : 'false');
|
|
2870
|
+
}
|
|
2871
|
+
catch {
|
|
2872
|
+
// sessionStorage might not be available
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Clear persisted state from sessionStorage
|
|
2877
|
+
*/
|
|
2878
|
+
clearPersistedState() {
|
|
2879
|
+
try {
|
|
2880
|
+
sessionStorage.removeItem(openStateKey(this.config.widgetId));
|
|
2881
|
+
}
|
|
2882
|
+
catch {
|
|
2883
|
+
// sessionStorage might not be available
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
/**
|
|
2887
|
+
* Check if widget was previously open (from sessionStorage)
|
|
2888
|
+
*/
|
|
2889
|
+
wasOpen() {
|
|
2890
|
+
try {
|
|
2891
|
+
return sessionStorage.getItem(openStateKey(this.config.widgetId)) === 'true';
|
|
2892
|
+
}
|
|
2893
|
+
catch {
|
|
2894
|
+
return false;
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2794
2897
|
/**
|
|
2795
2898
|
* Open the widget
|
|
2796
2899
|
*/
|
|
@@ -2809,6 +2912,7 @@ class WeldSDK {
|
|
|
2809
2912
|
if (launcherIframe?.element?.contentWindow) {
|
|
2810
2913
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2811
2914
|
}
|
|
2915
|
+
this.persistOpenState(true);
|
|
2812
2916
|
this.config.onOpen?.();
|
|
2813
2917
|
}
|
|
2814
2918
|
/**
|
|
@@ -2829,6 +2933,7 @@ class WeldSDK {
|
|
|
2829
2933
|
if (launcherIframe?.element?.contentWindow) {
|
|
2830
2934
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2831
2935
|
}
|
|
2936
|
+
this.persistOpenState(false);
|
|
2832
2937
|
this.config.onClose?.();
|
|
2833
2938
|
}
|
|
2834
2939
|
/**
|
|
@@ -3017,6 +3122,8 @@ class WeldSDK {
|
|
|
3017
3122
|
});
|
|
3018
3123
|
// Broadcast logout to iframes
|
|
3019
3124
|
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
3125
|
+
// Clear persisted widget state
|
|
3126
|
+
this.clearPersistedState();
|
|
3020
3127
|
// Clear any stored session data
|
|
3021
3128
|
try {
|
|
3022
3129
|
const prefix = 'weld-';
|
|
@@ -3158,6 +3265,63 @@ class WeldSDK {
|
|
|
3158
3265
|
this.logger.setLevel('warn');
|
|
3159
3266
|
this.logger.info('Debug mode disabled');
|
|
3160
3267
|
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Send a page change message to the widget iframe
|
|
3270
|
+
*/
|
|
3271
|
+
sendPageChange(url, title) {
|
|
3272
|
+
const widgetIframe = this.iframeManager.getIframe(exports.IframeType.WIDGET);
|
|
3273
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
3274
|
+
widgetIframe.element.contentWindow.postMessage({
|
|
3275
|
+
type: 'weld:page:change',
|
|
3276
|
+
url,
|
|
3277
|
+
title,
|
|
3278
|
+
timestamp: Date.now(),
|
|
3279
|
+
}, '*');
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
/**
|
|
3283
|
+
* Start tracking page URL changes (SPA navigations + popstate)
|
|
3284
|
+
*/
|
|
3285
|
+
startPageTracking() {
|
|
3286
|
+
let lastUrl = window.location.href;
|
|
3287
|
+
let debounceTimer = null;
|
|
3288
|
+
const notifyChange = () => {
|
|
3289
|
+
const currentUrl = window.location.href;
|
|
3290
|
+
if (currentUrl !== lastUrl) {
|
|
3291
|
+
lastUrl = currentUrl;
|
|
3292
|
+
this.sendPageChange(currentUrl, document.title);
|
|
3293
|
+
}
|
|
3294
|
+
};
|
|
3295
|
+
const debouncedNotify = () => {
|
|
3296
|
+
if (debounceTimer)
|
|
3297
|
+
clearTimeout(debounceTimer);
|
|
3298
|
+
debounceTimer = setTimeout(notifyChange, 300);
|
|
3299
|
+
};
|
|
3300
|
+
// Send initial page
|
|
3301
|
+
this.sendPageChange(window.location.href, document.title);
|
|
3302
|
+
// Monkey-patch history.pushState and history.replaceState
|
|
3303
|
+
const origPushState = history.pushState.bind(history);
|
|
3304
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
3305
|
+
history.pushState = function (...args) {
|
|
3306
|
+
origPushState(...args);
|
|
3307
|
+
debouncedNotify();
|
|
3308
|
+
};
|
|
3309
|
+
history.replaceState = function (...args) {
|
|
3310
|
+
origReplaceState(...args);
|
|
3311
|
+
debouncedNotify();
|
|
3312
|
+
};
|
|
3313
|
+
// Listen for popstate (browser back/forward)
|
|
3314
|
+
const handlePopstate = () => debouncedNotify();
|
|
3315
|
+
window.addEventListener('popstate', handlePopstate);
|
|
3316
|
+
// Store cleanup
|
|
3317
|
+
this.pageTrackingCleanup = () => {
|
|
3318
|
+
if (debounceTimer)
|
|
3319
|
+
clearTimeout(debounceTimer);
|
|
3320
|
+
window.removeEventListener('popstate', handlePopstate);
|
|
3321
|
+
history.pushState = origPushState;
|
|
3322
|
+
history.replaceState = origReplaceState;
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3161
3325
|
/**
|
|
3162
3326
|
* Ensure SDK is ready before operation
|
|
3163
3327
|
*/
|
|
@@ -3166,11 +3330,26 @@ class WeldSDK {
|
|
|
3166
3330
|
throw new Error('SDK not ready. Call init() first.');
|
|
3167
3331
|
}
|
|
3168
3332
|
}
|
|
3333
|
+
/**
|
|
3334
|
+
* Detach from the current component lifecycle without destroying the widget.
|
|
3335
|
+
* Use this as a React useEffect cleanup — the widget stays alive across navigations.
|
|
3336
|
+
*/
|
|
3337
|
+
detach() {
|
|
3338
|
+
// No-op: widget stays alive in the singleton registry
|
|
3339
|
+
this.logger.debug('WeldSDK detached (no-op, widget stays alive)');
|
|
3340
|
+
}
|
|
3169
3341
|
/**
|
|
3170
3342
|
* Destroy SDK and cleanup
|
|
3171
3343
|
*/
|
|
3172
3344
|
destroy() {
|
|
3173
3345
|
this.logger.info('Destroying WeldSDK');
|
|
3346
|
+
// Remove from singleton registry
|
|
3347
|
+
sdkRegistry.delete(this.config.widgetId);
|
|
3348
|
+
// Clear persisted state
|
|
3349
|
+
this.clearPersistedState();
|
|
3350
|
+
// Stop page tracking
|
|
3351
|
+
this.pageTrackingCleanup?.();
|
|
3352
|
+
this.pageTrackingCleanup = null;
|
|
3174
3353
|
// Remove event listener using bound handler
|
|
3175
3354
|
window.removeEventListener('message', this.boundHandleLauncherClick);
|
|
3176
3355
|
// Unsubscribe from all message broker subscriptions
|
|
@@ -3190,13 +3369,33 @@ class WeldSDK {
|
|
|
3190
3369
|
}
|
|
3191
3370
|
}
|
|
3192
3371
|
/**
|
|
3193
|
-
* Create and initialize WeldSDK instance
|
|
3372
|
+
* Create and initialize WeldSDK instance.
|
|
3373
|
+
* Uses singleton pattern — if an instance for the same widgetId already exists
|
|
3374
|
+
* and is not destroyed, updates callbacks and returns the existing instance.
|
|
3194
3375
|
*/
|
|
3195
3376
|
async function createWeldSDK(config) {
|
|
3377
|
+
const widgetId = config.widgetId;
|
|
3378
|
+
// Check for existing, non-destroyed instance
|
|
3379
|
+
const existing = sdkRegistry.get(widgetId);
|
|
3380
|
+
if (existing && existing.getStatus() !== 'destroyed') {
|
|
3381
|
+
existing.updateCallbacks(config);
|
|
3382
|
+
return existing;
|
|
3383
|
+
}
|
|
3196
3384
|
const sdk = new WeldSDK(config);
|
|
3385
|
+
sdkRegistry.set(widgetId, sdk);
|
|
3197
3386
|
await sdk.init();
|
|
3198
3387
|
return sdk;
|
|
3199
3388
|
}
|
|
3389
|
+
/**
|
|
3390
|
+
* Explicitly destroy a WeldSDK instance by widgetId.
|
|
3391
|
+
* Use this for logout or when you need to fully remove the widget.
|
|
3392
|
+
*/
|
|
3393
|
+
function destroyWeldSDK(widgetId) {
|
|
3394
|
+
const sdk = sdkRegistry.get(widgetId);
|
|
3395
|
+
if (sdk) {
|
|
3396
|
+
sdk.destroy();
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3200
3399
|
|
|
3201
3400
|
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
3202
3401
|
exports.HelpdeskWidget = WeldSDK;
|
|
@@ -3215,6 +3414,8 @@ exports.deepClone = deepClone;
|
|
|
3215
3414
|
exports.deepMerge = deepMerge;
|
|
3216
3415
|
exports.default = WeldSDK;
|
|
3217
3416
|
exports.defaultLogger = defaultLogger;
|
|
3417
|
+
exports.destroyHelpdeskWidget = destroyWeldSDK;
|
|
3418
|
+
exports.destroyWeldSDK = destroyWeldSDK;
|
|
3218
3419
|
exports.formatFileSize = formatFileSize;
|
|
3219
3420
|
exports.getStateValue = getStateValue;
|
|
3220
3421
|
exports.hasRequiredProperties = hasRequiredProperties;
|