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