@weldsuite/helpdesk-widget-sdk 1.0.15 → 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 +36 -41
- package/dist/angular.esm.js +483 -102
- package/dist/angular.esm.js.map +1 -1
- package/dist/angular.js +483 -102
- package/dist/angular.js.map +1 -1
- package/dist/index.d.ts +60 -56
- package/dist/index.esm.js +505 -104
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +506 -103
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +506 -103
- package/dist/index.umd.js.map +1 -1
- package/dist/react.d.ts +36 -41
- package/dist/react.esm.js +483 -102
- package/dist/react.esm.js.map +1 -1
- package/dist/react.js +483 -102
- package/dist/react.js.map +1 -1
- package/dist/vue-composables.esm.js +483 -102
- package/dist/vue-composables.esm.js.map +1 -1
- package/dist/vue-composables.js +483 -102
- package/dist/vue-composables.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -35,32 +35,6 @@ const DEFAULT_CONFIG = {
|
|
|
35
35
|
closeOnClick: true,
|
|
36
36
|
},
|
|
37
37
|
},
|
|
38
|
-
customization: {
|
|
39
|
-
primaryColor: '#000000',
|
|
40
|
-
accentColor: '#3b82f6',
|
|
41
|
-
backgroundColor: '#ffffff',
|
|
42
|
-
textColor: '#111827',
|
|
43
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
44
|
-
fontSize: '14px',
|
|
45
|
-
borderRadius: '12px',
|
|
46
|
-
},
|
|
47
|
-
features: {
|
|
48
|
-
attachments: true,
|
|
49
|
-
reactions: true,
|
|
50
|
-
typing: true,
|
|
51
|
-
readReceipts: true,
|
|
52
|
-
offlineMode: false,
|
|
53
|
-
fileUpload: true,
|
|
54
|
-
imageUpload: true,
|
|
55
|
-
voiceMessages: false,
|
|
56
|
-
videoMessages: false,
|
|
57
|
-
},
|
|
58
|
-
mobile: {
|
|
59
|
-
fullScreen: true,
|
|
60
|
-
scrollLock: true,
|
|
61
|
-
keyboardHandling: 'auto',
|
|
62
|
-
safeAreaInsets: true,
|
|
63
|
-
},
|
|
64
38
|
auth: {
|
|
65
39
|
enabled: true,
|
|
66
40
|
mode: 'anonymous',
|
|
@@ -104,6 +78,7 @@ function resolveConfig(config) {
|
|
|
104
78
|
validateConfig(config);
|
|
105
79
|
return {
|
|
106
80
|
widgetId: config.widgetId,
|
|
81
|
+
testMode: config.testMode,
|
|
107
82
|
api: {
|
|
108
83
|
...DEFAULT_CONFIG.api,
|
|
109
84
|
widgetId: config.widgetId,
|
|
@@ -133,18 +108,6 @@ function resolveConfig(config) {
|
|
|
133
108
|
...config.iframes?.backdrop,
|
|
134
109
|
},
|
|
135
110
|
},
|
|
136
|
-
customization: {
|
|
137
|
-
...DEFAULT_CONFIG.customization,
|
|
138
|
-
...config.customization,
|
|
139
|
-
},
|
|
140
|
-
features: {
|
|
141
|
-
...DEFAULT_CONFIG.features,
|
|
142
|
-
...config.features,
|
|
143
|
-
},
|
|
144
|
-
mobile: {
|
|
145
|
-
...DEFAULT_CONFIG.mobile,
|
|
146
|
-
...config.mobile,
|
|
147
|
-
},
|
|
148
111
|
auth: {
|
|
149
112
|
...DEFAULT_CONFIG.auth,
|
|
150
113
|
...config.auth,
|
|
@@ -387,6 +350,8 @@ class IframeManager {
|
|
|
387
350
|
this.modalContainer = null;
|
|
388
351
|
this.styleElement = null;
|
|
389
352
|
this.messageBroker = null;
|
|
353
|
+
// Guard flag to prevent double-binding event listeners
|
|
354
|
+
this.eventListenersBound = false;
|
|
390
355
|
this.config = config;
|
|
391
356
|
this.logger = new Logger(config.logging);
|
|
392
357
|
this.deviceInfo = detectDevice();
|
|
@@ -426,18 +391,27 @@ class IframeManager {
|
|
|
426
391
|
}
|
|
427
392
|
/**
|
|
428
393
|
* Create root container structure
|
|
394
|
+
* Reuses existing container if it has the same widgetId (singleton behavior)
|
|
429
395
|
*/
|
|
430
396
|
createRootContainer() {
|
|
431
|
-
|
|
432
|
-
let existingContainer = document.getElementById('weld-container');
|
|
397
|
+
const existingContainer = document.getElementById('weld-container');
|
|
433
398
|
if (existingContainer) {
|
|
434
|
-
|
|
399
|
+
// Reuse if same widgetId
|
|
400
|
+
if (existingContainer.getAttribute('data-widget-id') === this.config.widgetId) {
|
|
401
|
+
this.logger.debug('Reusing existing root container');
|
|
402
|
+
this.rootContainer = existingContainer;
|
|
403
|
+
this.appContainer = existingContainer.querySelector('.weld-app');
|
|
404
|
+
this.modalContainer = document.getElementById('weld-modal-container');
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
this.logger.warn('Weld container already exists with different widgetId, removing old instance');
|
|
435
408
|
existingContainer.remove();
|
|
436
409
|
}
|
|
437
410
|
// Create root container
|
|
438
411
|
this.rootContainer = document.createElement('div');
|
|
439
412
|
this.rootContainer.id = 'weld-container';
|
|
440
413
|
this.rootContainer.className = 'weld-namespace';
|
|
414
|
+
this.rootContainer.setAttribute('data-widget-id', this.config.widgetId);
|
|
441
415
|
// Create app container
|
|
442
416
|
this.appContainer = document.createElement('div');
|
|
443
417
|
this.appContainer.className = 'weld-app';
|
|
@@ -472,17 +446,7 @@ class IframeManager {
|
|
|
472
446
|
* Generate CSS for containers
|
|
473
447
|
*/
|
|
474
448
|
generateCSS() {
|
|
475
|
-
const { customization } = this.config;
|
|
476
449
|
return `
|
|
477
|
-
/* Weld Container */
|
|
478
|
-
#weld-container {
|
|
479
|
-
--weld-color-primary: ${customization.primaryColor};
|
|
480
|
-
--weld-color-accent: ${customization.accentColor};
|
|
481
|
-
--weld-font-family: ${customization.fontFamily};
|
|
482
|
-
--weld-font-size-base: ${customization.fontSize};
|
|
483
|
-
--weld-radius-xl: ${customization.borderRadius};
|
|
484
|
-
}
|
|
485
|
-
|
|
486
450
|
/* Import main stylesheet */
|
|
487
451
|
@import url('/styles/index.css');
|
|
488
452
|
|
|
@@ -506,20 +470,27 @@ class IframeManager {
|
|
|
506
470
|
* Create launcher iframe
|
|
507
471
|
*/
|
|
508
472
|
async createLauncherIframe() {
|
|
473
|
+
// Guard: skip if launcher iframe already exists
|
|
474
|
+
if (this.iframes.has(exports.IframeType.LAUNCHER)) {
|
|
475
|
+
this.logger.debug('Launcher iframe already exists, skipping creation');
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
509
478
|
const { iframes } = this.config;
|
|
510
479
|
const { launcher } = iframes;
|
|
511
480
|
// Create container
|
|
512
481
|
const container = document.createElement('div');
|
|
513
482
|
container.className = 'weld-launcher-frame';
|
|
514
483
|
container.setAttribute('data-state', 'visible');
|
|
484
|
+
// Container is larger than the button to allow hover animations (scale, shadow) without clipping
|
|
485
|
+
const launcherPadding = 10;
|
|
515
486
|
container.style.cssText = `
|
|
516
487
|
position: fixed;
|
|
517
|
-
bottom: ${launcher.position.bottom};
|
|
518
|
-
right: ${launcher.position.right};
|
|
519
|
-
width: ${launcher.size};
|
|
520
|
-
height: ${launcher.size};
|
|
488
|
+
bottom: calc(${launcher.position.bottom} - ${launcherPadding}px);
|
|
489
|
+
right: calc(${launcher.position.right} - ${launcherPadding}px);
|
|
490
|
+
width: calc(${launcher.size} + ${launcherPadding * 2}px);
|
|
491
|
+
height: calc(${launcher.size} + ${launcherPadding * 2}px);
|
|
521
492
|
z-index: 2147483003;
|
|
522
|
-
pointer-events:
|
|
493
|
+
pointer-events: none;
|
|
523
494
|
display: block;
|
|
524
495
|
`;
|
|
525
496
|
// Create iframe
|
|
@@ -531,8 +502,12 @@ class IframeManager {
|
|
|
531
502
|
width: 100%;
|
|
532
503
|
height: 100%;
|
|
533
504
|
border: none;
|
|
534
|
-
background:
|
|
505
|
+
background: none;
|
|
506
|
+
color-scheme: none;
|
|
535
507
|
display: block;
|
|
508
|
+
pointer-events: auto;
|
|
509
|
+
border-radius: 50%;
|
|
510
|
+
filter: drop-shadow(rgba(9, 14, 21, 0.54) 0px 1px 6px) drop-shadow(rgba(9, 14, 21, 0.9) 0px 2px 32px);
|
|
536
511
|
`;
|
|
537
512
|
iframe.setAttribute('allow', 'clipboard-write');
|
|
538
513
|
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
|
|
@@ -548,20 +523,36 @@ class IframeManager {
|
|
|
548
523
|
createdAt: Date.now(),
|
|
549
524
|
});
|
|
550
525
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
526
|
+
let launcherRetried = false;
|
|
551
527
|
iframe.onload = () => {
|
|
552
528
|
const metadata = this.iframes.get(exports.IframeType.LAUNCHER);
|
|
553
529
|
if (metadata) {
|
|
554
530
|
this.logger.debug('Launcher iframe DOM loaded');
|
|
555
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
556
531
|
this.messageBroker?.setIframeDomLoaded(exports.IframeType.LAUNCHER);
|
|
557
532
|
}
|
|
558
533
|
};
|
|
534
|
+
iframe.onerror = () => {
|
|
535
|
+
this.logger.error('Launcher iframe failed to load');
|
|
536
|
+
if (!launcherRetried) {
|
|
537
|
+
launcherRetried = true;
|
|
538
|
+
this.logger.info('Retrying launcher iframe load...');
|
|
539
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(launcher.url); }, 3000);
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
this.config.onError?.(new Error('Failed to load widget launcher'));
|
|
543
|
+
}
|
|
544
|
+
};
|
|
559
545
|
this.logger.debug('Launcher iframe created');
|
|
560
546
|
}
|
|
561
547
|
/**
|
|
562
548
|
* Create widget iframe
|
|
563
549
|
*/
|
|
564
550
|
async createWidgetIframe() {
|
|
551
|
+
// Guard: skip if widget iframe already exists
|
|
552
|
+
if (this.iframes.has(exports.IframeType.WIDGET)) {
|
|
553
|
+
this.logger.debug('Widget iframe already exists, skipping creation');
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
565
556
|
const { iframes } = this.config;
|
|
566
557
|
const { widget } = iframes;
|
|
567
558
|
// Create container
|
|
@@ -642,54 +633,32 @@ class IframeManager {
|
|
|
642
633
|
createdAt: Date.now(),
|
|
643
634
|
});
|
|
644
635
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
636
|
+
let widgetRetried = false;
|
|
645
637
|
iframe.onload = () => {
|
|
646
638
|
const metadata = this.iframes.get(exports.IframeType.WIDGET);
|
|
647
639
|
if (metadata) {
|
|
648
640
|
this.logger.debug('Widget iframe DOM loaded');
|
|
649
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
650
641
|
this.messageBroker?.setIframeDomLoaded(exports.IframeType.WIDGET);
|
|
651
642
|
}
|
|
652
643
|
};
|
|
644
|
+
iframe.onerror = () => {
|
|
645
|
+
this.logger.error('Widget iframe failed to load');
|
|
646
|
+
if (!widgetRetried) {
|
|
647
|
+
widgetRetried = true;
|
|
648
|
+
this.logger.info('Retrying widget iframe load...');
|
|
649
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(widget.url); }, 3000);
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
this.config.onError?.(new Error('Failed to load widget'));
|
|
653
|
+
}
|
|
654
|
+
};
|
|
653
655
|
this.logger.debug('Widget iframe created');
|
|
654
656
|
}
|
|
655
657
|
/**
|
|
656
|
-
* Create backdrop iframe
|
|
658
|
+
* Create backdrop iframe — disabled, widget stays non-modal so users can interact with the page
|
|
657
659
|
*/
|
|
658
660
|
async createBackdropIframe() {
|
|
659
|
-
|
|
660
|
-
this.logger.debug('Backdrop disabled, skipping creation');
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
// Create container
|
|
664
|
-
const container = document.createElement('div');
|
|
665
|
-
container.className = 'weld-backdrop-frame';
|
|
666
|
-
container.setAttribute('data-state', 'hidden');
|
|
667
|
-
container.style.cssText = `
|
|
668
|
-
position: fixed;
|
|
669
|
-
top: 0;
|
|
670
|
-
left: 0;
|
|
671
|
-
right: 0;
|
|
672
|
-
bottom: 0;
|
|
673
|
-
z-index: 2147483000;
|
|
674
|
-
background: transparent;
|
|
675
|
-
pointer-events: none;
|
|
676
|
-
opacity: 0;
|
|
677
|
-
transition: opacity 200ms ease;
|
|
678
|
-
`;
|
|
679
|
-
this.appContainer?.appendChild(container);
|
|
680
|
-
// Store metadata (backdrop doesn't have an iframe, just a div)
|
|
681
|
-
// We'll create a minimal "iframe" reference for consistency
|
|
682
|
-
const dummyIframe = document.createElement('iframe');
|
|
683
|
-
dummyIframe.style.display = 'none';
|
|
684
|
-
this.iframes.set(exports.IframeType.BACKDROP, {
|
|
685
|
-
type: exports.IframeType.BACKDROP,
|
|
686
|
-
element: dummyIframe,
|
|
687
|
-
container,
|
|
688
|
-
ready: true, // Backdrop is always ready
|
|
689
|
-
visible: false,
|
|
690
|
-
createdAt: Date.now(),
|
|
691
|
-
});
|
|
692
|
-
this.logger.debug('Backdrop created');
|
|
661
|
+
this.logger.debug('Backdrop disabled, skipping creation');
|
|
693
662
|
}
|
|
694
663
|
/**
|
|
695
664
|
* Build iframe URL with parameters
|
|
@@ -703,12 +672,21 @@ class IframeManager {
|
|
|
703
672
|
url.searchParams.set('device', this.deviceInfo.type);
|
|
704
673
|
url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
|
|
705
674
|
url.searchParams.set('parentOrigin', window.location.origin);
|
|
675
|
+
if (this.config.testMode) {
|
|
676
|
+
url.searchParams.set('testMode', 'true');
|
|
677
|
+
}
|
|
706
678
|
return url.toString();
|
|
707
679
|
}
|
|
708
680
|
/**
|
|
709
681
|
* Setup event listeners
|
|
710
682
|
*/
|
|
711
683
|
setupEventListeners() {
|
|
684
|
+
// Guard: prevent double-binding
|
|
685
|
+
if (this.eventListenersBound) {
|
|
686
|
+
this.logger.debug('Event listeners already bound, skipping');
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
this.eventListenersBound = true;
|
|
712
690
|
// Window resize - use bound handler for proper cleanup
|
|
713
691
|
window.addEventListener('resize', this.boundHandleResize);
|
|
714
692
|
// Orientation change - use bound handler for proper cleanup
|
|
@@ -811,7 +789,7 @@ class IframeManager {
|
|
|
811
789
|
iframe.container.style.transform = 'scale(1) translateY(0)';
|
|
812
790
|
}
|
|
813
791
|
// Handle mobile scroll lock
|
|
814
|
-
if (this.deviceInfo.isMobile && type === exports.IframeType.WIDGET
|
|
792
|
+
if (this.deviceInfo.isMobile && type === exports.IframeType.WIDGET) {
|
|
815
793
|
document.body.classList.add('weld-mobile-open');
|
|
816
794
|
}
|
|
817
795
|
// Hide launcher on mobile when widget is open (full-screen mode)
|
|
@@ -910,6 +888,8 @@ class IframeManager {
|
|
|
910
888
|
this.iframes.clear();
|
|
911
889
|
// Clear messageBroker reference
|
|
912
890
|
this.messageBroker = null;
|
|
891
|
+
// Reset guard flag
|
|
892
|
+
this.eventListenersBound = false;
|
|
913
893
|
this.logger.info('IframeManager destroyed');
|
|
914
894
|
}
|
|
915
895
|
}
|
|
@@ -977,6 +957,8 @@ var MessageType;
|
|
|
977
957
|
// Events
|
|
978
958
|
MessageType["EVENT_TRACK"] = "weld:event:track";
|
|
979
959
|
MessageType["ERROR_REPORT"] = "weld:error:report";
|
|
960
|
+
// Page tracking
|
|
961
|
+
MessageType["PAGE_CHANGE"] = "weld:page:change";
|
|
980
962
|
// API responses
|
|
981
963
|
MessageType["API_SUCCESS"] = "weld:api:success";
|
|
982
964
|
MessageType["API_ERROR"] = "weld:api:error";
|
|
@@ -1555,8 +1537,6 @@ class MessageBroker {
|
|
|
1555
1537
|
iframeType,
|
|
1556
1538
|
config: {
|
|
1557
1539
|
api: this.config.api,
|
|
1558
|
-
customization: this.config.customization,
|
|
1559
|
-
features: this.config.features,
|
|
1560
1540
|
},
|
|
1561
1541
|
};
|
|
1562
1542
|
const message = createMessage('weld:init', MessageOrigin.PARENT, initPayload);
|
|
@@ -2399,7 +2379,7 @@ class StateCoordinator {
|
|
|
2399
2379
|
}
|
|
2400
2380
|
}
|
|
2401
2381
|
|
|
2402
|
-
var version = "1.0.
|
|
2382
|
+
var version = "1.0.17";
|
|
2403
2383
|
var packageJson = {
|
|
2404
2384
|
version: version};
|
|
2405
2385
|
|
|
@@ -2407,6 +2387,16 @@ var packageJson = {
|
|
|
2407
2387
|
* Weld SDK - Main Entry Point
|
|
2408
2388
|
* Public API for the Weld helpdesk widget
|
|
2409
2389
|
*/
|
|
2390
|
+
/**
|
|
2391
|
+
* Module-level singleton registry keyed by widgetId
|
|
2392
|
+
*/
|
|
2393
|
+
const sdkRegistry = new Map();
|
|
2394
|
+
/**
|
|
2395
|
+
* SessionStorage key helpers
|
|
2396
|
+
*/
|
|
2397
|
+
function openStateKey(widgetId) {
|
|
2398
|
+
return `weld-widget-open-${widgetId}`;
|
|
2399
|
+
}
|
|
2410
2400
|
/**
|
|
2411
2401
|
* SDK initialization status
|
|
2412
2402
|
*/
|
|
@@ -2429,6 +2419,8 @@ class WeldSDK {
|
|
|
2429
2419
|
this.readyResolve = null;
|
|
2430
2420
|
// Subscription IDs for cleanup
|
|
2431
2421
|
this.subscriptionIds = [];
|
|
2422
|
+
// Page tracking cleanup
|
|
2423
|
+
this.pageTrackingCleanup = null;
|
|
2432
2424
|
/**
|
|
2433
2425
|
* Update user attributes (Intercom-style, with rate limiting)
|
|
2434
2426
|
* Limited to 20 calls per page load to prevent abuse
|
|
@@ -2467,6 +2459,13 @@ class WeldSDK {
|
|
|
2467
2459
|
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2468
2460
|
}
|
|
2469
2461
|
if (event.data?.type === 'launcher:clicked') {
|
|
2462
|
+
if (this.status !== SDKStatus.READY) {
|
|
2463
|
+
console.log('[Weld SDK] Launcher clicked but SDK not ready yet — waiting...');
|
|
2464
|
+
this.readyPromise?.then(() => {
|
|
2465
|
+
this.handleLauncherClickMessage(event);
|
|
2466
|
+
});
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2470
2469
|
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2471
2470
|
const state = this.stateCoordinator.getState();
|
|
2472
2471
|
if (state.widget.isOpen) {
|
|
@@ -2479,9 +2478,260 @@ class WeldSDK {
|
|
|
2479
2478
|
}
|
|
2480
2479
|
}
|
|
2481
2480
|
if (event.data?.type === 'weld:close') {
|
|
2481
|
+
if (this.status !== SDKStatus.READY)
|
|
2482
|
+
return;
|
|
2482
2483
|
console.log('[Weld SDK] Widget close requested');
|
|
2483
2484
|
this.close();
|
|
2484
2485
|
}
|
|
2486
|
+
if (event.data?.type === 'weld:unread-count') {
|
|
2487
|
+
const count = event.data.count ?? 0;
|
|
2488
|
+
// Forward to launcher iframe
|
|
2489
|
+
const launcherIframe = this.iframeManager.getIframe(exports.IframeType.LAUNCHER);
|
|
2490
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2491
|
+
launcherIframe.element.contentWindow.postMessage({
|
|
2492
|
+
type: 'weld:unread-count',
|
|
2493
|
+
count
|
|
2494
|
+
}, '*');
|
|
2495
|
+
}
|
|
2496
|
+
// Update state coordinator for external API consumers
|
|
2497
|
+
this.stateCoordinator.setBadgeCount(count);
|
|
2498
|
+
}
|
|
2499
|
+
if (event.data?.type === 'weld:image:open' && event.data?.url) {
|
|
2500
|
+
this.showImageLightbox(event.data.url);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
/**
|
|
2504
|
+
* Show fullscreen image lightbox on the parent page
|
|
2505
|
+
*/
|
|
2506
|
+
showImageLightbox(url) {
|
|
2507
|
+
// Remove existing lightbox if any
|
|
2508
|
+
const existing = document.getElementById('weld-image-lightbox');
|
|
2509
|
+
if (existing)
|
|
2510
|
+
existing.remove();
|
|
2511
|
+
// Zoom / pan state
|
|
2512
|
+
let scale = 1;
|
|
2513
|
+
let translateX = 0;
|
|
2514
|
+
let translateY = 0;
|
|
2515
|
+
let isDragging = false;
|
|
2516
|
+
let dragStartX = 0;
|
|
2517
|
+
let dragStartY = 0;
|
|
2518
|
+
let lastTranslateX = 0;
|
|
2519
|
+
let lastTranslateY = 0;
|
|
2520
|
+
const applyTransform = () => {
|
|
2521
|
+
img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
|
|
2522
|
+
};
|
|
2523
|
+
const resetTransform = () => {
|
|
2524
|
+
scale = 1;
|
|
2525
|
+
translateX = 0;
|
|
2526
|
+
translateY = 0;
|
|
2527
|
+
applyTransform();
|
|
2528
|
+
img.style.cursor = 'zoom-in';
|
|
2529
|
+
};
|
|
2530
|
+
const overlay = document.createElement('div');
|
|
2531
|
+
overlay.id = 'weld-image-lightbox';
|
|
2532
|
+
overlay.style.cssText = `
|
|
2533
|
+
position: fixed;
|
|
2534
|
+
inset: 0;
|
|
2535
|
+
z-index: 2147483647;
|
|
2536
|
+
background: rgba(0, 0, 0, 0.92);
|
|
2537
|
+
display: flex;
|
|
2538
|
+
align-items: center;
|
|
2539
|
+
justify-content: center;
|
|
2540
|
+
padding: 16px;
|
|
2541
|
+
cursor: pointer;
|
|
2542
|
+
overflow: hidden;
|
|
2543
|
+
`;
|
|
2544
|
+
// Close button
|
|
2545
|
+
const closeBtn = document.createElement('button');
|
|
2546
|
+
closeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
|
|
2547
|
+
closeBtn.style.cssText = `
|
|
2548
|
+
position: absolute;
|
|
2549
|
+
top: 16px;
|
|
2550
|
+
right: 16px;
|
|
2551
|
+
width: 40px;
|
|
2552
|
+
height: 40px;
|
|
2553
|
+
border-radius: 50%;
|
|
2554
|
+
border: none;
|
|
2555
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2556
|
+
color: white;
|
|
2557
|
+
cursor: pointer;
|
|
2558
|
+
display: flex;
|
|
2559
|
+
align-items: center;
|
|
2560
|
+
justify-content: center;
|
|
2561
|
+
transition: background 0.15s;
|
|
2562
|
+
`;
|
|
2563
|
+
closeBtn.onmouseenter = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
|
|
2564
|
+
closeBtn.onmouseleave = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
|
|
2565
|
+
// Download button
|
|
2566
|
+
const downloadBtn = document.createElement('a');
|
|
2567
|
+
downloadBtn.href = url;
|
|
2568
|
+
downloadBtn.download = '';
|
|
2569
|
+
downloadBtn.target = '_blank';
|
|
2570
|
+
downloadBtn.rel = 'noopener noreferrer';
|
|
2571
|
+
downloadBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>`;
|
|
2572
|
+
downloadBtn.style.cssText = `
|
|
2573
|
+
position: absolute;
|
|
2574
|
+
top: 16px;
|
|
2575
|
+
right: 64px;
|
|
2576
|
+
width: 40px;
|
|
2577
|
+
height: 40px;
|
|
2578
|
+
border-radius: 50%;
|
|
2579
|
+
border: none;
|
|
2580
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2581
|
+
color: white;
|
|
2582
|
+
cursor: pointer;
|
|
2583
|
+
display: flex;
|
|
2584
|
+
align-items: center;
|
|
2585
|
+
justify-content: center;
|
|
2586
|
+
transition: background 0.15s;
|
|
2587
|
+
text-decoration: none;
|
|
2588
|
+
`;
|
|
2589
|
+
downloadBtn.onmouseenter = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
|
|
2590
|
+
downloadBtn.onmouseleave = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
|
|
2591
|
+
// Image
|
|
2592
|
+
const img = document.createElement('img');
|
|
2593
|
+
img.src = url;
|
|
2594
|
+
img.alt = 'Full size';
|
|
2595
|
+
img.draggable = false;
|
|
2596
|
+
img.style.cssText = `
|
|
2597
|
+
max-width: 100%;
|
|
2598
|
+
max-height: 100%;
|
|
2599
|
+
object-fit: contain;
|
|
2600
|
+
border-radius: 8px;
|
|
2601
|
+
cursor: zoom-in;
|
|
2602
|
+
transition: transform 0.2s ease;
|
|
2603
|
+
user-select: none;
|
|
2604
|
+
`;
|
|
2605
|
+
// Click to toggle zoom
|
|
2606
|
+
img.addEventListener('click', (e) => {
|
|
2607
|
+
e.stopPropagation();
|
|
2608
|
+
if (scale === 1) {
|
|
2609
|
+
// Zoom in to 2.5x centered on click position
|
|
2610
|
+
const rect = img.getBoundingClientRect();
|
|
2611
|
+
const clickX = e.clientX - rect.left - rect.width / 2;
|
|
2612
|
+
const clickY = e.clientY - rect.top - rect.height / 2;
|
|
2613
|
+
scale = 2.5;
|
|
2614
|
+
translateX = -clickX * 1.5;
|
|
2615
|
+
translateY = -clickY * 1.5;
|
|
2616
|
+
applyTransform();
|
|
2617
|
+
img.style.cursor = 'zoom-out';
|
|
2618
|
+
}
|
|
2619
|
+
else {
|
|
2620
|
+
// Zoom out - reset
|
|
2621
|
+
resetTransform();
|
|
2622
|
+
}
|
|
2623
|
+
});
|
|
2624
|
+
// Mouse wheel zoom
|
|
2625
|
+
overlay.addEventListener('wheel', (e) => {
|
|
2626
|
+
e.preventDefault();
|
|
2627
|
+
const delta = e.deltaY > 0 ? -0.25 : 0.25;
|
|
2628
|
+
const newScale = Math.min(Math.max(scale + delta, 1), 5);
|
|
2629
|
+
if (newScale === 1) {
|
|
2630
|
+
resetTransform();
|
|
2631
|
+
}
|
|
2632
|
+
else {
|
|
2633
|
+
scale = newScale;
|
|
2634
|
+
applyTransform();
|
|
2635
|
+
img.style.cursor = 'zoom-out';
|
|
2636
|
+
}
|
|
2637
|
+
}, { passive: false });
|
|
2638
|
+
// Drag to pan when zoomed
|
|
2639
|
+
img.addEventListener('mousedown', (e) => {
|
|
2640
|
+
if (scale <= 1)
|
|
2641
|
+
return;
|
|
2642
|
+
e.preventDefault();
|
|
2643
|
+
isDragging = true;
|
|
2644
|
+
dragStartX = e.clientX;
|
|
2645
|
+
dragStartY = e.clientY;
|
|
2646
|
+
lastTranslateX = translateX;
|
|
2647
|
+
lastTranslateY = translateY;
|
|
2648
|
+
img.style.cursor = 'grabbing';
|
|
2649
|
+
img.style.transition = 'none';
|
|
2650
|
+
});
|
|
2651
|
+
window.addEventListener('mousemove', (e) => {
|
|
2652
|
+
if (!isDragging)
|
|
2653
|
+
return;
|
|
2654
|
+
translateX = lastTranslateX + (e.clientX - dragStartX);
|
|
2655
|
+
translateY = lastTranslateY + (e.clientY - dragStartY);
|
|
2656
|
+
applyTransform();
|
|
2657
|
+
});
|
|
2658
|
+
window.addEventListener('mouseup', () => {
|
|
2659
|
+
if (!isDragging)
|
|
2660
|
+
return;
|
|
2661
|
+
isDragging = false;
|
|
2662
|
+
img.style.cursor = scale > 1 ? 'zoom-out' : 'zoom-in';
|
|
2663
|
+
img.style.transition = 'transform 0.2s ease';
|
|
2664
|
+
});
|
|
2665
|
+
// Touch: pinch to zoom + drag to pan
|
|
2666
|
+
let lastTouchDist = 0;
|
|
2667
|
+
let lastTouchScale = 1;
|
|
2668
|
+
overlay.addEventListener('touchstart', (e) => {
|
|
2669
|
+
if (e.touches.length === 2) {
|
|
2670
|
+
e.preventDefault();
|
|
2671
|
+
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
|
2672
|
+
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
|
2673
|
+
lastTouchDist = Math.hypot(dx, dy);
|
|
2674
|
+
lastTouchScale = scale;
|
|
2675
|
+
}
|
|
2676
|
+
else if (e.touches.length === 1 && scale > 1) {
|
|
2677
|
+
isDragging = true;
|
|
2678
|
+
dragStartX = e.touches[0].clientX;
|
|
2679
|
+
dragStartY = e.touches[0].clientY;
|
|
2680
|
+
lastTranslateX = translateX;
|
|
2681
|
+
lastTranslateY = translateY;
|
|
2682
|
+
img.style.transition = 'none';
|
|
2683
|
+
}
|
|
2684
|
+
}, { passive: false });
|
|
2685
|
+
overlay.addEventListener('touchmove', (e) => {
|
|
2686
|
+
if (e.touches.length === 2) {
|
|
2687
|
+
e.preventDefault();
|
|
2688
|
+
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
|
2689
|
+
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
|
2690
|
+
const dist = Math.hypot(dx, dy);
|
|
2691
|
+
scale = Math.min(Math.max(lastTouchScale * (dist / lastTouchDist), 1), 5);
|
|
2692
|
+
if (scale === 1) {
|
|
2693
|
+
translateX = 0;
|
|
2694
|
+
translateY = 0;
|
|
2695
|
+
}
|
|
2696
|
+
applyTransform();
|
|
2697
|
+
}
|
|
2698
|
+
else if (e.touches.length === 1 && isDragging) {
|
|
2699
|
+
e.preventDefault();
|
|
2700
|
+
translateX = lastTranslateX + (e.touches[0].clientX - dragStartX);
|
|
2701
|
+
translateY = lastTranslateY + (e.touches[0].clientY - dragStartY);
|
|
2702
|
+
applyTransform();
|
|
2703
|
+
}
|
|
2704
|
+
}, { passive: false });
|
|
2705
|
+
overlay.addEventListener('touchend', (e) => {
|
|
2706
|
+
if (e.touches.length < 2) {
|
|
2707
|
+
lastTouchDist = 0;
|
|
2708
|
+
}
|
|
2709
|
+
if (e.touches.length === 0) {
|
|
2710
|
+
isDragging = false;
|
|
2711
|
+
img.style.transition = 'transform 0.2s ease';
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
2714
|
+
const close = () => {
|
|
2715
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
2716
|
+
overlay.remove();
|
|
2717
|
+
};
|
|
2718
|
+
// Only close on backdrop click when not zoomed (prevent accidental close while panning)
|
|
2719
|
+
overlay.addEventListener('click', (e) => {
|
|
2720
|
+
if (e.target === overlay && scale <= 1)
|
|
2721
|
+
close();
|
|
2722
|
+
});
|
|
2723
|
+
downloadBtn.addEventListener('click', (e) => e.stopPropagation());
|
|
2724
|
+
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); close(); });
|
|
2725
|
+
// Close on Escape
|
|
2726
|
+
const handleKeyDown = (e) => {
|
|
2727
|
+
if (e.key === 'Escape')
|
|
2728
|
+
close();
|
|
2729
|
+
};
|
|
2730
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
2731
|
+
overlay.appendChild(closeBtn);
|
|
2732
|
+
overlay.appendChild(downloadBtn);
|
|
2733
|
+
overlay.appendChild(img);
|
|
2734
|
+
document.body.appendChild(overlay);
|
|
2485
2735
|
}
|
|
2486
2736
|
/**
|
|
2487
2737
|
* Initialize the SDK and render widget
|
|
@@ -2507,6 +2757,13 @@ class WeldSDK {
|
|
|
2507
2757
|
this.logger.info('WeldSDK ready');
|
|
2508
2758
|
// Call onReady callback
|
|
2509
2759
|
this.config.onReady?.();
|
|
2760
|
+
// Start tracking page URL changes
|
|
2761
|
+
this.startPageTracking();
|
|
2762
|
+
// Auto-open if widget was previously open (persisted in sessionStorage)
|
|
2763
|
+
if (this.wasOpen()) {
|
|
2764
|
+
this.logger.info('Restoring previously open widget from sessionStorage');
|
|
2765
|
+
this.open();
|
|
2766
|
+
}
|
|
2510
2767
|
}
|
|
2511
2768
|
catch (error) {
|
|
2512
2769
|
this.status = SDKStatus.ERROR;
|
|
@@ -2585,6 +2842,58 @@ class WeldSDK {
|
|
|
2585
2842
|
isReady() {
|
|
2586
2843
|
return this.status === SDKStatus.READY;
|
|
2587
2844
|
}
|
|
2845
|
+
/**
|
|
2846
|
+
* Update callbacks on an existing instance (used by singleton reuse)
|
|
2847
|
+
*/
|
|
2848
|
+
updateCallbacks(config) {
|
|
2849
|
+
if (config.onReady !== undefined)
|
|
2850
|
+
this.config.onReady = config.onReady;
|
|
2851
|
+
if (config.onOpen !== undefined)
|
|
2852
|
+
this.config.onOpen = config.onOpen;
|
|
2853
|
+
if (config.onClose !== undefined)
|
|
2854
|
+
this.config.onClose = config.onClose;
|
|
2855
|
+
if (config.onError !== undefined)
|
|
2856
|
+
this.config.onError = config.onError;
|
|
2857
|
+
if (config.onDestroy !== undefined)
|
|
2858
|
+
this.config.onDestroy = config.onDestroy;
|
|
2859
|
+
if (config.onMinimize !== undefined)
|
|
2860
|
+
this.config.onMinimize = config.onMinimize;
|
|
2861
|
+
if (config.onMaximize !== undefined)
|
|
2862
|
+
this.config.onMaximize = config.onMaximize;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Persist open/closed state to sessionStorage
|
|
2866
|
+
*/
|
|
2867
|
+
persistOpenState(isOpen) {
|
|
2868
|
+
try {
|
|
2869
|
+
sessionStorage.setItem(openStateKey(this.config.widgetId), isOpen ? 'true' : 'false');
|
|
2870
|
+
}
|
|
2871
|
+
catch {
|
|
2872
|
+
// sessionStorage might not be available
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Clear persisted state from sessionStorage
|
|
2877
|
+
*/
|
|
2878
|
+
clearPersistedState() {
|
|
2879
|
+
try {
|
|
2880
|
+
sessionStorage.removeItem(openStateKey(this.config.widgetId));
|
|
2881
|
+
}
|
|
2882
|
+
catch {
|
|
2883
|
+
// sessionStorage might not be available
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
/**
|
|
2887
|
+
* Check if widget was previously open (from sessionStorage)
|
|
2888
|
+
*/
|
|
2889
|
+
wasOpen() {
|
|
2890
|
+
try {
|
|
2891
|
+
return sessionStorage.getItem(openStateKey(this.config.widgetId)) === 'true';
|
|
2892
|
+
}
|
|
2893
|
+
catch {
|
|
2894
|
+
return false;
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2588
2897
|
/**
|
|
2589
2898
|
* Open the widget
|
|
2590
2899
|
*/
|
|
@@ -2593,8 +2902,6 @@ class WeldSDK {
|
|
|
2593
2902
|
console.log('[Weld SDK] Opening widget...');
|
|
2594
2903
|
this.stateCoordinator.openWidget();
|
|
2595
2904
|
this.iframeManager.showIframe(exports.IframeType.WIDGET);
|
|
2596
|
-
this.iframeManager.showIframe(exports.IframeType.BACKDROP);
|
|
2597
|
-
// Keep launcher visible so user can click it to close the widget
|
|
2598
2905
|
// Send open message to the widget iframe
|
|
2599
2906
|
const widgetIframe = this.iframeManager.getIframe(exports.IframeType.WIDGET);
|
|
2600
2907
|
if (widgetIframe?.element?.contentWindow) {
|
|
@@ -2605,6 +2912,7 @@ class WeldSDK {
|
|
|
2605
2912
|
if (launcherIframe?.element?.contentWindow) {
|
|
2606
2913
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2607
2914
|
}
|
|
2915
|
+
this.persistOpenState(true);
|
|
2608
2916
|
this.config.onOpen?.();
|
|
2609
2917
|
}
|
|
2610
2918
|
/**
|
|
@@ -2615,8 +2923,6 @@ class WeldSDK {
|
|
|
2615
2923
|
console.log('[Weld SDK] Closing widget...');
|
|
2616
2924
|
this.stateCoordinator.closeWidget();
|
|
2617
2925
|
this.iframeManager.hideIframe(exports.IframeType.WIDGET);
|
|
2618
|
-
this.iframeManager.hideIframe(exports.IframeType.BACKDROP);
|
|
2619
|
-
// Launcher stays visible
|
|
2620
2926
|
// Send close message to the widget iframe
|
|
2621
2927
|
const widgetIframe = this.iframeManager.getIframe(exports.IframeType.WIDGET);
|
|
2622
2928
|
if (widgetIframe?.element?.contentWindow) {
|
|
@@ -2627,6 +2933,7 @@ class WeldSDK {
|
|
|
2627
2933
|
if (launcherIframe?.element?.contentWindow) {
|
|
2628
2934
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2629
2935
|
}
|
|
2936
|
+
this.persistOpenState(false);
|
|
2630
2937
|
this.config.onClose?.();
|
|
2631
2938
|
}
|
|
2632
2939
|
/**
|
|
@@ -2815,6 +3122,8 @@ class WeldSDK {
|
|
|
2815
3122
|
});
|
|
2816
3123
|
// Broadcast logout to iframes
|
|
2817
3124
|
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
3125
|
+
// Clear persisted widget state
|
|
3126
|
+
this.clearPersistedState();
|
|
2818
3127
|
// Clear any stored session data
|
|
2819
3128
|
try {
|
|
2820
3129
|
const prefix = 'weld-';
|
|
@@ -2956,6 +3265,63 @@ class WeldSDK {
|
|
|
2956
3265
|
this.logger.setLevel('warn');
|
|
2957
3266
|
this.logger.info('Debug mode disabled');
|
|
2958
3267
|
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Send a page change message to the widget iframe
|
|
3270
|
+
*/
|
|
3271
|
+
sendPageChange(url, title) {
|
|
3272
|
+
const widgetIframe = this.iframeManager.getIframe(exports.IframeType.WIDGET);
|
|
3273
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
3274
|
+
widgetIframe.element.contentWindow.postMessage({
|
|
3275
|
+
type: 'weld:page:change',
|
|
3276
|
+
url,
|
|
3277
|
+
title,
|
|
3278
|
+
timestamp: Date.now(),
|
|
3279
|
+
}, '*');
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
/**
|
|
3283
|
+
* Start tracking page URL changes (SPA navigations + popstate)
|
|
3284
|
+
*/
|
|
3285
|
+
startPageTracking() {
|
|
3286
|
+
let lastUrl = window.location.href;
|
|
3287
|
+
let debounceTimer = null;
|
|
3288
|
+
const notifyChange = () => {
|
|
3289
|
+
const currentUrl = window.location.href;
|
|
3290
|
+
if (currentUrl !== lastUrl) {
|
|
3291
|
+
lastUrl = currentUrl;
|
|
3292
|
+
this.sendPageChange(currentUrl, document.title);
|
|
3293
|
+
}
|
|
3294
|
+
};
|
|
3295
|
+
const debouncedNotify = () => {
|
|
3296
|
+
if (debounceTimer)
|
|
3297
|
+
clearTimeout(debounceTimer);
|
|
3298
|
+
debounceTimer = setTimeout(notifyChange, 300);
|
|
3299
|
+
};
|
|
3300
|
+
// Send initial page
|
|
3301
|
+
this.sendPageChange(window.location.href, document.title);
|
|
3302
|
+
// Monkey-patch history.pushState and history.replaceState
|
|
3303
|
+
const origPushState = history.pushState.bind(history);
|
|
3304
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
3305
|
+
history.pushState = function (...args) {
|
|
3306
|
+
origPushState(...args);
|
|
3307
|
+
debouncedNotify();
|
|
3308
|
+
};
|
|
3309
|
+
history.replaceState = function (...args) {
|
|
3310
|
+
origReplaceState(...args);
|
|
3311
|
+
debouncedNotify();
|
|
3312
|
+
};
|
|
3313
|
+
// Listen for popstate (browser back/forward)
|
|
3314
|
+
const handlePopstate = () => debouncedNotify();
|
|
3315
|
+
window.addEventListener('popstate', handlePopstate);
|
|
3316
|
+
// Store cleanup
|
|
3317
|
+
this.pageTrackingCleanup = () => {
|
|
3318
|
+
if (debounceTimer)
|
|
3319
|
+
clearTimeout(debounceTimer);
|
|
3320
|
+
window.removeEventListener('popstate', handlePopstate);
|
|
3321
|
+
history.pushState = origPushState;
|
|
3322
|
+
history.replaceState = origReplaceState;
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
2959
3325
|
/**
|
|
2960
3326
|
* Ensure SDK is ready before operation
|
|
2961
3327
|
*/
|
|
@@ -2964,11 +3330,26 @@ class WeldSDK {
|
|
|
2964
3330
|
throw new Error('SDK not ready. Call init() first.');
|
|
2965
3331
|
}
|
|
2966
3332
|
}
|
|
3333
|
+
/**
|
|
3334
|
+
* Detach from the current component lifecycle without destroying the widget.
|
|
3335
|
+
* Use this as a React useEffect cleanup — the widget stays alive across navigations.
|
|
3336
|
+
*/
|
|
3337
|
+
detach() {
|
|
3338
|
+
// No-op: widget stays alive in the singleton registry
|
|
3339
|
+
this.logger.debug('WeldSDK detached (no-op, widget stays alive)');
|
|
3340
|
+
}
|
|
2967
3341
|
/**
|
|
2968
3342
|
* Destroy SDK and cleanup
|
|
2969
3343
|
*/
|
|
2970
3344
|
destroy() {
|
|
2971
3345
|
this.logger.info('Destroying WeldSDK');
|
|
3346
|
+
// Remove from singleton registry
|
|
3347
|
+
sdkRegistry.delete(this.config.widgetId);
|
|
3348
|
+
// Clear persisted state
|
|
3349
|
+
this.clearPersistedState();
|
|
3350
|
+
// Stop page tracking
|
|
3351
|
+
this.pageTrackingCleanup?.();
|
|
3352
|
+
this.pageTrackingCleanup = null;
|
|
2972
3353
|
// Remove event listener using bound handler
|
|
2973
3354
|
window.removeEventListener('message', this.boundHandleLauncherClick);
|
|
2974
3355
|
// Unsubscribe from all message broker subscriptions
|
|
@@ -2988,13 +3369,33 @@ class WeldSDK {
|
|
|
2988
3369
|
}
|
|
2989
3370
|
}
|
|
2990
3371
|
/**
|
|
2991
|
-
* Create and initialize WeldSDK instance
|
|
3372
|
+
* Create and initialize WeldSDK instance.
|
|
3373
|
+
* Uses singleton pattern — if an instance for the same widgetId already exists
|
|
3374
|
+
* and is not destroyed, updates callbacks and returns the existing instance.
|
|
2992
3375
|
*/
|
|
2993
3376
|
async function createWeldSDK(config) {
|
|
3377
|
+
const widgetId = config.widgetId;
|
|
3378
|
+
// Check for existing, non-destroyed instance
|
|
3379
|
+
const existing = sdkRegistry.get(widgetId);
|
|
3380
|
+
if (existing && existing.getStatus() !== 'destroyed') {
|
|
3381
|
+
existing.updateCallbacks(config);
|
|
3382
|
+
return existing;
|
|
3383
|
+
}
|
|
2994
3384
|
const sdk = new WeldSDK(config);
|
|
3385
|
+
sdkRegistry.set(widgetId, sdk);
|
|
2995
3386
|
await sdk.init();
|
|
2996
3387
|
return sdk;
|
|
2997
3388
|
}
|
|
3389
|
+
/**
|
|
3390
|
+
* Explicitly destroy a WeldSDK instance by widgetId.
|
|
3391
|
+
* Use this for logout or when you need to fully remove the widget.
|
|
3392
|
+
*/
|
|
3393
|
+
function destroyWeldSDK(widgetId) {
|
|
3394
|
+
const sdk = sdkRegistry.get(widgetId);
|
|
3395
|
+
if (sdk) {
|
|
3396
|
+
sdk.destroy();
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
2998
3399
|
|
|
2999
3400
|
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
3000
3401
|
exports.HelpdeskWidget = WeldSDK;
|
|
@@ -3013,6 +3414,8 @@ exports.deepClone = deepClone;
|
|
|
3013
3414
|
exports.deepMerge = deepMerge;
|
|
3014
3415
|
exports.default = WeldSDK;
|
|
3015
3416
|
exports.defaultLogger = defaultLogger;
|
|
3417
|
+
exports.destroyHelpdeskWidget = destroyWeldSDK;
|
|
3418
|
+
exports.destroyWeldSDK = destroyWeldSDK;
|
|
3016
3419
|
exports.formatFileSize = formatFileSize;
|
|
3017
3420
|
exports.getStateValue = getStateValue;
|
|
3018
3421
|
exports.hasRequiredProperties = hasRequiredProperties;
|