@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.d.ts
CHANGED
|
@@ -29,41 +29,6 @@ interface PositionConfig {
|
|
|
29
29
|
left?: string;
|
|
30
30
|
top?: string;
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Customization configuration
|
|
34
|
-
*/
|
|
35
|
-
interface CustomizationConfig {
|
|
36
|
-
primaryColor?: string;
|
|
37
|
-
accentColor?: string;
|
|
38
|
-
backgroundColor?: string;
|
|
39
|
-
textColor?: string;
|
|
40
|
-
fontFamily?: string;
|
|
41
|
-
fontSize?: string;
|
|
42
|
-
borderRadius?: string;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Feature flags
|
|
46
|
-
*/
|
|
47
|
-
interface FeatureConfig {
|
|
48
|
-
attachments?: boolean;
|
|
49
|
-
reactions?: boolean;
|
|
50
|
-
typing?: boolean;
|
|
51
|
-
readReceipts?: boolean;
|
|
52
|
-
offlineMode?: boolean;
|
|
53
|
-
fileUpload?: boolean;
|
|
54
|
-
imageUpload?: boolean;
|
|
55
|
-
voiceMessages?: boolean;
|
|
56
|
-
videoMessages?: boolean;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Mobile configuration
|
|
60
|
-
*/
|
|
61
|
-
interface MobileConfig {
|
|
62
|
-
fullScreen?: boolean;
|
|
63
|
-
scrollLock?: boolean;
|
|
64
|
-
keyboardHandling?: 'auto' | 'manual';
|
|
65
|
-
safeAreaInsets?: boolean;
|
|
66
|
-
}
|
|
67
32
|
/**
|
|
68
33
|
* Iframe configuration
|
|
69
34
|
*/
|
|
@@ -156,15 +121,13 @@ interface SecurityConfig {
|
|
|
156
121
|
*/
|
|
157
122
|
interface WeldConfig {
|
|
158
123
|
widgetId: string;
|
|
124
|
+
testMode?: boolean;
|
|
159
125
|
api?: Partial<ApiConfig>;
|
|
160
126
|
iframes?: Partial<IframeConfig>;
|
|
161
127
|
position?: {
|
|
162
128
|
launcher?: PositionConfig;
|
|
163
129
|
widget?: PositionConfig;
|
|
164
130
|
};
|
|
165
|
-
customization?: CustomizationConfig;
|
|
166
|
-
features?: FeatureConfig;
|
|
167
|
-
mobile?: MobileConfig;
|
|
168
131
|
auth?: AuthConfig;
|
|
169
132
|
locale?: LocaleConfig;
|
|
170
133
|
logging?: LogConfig;
|
|
@@ -184,11 +147,9 @@ interface WeldConfig {
|
|
|
184
147
|
*/
|
|
185
148
|
interface ResolvedConfig {
|
|
186
149
|
widgetId: string;
|
|
150
|
+
testMode?: boolean;
|
|
187
151
|
api: ApiConfig;
|
|
188
152
|
iframes: IframeConfig;
|
|
189
|
-
customization: CustomizationConfig;
|
|
190
|
-
features: FeatureConfig;
|
|
191
|
-
mobile: MobileConfig;
|
|
192
153
|
auth: AuthConfig;
|
|
193
154
|
locale: LocaleConfig;
|
|
194
155
|
logging: LogConfig;
|
|
@@ -458,6 +419,7 @@ declare class WeldSDK {
|
|
|
458
419
|
private readyResolve;
|
|
459
420
|
private boundHandleLauncherClick;
|
|
460
421
|
private subscriptionIds;
|
|
422
|
+
private pageTrackingCleanup;
|
|
461
423
|
constructor(config: WeldConfig);
|
|
462
424
|
/**
|
|
463
425
|
* Handle launcher click messages from iframe
|
|
@@ -491,6 +453,22 @@ declare class WeldSDK {
|
|
|
491
453
|
* Check if SDK is ready
|
|
492
454
|
*/
|
|
493
455
|
isReady(): boolean;
|
|
456
|
+
/**
|
|
457
|
+
* Update callbacks on an existing instance (used by singleton reuse)
|
|
458
|
+
*/
|
|
459
|
+
updateCallbacks(config: Partial<WeldConfig>): void;
|
|
460
|
+
/**
|
|
461
|
+
* Persist open/closed state to sessionStorage
|
|
462
|
+
*/
|
|
463
|
+
private persistOpenState;
|
|
464
|
+
/**
|
|
465
|
+
* Clear persisted state from sessionStorage
|
|
466
|
+
*/
|
|
467
|
+
private clearPersistedState;
|
|
468
|
+
/**
|
|
469
|
+
* Check if widget was previously open (from sessionStorage)
|
|
470
|
+
*/
|
|
471
|
+
private wasOpen;
|
|
494
472
|
/**
|
|
495
473
|
* Open the widget
|
|
496
474
|
*/
|
|
@@ -646,10 +624,23 @@ declare class WeldSDK {
|
|
|
646
624
|
* Disable debug mode
|
|
647
625
|
*/
|
|
648
626
|
disableDebug(): void;
|
|
627
|
+
/**
|
|
628
|
+
* Send a page change message to the widget iframe
|
|
629
|
+
*/
|
|
630
|
+
private sendPageChange;
|
|
631
|
+
/**
|
|
632
|
+
* Start tracking page URL changes (SPA navigations + popstate)
|
|
633
|
+
*/
|
|
634
|
+
private startPageTracking;
|
|
649
635
|
/**
|
|
650
636
|
* Ensure SDK is ready before operation
|
|
651
637
|
*/
|
|
652
638
|
private ensureReady;
|
|
639
|
+
/**
|
|
640
|
+
* Detach from the current component lifecycle without destroying the widget.
|
|
641
|
+
* Use this as a React useEffect cleanup — the widget stays alive across navigations.
|
|
642
|
+
*/
|
|
643
|
+
detach(): void;
|
|
653
644
|
/**
|
|
654
645
|
* Destroy SDK and cleanup
|
|
655
646
|
*/
|
package/dist/angular.esm.js
CHANGED
|
@@ -93,32 +93,6 @@ const DEFAULT_CONFIG = {
|
|
|
93
93
|
closeOnClick: true,
|
|
94
94
|
},
|
|
95
95
|
},
|
|
96
|
-
customization: {
|
|
97
|
-
primaryColor: '#000000',
|
|
98
|
-
accentColor: '#3b82f6',
|
|
99
|
-
backgroundColor: '#ffffff',
|
|
100
|
-
textColor: '#111827',
|
|
101
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
102
|
-
fontSize: '14px',
|
|
103
|
-
borderRadius: '12px',
|
|
104
|
-
},
|
|
105
|
-
features: {
|
|
106
|
-
attachments: true,
|
|
107
|
-
reactions: true,
|
|
108
|
-
typing: true,
|
|
109
|
-
readReceipts: true,
|
|
110
|
-
offlineMode: false,
|
|
111
|
-
fileUpload: true,
|
|
112
|
-
imageUpload: true,
|
|
113
|
-
voiceMessages: false,
|
|
114
|
-
videoMessages: false,
|
|
115
|
-
},
|
|
116
|
-
mobile: {
|
|
117
|
-
fullScreen: true,
|
|
118
|
-
scrollLock: true,
|
|
119
|
-
keyboardHandling: 'auto',
|
|
120
|
-
safeAreaInsets: true,
|
|
121
|
-
},
|
|
122
96
|
auth: {
|
|
123
97
|
enabled: true,
|
|
124
98
|
mode: 'anonymous',
|
|
@@ -162,6 +136,7 @@ function resolveConfig(config) {
|
|
|
162
136
|
validateConfig(config);
|
|
163
137
|
return {
|
|
164
138
|
widgetId: config.widgetId,
|
|
139
|
+
testMode: config.testMode,
|
|
165
140
|
api: {
|
|
166
141
|
...DEFAULT_CONFIG.api,
|
|
167
142
|
widgetId: config.widgetId,
|
|
@@ -191,18 +166,6 @@ function resolveConfig(config) {
|
|
|
191
166
|
...config.iframes?.backdrop,
|
|
192
167
|
},
|
|
193
168
|
},
|
|
194
|
-
customization: {
|
|
195
|
-
...DEFAULT_CONFIG.customization,
|
|
196
|
-
...config.customization,
|
|
197
|
-
},
|
|
198
|
-
features: {
|
|
199
|
-
...DEFAULT_CONFIG.features,
|
|
200
|
-
...config.features,
|
|
201
|
-
},
|
|
202
|
-
mobile: {
|
|
203
|
-
...DEFAULT_CONFIG.mobile,
|
|
204
|
-
...config.mobile,
|
|
205
|
-
},
|
|
206
169
|
auth: {
|
|
207
170
|
...DEFAULT_CONFIG.auth,
|
|
208
171
|
...config.auth,
|
|
@@ -445,6 +408,8 @@ class IframeManager {
|
|
|
445
408
|
this.modalContainer = null;
|
|
446
409
|
this.styleElement = null;
|
|
447
410
|
this.messageBroker = null;
|
|
411
|
+
// Guard flag to prevent double-binding event listeners
|
|
412
|
+
this.eventListenersBound = false;
|
|
448
413
|
this.config = config;
|
|
449
414
|
this.logger = new Logger(config.logging);
|
|
450
415
|
this.deviceInfo = detectDevice();
|
|
@@ -484,18 +449,27 @@ class IframeManager {
|
|
|
484
449
|
}
|
|
485
450
|
/**
|
|
486
451
|
* Create root container structure
|
|
452
|
+
* Reuses existing container if it has the same widgetId (singleton behavior)
|
|
487
453
|
*/
|
|
488
454
|
createRootContainer() {
|
|
489
|
-
|
|
490
|
-
let existingContainer = document.getElementById('weld-container');
|
|
455
|
+
const existingContainer = document.getElementById('weld-container');
|
|
491
456
|
if (existingContainer) {
|
|
492
|
-
|
|
457
|
+
// Reuse if same widgetId
|
|
458
|
+
if (existingContainer.getAttribute('data-widget-id') === this.config.widgetId) {
|
|
459
|
+
this.logger.debug('Reusing existing root container');
|
|
460
|
+
this.rootContainer = existingContainer;
|
|
461
|
+
this.appContainer = existingContainer.querySelector('.weld-app');
|
|
462
|
+
this.modalContainer = document.getElementById('weld-modal-container');
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
this.logger.warn('Weld container already exists with different widgetId, removing old instance');
|
|
493
466
|
existingContainer.remove();
|
|
494
467
|
}
|
|
495
468
|
// Create root container
|
|
496
469
|
this.rootContainer = document.createElement('div');
|
|
497
470
|
this.rootContainer.id = 'weld-container';
|
|
498
471
|
this.rootContainer.className = 'weld-namespace';
|
|
472
|
+
this.rootContainer.setAttribute('data-widget-id', this.config.widgetId);
|
|
499
473
|
// Create app container
|
|
500
474
|
this.appContainer = document.createElement('div');
|
|
501
475
|
this.appContainer.className = 'weld-app';
|
|
@@ -530,17 +504,7 @@ class IframeManager {
|
|
|
530
504
|
* Generate CSS for containers
|
|
531
505
|
*/
|
|
532
506
|
generateCSS() {
|
|
533
|
-
const { customization } = this.config;
|
|
534
507
|
return `
|
|
535
|
-
/* Weld Container */
|
|
536
|
-
#weld-container {
|
|
537
|
-
--weld-color-primary: ${customization.primaryColor};
|
|
538
|
-
--weld-color-accent: ${customization.accentColor};
|
|
539
|
-
--weld-font-family: ${customization.fontFamily};
|
|
540
|
-
--weld-font-size-base: ${customization.fontSize};
|
|
541
|
-
--weld-radius-xl: ${customization.borderRadius};
|
|
542
|
-
}
|
|
543
|
-
|
|
544
508
|
/* Import main stylesheet */
|
|
545
509
|
@import url('/styles/index.css');
|
|
546
510
|
|
|
@@ -564,6 +528,11 @@ class IframeManager {
|
|
|
564
528
|
* Create launcher iframe
|
|
565
529
|
*/
|
|
566
530
|
async createLauncherIframe() {
|
|
531
|
+
// Guard: skip if launcher iframe already exists
|
|
532
|
+
if (this.iframes.has(IframeType.LAUNCHER)) {
|
|
533
|
+
this.logger.debug('Launcher iframe already exists, skipping creation');
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
567
536
|
const { iframes } = this.config;
|
|
568
537
|
const { launcher } = iframes;
|
|
569
538
|
// Create container
|
|
@@ -591,9 +560,12 @@ class IframeManager {
|
|
|
591
560
|
width: 100%;
|
|
592
561
|
height: 100%;
|
|
593
562
|
border: none;
|
|
594
|
-
background:
|
|
563
|
+
background: none;
|
|
564
|
+
color-scheme: none;
|
|
595
565
|
display: block;
|
|
596
566
|
pointer-events: auto;
|
|
567
|
+
border-radius: 50%;
|
|
568
|
+
filter: drop-shadow(rgba(9, 14, 21, 0.54) 0px 1px 6px) drop-shadow(rgba(9, 14, 21, 0.9) 0px 2px 32px);
|
|
597
569
|
`;
|
|
598
570
|
iframe.setAttribute('allow', 'clipboard-write');
|
|
599
571
|
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
|
|
@@ -609,20 +581,36 @@ class IframeManager {
|
|
|
609
581
|
createdAt: Date.now(),
|
|
610
582
|
});
|
|
611
583
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
584
|
+
let launcherRetried = false;
|
|
612
585
|
iframe.onload = () => {
|
|
613
586
|
const metadata = this.iframes.get(IframeType.LAUNCHER);
|
|
614
587
|
if (metadata) {
|
|
615
588
|
this.logger.debug('Launcher iframe DOM loaded');
|
|
616
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
617
589
|
this.messageBroker?.setIframeDomLoaded(IframeType.LAUNCHER);
|
|
618
590
|
}
|
|
619
591
|
};
|
|
592
|
+
iframe.onerror = () => {
|
|
593
|
+
this.logger.error('Launcher iframe failed to load');
|
|
594
|
+
if (!launcherRetried) {
|
|
595
|
+
launcherRetried = true;
|
|
596
|
+
this.logger.info('Retrying launcher iframe load...');
|
|
597
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(launcher.url); }, 3000);
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
this.config.onError?.(new Error('Failed to load widget launcher'));
|
|
601
|
+
}
|
|
602
|
+
};
|
|
620
603
|
this.logger.debug('Launcher iframe created');
|
|
621
604
|
}
|
|
622
605
|
/**
|
|
623
606
|
* Create widget iframe
|
|
624
607
|
*/
|
|
625
608
|
async createWidgetIframe() {
|
|
609
|
+
// Guard: skip if widget iframe already exists
|
|
610
|
+
if (this.iframes.has(IframeType.WIDGET)) {
|
|
611
|
+
this.logger.debug('Widget iframe already exists, skipping creation');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
626
614
|
const { iframes } = this.config;
|
|
627
615
|
const { widget } = iframes;
|
|
628
616
|
// Create container
|
|
@@ -703,14 +691,25 @@ class IframeManager {
|
|
|
703
691
|
createdAt: Date.now(),
|
|
704
692
|
});
|
|
705
693
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
694
|
+
let widgetRetried = false;
|
|
706
695
|
iframe.onload = () => {
|
|
707
696
|
const metadata = this.iframes.get(IframeType.WIDGET);
|
|
708
697
|
if (metadata) {
|
|
709
698
|
this.logger.debug('Widget iframe DOM loaded');
|
|
710
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
711
699
|
this.messageBroker?.setIframeDomLoaded(IframeType.WIDGET);
|
|
712
700
|
}
|
|
713
701
|
};
|
|
702
|
+
iframe.onerror = () => {
|
|
703
|
+
this.logger.error('Widget iframe failed to load');
|
|
704
|
+
if (!widgetRetried) {
|
|
705
|
+
widgetRetried = true;
|
|
706
|
+
this.logger.info('Retrying widget iframe load...');
|
|
707
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(widget.url); }, 3000);
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
this.config.onError?.(new Error('Failed to load widget'));
|
|
711
|
+
}
|
|
712
|
+
};
|
|
714
713
|
this.logger.debug('Widget iframe created');
|
|
715
714
|
}
|
|
716
715
|
/**
|
|
@@ -731,12 +730,21 @@ class IframeManager {
|
|
|
731
730
|
url.searchParams.set('device', this.deviceInfo.type);
|
|
732
731
|
url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
|
|
733
732
|
url.searchParams.set('parentOrigin', window.location.origin);
|
|
733
|
+
if (this.config.testMode) {
|
|
734
|
+
url.searchParams.set('testMode', 'true');
|
|
735
|
+
}
|
|
734
736
|
return url.toString();
|
|
735
737
|
}
|
|
736
738
|
/**
|
|
737
739
|
* Setup event listeners
|
|
738
740
|
*/
|
|
739
741
|
setupEventListeners() {
|
|
742
|
+
// Guard: prevent double-binding
|
|
743
|
+
if (this.eventListenersBound) {
|
|
744
|
+
this.logger.debug('Event listeners already bound, skipping');
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
this.eventListenersBound = true;
|
|
740
748
|
// Window resize - use bound handler for proper cleanup
|
|
741
749
|
window.addEventListener('resize', this.boundHandleResize);
|
|
742
750
|
// Orientation change - use bound handler for proper cleanup
|
|
@@ -839,7 +847,7 @@ class IframeManager {
|
|
|
839
847
|
iframe.container.style.transform = 'scale(1) translateY(0)';
|
|
840
848
|
}
|
|
841
849
|
// Handle mobile scroll lock
|
|
842
|
-
if (this.deviceInfo.isMobile && type === IframeType.WIDGET
|
|
850
|
+
if (this.deviceInfo.isMobile && type === IframeType.WIDGET) {
|
|
843
851
|
document.body.classList.add('weld-mobile-open');
|
|
844
852
|
}
|
|
845
853
|
// Hide launcher on mobile when widget is open (full-screen mode)
|
|
@@ -938,6 +946,8 @@ class IframeManager {
|
|
|
938
946
|
this.iframes.clear();
|
|
939
947
|
// Clear messageBroker reference
|
|
940
948
|
this.messageBroker = null;
|
|
949
|
+
// Reset guard flag
|
|
950
|
+
this.eventListenersBound = false;
|
|
941
951
|
this.logger.info('IframeManager destroyed');
|
|
942
952
|
}
|
|
943
953
|
}
|
|
@@ -1005,6 +1015,8 @@ var MessageType;
|
|
|
1005
1015
|
// Events
|
|
1006
1016
|
MessageType["EVENT_TRACK"] = "weld:event:track";
|
|
1007
1017
|
MessageType["ERROR_REPORT"] = "weld:error:report";
|
|
1018
|
+
// Page tracking
|
|
1019
|
+
MessageType["PAGE_CHANGE"] = "weld:page:change";
|
|
1008
1020
|
// API responses
|
|
1009
1021
|
MessageType["API_SUCCESS"] = "weld:api:success";
|
|
1010
1022
|
MessageType["API_ERROR"] = "weld:api:error";
|
|
@@ -1544,8 +1556,6 @@ class MessageBroker {
|
|
|
1544
1556
|
iframeType,
|
|
1545
1557
|
config: {
|
|
1546
1558
|
api: this.config.api,
|
|
1547
|
-
customization: this.config.customization,
|
|
1548
|
-
features: this.config.features,
|
|
1549
1559
|
},
|
|
1550
1560
|
};
|
|
1551
1561
|
const message = createMessage('weld:init', MessageOrigin.PARENT, initPayload);
|
|
@@ -2218,7 +2228,7 @@ class StateCoordinator {
|
|
|
2218
2228
|
}
|
|
2219
2229
|
}
|
|
2220
2230
|
|
|
2221
|
-
var version = "1.0.
|
|
2231
|
+
var version = "1.0.17";
|
|
2222
2232
|
var packageJson = {
|
|
2223
2233
|
version: version};
|
|
2224
2234
|
|
|
@@ -2226,6 +2236,16 @@ var packageJson = {
|
|
|
2226
2236
|
* Weld SDK - Main Entry Point
|
|
2227
2237
|
* Public API for the Weld helpdesk widget
|
|
2228
2238
|
*/
|
|
2239
|
+
/**
|
|
2240
|
+
* Module-level singleton registry keyed by widgetId
|
|
2241
|
+
*/
|
|
2242
|
+
const sdkRegistry = new Map();
|
|
2243
|
+
/**
|
|
2244
|
+
* SessionStorage key helpers
|
|
2245
|
+
*/
|
|
2246
|
+
function openStateKey(widgetId) {
|
|
2247
|
+
return `weld-widget-open-${widgetId}`;
|
|
2248
|
+
}
|
|
2229
2249
|
/**
|
|
2230
2250
|
* SDK initialization status
|
|
2231
2251
|
*/
|
|
@@ -2248,6 +2268,8 @@ class WeldSDK {
|
|
|
2248
2268
|
this.readyResolve = null;
|
|
2249
2269
|
// Subscription IDs for cleanup
|
|
2250
2270
|
this.subscriptionIds = [];
|
|
2271
|
+
// Page tracking cleanup
|
|
2272
|
+
this.pageTrackingCleanup = null;
|
|
2251
2273
|
/**
|
|
2252
2274
|
* Update user attributes (Intercom-style, with rate limiting)
|
|
2253
2275
|
* Limited to 20 calls per page load to prevent abuse
|
|
@@ -2286,6 +2308,13 @@ class WeldSDK {
|
|
|
2286
2308
|
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2287
2309
|
}
|
|
2288
2310
|
if (event.data?.type === 'launcher:clicked') {
|
|
2311
|
+
if (this.status !== SDKStatus.READY) {
|
|
2312
|
+
console.log('[Weld SDK] Launcher clicked but SDK not ready yet — waiting...');
|
|
2313
|
+
this.readyPromise?.then(() => {
|
|
2314
|
+
this.handleLauncherClickMessage(event);
|
|
2315
|
+
});
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2289
2318
|
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2290
2319
|
const state = this.stateCoordinator.getState();
|
|
2291
2320
|
if (state.widget.isOpen) {
|
|
@@ -2298,9 +2327,24 @@ class WeldSDK {
|
|
|
2298
2327
|
}
|
|
2299
2328
|
}
|
|
2300
2329
|
if (event.data?.type === 'weld:close') {
|
|
2330
|
+
if (this.status !== SDKStatus.READY)
|
|
2331
|
+
return;
|
|
2301
2332
|
console.log('[Weld SDK] Widget close requested');
|
|
2302
2333
|
this.close();
|
|
2303
2334
|
}
|
|
2335
|
+
if (event.data?.type === 'weld:unread-count') {
|
|
2336
|
+
const count = event.data.count ?? 0;
|
|
2337
|
+
// Forward to launcher iframe
|
|
2338
|
+
const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
|
|
2339
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2340
|
+
launcherIframe.element.contentWindow.postMessage({
|
|
2341
|
+
type: 'weld:unread-count',
|
|
2342
|
+
count
|
|
2343
|
+
}, '*');
|
|
2344
|
+
}
|
|
2345
|
+
// Update state coordinator for external API consumers
|
|
2346
|
+
this.stateCoordinator.setBadgeCount(count);
|
|
2347
|
+
}
|
|
2304
2348
|
if (event.data?.type === 'weld:image:open' && event.data?.url) {
|
|
2305
2349
|
this.showImageLightbox(event.data.url);
|
|
2306
2350
|
}
|
|
@@ -2562,6 +2606,13 @@ class WeldSDK {
|
|
|
2562
2606
|
this.logger.info('WeldSDK ready');
|
|
2563
2607
|
// Call onReady callback
|
|
2564
2608
|
this.config.onReady?.();
|
|
2609
|
+
// Start tracking page URL changes
|
|
2610
|
+
this.startPageTracking();
|
|
2611
|
+
// Auto-open if widget was previously open (persisted in sessionStorage)
|
|
2612
|
+
if (this.wasOpen()) {
|
|
2613
|
+
this.logger.info('Restoring previously open widget from sessionStorage');
|
|
2614
|
+
this.open();
|
|
2615
|
+
}
|
|
2565
2616
|
}
|
|
2566
2617
|
catch (error) {
|
|
2567
2618
|
this.status = SDKStatus.ERROR;
|
|
@@ -2640,6 +2691,58 @@ class WeldSDK {
|
|
|
2640
2691
|
isReady() {
|
|
2641
2692
|
return this.status === SDKStatus.READY;
|
|
2642
2693
|
}
|
|
2694
|
+
/**
|
|
2695
|
+
* Update callbacks on an existing instance (used by singleton reuse)
|
|
2696
|
+
*/
|
|
2697
|
+
updateCallbacks(config) {
|
|
2698
|
+
if (config.onReady !== undefined)
|
|
2699
|
+
this.config.onReady = config.onReady;
|
|
2700
|
+
if (config.onOpen !== undefined)
|
|
2701
|
+
this.config.onOpen = config.onOpen;
|
|
2702
|
+
if (config.onClose !== undefined)
|
|
2703
|
+
this.config.onClose = config.onClose;
|
|
2704
|
+
if (config.onError !== undefined)
|
|
2705
|
+
this.config.onError = config.onError;
|
|
2706
|
+
if (config.onDestroy !== undefined)
|
|
2707
|
+
this.config.onDestroy = config.onDestroy;
|
|
2708
|
+
if (config.onMinimize !== undefined)
|
|
2709
|
+
this.config.onMinimize = config.onMinimize;
|
|
2710
|
+
if (config.onMaximize !== undefined)
|
|
2711
|
+
this.config.onMaximize = config.onMaximize;
|
|
2712
|
+
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Persist open/closed state to sessionStorage
|
|
2715
|
+
*/
|
|
2716
|
+
persistOpenState(isOpen) {
|
|
2717
|
+
try {
|
|
2718
|
+
sessionStorage.setItem(openStateKey(this.config.widgetId), isOpen ? 'true' : 'false');
|
|
2719
|
+
}
|
|
2720
|
+
catch {
|
|
2721
|
+
// sessionStorage might not be available
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Clear persisted state from sessionStorage
|
|
2726
|
+
*/
|
|
2727
|
+
clearPersistedState() {
|
|
2728
|
+
try {
|
|
2729
|
+
sessionStorage.removeItem(openStateKey(this.config.widgetId));
|
|
2730
|
+
}
|
|
2731
|
+
catch {
|
|
2732
|
+
// sessionStorage might not be available
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Check if widget was previously open (from sessionStorage)
|
|
2737
|
+
*/
|
|
2738
|
+
wasOpen() {
|
|
2739
|
+
try {
|
|
2740
|
+
return sessionStorage.getItem(openStateKey(this.config.widgetId)) === 'true';
|
|
2741
|
+
}
|
|
2742
|
+
catch {
|
|
2743
|
+
return false;
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2643
2746
|
/**
|
|
2644
2747
|
* Open the widget
|
|
2645
2748
|
*/
|
|
@@ -2658,6 +2761,7 @@ class WeldSDK {
|
|
|
2658
2761
|
if (launcherIframe?.element?.contentWindow) {
|
|
2659
2762
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2660
2763
|
}
|
|
2764
|
+
this.persistOpenState(true);
|
|
2661
2765
|
this.config.onOpen?.();
|
|
2662
2766
|
}
|
|
2663
2767
|
/**
|
|
@@ -2678,6 +2782,7 @@ class WeldSDK {
|
|
|
2678
2782
|
if (launcherIframe?.element?.contentWindow) {
|
|
2679
2783
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2680
2784
|
}
|
|
2785
|
+
this.persistOpenState(false);
|
|
2681
2786
|
this.config.onClose?.();
|
|
2682
2787
|
}
|
|
2683
2788
|
/**
|
|
@@ -2866,6 +2971,8 @@ class WeldSDK {
|
|
|
2866
2971
|
});
|
|
2867
2972
|
// Broadcast logout to iframes
|
|
2868
2973
|
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
2974
|
+
// Clear persisted widget state
|
|
2975
|
+
this.clearPersistedState();
|
|
2869
2976
|
// Clear any stored session data
|
|
2870
2977
|
try {
|
|
2871
2978
|
const prefix = 'weld-';
|
|
@@ -3007,6 +3114,63 @@ class WeldSDK {
|
|
|
3007
3114
|
this.logger.setLevel('warn');
|
|
3008
3115
|
this.logger.info('Debug mode disabled');
|
|
3009
3116
|
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Send a page change message to the widget iframe
|
|
3119
|
+
*/
|
|
3120
|
+
sendPageChange(url, title) {
|
|
3121
|
+
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
3122
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
3123
|
+
widgetIframe.element.contentWindow.postMessage({
|
|
3124
|
+
type: 'weld:page:change',
|
|
3125
|
+
url,
|
|
3126
|
+
title,
|
|
3127
|
+
timestamp: Date.now(),
|
|
3128
|
+
}, '*');
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
/**
|
|
3132
|
+
* Start tracking page URL changes (SPA navigations + popstate)
|
|
3133
|
+
*/
|
|
3134
|
+
startPageTracking() {
|
|
3135
|
+
let lastUrl = window.location.href;
|
|
3136
|
+
let debounceTimer = null;
|
|
3137
|
+
const notifyChange = () => {
|
|
3138
|
+
const currentUrl = window.location.href;
|
|
3139
|
+
if (currentUrl !== lastUrl) {
|
|
3140
|
+
lastUrl = currentUrl;
|
|
3141
|
+
this.sendPageChange(currentUrl, document.title);
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
const debouncedNotify = () => {
|
|
3145
|
+
if (debounceTimer)
|
|
3146
|
+
clearTimeout(debounceTimer);
|
|
3147
|
+
debounceTimer = setTimeout(notifyChange, 300);
|
|
3148
|
+
};
|
|
3149
|
+
// Send initial page
|
|
3150
|
+
this.sendPageChange(window.location.href, document.title);
|
|
3151
|
+
// Monkey-patch history.pushState and history.replaceState
|
|
3152
|
+
const origPushState = history.pushState.bind(history);
|
|
3153
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
3154
|
+
history.pushState = function (...args) {
|
|
3155
|
+
origPushState(...args);
|
|
3156
|
+
debouncedNotify();
|
|
3157
|
+
};
|
|
3158
|
+
history.replaceState = function (...args) {
|
|
3159
|
+
origReplaceState(...args);
|
|
3160
|
+
debouncedNotify();
|
|
3161
|
+
};
|
|
3162
|
+
// Listen for popstate (browser back/forward)
|
|
3163
|
+
const handlePopstate = () => debouncedNotify();
|
|
3164
|
+
window.addEventListener('popstate', handlePopstate);
|
|
3165
|
+
// Store cleanup
|
|
3166
|
+
this.pageTrackingCleanup = () => {
|
|
3167
|
+
if (debounceTimer)
|
|
3168
|
+
clearTimeout(debounceTimer);
|
|
3169
|
+
window.removeEventListener('popstate', handlePopstate);
|
|
3170
|
+
history.pushState = origPushState;
|
|
3171
|
+
history.replaceState = origReplaceState;
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3010
3174
|
/**
|
|
3011
3175
|
* Ensure SDK is ready before operation
|
|
3012
3176
|
*/
|
|
@@ -3015,11 +3179,26 @@ class WeldSDK {
|
|
|
3015
3179
|
throw new Error('SDK not ready. Call init() first.');
|
|
3016
3180
|
}
|
|
3017
3181
|
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Detach from the current component lifecycle without destroying the widget.
|
|
3184
|
+
* Use this as a React useEffect cleanup — the widget stays alive across navigations.
|
|
3185
|
+
*/
|
|
3186
|
+
detach() {
|
|
3187
|
+
// No-op: widget stays alive in the singleton registry
|
|
3188
|
+
this.logger.debug('WeldSDK detached (no-op, widget stays alive)');
|
|
3189
|
+
}
|
|
3018
3190
|
/**
|
|
3019
3191
|
* Destroy SDK and cleanup
|
|
3020
3192
|
*/
|
|
3021
3193
|
destroy() {
|
|
3022
3194
|
this.logger.info('Destroying WeldSDK');
|
|
3195
|
+
// Remove from singleton registry
|
|
3196
|
+
sdkRegistry.delete(this.config.widgetId);
|
|
3197
|
+
// Clear persisted state
|
|
3198
|
+
this.clearPersistedState();
|
|
3199
|
+
// Stop page tracking
|
|
3200
|
+
this.pageTrackingCleanup?.();
|
|
3201
|
+
this.pageTrackingCleanup = null;
|
|
3023
3202
|
// Remove event listener using bound handler
|
|
3024
3203
|
window.removeEventListener('message', this.boundHandleLauncherClick);
|
|
3025
3204
|
// Unsubscribe from all message broker subscriptions
|