@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/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,20 +472,27 @@ 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
|
|
514
483
|
const container = document.createElement('div');
|
|
515
484
|
container.className = 'weld-launcher-frame';
|
|
516
485
|
container.setAttribute('data-state', 'visible');
|
|
486
|
+
// Container is larger than the button to allow hover animations (scale, shadow) without clipping
|
|
487
|
+
const launcherPadding = 10;
|
|
517
488
|
container.style.cssText = `
|
|
518
489
|
position: fixed;
|
|
519
|
-
bottom: ${launcher.position.bottom};
|
|
520
|
-
right: ${launcher.position.right};
|
|
521
|
-
width: ${launcher.size};
|
|
522
|
-
height: ${launcher.size};
|
|
490
|
+
bottom: calc(${launcher.position.bottom} - ${launcherPadding}px);
|
|
491
|
+
right: calc(${launcher.position.right} - ${launcherPadding}px);
|
|
492
|
+
width: calc(${launcher.size} + ${launcherPadding * 2}px);
|
|
493
|
+
height: calc(${launcher.size} + ${launcherPadding * 2}px);
|
|
523
494
|
z-index: 2147483003;
|
|
524
|
-
pointer-events:
|
|
495
|
+
pointer-events: none;
|
|
525
496
|
display: block;
|
|
526
497
|
`;
|
|
527
498
|
// Create iframe
|
|
@@ -533,8 +504,12 @@ class IframeManager {
|
|
|
533
504
|
width: 100%;
|
|
534
505
|
height: 100%;
|
|
535
506
|
border: none;
|
|
536
|
-
background:
|
|
507
|
+
background: none;
|
|
508
|
+
color-scheme: none;
|
|
537
509
|
display: block;
|
|
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);
|
|
538
513
|
`;
|
|
539
514
|
iframe.setAttribute('allow', 'clipboard-write');
|
|
540
515
|
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
|
|
@@ -550,20 +525,36 @@ class IframeManager {
|
|
|
550
525
|
createdAt: Date.now(),
|
|
551
526
|
});
|
|
552
527
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
528
|
+
let launcherRetried = false;
|
|
553
529
|
iframe.onload = () => {
|
|
554
530
|
const metadata = this.iframes.get(IframeType.LAUNCHER);
|
|
555
531
|
if (metadata) {
|
|
556
532
|
this.logger.debug('Launcher iframe DOM loaded');
|
|
557
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
558
533
|
this.messageBroker?.setIframeDomLoaded(IframeType.LAUNCHER);
|
|
559
534
|
}
|
|
560
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
|
+
};
|
|
561
547
|
this.logger.debug('Launcher iframe created');
|
|
562
548
|
}
|
|
563
549
|
/**
|
|
564
550
|
* Create widget iframe
|
|
565
551
|
*/
|
|
566
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
|
+
}
|
|
567
558
|
const { iframes } = this.config;
|
|
568
559
|
const { widget } = iframes;
|
|
569
560
|
// Create container
|
|
@@ -644,54 +635,32 @@ class IframeManager {
|
|
|
644
635
|
createdAt: Date.now(),
|
|
645
636
|
});
|
|
646
637
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
638
|
+
let widgetRetried = false;
|
|
647
639
|
iframe.onload = () => {
|
|
648
640
|
const metadata = this.iframes.get(IframeType.WIDGET);
|
|
649
641
|
if (metadata) {
|
|
650
642
|
this.logger.debug('Widget iframe DOM loaded');
|
|
651
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
652
643
|
this.messageBroker?.setIframeDomLoaded(IframeType.WIDGET);
|
|
653
644
|
}
|
|
654
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
|
+
};
|
|
655
657
|
this.logger.debug('Widget iframe created');
|
|
656
658
|
}
|
|
657
659
|
/**
|
|
658
|
-
* Create backdrop iframe
|
|
660
|
+
* Create backdrop iframe — disabled, widget stays non-modal so users can interact with the page
|
|
659
661
|
*/
|
|
660
662
|
async createBackdropIframe() {
|
|
661
|
-
|
|
662
|
-
this.logger.debug('Backdrop disabled, skipping creation');
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
// Create container
|
|
666
|
-
const container = document.createElement('div');
|
|
667
|
-
container.className = 'weld-backdrop-frame';
|
|
668
|
-
container.setAttribute('data-state', 'hidden');
|
|
669
|
-
container.style.cssText = `
|
|
670
|
-
position: fixed;
|
|
671
|
-
top: 0;
|
|
672
|
-
left: 0;
|
|
673
|
-
right: 0;
|
|
674
|
-
bottom: 0;
|
|
675
|
-
z-index: 2147483000;
|
|
676
|
-
background: transparent;
|
|
677
|
-
pointer-events: none;
|
|
678
|
-
opacity: 0;
|
|
679
|
-
transition: opacity 200ms ease;
|
|
680
|
-
`;
|
|
681
|
-
this.appContainer?.appendChild(container);
|
|
682
|
-
// Store metadata (backdrop doesn't have an iframe, just a div)
|
|
683
|
-
// We'll create a minimal "iframe" reference for consistency
|
|
684
|
-
const dummyIframe = document.createElement('iframe');
|
|
685
|
-
dummyIframe.style.display = 'none';
|
|
686
|
-
this.iframes.set(IframeType.BACKDROP, {
|
|
687
|
-
type: IframeType.BACKDROP,
|
|
688
|
-
element: dummyIframe,
|
|
689
|
-
container,
|
|
690
|
-
ready: true, // Backdrop is always ready
|
|
691
|
-
visible: false,
|
|
692
|
-
createdAt: Date.now(),
|
|
693
|
-
});
|
|
694
|
-
this.logger.debug('Backdrop created');
|
|
663
|
+
this.logger.debug('Backdrop disabled, skipping creation');
|
|
695
664
|
}
|
|
696
665
|
/**
|
|
697
666
|
* Build iframe URL with parameters
|
|
@@ -705,12 +674,21 @@ class IframeManager {
|
|
|
705
674
|
url.searchParams.set('device', this.deviceInfo.type);
|
|
706
675
|
url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
|
|
707
676
|
url.searchParams.set('parentOrigin', window.location.origin);
|
|
677
|
+
if (this.config.testMode) {
|
|
678
|
+
url.searchParams.set('testMode', 'true');
|
|
679
|
+
}
|
|
708
680
|
return url.toString();
|
|
709
681
|
}
|
|
710
682
|
/**
|
|
711
683
|
* Setup event listeners
|
|
712
684
|
*/
|
|
713
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;
|
|
714
692
|
// Window resize - use bound handler for proper cleanup
|
|
715
693
|
window.addEventListener('resize', this.boundHandleResize);
|
|
716
694
|
// Orientation change - use bound handler for proper cleanup
|
|
@@ -813,7 +791,7 @@ class IframeManager {
|
|
|
813
791
|
iframe.container.style.transform = 'scale(1) translateY(0)';
|
|
814
792
|
}
|
|
815
793
|
// Handle mobile scroll lock
|
|
816
|
-
if (this.deviceInfo.isMobile && type === IframeType.WIDGET
|
|
794
|
+
if (this.deviceInfo.isMobile && type === IframeType.WIDGET) {
|
|
817
795
|
document.body.classList.add('weld-mobile-open');
|
|
818
796
|
}
|
|
819
797
|
// Hide launcher on mobile when widget is open (full-screen mode)
|
|
@@ -912,6 +890,8 @@ class IframeManager {
|
|
|
912
890
|
this.iframes.clear();
|
|
913
891
|
// Clear messageBroker reference
|
|
914
892
|
this.messageBroker = null;
|
|
893
|
+
// Reset guard flag
|
|
894
|
+
this.eventListenersBound = false;
|
|
915
895
|
this.logger.info('IframeManager destroyed');
|
|
916
896
|
}
|
|
917
897
|
}
|
|
@@ -979,6 +959,8 @@ var MessageType;
|
|
|
979
959
|
// Events
|
|
980
960
|
MessageType["EVENT_TRACK"] = "weld:event:track";
|
|
981
961
|
MessageType["ERROR_REPORT"] = "weld:error:report";
|
|
962
|
+
// Page tracking
|
|
963
|
+
MessageType["PAGE_CHANGE"] = "weld:page:change";
|
|
982
964
|
// API responses
|
|
983
965
|
MessageType["API_SUCCESS"] = "weld:api:success";
|
|
984
966
|
MessageType["API_ERROR"] = "weld:api:error";
|
|
@@ -1518,8 +1500,6 @@ class MessageBroker {
|
|
|
1518
1500
|
iframeType,
|
|
1519
1501
|
config: {
|
|
1520
1502
|
api: this.config.api,
|
|
1521
|
-
customization: this.config.customization,
|
|
1522
|
-
features: this.config.features,
|
|
1523
1503
|
},
|
|
1524
1504
|
};
|
|
1525
1505
|
const message = createMessage('weld:init', MessageOrigin.PARENT, initPayload);
|
|
@@ -2192,7 +2172,7 @@ class StateCoordinator {
|
|
|
2192
2172
|
}
|
|
2193
2173
|
}
|
|
2194
2174
|
|
|
2195
|
-
var version = "1.0.
|
|
2175
|
+
var version = "1.0.17";
|
|
2196
2176
|
var packageJson = {
|
|
2197
2177
|
version: version};
|
|
2198
2178
|
|
|
@@ -2200,6 +2180,16 @@ var packageJson = {
|
|
|
2200
2180
|
* Weld SDK - Main Entry Point
|
|
2201
2181
|
* Public API for the Weld helpdesk widget
|
|
2202
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
|
+
}
|
|
2203
2193
|
/**
|
|
2204
2194
|
* SDK initialization status
|
|
2205
2195
|
*/
|
|
@@ -2222,6 +2212,8 @@ class WeldSDK {
|
|
|
2222
2212
|
this.readyResolve = null;
|
|
2223
2213
|
// Subscription IDs for cleanup
|
|
2224
2214
|
this.subscriptionIds = [];
|
|
2215
|
+
// Page tracking cleanup
|
|
2216
|
+
this.pageTrackingCleanup = null;
|
|
2225
2217
|
/**
|
|
2226
2218
|
* Update user attributes (Intercom-style, with rate limiting)
|
|
2227
2219
|
* Limited to 20 calls per page load to prevent abuse
|
|
@@ -2260,6 +2252,13 @@ class WeldSDK {
|
|
|
2260
2252
|
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2261
2253
|
}
|
|
2262
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
|
+
}
|
|
2263
2262
|
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2264
2263
|
const state = this.stateCoordinator.getState();
|
|
2265
2264
|
if (state.widget.isOpen) {
|
|
@@ -2272,9 +2271,260 @@ class WeldSDK {
|
|
|
2272
2271
|
}
|
|
2273
2272
|
}
|
|
2274
2273
|
if (event.data?.type === 'weld:close') {
|
|
2274
|
+
if (this.status !== SDKStatus.READY)
|
|
2275
|
+
return;
|
|
2275
2276
|
console.log('[Weld SDK] Widget close requested');
|
|
2276
2277
|
this.close();
|
|
2277
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
|
+
}
|
|
2292
|
+
if (event.data?.type === 'weld:image:open' && event.data?.url) {
|
|
2293
|
+
this.showImageLightbox(event.data.url);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Show fullscreen image lightbox on the parent page
|
|
2298
|
+
*/
|
|
2299
|
+
showImageLightbox(url) {
|
|
2300
|
+
// Remove existing lightbox if any
|
|
2301
|
+
const existing = document.getElementById('weld-image-lightbox');
|
|
2302
|
+
if (existing)
|
|
2303
|
+
existing.remove();
|
|
2304
|
+
// Zoom / pan state
|
|
2305
|
+
let scale = 1;
|
|
2306
|
+
let translateX = 0;
|
|
2307
|
+
let translateY = 0;
|
|
2308
|
+
let isDragging = false;
|
|
2309
|
+
let dragStartX = 0;
|
|
2310
|
+
let dragStartY = 0;
|
|
2311
|
+
let lastTranslateX = 0;
|
|
2312
|
+
let lastTranslateY = 0;
|
|
2313
|
+
const applyTransform = () => {
|
|
2314
|
+
img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
|
|
2315
|
+
};
|
|
2316
|
+
const resetTransform = () => {
|
|
2317
|
+
scale = 1;
|
|
2318
|
+
translateX = 0;
|
|
2319
|
+
translateY = 0;
|
|
2320
|
+
applyTransform();
|
|
2321
|
+
img.style.cursor = 'zoom-in';
|
|
2322
|
+
};
|
|
2323
|
+
const overlay = document.createElement('div');
|
|
2324
|
+
overlay.id = 'weld-image-lightbox';
|
|
2325
|
+
overlay.style.cssText = `
|
|
2326
|
+
position: fixed;
|
|
2327
|
+
inset: 0;
|
|
2328
|
+
z-index: 2147483647;
|
|
2329
|
+
background: rgba(0, 0, 0, 0.92);
|
|
2330
|
+
display: flex;
|
|
2331
|
+
align-items: center;
|
|
2332
|
+
justify-content: center;
|
|
2333
|
+
padding: 16px;
|
|
2334
|
+
cursor: pointer;
|
|
2335
|
+
overflow: hidden;
|
|
2336
|
+
`;
|
|
2337
|
+
// Close button
|
|
2338
|
+
const closeBtn = document.createElement('button');
|
|
2339
|
+
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>`;
|
|
2340
|
+
closeBtn.style.cssText = `
|
|
2341
|
+
position: absolute;
|
|
2342
|
+
top: 16px;
|
|
2343
|
+
right: 16px;
|
|
2344
|
+
width: 40px;
|
|
2345
|
+
height: 40px;
|
|
2346
|
+
border-radius: 50%;
|
|
2347
|
+
border: none;
|
|
2348
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2349
|
+
color: white;
|
|
2350
|
+
cursor: pointer;
|
|
2351
|
+
display: flex;
|
|
2352
|
+
align-items: center;
|
|
2353
|
+
justify-content: center;
|
|
2354
|
+
transition: background 0.15s;
|
|
2355
|
+
`;
|
|
2356
|
+
closeBtn.onmouseenter = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
|
|
2357
|
+
closeBtn.onmouseleave = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
|
|
2358
|
+
// Download button
|
|
2359
|
+
const downloadBtn = document.createElement('a');
|
|
2360
|
+
downloadBtn.href = url;
|
|
2361
|
+
downloadBtn.download = '';
|
|
2362
|
+
downloadBtn.target = '_blank';
|
|
2363
|
+
downloadBtn.rel = 'noopener noreferrer';
|
|
2364
|
+
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>`;
|
|
2365
|
+
downloadBtn.style.cssText = `
|
|
2366
|
+
position: absolute;
|
|
2367
|
+
top: 16px;
|
|
2368
|
+
right: 64px;
|
|
2369
|
+
width: 40px;
|
|
2370
|
+
height: 40px;
|
|
2371
|
+
border-radius: 50%;
|
|
2372
|
+
border: none;
|
|
2373
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2374
|
+
color: white;
|
|
2375
|
+
cursor: pointer;
|
|
2376
|
+
display: flex;
|
|
2377
|
+
align-items: center;
|
|
2378
|
+
justify-content: center;
|
|
2379
|
+
transition: background 0.15s;
|
|
2380
|
+
text-decoration: none;
|
|
2381
|
+
`;
|
|
2382
|
+
downloadBtn.onmouseenter = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
|
|
2383
|
+
downloadBtn.onmouseleave = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
|
|
2384
|
+
// Image
|
|
2385
|
+
const img = document.createElement('img');
|
|
2386
|
+
img.src = url;
|
|
2387
|
+
img.alt = 'Full size';
|
|
2388
|
+
img.draggable = false;
|
|
2389
|
+
img.style.cssText = `
|
|
2390
|
+
max-width: 100%;
|
|
2391
|
+
max-height: 100%;
|
|
2392
|
+
object-fit: contain;
|
|
2393
|
+
border-radius: 8px;
|
|
2394
|
+
cursor: zoom-in;
|
|
2395
|
+
transition: transform 0.2s ease;
|
|
2396
|
+
user-select: none;
|
|
2397
|
+
`;
|
|
2398
|
+
// Click to toggle zoom
|
|
2399
|
+
img.addEventListener('click', (e) => {
|
|
2400
|
+
e.stopPropagation();
|
|
2401
|
+
if (scale === 1) {
|
|
2402
|
+
// Zoom in to 2.5x centered on click position
|
|
2403
|
+
const rect = img.getBoundingClientRect();
|
|
2404
|
+
const clickX = e.clientX - rect.left - rect.width / 2;
|
|
2405
|
+
const clickY = e.clientY - rect.top - rect.height / 2;
|
|
2406
|
+
scale = 2.5;
|
|
2407
|
+
translateX = -clickX * 1.5;
|
|
2408
|
+
translateY = -clickY * 1.5;
|
|
2409
|
+
applyTransform();
|
|
2410
|
+
img.style.cursor = 'zoom-out';
|
|
2411
|
+
}
|
|
2412
|
+
else {
|
|
2413
|
+
// Zoom out - reset
|
|
2414
|
+
resetTransform();
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2417
|
+
// Mouse wheel zoom
|
|
2418
|
+
overlay.addEventListener('wheel', (e) => {
|
|
2419
|
+
e.preventDefault();
|
|
2420
|
+
const delta = e.deltaY > 0 ? -0.25 : 0.25;
|
|
2421
|
+
const newScale = Math.min(Math.max(scale + delta, 1), 5);
|
|
2422
|
+
if (newScale === 1) {
|
|
2423
|
+
resetTransform();
|
|
2424
|
+
}
|
|
2425
|
+
else {
|
|
2426
|
+
scale = newScale;
|
|
2427
|
+
applyTransform();
|
|
2428
|
+
img.style.cursor = 'zoom-out';
|
|
2429
|
+
}
|
|
2430
|
+
}, { passive: false });
|
|
2431
|
+
// Drag to pan when zoomed
|
|
2432
|
+
img.addEventListener('mousedown', (e) => {
|
|
2433
|
+
if (scale <= 1)
|
|
2434
|
+
return;
|
|
2435
|
+
e.preventDefault();
|
|
2436
|
+
isDragging = true;
|
|
2437
|
+
dragStartX = e.clientX;
|
|
2438
|
+
dragStartY = e.clientY;
|
|
2439
|
+
lastTranslateX = translateX;
|
|
2440
|
+
lastTranslateY = translateY;
|
|
2441
|
+
img.style.cursor = 'grabbing';
|
|
2442
|
+
img.style.transition = 'none';
|
|
2443
|
+
});
|
|
2444
|
+
window.addEventListener('mousemove', (e) => {
|
|
2445
|
+
if (!isDragging)
|
|
2446
|
+
return;
|
|
2447
|
+
translateX = lastTranslateX + (e.clientX - dragStartX);
|
|
2448
|
+
translateY = lastTranslateY + (e.clientY - dragStartY);
|
|
2449
|
+
applyTransform();
|
|
2450
|
+
});
|
|
2451
|
+
window.addEventListener('mouseup', () => {
|
|
2452
|
+
if (!isDragging)
|
|
2453
|
+
return;
|
|
2454
|
+
isDragging = false;
|
|
2455
|
+
img.style.cursor = scale > 1 ? 'zoom-out' : 'zoom-in';
|
|
2456
|
+
img.style.transition = 'transform 0.2s ease';
|
|
2457
|
+
});
|
|
2458
|
+
// Touch: pinch to zoom + drag to pan
|
|
2459
|
+
let lastTouchDist = 0;
|
|
2460
|
+
let lastTouchScale = 1;
|
|
2461
|
+
overlay.addEventListener('touchstart', (e) => {
|
|
2462
|
+
if (e.touches.length === 2) {
|
|
2463
|
+
e.preventDefault();
|
|
2464
|
+
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
|
2465
|
+
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
|
2466
|
+
lastTouchDist = Math.hypot(dx, dy);
|
|
2467
|
+
lastTouchScale = scale;
|
|
2468
|
+
}
|
|
2469
|
+
else if (e.touches.length === 1 && scale > 1) {
|
|
2470
|
+
isDragging = true;
|
|
2471
|
+
dragStartX = e.touches[0].clientX;
|
|
2472
|
+
dragStartY = e.touches[0].clientY;
|
|
2473
|
+
lastTranslateX = translateX;
|
|
2474
|
+
lastTranslateY = translateY;
|
|
2475
|
+
img.style.transition = 'none';
|
|
2476
|
+
}
|
|
2477
|
+
}, { passive: false });
|
|
2478
|
+
overlay.addEventListener('touchmove', (e) => {
|
|
2479
|
+
if (e.touches.length === 2) {
|
|
2480
|
+
e.preventDefault();
|
|
2481
|
+
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
|
2482
|
+
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
|
2483
|
+
const dist = Math.hypot(dx, dy);
|
|
2484
|
+
scale = Math.min(Math.max(lastTouchScale * (dist / lastTouchDist), 1), 5);
|
|
2485
|
+
if (scale === 1) {
|
|
2486
|
+
translateX = 0;
|
|
2487
|
+
translateY = 0;
|
|
2488
|
+
}
|
|
2489
|
+
applyTransform();
|
|
2490
|
+
}
|
|
2491
|
+
else if (e.touches.length === 1 && isDragging) {
|
|
2492
|
+
e.preventDefault();
|
|
2493
|
+
translateX = lastTranslateX + (e.touches[0].clientX - dragStartX);
|
|
2494
|
+
translateY = lastTranslateY + (e.touches[0].clientY - dragStartY);
|
|
2495
|
+
applyTransform();
|
|
2496
|
+
}
|
|
2497
|
+
}, { passive: false });
|
|
2498
|
+
overlay.addEventListener('touchend', (e) => {
|
|
2499
|
+
if (e.touches.length < 2) {
|
|
2500
|
+
lastTouchDist = 0;
|
|
2501
|
+
}
|
|
2502
|
+
if (e.touches.length === 0) {
|
|
2503
|
+
isDragging = false;
|
|
2504
|
+
img.style.transition = 'transform 0.2s ease';
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
const close = () => {
|
|
2508
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
2509
|
+
overlay.remove();
|
|
2510
|
+
};
|
|
2511
|
+
// Only close on backdrop click when not zoomed (prevent accidental close while panning)
|
|
2512
|
+
overlay.addEventListener('click', (e) => {
|
|
2513
|
+
if (e.target === overlay && scale <= 1)
|
|
2514
|
+
close();
|
|
2515
|
+
});
|
|
2516
|
+
downloadBtn.addEventListener('click', (e) => e.stopPropagation());
|
|
2517
|
+
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); close(); });
|
|
2518
|
+
// Close on Escape
|
|
2519
|
+
const handleKeyDown = (e) => {
|
|
2520
|
+
if (e.key === 'Escape')
|
|
2521
|
+
close();
|
|
2522
|
+
};
|
|
2523
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
2524
|
+
overlay.appendChild(closeBtn);
|
|
2525
|
+
overlay.appendChild(downloadBtn);
|
|
2526
|
+
overlay.appendChild(img);
|
|
2527
|
+
document.body.appendChild(overlay);
|
|
2278
2528
|
}
|
|
2279
2529
|
/**
|
|
2280
2530
|
* Initialize the SDK and render widget
|
|
@@ -2300,6 +2550,13 @@ class WeldSDK {
|
|
|
2300
2550
|
this.logger.info('WeldSDK ready');
|
|
2301
2551
|
// Call onReady callback
|
|
2302
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
|
+
}
|
|
2303
2560
|
}
|
|
2304
2561
|
catch (error) {
|
|
2305
2562
|
this.status = SDKStatus.ERROR;
|
|
@@ -2378,6 +2635,58 @@ class WeldSDK {
|
|
|
2378
2635
|
isReady() {
|
|
2379
2636
|
return this.status === SDKStatus.READY;
|
|
2380
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
|
+
}
|
|
2381
2690
|
/**
|
|
2382
2691
|
* Open the widget
|
|
2383
2692
|
*/
|
|
@@ -2386,8 +2695,6 @@ class WeldSDK {
|
|
|
2386
2695
|
console.log('[Weld SDK] Opening widget...');
|
|
2387
2696
|
this.stateCoordinator.openWidget();
|
|
2388
2697
|
this.iframeManager.showIframe(IframeType.WIDGET);
|
|
2389
|
-
this.iframeManager.showIframe(IframeType.BACKDROP);
|
|
2390
|
-
// Keep launcher visible so user can click it to close the widget
|
|
2391
2698
|
// Send open message to the widget iframe
|
|
2392
2699
|
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
2393
2700
|
if (widgetIframe?.element?.contentWindow) {
|
|
@@ -2398,6 +2705,7 @@ class WeldSDK {
|
|
|
2398
2705
|
if (launcherIframe?.element?.contentWindow) {
|
|
2399
2706
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2400
2707
|
}
|
|
2708
|
+
this.persistOpenState(true);
|
|
2401
2709
|
this.config.onOpen?.();
|
|
2402
2710
|
}
|
|
2403
2711
|
/**
|
|
@@ -2408,8 +2716,6 @@ class WeldSDK {
|
|
|
2408
2716
|
console.log('[Weld SDK] Closing widget...');
|
|
2409
2717
|
this.stateCoordinator.closeWidget();
|
|
2410
2718
|
this.iframeManager.hideIframe(IframeType.WIDGET);
|
|
2411
|
-
this.iframeManager.hideIframe(IframeType.BACKDROP);
|
|
2412
|
-
// Launcher stays visible
|
|
2413
2719
|
// Send close message to the widget iframe
|
|
2414
2720
|
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
2415
2721
|
if (widgetIframe?.element?.contentWindow) {
|
|
@@ -2420,6 +2726,7 @@ class WeldSDK {
|
|
|
2420
2726
|
if (launcherIframe?.element?.contentWindow) {
|
|
2421
2727
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2422
2728
|
}
|
|
2729
|
+
this.persistOpenState(false);
|
|
2423
2730
|
this.config.onClose?.();
|
|
2424
2731
|
}
|
|
2425
2732
|
/**
|
|
@@ -2608,6 +2915,8 @@ class WeldSDK {
|
|
|
2608
2915
|
});
|
|
2609
2916
|
// Broadcast logout to iframes
|
|
2610
2917
|
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
2918
|
+
// Clear persisted widget state
|
|
2919
|
+
this.clearPersistedState();
|
|
2611
2920
|
// Clear any stored session data
|
|
2612
2921
|
try {
|
|
2613
2922
|
const prefix = 'weld-';
|
|
@@ -2749,6 +3058,63 @@ class WeldSDK {
|
|
|
2749
3058
|
this.logger.setLevel('warn');
|
|
2750
3059
|
this.logger.info('Debug mode disabled');
|
|
2751
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
|
+
}
|
|
2752
3118
|
/**
|
|
2753
3119
|
* Ensure SDK is ready before operation
|
|
2754
3120
|
*/
|
|
@@ -2757,11 +3123,26 @@ class WeldSDK {
|
|
|
2757
3123
|
throw new Error('SDK not ready. Call init() first.');
|
|
2758
3124
|
}
|
|
2759
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
|
+
}
|
|
2760
3134
|
/**
|
|
2761
3135
|
* Destroy SDK and cleanup
|
|
2762
3136
|
*/
|
|
2763
3137
|
destroy() {
|
|
2764
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;
|
|
2765
3146
|
// Remove event listener using bound handler
|
|
2766
3147
|
window.removeEventListener('message', this.boundHandleLauncherClick);
|
|
2767
3148
|
// Unsubscribe from all message broker subscriptions
|