@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
|
@@ -33,32 +33,6 @@ const DEFAULT_CONFIG = {
|
|
|
33
33
|
closeOnClick: true,
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
|
-
customization: {
|
|
37
|
-
primaryColor: '#000000',
|
|
38
|
-
accentColor: '#3b82f6',
|
|
39
|
-
backgroundColor: '#ffffff',
|
|
40
|
-
textColor: '#111827',
|
|
41
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
42
|
-
fontSize: '14px',
|
|
43
|
-
borderRadius: '12px',
|
|
44
|
-
},
|
|
45
|
-
features: {
|
|
46
|
-
attachments: true,
|
|
47
|
-
reactions: true,
|
|
48
|
-
typing: true,
|
|
49
|
-
readReceipts: true,
|
|
50
|
-
offlineMode: false,
|
|
51
|
-
fileUpload: true,
|
|
52
|
-
imageUpload: true,
|
|
53
|
-
voiceMessages: false,
|
|
54
|
-
videoMessages: false,
|
|
55
|
-
},
|
|
56
|
-
mobile: {
|
|
57
|
-
fullScreen: true,
|
|
58
|
-
scrollLock: true,
|
|
59
|
-
keyboardHandling: 'auto',
|
|
60
|
-
safeAreaInsets: true,
|
|
61
|
-
},
|
|
62
36
|
auth: {
|
|
63
37
|
enabled: true,
|
|
64
38
|
mode: 'anonymous',
|
|
@@ -102,6 +76,7 @@ function resolveConfig(config) {
|
|
|
102
76
|
validateConfig(config);
|
|
103
77
|
return {
|
|
104
78
|
widgetId: config.widgetId,
|
|
79
|
+
testMode: config.testMode,
|
|
105
80
|
api: {
|
|
106
81
|
...DEFAULT_CONFIG.api,
|
|
107
82
|
widgetId: config.widgetId,
|
|
@@ -131,18 +106,6 @@ function resolveConfig(config) {
|
|
|
131
106
|
...config.iframes?.backdrop,
|
|
132
107
|
},
|
|
133
108
|
},
|
|
134
|
-
customization: {
|
|
135
|
-
...DEFAULT_CONFIG.customization,
|
|
136
|
-
...config.customization,
|
|
137
|
-
},
|
|
138
|
-
features: {
|
|
139
|
-
...DEFAULT_CONFIG.features,
|
|
140
|
-
...config.features,
|
|
141
|
-
},
|
|
142
|
-
mobile: {
|
|
143
|
-
...DEFAULT_CONFIG.mobile,
|
|
144
|
-
...config.mobile,
|
|
145
|
-
},
|
|
146
109
|
auth: {
|
|
147
110
|
...DEFAULT_CONFIG.auth,
|
|
148
111
|
...config.auth,
|
|
@@ -385,6 +348,8 @@ class IframeManager {
|
|
|
385
348
|
this.modalContainer = null;
|
|
386
349
|
this.styleElement = null;
|
|
387
350
|
this.messageBroker = null;
|
|
351
|
+
// Guard flag to prevent double-binding event listeners
|
|
352
|
+
this.eventListenersBound = false;
|
|
388
353
|
this.config = config;
|
|
389
354
|
this.logger = new Logger(config.logging);
|
|
390
355
|
this.deviceInfo = detectDevice();
|
|
@@ -424,18 +389,27 @@ class IframeManager {
|
|
|
424
389
|
}
|
|
425
390
|
/**
|
|
426
391
|
* Create root container structure
|
|
392
|
+
* Reuses existing container if it has the same widgetId (singleton behavior)
|
|
427
393
|
*/
|
|
428
394
|
createRootContainer() {
|
|
429
|
-
|
|
430
|
-
let existingContainer = document.getElementById('weld-container');
|
|
395
|
+
const existingContainer = document.getElementById('weld-container');
|
|
431
396
|
if (existingContainer) {
|
|
432
|
-
|
|
397
|
+
// Reuse if same widgetId
|
|
398
|
+
if (existingContainer.getAttribute('data-widget-id') === this.config.widgetId) {
|
|
399
|
+
this.logger.debug('Reusing existing root container');
|
|
400
|
+
this.rootContainer = existingContainer;
|
|
401
|
+
this.appContainer = existingContainer.querySelector('.weld-app');
|
|
402
|
+
this.modalContainer = document.getElementById('weld-modal-container');
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
this.logger.warn('Weld container already exists with different widgetId, removing old instance');
|
|
433
406
|
existingContainer.remove();
|
|
434
407
|
}
|
|
435
408
|
// Create root container
|
|
436
409
|
this.rootContainer = document.createElement('div');
|
|
437
410
|
this.rootContainer.id = 'weld-container';
|
|
438
411
|
this.rootContainer.className = 'weld-namespace';
|
|
412
|
+
this.rootContainer.setAttribute('data-widget-id', this.config.widgetId);
|
|
439
413
|
// Create app container
|
|
440
414
|
this.appContainer = document.createElement('div');
|
|
441
415
|
this.appContainer.className = 'weld-app';
|
|
@@ -470,17 +444,7 @@ class IframeManager {
|
|
|
470
444
|
* Generate CSS for containers
|
|
471
445
|
*/
|
|
472
446
|
generateCSS() {
|
|
473
|
-
const { customization } = this.config;
|
|
474
447
|
return `
|
|
475
|
-
/* Weld Container */
|
|
476
|
-
#weld-container {
|
|
477
|
-
--weld-color-primary: ${customization.primaryColor};
|
|
478
|
-
--weld-color-accent: ${customization.accentColor};
|
|
479
|
-
--weld-font-family: ${customization.fontFamily};
|
|
480
|
-
--weld-font-size-base: ${customization.fontSize};
|
|
481
|
-
--weld-radius-xl: ${customization.borderRadius};
|
|
482
|
-
}
|
|
483
|
-
|
|
484
448
|
/* Import main stylesheet */
|
|
485
449
|
@import url('/styles/index.css');
|
|
486
450
|
|
|
@@ -504,6 +468,11 @@ class IframeManager {
|
|
|
504
468
|
* Create launcher iframe
|
|
505
469
|
*/
|
|
506
470
|
async createLauncherIframe() {
|
|
471
|
+
// Guard: skip if launcher iframe already exists
|
|
472
|
+
if (this.iframes.has(IframeType.LAUNCHER)) {
|
|
473
|
+
this.logger.debug('Launcher iframe already exists, skipping creation');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
507
476
|
const { iframes } = this.config;
|
|
508
477
|
const { launcher } = iframes;
|
|
509
478
|
// Create container
|
|
@@ -531,9 +500,12 @@ class IframeManager {
|
|
|
531
500
|
width: 100%;
|
|
532
501
|
height: 100%;
|
|
533
502
|
border: none;
|
|
534
|
-
background:
|
|
503
|
+
background: none;
|
|
504
|
+
color-scheme: none;
|
|
535
505
|
display: block;
|
|
536
506
|
pointer-events: auto;
|
|
507
|
+
border-radius: 50%;
|
|
508
|
+
filter: drop-shadow(rgba(9, 14, 21, 0.54) 0px 1px 6px) drop-shadow(rgba(9, 14, 21, 0.9) 0px 2px 32px);
|
|
537
509
|
`;
|
|
538
510
|
iframe.setAttribute('allow', 'clipboard-write');
|
|
539
511
|
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
|
|
@@ -549,20 +521,36 @@ class IframeManager {
|
|
|
549
521
|
createdAt: Date.now(),
|
|
550
522
|
});
|
|
551
523
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
524
|
+
let launcherRetried = false;
|
|
552
525
|
iframe.onload = () => {
|
|
553
526
|
const metadata = this.iframes.get(IframeType.LAUNCHER);
|
|
554
527
|
if (metadata) {
|
|
555
528
|
this.logger.debug('Launcher iframe DOM loaded');
|
|
556
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
557
529
|
this.messageBroker?.setIframeDomLoaded(IframeType.LAUNCHER);
|
|
558
530
|
}
|
|
559
531
|
};
|
|
532
|
+
iframe.onerror = () => {
|
|
533
|
+
this.logger.error('Launcher iframe failed to load');
|
|
534
|
+
if (!launcherRetried) {
|
|
535
|
+
launcherRetried = true;
|
|
536
|
+
this.logger.info('Retrying launcher iframe load...');
|
|
537
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(launcher.url); }, 3000);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
this.config.onError?.(new Error('Failed to load widget launcher'));
|
|
541
|
+
}
|
|
542
|
+
};
|
|
560
543
|
this.logger.debug('Launcher iframe created');
|
|
561
544
|
}
|
|
562
545
|
/**
|
|
563
546
|
* Create widget iframe
|
|
564
547
|
*/
|
|
565
548
|
async createWidgetIframe() {
|
|
549
|
+
// Guard: skip if widget iframe already exists
|
|
550
|
+
if (this.iframes.has(IframeType.WIDGET)) {
|
|
551
|
+
this.logger.debug('Widget iframe already exists, skipping creation');
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
566
554
|
const { iframes } = this.config;
|
|
567
555
|
const { widget } = iframes;
|
|
568
556
|
// Create container
|
|
@@ -643,14 +631,25 @@ class IframeManager {
|
|
|
643
631
|
createdAt: Date.now(),
|
|
644
632
|
});
|
|
645
633
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
634
|
+
let widgetRetried = false;
|
|
646
635
|
iframe.onload = () => {
|
|
647
636
|
const metadata = this.iframes.get(IframeType.WIDGET);
|
|
648
637
|
if (metadata) {
|
|
649
638
|
this.logger.debug('Widget iframe DOM loaded');
|
|
650
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
651
639
|
this.messageBroker?.setIframeDomLoaded(IframeType.WIDGET);
|
|
652
640
|
}
|
|
653
641
|
};
|
|
642
|
+
iframe.onerror = () => {
|
|
643
|
+
this.logger.error('Widget iframe failed to load');
|
|
644
|
+
if (!widgetRetried) {
|
|
645
|
+
widgetRetried = true;
|
|
646
|
+
this.logger.info('Retrying widget iframe load...');
|
|
647
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(widget.url); }, 3000);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
this.config.onError?.(new Error('Failed to load widget'));
|
|
651
|
+
}
|
|
652
|
+
};
|
|
654
653
|
this.logger.debug('Widget iframe created');
|
|
655
654
|
}
|
|
656
655
|
/**
|
|
@@ -671,12 +670,21 @@ class IframeManager {
|
|
|
671
670
|
url.searchParams.set('device', this.deviceInfo.type);
|
|
672
671
|
url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
|
|
673
672
|
url.searchParams.set('parentOrigin', window.location.origin);
|
|
673
|
+
if (this.config.testMode) {
|
|
674
|
+
url.searchParams.set('testMode', 'true');
|
|
675
|
+
}
|
|
674
676
|
return url.toString();
|
|
675
677
|
}
|
|
676
678
|
/**
|
|
677
679
|
* Setup event listeners
|
|
678
680
|
*/
|
|
679
681
|
setupEventListeners() {
|
|
682
|
+
// Guard: prevent double-binding
|
|
683
|
+
if (this.eventListenersBound) {
|
|
684
|
+
this.logger.debug('Event listeners already bound, skipping');
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
this.eventListenersBound = true;
|
|
680
688
|
// Window resize - use bound handler for proper cleanup
|
|
681
689
|
window.addEventListener('resize', this.boundHandleResize);
|
|
682
690
|
// Orientation change - use bound handler for proper cleanup
|
|
@@ -779,7 +787,7 @@ class IframeManager {
|
|
|
779
787
|
iframe.container.style.transform = 'scale(1) translateY(0)';
|
|
780
788
|
}
|
|
781
789
|
// Handle mobile scroll lock
|
|
782
|
-
if (this.deviceInfo.isMobile && type === IframeType.WIDGET
|
|
790
|
+
if (this.deviceInfo.isMobile && type === IframeType.WIDGET) {
|
|
783
791
|
document.body.classList.add('weld-mobile-open');
|
|
784
792
|
}
|
|
785
793
|
// Hide launcher on mobile when widget is open (full-screen mode)
|
|
@@ -878,6 +886,8 @@ class IframeManager {
|
|
|
878
886
|
this.iframes.clear();
|
|
879
887
|
// Clear messageBroker reference
|
|
880
888
|
this.messageBroker = null;
|
|
889
|
+
// Reset guard flag
|
|
890
|
+
this.eventListenersBound = false;
|
|
881
891
|
this.logger.info('IframeManager destroyed');
|
|
882
892
|
}
|
|
883
893
|
}
|
|
@@ -945,6 +955,8 @@ var MessageType;
|
|
|
945
955
|
// Events
|
|
946
956
|
MessageType["EVENT_TRACK"] = "weld:event:track";
|
|
947
957
|
MessageType["ERROR_REPORT"] = "weld:error:report";
|
|
958
|
+
// Page tracking
|
|
959
|
+
MessageType["PAGE_CHANGE"] = "weld:page:change";
|
|
948
960
|
// API responses
|
|
949
961
|
MessageType["API_SUCCESS"] = "weld:api:success";
|
|
950
962
|
MessageType["API_ERROR"] = "weld:api:error";
|
|
@@ -1484,8 +1496,6 @@ class MessageBroker {
|
|
|
1484
1496
|
iframeType,
|
|
1485
1497
|
config: {
|
|
1486
1498
|
api: this.config.api,
|
|
1487
|
-
customization: this.config.customization,
|
|
1488
|
-
features: this.config.features,
|
|
1489
1499
|
},
|
|
1490
1500
|
};
|
|
1491
1501
|
const message = createMessage('weld:init', MessageOrigin.PARENT, initPayload);
|
|
@@ -2158,7 +2168,7 @@ class StateCoordinator {
|
|
|
2158
2168
|
}
|
|
2159
2169
|
}
|
|
2160
2170
|
|
|
2161
|
-
var version = "1.0.
|
|
2171
|
+
var version = "1.0.17";
|
|
2162
2172
|
var packageJson = {
|
|
2163
2173
|
version: version};
|
|
2164
2174
|
|
|
@@ -2166,6 +2176,16 @@ var packageJson = {
|
|
|
2166
2176
|
* Weld SDK - Main Entry Point
|
|
2167
2177
|
* Public API for the Weld helpdesk widget
|
|
2168
2178
|
*/
|
|
2179
|
+
/**
|
|
2180
|
+
* Module-level singleton registry keyed by widgetId
|
|
2181
|
+
*/
|
|
2182
|
+
const sdkRegistry = new Map();
|
|
2183
|
+
/**
|
|
2184
|
+
* SessionStorage key helpers
|
|
2185
|
+
*/
|
|
2186
|
+
function openStateKey(widgetId) {
|
|
2187
|
+
return `weld-widget-open-${widgetId}`;
|
|
2188
|
+
}
|
|
2169
2189
|
/**
|
|
2170
2190
|
* SDK initialization status
|
|
2171
2191
|
*/
|
|
@@ -2188,6 +2208,8 @@ class WeldSDK {
|
|
|
2188
2208
|
this.readyResolve = null;
|
|
2189
2209
|
// Subscription IDs for cleanup
|
|
2190
2210
|
this.subscriptionIds = [];
|
|
2211
|
+
// Page tracking cleanup
|
|
2212
|
+
this.pageTrackingCleanup = null;
|
|
2191
2213
|
/**
|
|
2192
2214
|
* Update user attributes (Intercom-style, with rate limiting)
|
|
2193
2215
|
* Limited to 20 calls per page load to prevent abuse
|
|
@@ -2226,6 +2248,13 @@ class WeldSDK {
|
|
|
2226
2248
|
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2227
2249
|
}
|
|
2228
2250
|
if (event.data?.type === 'launcher:clicked') {
|
|
2251
|
+
if (this.status !== SDKStatus.READY) {
|
|
2252
|
+
console.log('[Weld SDK] Launcher clicked but SDK not ready yet — waiting...');
|
|
2253
|
+
this.readyPromise?.then(() => {
|
|
2254
|
+
this.handleLauncherClickMessage(event);
|
|
2255
|
+
});
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2229
2258
|
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2230
2259
|
const state = this.stateCoordinator.getState();
|
|
2231
2260
|
if (state.widget.isOpen) {
|
|
@@ -2238,9 +2267,24 @@ class WeldSDK {
|
|
|
2238
2267
|
}
|
|
2239
2268
|
}
|
|
2240
2269
|
if (event.data?.type === 'weld:close') {
|
|
2270
|
+
if (this.status !== SDKStatus.READY)
|
|
2271
|
+
return;
|
|
2241
2272
|
console.log('[Weld SDK] Widget close requested');
|
|
2242
2273
|
this.close();
|
|
2243
2274
|
}
|
|
2275
|
+
if (event.data?.type === 'weld:unread-count') {
|
|
2276
|
+
const count = event.data.count ?? 0;
|
|
2277
|
+
// Forward to launcher iframe
|
|
2278
|
+
const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
|
|
2279
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2280
|
+
launcherIframe.element.contentWindow.postMessage({
|
|
2281
|
+
type: 'weld:unread-count',
|
|
2282
|
+
count
|
|
2283
|
+
}, '*');
|
|
2284
|
+
}
|
|
2285
|
+
// Update state coordinator for external API consumers
|
|
2286
|
+
this.stateCoordinator.setBadgeCount(count);
|
|
2287
|
+
}
|
|
2244
2288
|
if (event.data?.type === 'weld:image:open' && event.data?.url) {
|
|
2245
2289
|
this.showImageLightbox(event.data.url);
|
|
2246
2290
|
}
|
|
@@ -2502,6 +2546,13 @@ class WeldSDK {
|
|
|
2502
2546
|
this.logger.info('WeldSDK ready');
|
|
2503
2547
|
// Call onReady callback
|
|
2504
2548
|
this.config.onReady?.();
|
|
2549
|
+
// Start tracking page URL changes
|
|
2550
|
+
this.startPageTracking();
|
|
2551
|
+
// Auto-open if widget was previously open (persisted in sessionStorage)
|
|
2552
|
+
if (this.wasOpen()) {
|
|
2553
|
+
this.logger.info('Restoring previously open widget from sessionStorage');
|
|
2554
|
+
this.open();
|
|
2555
|
+
}
|
|
2505
2556
|
}
|
|
2506
2557
|
catch (error) {
|
|
2507
2558
|
this.status = SDKStatus.ERROR;
|
|
@@ -2580,6 +2631,58 @@ class WeldSDK {
|
|
|
2580
2631
|
isReady() {
|
|
2581
2632
|
return this.status === SDKStatus.READY;
|
|
2582
2633
|
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Update callbacks on an existing instance (used by singleton reuse)
|
|
2636
|
+
*/
|
|
2637
|
+
updateCallbacks(config) {
|
|
2638
|
+
if (config.onReady !== undefined)
|
|
2639
|
+
this.config.onReady = config.onReady;
|
|
2640
|
+
if (config.onOpen !== undefined)
|
|
2641
|
+
this.config.onOpen = config.onOpen;
|
|
2642
|
+
if (config.onClose !== undefined)
|
|
2643
|
+
this.config.onClose = config.onClose;
|
|
2644
|
+
if (config.onError !== undefined)
|
|
2645
|
+
this.config.onError = config.onError;
|
|
2646
|
+
if (config.onDestroy !== undefined)
|
|
2647
|
+
this.config.onDestroy = config.onDestroy;
|
|
2648
|
+
if (config.onMinimize !== undefined)
|
|
2649
|
+
this.config.onMinimize = config.onMinimize;
|
|
2650
|
+
if (config.onMaximize !== undefined)
|
|
2651
|
+
this.config.onMaximize = config.onMaximize;
|
|
2652
|
+
}
|
|
2653
|
+
/**
|
|
2654
|
+
* Persist open/closed state to sessionStorage
|
|
2655
|
+
*/
|
|
2656
|
+
persistOpenState(isOpen) {
|
|
2657
|
+
try {
|
|
2658
|
+
sessionStorage.setItem(openStateKey(this.config.widgetId), isOpen ? 'true' : 'false');
|
|
2659
|
+
}
|
|
2660
|
+
catch {
|
|
2661
|
+
// sessionStorage might not be available
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Clear persisted state from sessionStorage
|
|
2666
|
+
*/
|
|
2667
|
+
clearPersistedState() {
|
|
2668
|
+
try {
|
|
2669
|
+
sessionStorage.removeItem(openStateKey(this.config.widgetId));
|
|
2670
|
+
}
|
|
2671
|
+
catch {
|
|
2672
|
+
// sessionStorage might not be available
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* Check if widget was previously open (from sessionStorage)
|
|
2677
|
+
*/
|
|
2678
|
+
wasOpen() {
|
|
2679
|
+
try {
|
|
2680
|
+
return sessionStorage.getItem(openStateKey(this.config.widgetId)) === 'true';
|
|
2681
|
+
}
|
|
2682
|
+
catch {
|
|
2683
|
+
return false;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2583
2686
|
/**
|
|
2584
2687
|
* Open the widget
|
|
2585
2688
|
*/
|
|
@@ -2598,6 +2701,7 @@ class WeldSDK {
|
|
|
2598
2701
|
if (launcherIframe?.element?.contentWindow) {
|
|
2599
2702
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2600
2703
|
}
|
|
2704
|
+
this.persistOpenState(true);
|
|
2601
2705
|
this.config.onOpen?.();
|
|
2602
2706
|
}
|
|
2603
2707
|
/**
|
|
@@ -2618,6 +2722,7 @@ class WeldSDK {
|
|
|
2618
2722
|
if (launcherIframe?.element?.contentWindow) {
|
|
2619
2723
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2620
2724
|
}
|
|
2725
|
+
this.persistOpenState(false);
|
|
2621
2726
|
this.config.onClose?.();
|
|
2622
2727
|
}
|
|
2623
2728
|
/**
|
|
@@ -2806,6 +2911,8 @@ class WeldSDK {
|
|
|
2806
2911
|
});
|
|
2807
2912
|
// Broadcast logout to iframes
|
|
2808
2913
|
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
2914
|
+
// Clear persisted widget state
|
|
2915
|
+
this.clearPersistedState();
|
|
2809
2916
|
// Clear any stored session data
|
|
2810
2917
|
try {
|
|
2811
2918
|
const prefix = 'weld-';
|
|
@@ -2947,6 +3054,63 @@ class WeldSDK {
|
|
|
2947
3054
|
this.logger.setLevel('warn');
|
|
2948
3055
|
this.logger.info('Debug mode disabled');
|
|
2949
3056
|
}
|
|
3057
|
+
/**
|
|
3058
|
+
* Send a page change message to the widget iframe
|
|
3059
|
+
*/
|
|
3060
|
+
sendPageChange(url, title) {
|
|
3061
|
+
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
3062
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
3063
|
+
widgetIframe.element.contentWindow.postMessage({
|
|
3064
|
+
type: 'weld:page:change',
|
|
3065
|
+
url,
|
|
3066
|
+
title,
|
|
3067
|
+
timestamp: Date.now(),
|
|
3068
|
+
}, '*');
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Start tracking page URL changes (SPA navigations + popstate)
|
|
3073
|
+
*/
|
|
3074
|
+
startPageTracking() {
|
|
3075
|
+
let lastUrl = window.location.href;
|
|
3076
|
+
let debounceTimer = null;
|
|
3077
|
+
const notifyChange = () => {
|
|
3078
|
+
const currentUrl = window.location.href;
|
|
3079
|
+
if (currentUrl !== lastUrl) {
|
|
3080
|
+
lastUrl = currentUrl;
|
|
3081
|
+
this.sendPageChange(currentUrl, document.title);
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
const debouncedNotify = () => {
|
|
3085
|
+
if (debounceTimer)
|
|
3086
|
+
clearTimeout(debounceTimer);
|
|
3087
|
+
debounceTimer = setTimeout(notifyChange, 300);
|
|
3088
|
+
};
|
|
3089
|
+
// Send initial page
|
|
3090
|
+
this.sendPageChange(window.location.href, document.title);
|
|
3091
|
+
// Monkey-patch history.pushState and history.replaceState
|
|
3092
|
+
const origPushState = history.pushState.bind(history);
|
|
3093
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
3094
|
+
history.pushState = function (...args) {
|
|
3095
|
+
origPushState(...args);
|
|
3096
|
+
debouncedNotify();
|
|
3097
|
+
};
|
|
3098
|
+
history.replaceState = function (...args) {
|
|
3099
|
+
origReplaceState(...args);
|
|
3100
|
+
debouncedNotify();
|
|
3101
|
+
};
|
|
3102
|
+
// Listen for popstate (browser back/forward)
|
|
3103
|
+
const handlePopstate = () => debouncedNotify();
|
|
3104
|
+
window.addEventListener('popstate', handlePopstate);
|
|
3105
|
+
// Store cleanup
|
|
3106
|
+
this.pageTrackingCleanup = () => {
|
|
3107
|
+
if (debounceTimer)
|
|
3108
|
+
clearTimeout(debounceTimer);
|
|
3109
|
+
window.removeEventListener('popstate', handlePopstate);
|
|
3110
|
+
history.pushState = origPushState;
|
|
3111
|
+
history.replaceState = origReplaceState;
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
2950
3114
|
/**
|
|
2951
3115
|
* Ensure SDK is ready before operation
|
|
2952
3116
|
*/
|
|
@@ -2955,11 +3119,26 @@ class WeldSDK {
|
|
|
2955
3119
|
throw new Error('SDK not ready. Call init() first.');
|
|
2956
3120
|
}
|
|
2957
3121
|
}
|
|
3122
|
+
/**
|
|
3123
|
+
* Detach from the current component lifecycle without destroying the widget.
|
|
3124
|
+
* Use this as a React useEffect cleanup — the widget stays alive across navigations.
|
|
3125
|
+
*/
|
|
3126
|
+
detach() {
|
|
3127
|
+
// No-op: widget stays alive in the singleton registry
|
|
3128
|
+
this.logger.debug('WeldSDK detached (no-op, widget stays alive)');
|
|
3129
|
+
}
|
|
2958
3130
|
/**
|
|
2959
3131
|
* Destroy SDK and cleanup
|
|
2960
3132
|
*/
|
|
2961
3133
|
destroy() {
|
|
2962
3134
|
this.logger.info('Destroying WeldSDK');
|
|
3135
|
+
// Remove from singleton registry
|
|
3136
|
+
sdkRegistry.delete(this.config.widgetId);
|
|
3137
|
+
// Clear persisted state
|
|
3138
|
+
this.clearPersistedState();
|
|
3139
|
+
// Stop page tracking
|
|
3140
|
+
this.pageTrackingCleanup?.();
|
|
3141
|
+
this.pageTrackingCleanup = null;
|
|
2963
3142
|
// Remove event listener using bound handler
|
|
2964
3143
|
window.removeEventListener('message', this.boundHandleLauncherClick);
|
|
2965
3144
|
// Unsubscribe from all message broker subscriptions
|