@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/angular.js
CHANGED
|
@@ -95,32 +95,6 @@ const DEFAULT_CONFIG = {
|
|
|
95
95
|
closeOnClick: true,
|
|
96
96
|
},
|
|
97
97
|
},
|
|
98
|
-
customization: {
|
|
99
|
-
primaryColor: '#000000',
|
|
100
|
-
accentColor: '#3b82f6',
|
|
101
|
-
backgroundColor: '#ffffff',
|
|
102
|
-
textColor: '#111827',
|
|
103
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
104
|
-
fontSize: '14px',
|
|
105
|
-
borderRadius: '12px',
|
|
106
|
-
},
|
|
107
|
-
features: {
|
|
108
|
-
attachments: true,
|
|
109
|
-
reactions: true,
|
|
110
|
-
typing: true,
|
|
111
|
-
readReceipts: true,
|
|
112
|
-
offlineMode: false,
|
|
113
|
-
fileUpload: true,
|
|
114
|
-
imageUpload: true,
|
|
115
|
-
voiceMessages: false,
|
|
116
|
-
videoMessages: false,
|
|
117
|
-
},
|
|
118
|
-
mobile: {
|
|
119
|
-
fullScreen: true,
|
|
120
|
-
scrollLock: true,
|
|
121
|
-
keyboardHandling: 'auto',
|
|
122
|
-
safeAreaInsets: true,
|
|
123
|
-
},
|
|
124
98
|
auth: {
|
|
125
99
|
enabled: true,
|
|
126
100
|
mode: 'anonymous',
|
|
@@ -164,6 +138,7 @@ function resolveConfig(config) {
|
|
|
164
138
|
validateConfig(config);
|
|
165
139
|
return {
|
|
166
140
|
widgetId: config.widgetId,
|
|
141
|
+
testMode: config.testMode,
|
|
167
142
|
api: {
|
|
168
143
|
...DEFAULT_CONFIG.api,
|
|
169
144
|
widgetId: config.widgetId,
|
|
@@ -193,18 +168,6 @@ function resolveConfig(config) {
|
|
|
193
168
|
...config.iframes?.backdrop,
|
|
194
169
|
},
|
|
195
170
|
},
|
|
196
|
-
customization: {
|
|
197
|
-
...DEFAULT_CONFIG.customization,
|
|
198
|
-
...config.customization,
|
|
199
|
-
},
|
|
200
|
-
features: {
|
|
201
|
-
...DEFAULT_CONFIG.features,
|
|
202
|
-
...config.features,
|
|
203
|
-
},
|
|
204
|
-
mobile: {
|
|
205
|
-
...DEFAULT_CONFIG.mobile,
|
|
206
|
-
...config.mobile,
|
|
207
|
-
},
|
|
208
171
|
auth: {
|
|
209
172
|
...DEFAULT_CONFIG.auth,
|
|
210
173
|
...config.auth,
|
|
@@ -447,6 +410,8 @@ class IframeManager {
|
|
|
447
410
|
this.modalContainer = null;
|
|
448
411
|
this.styleElement = null;
|
|
449
412
|
this.messageBroker = null;
|
|
413
|
+
// Guard flag to prevent double-binding event listeners
|
|
414
|
+
this.eventListenersBound = false;
|
|
450
415
|
this.config = config;
|
|
451
416
|
this.logger = new Logger(config.logging);
|
|
452
417
|
this.deviceInfo = detectDevice();
|
|
@@ -486,18 +451,27 @@ class IframeManager {
|
|
|
486
451
|
}
|
|
487
452
|
/**
|
|
488
453
|
* Create root container structure
|
|
454
|
+
* Reuses existing container if it has the same widgetId (singleton behavior)
|
|
489
455
|
*/
|
|
490
456
|
createRootContainer() {
|
|
491
|
-
|
|
492
|
-
let existingContainer = document.getElementById('weld-container');
|
|
457
|
+
const existingContainer = document.getElementById('weld-container');
|
|
493
458
|
if (existingContainer) {
|
|
494
|
-
|
|
459
|
+
// Reuse if same widgetId
|
|
460
|
+
if (existingContainer.getAttribute('data-widget-id') === this.config.widgetId) {
|
|
461
|
+
this.logger.debug('Reusing existing root container');
|
|
462
|
+
this.rootContainer = existingContainer;
|
|
463
|
+
this.appContainer = existingContainer.querySelector('.weld-app');
|
|
464
|
+
this.modalContainer = document.getElementById('weld-modal-container');
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
this.logger.warn('Weld container already exists with different widgetId, removing old instance');
|
|
495
468
|
existingContainer.remove();
|
|
496
469
|
}
|
|
497
470
|
// Create root container
|
|
498
471
|
this.rootContainer = document.createElement('div');
|
|
499
472
|
this.rootContainer.id = 'weld-container';
|
|
500
473
|
this.rootContainer.className = 'weld-namespace';
|
|
474
|
+
this.rootContainer.setAttribute('data-widget-id', this.config.widgetId);
|
|
501
475
|
// Create app container
|
|
502
476
|
this.appContainer = document.createElement('div');
|
|
503
477
|
this.appContainer.className = 'weld-app';
|
|
@@ -532,17 +506,7 @@ class IframeManager {
|
|
|
532
506
|
* Generate CSS for containers
|
|
533
507
|
*/
|
|
534
508
|
generateCSS() {
|
|
535
|
-
const { customization } = this.config;
|
|
536
509
|
return `
|
|
537
|
-
/* Weld Container */
|
|
538
|
-
#weld-container {
|
|
539
|
-
--weld-color-primary: ${customization.primaryColor};
|
|
540
|
-
--weld-color-accent: ${customization.accentColor};
|
|
541
|
-
--weld-font-family: ${customization.fontFamily};
|
|
542
|
-
--weld-font-size-base: ${customization.fontSize};
|
|
543
|
-
--weld-radius-xl: ${customization.borderRadius};
|
|
544
|
-
}
|
|
545
|
-
|
|
546
510
|
/* Import main stylesheet */
|
|
547
511
|
@import url('/styles/index.css');
|
|
548
512
|
|
|
@@ -566,6 +530,11 @@ class IframeManager {
|
|
|
566
530
|
* Create launcher iframe
|
|
567
531
|
*/
|
|
568
532
|
async createLauncherIframe() {
|
|
533
|
+
// Guard: skip if launcher iframe already exists
|
|
534
|
+
if (this.iframes.has(IframeType.LAUNCHER)) {
|
|
535
|
+
this.logger.debug('Launcher iframe already exists, skipping creation');
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
569
538
|
const { iframes } = this.config;
|
|
570
539
|
const { launcher } = iframes;
|
|
571
540
|
// Create container
|
|
@@ -593,9 +562,12 @@ class IframeManager {
|
|
|
593
562
|
width: 100%;
|
|
594
563
|
height: 100%;
|
|
595
564
|
border: none;
|
|
596
|
-
background:
|
|
565
|
+
background: none;
|
|
566
|
+
color-scheme: none;
|
|
597
567
|
display: block;
|
|
598
568
|
pointer-events: auto;
|
|
569
|
+
border-radius: 50%;
|
|
570
|
+
filter: drop-shadow(rgba(9, 14, 21, 0.54) 0px 1px 6px) drop-shadow(rgba(9, 14, 21, 0.9) 0px 2px 32px);
|
|
599
571
|
`;
|
|
600
572
|
iframe.setAttribute('allow', 'clipboard-write');
|
|
601
573
|
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
|
|
@@ -611,20 +583,36 @@ class IframeManager {
|
|
|
611
583
|
createdAt: Date.now(),
|
|
612
584
|
});
|
|
613
585
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
586
|
+
let launcherRetried = false;
|
|
614
587
|
iframe.onload = () => {
|
|
615
588
|
const metadata = this.iframes.get(IframeType.LAUNCHER);
|
|
616
589
|
if (metadata) {
|
|
617
590
|
this.logger.debug('Launcher iframe DOM loaded');
|
|
618
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
619
591
|
this.messageBroker?.setIframeDomLoaded(IframeType.LAUNCHER);
|
|
620
592
|
}
|
|
621
593
|
};
|
|
594
|
+
iframe.onerror = () => {
|
|
595
|
+
this.logger.error('Launcher iframe failed to load');
|
|
596
|
+
if (!launcherRetried) {
|
|
597
|
+
launcherRetried = true;
|
|
598
|
+
this.logger.info('Retrying launcher iframe load...');
|
|
599
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(launcher.url); }, 3000);
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
this.config.onError?.(new Error('Failed to load widget launcher'));
|
|
603
|
+
}
|
|
604
|
+
};
|
|
622
605
|
this.logger.debug('Launcher iframe created');
|
|
623
606
|
}
|
|
624
607
|
/**
|
|
625
608
|
* Create widget iframe
|
|
626
609
|
*/
|
|
627
610
|
async createWidgetIframe() {
|
|
611
|
+
// Guard: skip if widget iframe already exists
|
|
612
|
+
if (this.iframes.has(IframeType.WIDGET)) {
|
|
613
|
+
this.logger.debug('Widget iframe already exists, skipping creation');
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
628
616
|
const { iframes } = this.config;
|
|
629
617
|
const { widget } = iframes;
|
|
630
618
|
// Create container
|
|
@@ -705,14 +693,25 @@ class IframeManager {
|
|
|
705
693
|
createdAt: Date.now(),
|
|
706
694
|
});
|
|
707
695
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
696
|
+
let widgetRetried = false;
|
|
708
697
|
iframe.onload = () => {
|
|
709
698
|
const metadata = this.iframes.get(IframeType.WIDGET);
|
|
710
699
|
if (metadata) {
|
|
711
700
|
this.logger.debug('Widget iframe DOM loaded');
|
|
712
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
713
701
|
this.messageBroker?.setIframeDomLoaded(IframeType.WIDGET);
|
|
714
702
|
}
|
|
715
703
|
};
|
|
704
|
+
iframe.onerror = () => {
|
|
705
|
+
this.logger.error('Widget iframe failed to load');
|
|
706
|
+
if (!widgetRetried) {
|
|
707
|
+
widgetRetried = true;
|
|
708
|
+
this.logger.info('Retrying widget iframe load...');
|
|
709
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(widget.url); }, 3000);
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
this.config.onError?.(new Error('Failed to load widget'));
|
|
713
|
+
}
|
|
714
|
+
};
|
|
716
715
|
this.logger.debug('Widget iframe created');
|
|
717
716
|
}
|
|
718
717
|
/**
|
|
@@ -733,12 +732,21 @@ class IframeManager {
|
|
|
733
732
|
url.searchParams.set('device', this.deviceInfo.type);
|
|
734
733
|
url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
|
|
735
734
|
url.searchParams.set('parentOrigin', window.location.origin);
|
|
735
|
+
if (this.config.testMode) {
|
|
736
|
+
url.searchParams.set('testMode', 'true');
|
|
737
|
+
}
|
|
736
738
|
return url.toString();
|
|
737
739
|
}
|
|
738
740
|
/**
|
|
739
741
|
* Setup event listeners
|
|
740
742
|
*/
|
|
741
743
|
setupEventListeners() {
|
|
744
|
+
// Guard: prevent double-binding
|
|
745
|
+
if (this.eventListenersBound) {
|
|
746
|
+
this.logger.debug('Event listeners already bound, skipping');
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
this.eventListenersBound = true;
|
|
742
750
|
// Window resize - use bound handler for proper cleanup
|
|
743
751
|
window.addEventListener('resize', this.boundHandleResize);
|
|
744
752
|
// Orientation change - use bound handler for proper cleanup
|
|
@@ -841,7 +849,7 @@ class IframeManager {
|
|
|
841
849
|
iframe.container.style.transform = 'scale(1) translateY(0)';
|
|
842
850
|
}
|
|
843
851
|
// Handle mobile scroll lock
|
|
844
|
-
if (this.deviceInfo.isMobile && type === IframeType.WIDGET
|
|
852
|
+
if (this.deviceInfo.isMobile && type === IframeType.WIDGET) {
|
|
845
853
|
document.body.classList.add('weld-mobile-open');
|
|
846
854
|
}
|
|
847
855
|
// Hide launcher on mobile when widget is open (full-screen mode)
|
|
@@ -940,6 +948,8 @@ class IframeManager {
|
|
|
940
948
|
this.iframes.clear();
|
|
941
949
|
// Clear messageBroker reference
|
|
942
950
|
this.messageBroker = null;
|
|
951
|
+
// Reset guard flag
|
|
952
|
+
this.eventListenersBound = false;
|
|
943
953
|
this.logger.info('IframeManager destroyed');
|
|
944
954
|
}
|
|
945
955
|
}
|
|
@@ -1007,6 +1017,8 @@ var MessageType;
|
|
|
1007
1017
|
// Events
|
|
1008
1018
|
MessageType["EVENT_TRACK"] = "weld:event:track";
|
|
1009
1019
|
MessageType["ERROR_REPORT"] = "weld:error:report";
|
|
1020
|
+
// Page tracking
|
|
1021
|
+
MessageType["PAGE_CHANGE"] = "weld:page:change";
|
|
1010
1022
|
// API responses
|
|
1011
1023
|
MessageType["API_SUCCESS"] = "weld:api:success";
|
|
1012
1024
|
MessageType["API_ERROR"] = "weld:api:error";
|
|
@@ -1546,8 +1558,6 @@ class MessageBroker {
|
|
|
1546
1558
|
iframeType,
|
|
1547
1559
|
config: {
|
|
1548
1560
|
api: this.config.api,
|
|
1549
|
-
customization: this.config.customization,
|
|
1550
|
-
features: this.config.features,
|
|
1551
1561
|
},
|
|
1552
1562
|
};
|
|
1553
1563
|
const message = createMessage('weld:init', MessageOrigin.PARENT, initPayload);
|
|
@@ -2220,7 +2230,7 @@ class StateCoordinator {
|
|
|
2220
2230
|
}
|
|
2221
2231
|
}
|
|
2222
2232
|
|
|
2223
|
-
var version = "1.0.
|
|
2233
|
+
var version = "1.0.17";
|
|
2224
2234
|
var packageJson = {
|
|
2225
2235
|
version: version};
|
|
2226
2236
|
|
|
@@ -2228,6 +2238,16 @@ var packageJson = {
|
|
|
2228
2238
|
* Weld SDK - Main Entry Point
|
|
2229
2239
|
* Public API for the Weld helpdesk widget
|
|
2230
2240
|
*/
|
|
2241
|
+
/**
|
|
2242
|
+
* Module-level singleton registry keyed by widgetId
|
|
2243
|
+
*/
|
|
2244
|
+
const sdkRegistry = new Map();
|
|
2245
|
+
/**
|
|
2246
|
+
* SessionStorage key helpers
|
|
2247
|
+
*/
|
|
2248
|
+
function openStateKey(widgetId) {
|
|
2249
|
+
return `weld-widget-open-${widgetId}`;
|
|
2250
|
+
}
|
|
2231
2251
|
/**
|
|
2232
2252
|
* SDK initialization status
|
|
2233
2253
|
*/
|
|
@@ -2250,6 +2270,8 @@ class WeldSDK {
|
|
|
2250
2270
|
this.readyResolve = null;
|
|
2251
2271
|
// Subscription IDs for cleanup
|
|
2252
2272
|
this.subscriptionIds = [];
|
|
2273
|
+
// Page tracking cleanup
|
|
2274
|
+
this.pageTrackingCleanup = null;
|
|
2253
2275
|
/**
|
|
2254
2276
|
* Update user attributes (Intercom-style, with rate limiting)
|
|
2255
2277
|
* Limited to 20 calls per page load to prevent abuse
|
|
@@ -2288,6 +2310,13 @@ class WeldSDK {
|
|
|
2288
2310
|
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2289
2311
|
}
|
|
2290
2312
|
if (event.data?.type === 'launcher:clicked') {
|
|
2313
|
+
if (this.status !== SDKStatus.READY) {
|
|
2314
|
+
console.log('[Weld SDK] Launcher clicked but SDK not ready yet — waiting...');
|
|
2315
|
+
this.readyPromise?.then(() => {
|
|
2316
|
+
this.handleLauncherClickMessage(event);
|
|
2317
|
+
});
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2291
2320
|
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2292
2321
|
const state = this.stateCoordinator.getState();
|
|
2293
2322
|
if (state.widget.isOpen) {
|
|
@@ -2300,9 +2329,24 @@ class WeldSDK {
|
|
|
2300
2329
|
}
|
|
2301
2330
|
}
|
|
2302
2331
|
if (event.data?.type === 'weld:close') {
|
|
2332
|
+
if (this.status !== SDKStatus.READY)
|
|
2333
|
+
return;
|
|
2303
2334
|
console.log('[Weld SDK] Widget close requested');
|
|
2304
2335
|
this.close();
|
|
2305
2336
|
}
|
|
2337
|
+
if (event.data?.type === 'weld:unread-count') {
|
|
2338
|
+
const count = event.data.count ?? 0;
|
|
2339
|
+
// Forward to launcher iframe
|
|
2340
|
+
const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
|
|
2341
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2342
|
+
launcherIframe.element.contentWindow.postMessage({
|
|
2343
|
+
type: 'weld:unread-count',
|
|
2344
|
+
count
|
|
2345
|
+
}, '*');
|
|
2346
|
+
}
|
|
2347
|
+
// Update state coordinator for external API consumers
|
|
2348
|
+
this.stateCoordinator.setBadgeCount(count);
|
|
2349
|
+
}
|
|
2306
2350
|
if (event.data?.type === 'weld:image:open' && event.data?.url) {
|
|
2307
2351
|
this.showImageLightbox(event.data.url);
|
|
2308
2352
|
}
|
|
@@ -2564,6 +2608,13 @@ class WeldSDK {
|
|
|
2564
2608
|
this.logger.info('WeldSDK ready');
|
|
2565
2609
|
// Call onReady callback
|
|
2566
2610
|
this.config.onReady?.();
|
|
2611
|
+
// Start tracking page URL changes
|
|
2612
|
+
this.startPageTracking();
|
|
2613
|
+
// Auto-open if widget was previously open (persisted in sessionStorage)
|
|
2614
|
+
if (this.wasOpen()) {
|
|
2615
|
+
this.logger.info('Restoring previously open widget from sessionStorage');
|
|
2616
|
+
this.open();
|
|
2617
|
+
}
|
|
2567
2618
|
}
|
|
2568
2619
|
catch (error) {
|
|
2569
2620
|
this.status = SDKStatus.ERROR;
|
|
@@ -2642,6 +2693,58 @@ class WeldSDK {
|
|
|
2642
2693
|
isReady() {
|
|
2643
2694
|
return this.status === SDKStatus.READY;
|
|
2644
2695
|
}
|
|
2696
|
+
/**
|
|
2697
|
+
* Update callbacks on an existing instance (used by singleton reuse)
|
|
2698
|
+
*/
|
|
2699
|
+
updateCallbacks(config) {
|
|
2700
|
+
if (config.onReady !== undefined)
|
|
2701
|
+
this.config.onReady = config.onReady;
|
|
2702
|
+
if (config.onOpen !== undefined)
|
|
2703
|
+
this.config.onOpen = config.onOpen;
|
|
2704
|
+
if (config.onClose !== undefined)
|
|
2705
|
+
this.config.onClose = config.onClose;
|
|
2706
|
+
if (config.onError !== undefined)
|
|
2707
|
+
this.config.onError = config.onError;
|
|
2708
|
+
if (config.onDestroy !== undefined)
|
|
2709
|
+
this.config.onDestroy = config.onDestroy;
|
|
2710
|
+
if (config.onMinimize !== undefined)
|
|
2711
|
+
this.config.onMinimize = config.onMinimize;
|
|
2712
|
+
if (config.onMaximize !== undefined)
|
|
2713
|
+
this.config.onMaximize = config.onMaximize;
|
|
2714
|
+
}
|
|
2715
|
+
/**
|
|
2716
|
+
* Persist open/closed state to sessionStorage
|
|
2717
|
+
*/
|
|
2718
|
+
persistOpenState(isOpen) {
|
|
2719
|
+
try {
|
|
2720
|
+
sessionStorage.setItem(openStateKey(this.config.widgetId), isOpen ? 'true' : 'false');
|
|
2721
|
+
}
|
|
2722
|
+
catch {
|
|
2723
|
+
// sessionStorage might not be available
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Clear persisted state from sessionStorage
|
|
2728
|
+
*/
|
|
2729
|
+
clearPersistedState() {
|
|
2730
|
+
try {
|
|
2731
|
+
sessionStorage.removeItem(openStateKey(this.config.widgetId));
|
|
2732
|
+
}
|
|
2733
|
+
catch {
|
|
2734
|
+
// sessionStorage might not be available
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Check if widget was previously open (from sessionStorage)
|
|
2739
|
+
*/
|
|
2740
|
+
wasOpen() {
|
|
2741
|
+
try {
|
|
2742
|
+
return sessionStorage.getItem(openStateKey(this.config.widgetId)) === 'true';
|
|
2743
|
+
}
|
|
2744
|
+
catch {
|
|
2745
|
+
return false;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2645
2748
|
/**
|
|
2646
2749
|
* Open the widget
|
|
2647
2750
|
*/
|
|
@@ -2660,6 +2763,7 @@ class WeldSDK {
|
|
|
2660
2763
|
if (launcherIframe?.element?.contentWindow) {
|
|
2661
2764
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2662
2765
|
}
|
|
2766
|
+
this.persistOpenState(true);
|
|
2663
2767
|
this.config.onOpen?.();
|
|
2664
2768
|
}
|
|
2665
2769
|
/**
|
|
@@ -2680,6 +2784,7 @@ class WeldSDK {
|
|
|
2680
2784
|
if (launcherIframe?.element?.contentWindow) {
|
|
2681
2785
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2682
2786
|
}
|
|
2787
|
+
this.persistOpenState(false);
|
|
2683
2788
|
this.config.onClose?.();
|
|
2684
2789
|
}
|
|
2685
2790
|
/**
|
|
@@ -2868,6 +2973,8 @@ class WeldSDK {
|
|
|
2868
2973
|
});
|
|
2869
2974
|
// Broadcast logout to iframes
|
|
2870
2975
|
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
2976
|
+
// Clear persisted widget state
|
|
2977
|
+
this.clearPersistedState();
|
|
2871
2978
|
// Clear any stored session data
|
|
2872
2979
|
try {
|
|
2873
2980
|
const prefix = 'weld-';
|
|
@@ -3009,6 +3116,63 @@ class WeldSDK {
|
|
|
3009
3116
|
this.logger.setLevel('warn');
|
|
3010
3117
|
this.logger.info('Debug mode disabled');
|
|
3011
3118
|
}
|
|
3119
|
+
/**
|
|
3120
|
+
* Send a page change message to the widget iframe
|
|
3121
|
+
*/
|
|
3122
|
+
sendPageChange(url, title) {
|
|
3123
|
+
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
3124
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
3125
|
+
widgetIframe.element.contentWindow.postMessage({
|
|
3126
|
+
type: 'weld:page:change',
|
|
3127
|
+
url,
|
|
3128
|
+
title,
|
|
3129
|
+
timestamp: Date.now(),
|
|
3130
|
+
}, '*');
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
/**
|
|
3134
|
+
* Start tracking page URL changes (SPA navigations + popstate)
|
|
3135
|
+
*/
|
|
3136
|
+
startPageTracking() {
|
|
3137
|
+
let lastUrl = window.location.href;
|
|
3138
|
+
let debounceTimer = null;
|
|
3139
|
+
const notifyChange = () => {
|
|
3140
|
+
const currentUrl = window.location.href;
|
|
3141
|
+
if (currentUrl !== lastUrl) {
|
|
3142
|
+
lastUrl = currentUrl;
|
|
3143
|
+
this.sendPageChange(currentUrl, document.title);
|
|
3144
|
+
}
|
|
3145
|
+
};
|
|
3146
|
+
const debouncedNotify = () => {
|
|
3147
|
+
if (debounceTimer)
|
|
3148
|
+
clearTimeout(debounceTimer);
|
|
3149
|
+
debounceTimer = setTimeout(notifyChange, 300);
|
|
3150
|
+
};
|
|
3151
|
+
// Send initial page
|
|
3152
|
+
this.sendPageChange(window.location.href, document.title);
|
|
3153
|
+
// Monkey-patch history.pushState and history.replaceState
|
|
3154
|
+
const origPushState = history.pushState.bind(history);
|
|
3155
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
3156
|
+
history.pushState = function (...args) {
|
|
3157
|
+
origPushState(...args);
|
|
3158
|
+
debouncedNotify();
|
|
3159
|
+
};
|
|
3160
|
+
history.replaceState = function (...args) {
|
|
3161
|
+
origReplaceState(...args);
|
|
3162
|
+
debouncedNotify();
|
|
3163
|
+
};
|
|
3164
|
+
// Listen for popstate (browser back/forward)
|
|
3165
|
+
const handlePopstate = () => debouncedNotify();
|
|
3166
|
+
window.addEventListener('popstate', handlePopstate);
|
|
3167
|
+
// Store cleanup
|
|
3168
|
+
this.pageTrackingCleanup = () => {
|
|
3169
|
+
if (debounceTimer)
|
|
3170
|
+
clearTimeout(debounceTimer);
|
|
3171
|
+
window.removeEventListener('popstate', handlePopstate);
|
|
3172
|
+
history.pushState = origPushState;
|
|
3173
|
+
history.replaceState = origReplaceState;
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3012
3176
|
/**
|
|
3013
3177
|
* Ensure SDK is ready before operation
|
|
3014
3178
|
*/
|
|
@@ -3017,11 +3181,26 @@ class WeldSDK {
|
|
|
3017
3181
|
throw new Error('SDK not ready. Call init() first.');
|
|
3018
3182
|
}
|
|
3019
3183
|
}
|
|
3184
|
+
/**
|
|
3185
|
+
* Detach from the current component lifecycle without destroying the widget.
|
|
3186
|
+
* Use this as a React useEffect cleanup — the widget stays alive across navigations.
|
|
3187
|
+
*/
|
|
3188
|
+
detach() {
|
|
3189
|
+
// No-op: widget stays alive in the singleton registry
|
|
3190
|
+
this.logger.debug('WeldSDK detached (no-op, widget stays alive)');
|
|
3191
|
+
}
|
|
3020
3192
|
/**
|
|
3021
3193
|
* Destroy SDK and cleanup
|
|
3022
3194
|
*/
|
|
3023
3195
|
destroy() {
|
|
3024
3196
|
this.logger.info('Destroying WeldSDK');
|
|
3197
|
+
// Remove from singleton registry
|
|
3198
|
+
sdkRegistry.delete(this.config.widgetId);
|
|
3199
|
+
// Clear persisted state
|
|
3200
|
+
this.clearPersistedState();
|
|
3201
|
+
// Stop page tracking
|
|
3202
|
+
this.pageTrackingCleanup?.();
|
|
3203
|
+
this.pageTrackingCleanup = null;
|
|
3025
3204
|
// Remove event listener using bound handler
|
|
3026
3205
|
window.removeEventListener('message', this.boundHandleLauncherClick);
|
|
3027
3206
|
// Unsubscribe from all message broker subscriptions
|