@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/react.js
CHANGED
|
@@ -37,32 +37,6 @@ const DEFAULT_CONFIG = {
|
|
|
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 @@ function resolveConfig(config) {
|
|
|
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 @@ function resolveConfig(config) {
|
|
|
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 @@ class IframeManager {
|
|
|
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 @@ class IframeManager {
|
|
|
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 @@ class IframeManager {
|
|
|
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 @@ class IframeManager {
|
|
|
508
472
|
* Create launcher iframe
|
|
509
473
|
*/
|
|
510
474
|
async createLauncherIframe() {
|
|
475
|
+
// Guard: skip if launcher iframe already exists
|
|
476
|
+
if (this.iframes.has(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 @@ class IframeManager {
|
|
|
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 @@ class IframeManager {
|
|
|
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(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(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(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 @@ class IframeManager {
|
|
|
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(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(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 @@ class IframeManager {
|
|
|
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 @@ class IframeManager {
|
|
|
783
791
|
iframe.container.style.transform = 'scale(1) translateY(0)';
|
|
784
792
|
}
|
|
785
793
|
// Handle mobile scroll lock
|
|
786
|
-
if (this.deviceInfo.isMobile && type === IframeType.WIDGET
|
|
794
|
+
if (this.deviceInfo.isMobile && type === 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 @@ class IframeManager {
|
|
|
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 @@ var MessageType;
|
|
|
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";
|
|
@@ -1488,8 +1500,6 @@ class MessageBroker {
|
|
|
1488
1500
|
iframeType,
|
|
1489
1501
|
config: {
|
|
1490
1502
|
api: this.config.api,
|
|
1491
|
-
customization: this.config.customization,
|
|
1492
|
-
features: this.config.features,
|
|
1493
1503
|
},
|
|
1494
1504
|
};
|
|
1495
1505
|
const message = createMessage('weld:init', MessageOrigin.PARENT, initPayload);
|
|
@@ -2162,7 +2172,7 @@ class StateCoordinator {
|
|
|
2162
2172
|
}
|
|
2163
2173
|
}
|
|
2164
2174
|
|
|
2165
|
-
var version = "1.0.
|
|
2175
|
+
var version = "1.0.17";
|
|
2166
2176
|
var packageJson = {
|
|
2167
2177
|
version: version};
|
|
2168
2178
|
|
|
@@ -2170,6 +2180,16 @@ var packageJson = {
|
|
|
2170
2180
|
* Weld SDK - Main Entry Point
|
|
2171
2181
|
* Public API for the Weld helpdesk widget
|
|
2172
2182
|
*/
|
|
2183
|
+
/**
|
|
2184
|
+
* Module-level singleton registry keyed by widgetId
|
|
2185
|
+
*/
|
|
2186
|
+
const sdkRegistry = new Map();
|
|
2187
|
+
/**
|
|
2188
|
+
* SessionStorage key helpers
|
|
2189
|
+
*/
|
|
2190
|
+
function openStateKey(widgetId) {
|
|
2191
|
+
return `weld-widget-open-${widgetId}`;
|
|
2192
|
+
}
|
|
2173
2193
|
/**
|
|
2174
2194
|
* SDK initialization status
|
|
2175
2195
|
*/
|
|
@@ -2192,6 +2212,8 @@ class WeldSDK {
|
|
|
2192
2212
|
this.readyResolve = null;
|
|
2193
2213
|
// Subscription IDs for cleanup
|
|
2194
2214
|
this.subscriptionIds = [];
|
|
2215
|
+
// Page tracking cleanup
|
|
2216
|
+
this.pageTrackingCleanup = null;
|
|
2195
2217
|
/**
|
|
2196
2218
|
* Update user attributes (Intercom-style, with rate limiting)
|
|
2197
2219
|
* Limited to 20 calls per page load to prevent abuse
|
|
@@ -2230,6 +2252,13 @@ class WeldSDK {
|
|
|
2230
2252
|
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2231
2253
|
}
|
|
2232
2254
|
if (event.data?.type === 'launcher:clicked') {
|
|
2255
|
+
if (this.status !== SDKStatus.READY) {
|
|
2256
|
+
console.log('[Weld SDK] Launcher clicked but SDK not ready yet — waiting...');
|
|
2257
|
+
this.readyPromise?.then(() => {
|
|
2258
|
+
this.handleLauncherClickMessage(event);
|
|
2259
|
+
});
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2233
2262
|
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2234
2263
|
const state = this.stateCoordinator.getState();
|
|
2235
2264
|
if (state.widget.isOpen) {
|
|
@@ -2242,9 +2271,24 @@ class WeldSDK {
|
|
|
2242
2271
|
}
|
|
2243
2272
|
}
|
|
2244
2273
|
if (event.data?.type === 'weld:close') {
|
|
2274
|
+
if (this.status !== SDKStatus.READY)
|
|
2275
|
+
return;
|
|
2245
2276
|
console.log('[Weld SDK] Widget close requested');
|
|
2246
2277
|
this.close();
|
|
2247
2278
|
}
|
|
2279
|
+
if (event.data?.type === 'weld:unread-count') {
|
|
2280
|
+
const count = event.data.count ?? 0;
|
|
2281
|
+
// Forward to launcher iframe
|
|
2282
|
+
const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
|
|
2283
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2284
|
+
launcherIframe.element.contentWindow.postMessage({
|
|
2285
|
+
type: 'weld:unread-count',
|
|
2286
|
+
count
|
|
2287
|
+
}, '*');
|
|
2288
|
+
}
|
|
2289
|
+
// Update state coordinator for external API consumers
|
|
2290
|
+
this.stateCoordinator.setBadgeCount(count);
|
|
2291
|
+
}
|
|
2248
2292
|
if (event.data?.type === 'weld:image:open' && event.data?.url) {
|
|
2249
2293
|
this.showImageLightbox(event.data.url);
|
|
2250
2294
|
}
|
|
@@ -2506,6 +2550,13 @@ class WeldSDK {
|
|
|
2506
2550
|
this.logger.info('WeldSDK ready');
|
|
2507
2551
|
// Call onReady callback
|
|
2508
2552
|
this.config.onReady?.();
|
|
2553
|
+
// Start tracking page URL changes
|
|
2554
|
+
this.startPageTracking();
|
|
2555
|
+
// Auto-open if widget was previously open (persisted in sessionStorage)
|
|
2556
|
+
if (this.wasOpen()) {
|
|
2557
|
+
this.logger.info('Restoring previously open widget from sessionStorage');
|
|
2558
|
+
this.open();
|
|
2559
|
+
}
|
|
2509
2560
|
}
|
|
2510
2561
|
catch (error) {
|
|
2511
2562
|
this.status = SDKStatus.ERROR;
|
|
@@ -2584,6 +2635,58 @@ class WeldSDK {
|
|
|
2584
2635
|
isReady() {
|
|
2585
2636
|
return this.status === SDKStatus.READY;
|
|
2586
2637
|
}
|
|
2638
|
+
/**
|
|
2639
|
+
* Update callbacks on an existing instance (used by singleton reuse)
|
|
2640
|
+
*/
|
|
2641
|
+
updateCallbacks(config) {
|
|
2642
|
+
if (config.onReady !== undefined)
|
|
2643
|
+
this.config.onReady = config.onReady;
|
|
2644
|
+
if (config.onOpen !== undefined)
|
|
2645
|
+
this.config.onOpen = config.onOpen;
|
|
2646
|
+
if (config.onClose !== undefined)
|
|
2647
|
+
this.config.onClose = config.onClose;
|
|
2648
|
+
if (config.onError !== undefined)
|
|
2649
|
+
this.config.onError = config.onError;
|
|
2650
|
+
if (config.onDestroy !== undefined)
|
|
2651
|
+
this.config.onDestroy = config.onDestroy;
|
|
2652
|
+
if (config.onMinimize !== undefined)
|
|
2653
|
+
this.config.onMinimize = config.onMinimize;
|
|
2654
|
+
if (config.onMaximize !== undefined)
|
|
2655
|
+
this.config.onMaximize = config.onMaximize;
|
|
2656
|
+
}
|
|
2657
|
+
/**
|
|
2658
|
+
* Persist open/closed state to sessionStorage
|
|
2659
|
+
*/
|
|
2660
|
+
persistOpenState(isOpen) {
|
|
2661
|
+
try {
|
|
2662
|
+
sessionStorage.setItem(openStateKey(this.config.widgetId), isOpen ? 'true' : 'false');
|
|
2663
|
+
}
|
|
2664
|
+
catch {
|
|
2665
|
+
// sessionStorage might not be available
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
/**
|
|
2669
|
+
* Clear persisted state from sessionStorage
|
|
2670
|
+
*/
|
|
2671
|
+
clearPersistedState() {
|
|
2672
|
+
try {
|
|
2673
|
+
sessionStorage.removeItem(openStateKey(this.config.widgetId));
|
|
2674
|
+
}
|
|
2675
|
+
catch {
|
|
2676
|
+
// sessionStorage might not be available
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Check if widget was previously open (from sessionStorage)
|
|
2681
|
+
*/
|
|
2682
|
+
wasOpen() {
|
|
2683
|
+
try {
|
|
2684
|
+
return sessionStorage.getItem(openStateKey(this.config.widgetId)) === 'true';
|
|
2685
|
+
}
|
|
2686
|
+
catch {
|
|
2687
|
+
return false;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2587
2690
|
/**
|
|
2588
2691
|
* Open the widget
|
|
2589
2692
|
*/
|
|
@@ -2602,6 +2705,7 @@ class WeldSDK {
|
|
|
2602
2705
|
if (launcherIframe?.element?.contentWindow) {
|
|
2603
2706
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2604
2707
|
}
|
|
2708
|
+
this.persistOpenState(true);
|
|
2605
2709
|
this.config.onOpen?.();
|
|
2606
2710
|
}
|
|
2607
2711
|
/**
|
|
@@ -2622,6 +2726,7 @@ class WeldSDK {
|
|
|
2622
2726
|
if (launcherIframe?.element?.contentWindow) {
|
|
2623
2727
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2624
2728
|
}
|
|
2729
|
+
this.persistOpenState(false);
|
|
2625
2730
|
this.config.onClose?.();
|
|
2626
2731
|
}
|
|
2627
2732
|
/**
|
|
@@ -2810,6 +2915,8 @@ class WeldSDK {
|
|
|
2810
2915
|
});
|
|
2811
2916
|
// Broadcast logout to iframes
|
|
2812
2917
|
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
2918
|
+
// Clear persisted widget state
|
|
2919
|
+
this.clearPersistedState();
|
|
2813
2920
|
// Clear any stored session data
|
|
2814
2921
|
try {
|
|
2815
2922
|
const prefix = 'weld-';
|
|
@@ -2951,6 +3058,63 @@ class WeldSDK {
|
|
|
2951
3058
|
this.logger.setLevel('warn');
|
|
2952
3059
|
this.logger.info('Debug mode disabled');
|
|
2953
3060
|
}
|
|
3061
|
+
/**
|
|
3062
|
+
* Send a page change message to the widget iframe
|
|
3063
|
+
*/
|
|
3064
|
+
sendPageChange(url, title) {
|
|
3065
|
+
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
3066
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
3067
|
+
widgetIframe.element.contentWindow.postMessage({
|
|
3068
|
+
type: 'weld:page:change',
|
|
3069
|
+
url,
|
|
3070
|
+
title,
|
|
3071
|
+
timestamp: Date.now(),
|
|
3072
|
+
}, '*');
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
/**
|
|
3076
|
+
* Start tracking page URL changes (SPA navigations + popstate)
|
|
3077
|
+
*/
|
|
3078
|
+
startPageTracking() {
|
|
3079
|
+
let lastUrl = window.location.href;
|
|
3080
|
+
let debounceTimer = null;
|
|
3081
|
+
const notifyChange = () => {
|
|
3082
|
+
const currentUrl = window.location.href;
|
|
3083
|
+
if (currentUrl !== lastUrl) {
|
|
3084
|
+
lastUrl = currentUrl;
|
|
3085
|
+
this.sendPageChange(currentUrl, document.title);
|
|
3086
|
+
}
|
|
3087
|
+
};
|
|
3088
|
+
const debouncedNotify = () => {
|
|
3089
|
+
if (debounceTimer)
|
|
3090
|
+
clearTimeout(debounceTimer);
|
|
3091
|
+
debounceTimer = setTimeout(notifyChange, 300);
|
|
3092
|
+
};
|
|
3093
|
+
// Send initial page
|
|
3094
|
+
this.sendPageChange(window.location.href, document.title);
|
|
3095
|
+
// Monkey-patch history.pushState and history.replaceState
|
|
3096
|
+
const origPushState = history.pushState.bind(history);
|
|
3097
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
3098
|
+
history.pushState = function (...args) {
|
|
3099
|
+
origPushState(...args);
|
|
3100
|
+
debouncedNotify();
|
|
3101
|
+
};
|
|
3102
|
+
history.replaceState = function (...args) {
|
|
3103
|
+
origReplaceState(...args);
|
|
3104
|
+
debouncedNotify();
|
|
3105
|
+
};
|
|
3106
|
+
// Listen for popstate (browser back/forward)
|
|
3107
|
+
const handlePopstate = () => debouncedNotify();
|
|
3108
|
+
window.addEventListener('popstate', handlePopstate);
|
|
3109
|
+
// Store cleanup
|
|
3110
|
+
this.pageTrackingCleanup = () => {
|
|
3111
|
+
if (debounceTimer)
|
|
3112
|
+
clearTimeout(debounceTimer);
|
|
3113
|
+
window.removeEventListener('popstate', handlePopstate);
|
|
3114
|
+
history.pushState = origPushState;
|
|
3115
|
+
history.replaceState = origReplaceState;
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
2954
3118
|
/**
|
|
2955
3119
|
* Ensure SDK is ready before operation
|
|
2956
3120
|
*/
|
|
@@ -2959,11 +3123,26 @@ class WeldSDK {
|
|
|
2959
3123
|
throw new Error('SDK not ready. Call init() first.');
|
|
2960
3124
|
}
|
|
2961
3125
|
}
|
|
3126
|
+
/**
|
|
3127
|
+
* Detach from the current component lifecycle without destroying the widget.
|
|
3128
|
+
* Use this as a React useEffect cleanup — the widget stays alive across navigations.
|
|
3129
|
+
*/
|
|
3130
|
+
detach() {
|
|
3131
|
+
// No-op: widget stays alive in the singleton registry
|
|
3132
|
+
this.logger.debug('WeldSDK detached (no-op, widget stays alive)');
|
|
3133
|
+
}
|
|
2962
3134
|
/**
|
|
2963
3135
|
* Destroy SDK and cleanup
|
|
2964
3136
|
*/
|
|
2965
3137
|
destroy() {
|
|
2966
3138
|
this.logger.info('Destroying WeldSDK');
|
|
3139
|
+
// Remove from singleton registry
|
|
3140
|
+
sdkRegistry.delete(this.config.widgetId);
|
|
3141
|
+
// Clear persisted state
|
|
3142
|
+
this.clearPersistedState();
|
|
3143
|
+
// Stop page tracking
|
|
3144
|
+
this.pageTrackingCleanup?.();
|
|
3145
|
+
this.pageTrackingCleanup = null;
|
|
2967
3146
|
// Remove event listener using bound handler
|
|
2968
3147
|
window.removeEventListener('message', this.boundHandleLauncherClick);
|
|
2969
3148
|
// Unsubscribe from all message broker subscriptions
|