@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.esm.js
CHANGED
|
@@ -33,32 +33,6 @@ const DEFAULT_CONFIG = {
|
|
|
33
33
|
closeOnClick: true,
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
|
-
customization: {
|
|
37
|
-
primaryColor: '#000000',
|
|
38
|
-
accentColor: '#3b82f6',
|
|
39
|
-
backgroundColor: '#ffffff',
|
|
40
|
-
textColor: '#111827',
|
|
41
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
42
|
-
fontSize: '14px',
|
|
43
|
-
borderRadius: '12px',
|
|
44
|
-
},
|
|
45
|
-
features: {
|
|
46
|
-
attachments: true,
|
|
47
|
-
reactions: true,
|
|
48
|
-
typing: true,
|
|
49
|
-
readReceipts: true,
|
|
50
|
-
offlineMode: false,
|
|
51
|
-
fileUpload: true,
|
|
52
|
-
imageUpload: true,
|
|
53
|
-
voiceMessages: false,
|
|
54
|
-
videoMessages: false,
|
|
55
|
-
},
|
|
56
|
-
mobile: {
|
|
57
|
-
fullScreen: true,
|
|
58
|
-
scrollLock: true,
|
|
59
|
-
keyboardHandling: 'auto',
|
|
60
|
-
safeAreaInsets: true,
|
|
61
|
-
},
|
|
62
36
|
auth: {
|
|
63
37
|
enabled: true,
|
|
64
38
|
mode: 'anonymous',
|
|
@@ -102,6 +76,7 @@ function resolveConfig(config) {
|
|
|
102
76
|
validateConfig(config);
|
|
103
77
|
return {
|
|
104
78
|
widgetId: config.widgetId,
|
|
79
|
+
testMode: config.testMode,
|
|
105
80
|
api: {
|
|
106
81
|
...DEFAULT_CONFIG.api,
|
|
107
82
|
widgetId: config.widgetId,
|
|
@@ -131,18 +106,6 @@ function resolveConfig(config) {
|
|
|
131
106
|
...config.iframes?.backdrop,
|
|
132
107
|
},
|
|
133
108
|
},
|
|
134
|
-
customization: {
|
|
135
|
-
...DEFAULT_CONFIG.customization,
|
|
136
|
-
...config.customization,
|
|
137
|
-
},
|
|
138
|
-
features: {
|
|
139
|
-
...DEFAULT_CONFIG.features,
|
|
140
|
-
...config.features,
|
|
141
|
-
},
|
|
142
|
-
mobile: {
|
|
143
|
-
...DEFAULT_CONFIG.mobile,
|
|
144
|
-
...config.mobile,
|
|
145
|
-
},
|
|
146
109
|
auth: {
|
|
147
110
|
...DEFAULT_CONFIG.auth,
|
|
148
111
|
...config.auth,
|
|
@@ -385,6 +348,8 @@ class IframeManager {
|
|
|
385
348
|
this.modalContainer = null;
|
|
386
349
|
this.styleElement = null;
|
|
387
350
|
this.messageBroker = null;
|
|
351
|
+
// Guard flag to prevent double-binding event listeners
|
|
352
|
+
this.eventListenersBound = false;
|
|
388
353
|
this.config = config;
|
|
389
354
|
this.logger = new Logger(config.logging);
|
|
390
355
|
this.deviceInfo = detectDevice();
|
|
@@ -424,18 +389,27 @@ class IframeManager {
|
|
|
424
389
|
}
|
|
425
390
|
/**
|
|
426
391
|
* Create root container structure
|
|
392
|
+
* Reuses existing container if it has the same widgetId (singleton behavior)
|
|
427
393
|
*/
|
|
428
394
|
createRootContainer() {
|
|
429
|
-
|
|
430
|
-
let existingContainer = document.getElementById('weld-container');
|
|
395
|
+
const existingContainer = document.getElementById('weld-container');
|
|
431
396
|
if (existingContainer) {
|
|
432
|
-
|
|
397
|
+
// Reuse if same widgetId
|
|
398
|
+
if (existingContainer.getAttribute('data-widget-id') === this.config.widgetId) {
|
|
399
|
+
this.logger.debug('Reusing existing root container');
|
|
400
|
+
this.rootContainer = existingContainer;
|
|
401
|
+
this.appContainer = existingContainer.querySelector('.weld-app');
|
|
402
|
+
this.modalContainer = document.getElementById('weld-modal-container');
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
this.logger.warn('Weld container already exists with different widgetId, removing old instance');
|
|
433
406
|
existingContainer.remove();
|
|
434
407
|
}
|
|
435
408
|
// Create root container
|
|
436
409
|
this.rootContainer = document.createElement('div');
|
|
437
410
|
this.rootContainer.id = 'weld-container';
|
|
438
411
|
this.rootContainer.className = 'weld-namespace';
|
|
412
|
+
this.rootContainer.setAttribute('data-widget-id', this.config.widgetId);
|
|
439
413
|
// Create app container
|
|
440
414
|
this.appContainer = document.createElement('div');
|
|
441
415
|
this.appContainer.className = 'weld-app';
|
|
@@ -470,17 +444,7 @@ class IframeManager {
|
|
|
470
444
|
* Generate CSS for containers
|
|
471
445
|
*/
|
|
472
446
|
generateCSS() {
|
|
473
|
-
const { customization } = this.config;
|
|
474
447
|
return `
|
|
475
|
-
/* Weld Container */
|
|
476
|
-
#weld-container {
|
|
477
|
-
--weld-color-primary: ${customization.primaryColor};
|
|
478
|
-
--weld-color-accent: ${customization.accentColor};
|
|
479
|
-
--weld-font-family: ${customization.fontFamily};
|
|
480
|
-
--weld-font-size-base: ${customization.fontSize};
|
|
481
|
-
--weld-radius-xl: ${customization.borderRadius};
|
|
482
|
-
}
|
|
483
|
-
|
|
484
448
|
/* Import main stylesheet */
|
|
485
449
|
@import url('/styles/index.css');
|
|
486
450
|
|
|
@@ -504,20 +468,27 @@ class IframeManager {
|
|
|
504
468
|
* Create launcher iframe
|
|
505
469
|
*/
|
|
506
470
|
async createLauncherIframe() {
|
|
471
|
+
// Guard: skip if launcher iframe already exists
|
|
472
|
+
if (this.iframes.has(IframeType.LAUNCHER)) {
|
|
473
|
+
this.logger.debug('Launcher iframe already exists, skipping creation');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
507
476
|
const { iframes } = this.config;
|
|
508
477
|
const { launcher } = iframes;
|
|
509
478
|
// Create container
|
|
510
479
|
const container = document.createElement('div');
|
|
511
480
|
container.className = 'weld-launcher-frame';
|
|
512
481
|
container.setAttribute('data-state', 'visible');
|
|
482
|
+
// Container is larger than the button to allow hover animations (scale, shadow) without clipping
|
|
483
|
+
const launcherPadding = 10;
|
|
513
484
|
container.style.cssText = `
|
|
514
485
|
position: fixed;
|
|
515
|
-
bottom: ${launcher.position.bottom};
|
|
516
|
-
right: ${launcher.position.right};
|
|
517
|
-
width: ${launcher.size};
|
|
518
|
-
height: ${launcher.size};
|
|
486
|
+
bottom: calc(${launcher.position.bottom} - ${launcherPadding}px);
|
|
487
|
+
right: calc(${launcher.position.right} - ${launcherPadding}px);
|
|
488
|
+
width: calc(${launcher.size} + ${launcherPadding * 2}px);
|
|
489
|
+
height: calc(${launcher.size} + ${launcherPadding * 2}px);
|
|
519
490
|
z-index: 2147483003;
|
|
520
|
-
pointer-events:
|
|
491
|
+
pointer-events: none;
|
|
521
492
|
display: block;
|
|
522
493
|
`;
|
|
523
494
|
// Create iframe
|
|
@@ -529,8 +500,12 @@ class IframeManager {
|
|
|
529
500
|
width: 100%;
|
|
530
501
|
height: 100%;
|
|
531
502
|
border: none;
|
|
532
|
-
background:
|
|
503
|
+
background: none;
|
|
504
|
+
color-scheme: none;
|
|
533
505
|
display: block;
|
|
506
|
+
pointer-events: auto;
|
|
507
|
+
border-radius: 50%;
|
|
508
|
+
filter: drop-shadow(rgba(9, 14, 21, 0.54) 0px 1px 6px) drop-shadow(rgba(9, 14, 21, 0.9) 0px 2px 32px);
|
|
534
509
|
`;
|
|
535
510
|
iframe.setAttribute('allow', 'clipboard-write');
|
|
536
511
|
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
|
|
@@ -546,20 +521,36 @@ class IframeManager {
|
|
|
546
521
|
createdAt: Date.now(),
|
|
547
522
|
});
|
|
548
523
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
524
|
+
let launcherRetried = false;
|
|
549
525
|
iframe.onload = () => {
|
|
550
526
|
const metadata = this.iframes.get(IframeType.LAUNCHER);
|
|
551
527
|
if (metadata) {
|
|
552
528
|
this.logger.debug('Launcher iframe DOM loaded');
|
|
553
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
554
529
|
this.messageBroker?.setIframeDomLoaded(IframeType.LAUNCHER);
|
|
555
530
|
}
|
|
556
531
|
};
|
|
532
|
+
iframe.onerror = () => {
|
|
533
|
+
this.logger.error('Launcher iframe failed to load');
|
|
534
|
+
if (!launcherRetried) {
|
|
535
|
+
launcherRetried = true;
|
|
536
|
+
this.logger.info('Retrying launcher iframe load...');
|
|
537
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(launcher.url); }, 3000);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
this.config.onError?.(new Error('Failed to load widget launcher'));
|
|
541
|
+
}
|
|
542
|
+
};
|
|
557
543
|
this.logger.debug('Launcher iframe created');
|
|
558
544
|
}
|
|
559
545
|
/**
|
|
560
546
|
* Create widget iframe
|
|
561
547
|
*/
|
|
562
548
|
async createWidgetIframe() {
|
|
549
|
+
// Guard: skip if widget iframe already exists
|
|
550
|
+
if (this.iframes.has(IframeType.WIDGET)) {
|
|
551
|
+
this.logger.debug('Widget iframe already exists, skipping creation');
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
563
554
|
const { iframes } = this.config;
|
|
564
555
|
const { widget } = iframes;
|
|
565
556
|
// Create container
|
|
@@ -640,54 +631,32 @@ class IframeManager {
|
|
|
640
631
|
createdAt: Date.now(),
|
|
641
632
|
});
|
|
642
633
|
// When DOM loads, notify MessageBroker to send weld:init
|
|
634
|
+
let widgetRetried = false;
|
|
643
635
|
iframe.onload = () => {
|
|
644
636
|
const metadata = this.iframes.get(IframeType.WIDGET);
|
|
645
637
|
if (metadata) {
|
|
646
638
|
this.logger.debug('Widget iframe DOM loaded');
|
|
647
|
-
// Notify MessageBroker that DOM is loaded (triggers weld:init)
|
|
648
639
|
this.messageBroker?.setIframeDomLoaded(IframeType.WIDGET);
|
|
649
640
|
}
|
|
650
641
|
};
|
|
642
|
+
iframe.onerror = () => {
|
|
643
|
+
this.logger.error('Widget iframe failed to load');
|
|
644
|
+
if (!widgetRetried) {
|
|
645
|
+
widgetRetried = true;
|
|
646
|
+
this.logger.info('Retrying widget iframe load...');
|
|
647
|
+
setTimeout(() => { iframe.src = this.buildIframeUrl(widget.url); }, 3000);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
this.config.onError?.(new Error('Failed to load widget'));
|
|
651
|
+
}
|
|
652
|
+
};
|
|
651
653
|
this.logger.debug('Widget iframe created');
|
|
652
654
|
}
|
|
653
655
|
/**
|
|
654
|
-
* Create backdrop iframe
|
|
656
|
+
* Create backdrop iframe — disabled, widget stays non-modal so users can interact with the page
|
|
655
657
|
*/
|
|
656
658
|
async createBackdropIframe() {
|
|
657
|
-
|
|
658
|
-
this.logger.debug('Backdrop disabled, skipping creation');
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
// Create container
|
|
662
|
-
const container = document.createElement('div');
|
|
663
|
-
container.className = 'weld-backdrop-frame';
|
|
664
|
-
container.setAttribute('data-state', 'hidden');
|
|
665
|
-
container.style.cssText = `
|
|
666
|
-
position: fixed;
|
|
667
|
-
top: 0;
|
|
668
|
-
left: 0;
|
|
669
|
-
right: 0;
|
|
670
|
-
bottom: 0;
|
|
671
|
-
z-index: 2147483000;
|
|
672
|
-
background: transparent;
|
|
673
|
-
pointer-events: none;
|
|
674
|
-
opacity: 0;
|
|
675
|
-
transition: opacity 200ms ease;
|
|
676
|
-
`;
|
|
677
|
-
this.appContainer?.appendChild(container);
|
|
678
|
-
// Store metadata (backdrop doesn't have an iframe, just a div)
|
|
679
|
-
// We'll create a minimal "iframe" reference for consistency
|
|
680
|
-
const dummyIframe = document.createElement('iframe');
|
|
681
|
-
dummyIframe.style.display = 'none';
|
|
682
|
-
this.iframes.set(IframeType.BACKDROP, {
|
|
683
|
-
type: IframeType.BACKDROP,
|
|
684
|
-
element: dummyIframe,
|
|
685
|
-
container,
|
|
686
|
-
ready: true, // Backdrop is always ready
|
|
687
|
-
visible: false,
|
|
688
|
-
createdAt: Date.now(),
|
|
689
|
-
});
|
|
690
|
-
this.logger.debug('Backdrop created');
|
|
659
|
+
this.logger.debug('Backdrop disabled, skipping creation');
|
|
691
660
|
}
|
|
692
661
|
/**
|
|
693
662
|
* Build iframe URL with parameters
|
|
@@ -701,12 +670,21 @@ class IframeManager {
|
|
|
701
670
|
url.searchParams.set('device', this.deviceInfo.type);
|
|
702
671
|
url.searchParams.set('mobile', String(this.deviceInfo.isMobile));
|
|
703
672
|
url.searchParams.set('parentOrigin', window.location.origin);
|
|
673
|
+
if (this.config.testMode) {
|
|
674
|
+
url.searchParams.set('testMode', 'true');
|
|
675
|
+
}
|
|
704
676
|
return url.toString();
|
|
705
677
|
}
|
|
706
678
|
/**
|
|
707
679
|
* Setup event listeners
|
|
708
680
|
*/
|
|
709
681
|
setupEventListeners() {
|
|
682
|
+
// Guard: prevent double-binding
|
|
683
|
+
if (this.eventListenersBound) {
|
|
684
|
+
this.logger.debug('Event listeners already bound, skipping');
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
this.eventListenersBound = true;
|
|
710
688
|
// Window resize - use bound handler for proper cleanup
|
|
711
689
|
window.addEventListener('resize', this.boundHandleResize);
|
|
712
690
|
// Orientation change - use bound handler for proper cleanup
|
|
@@ -809,7 +787,7 @@ class IframeManager {
|
|
|
809
787
|
iframe.container.style.transform = 'scale(1) translateY(0)';
|
|
810
788
|
}
|
|
811
789
|
// Handle mobile scroll lock
|
|
812
|
-
if (this.deviceInfo.isMobile && type === IframeType.WIDGET
|
|
790
|
+
if (this.deviceInfo.isMobile && type === IframeType.WIDGET) {
|
|
813
791
|
document.body.classList.add('weld-mobile-open');
|
|
814
792
|
}
|
|
815
793
|
// Hide launcher on mobile when widget is open (full-screen mode)
|
|
@@ -908,6 +886,8 @@ class IframeManager {
|
|
|
908
886
|
this.iframes.clear();
|
|
909
887
|
// Clear messageBroker reference
|
|
910
888
|
this.messageBroker = null;
|
|
889
|
+
// Reset guard flag
|
|
890
|
+
this.eventListenersBound = false;
|
|
911
891
|
this.logger.info('IframeManager destroyed');
|
|
912
892
|
}
|
|
913
893
|
}
|
|
@@ -975,6 +955,8 @@ var MessageType;
|
|
|
975
955
|
// Events
|
|
976
956
|
MessageType["EVENT_TRACK"] = "weld:event:track";
|
|
977
957
|
MessageType["ERROR_REPORT"] = "weld:error:report";
|
|
958
|
+
// Page tracking
|
|
959
|
+
MessageType["PAGE_CHANGE"] = "weld:page:change";
|
|
978
960
|
// API responses
|
|
979
961
|
MessageType["API_SUCCESS"] = "weld:api:success";
|
|
980
962
|
MessageType["API_ERROR"] = "weld:api:error";
|
|
@@ -1514,8 +1496,6 @@ class MessageBroker {
|
|
|
1514
1496
|
iframeType,
|
|
1515
1497
|
config: {
|
|
1516
1498
|
api: this.config.api,
|
|
1517
|
-
customization: this.config.customization,
|
|
1518
|
-
features: this.config.features,
|
|
1519
1499
|
},
|
|
1520
1500
|
};
|
|
1521
1501
|
const message = createMessage('weld:init', MessageOrigin.PARENT, initPayload);
|
|
@@ -2188,7 +2168,7 @@ class StateCoordinator {
|
|
|
2188
2168
|
}
|
|
2189
2169
|
}
|
|
2190
2170
|
|
|
2191
|
-
var version = "1.0.
|
|
2171
|
+
var version = "1.0.17";
|
|
2192
2172
|
var packageJson = {
|
|
2193
2173
|
version: version};
|
|
2194
2174
|
|
|
@@ -2196,6 +2176,16 @@ var packageJson = {
|
|
|
2196
2176
|
* Weld SDK - Main Entry Point
|
|
2197
2177
|
* Public API for the Weld helpdesk widget
|
|
2198
2178
|
*/
|
|
2179
|
+
/**
|
|
2180
|
+
* Module-level singleton registry keyed by widgetId
|
|
2181
|
+
*/
|
|
2182
|
+
const sdkRegistry = new Map();
|
|
2183
|
+
/**
|
|
2184
|
+
* SessionStorage key helpers
|
|
2185
|
+
*/
|
|
2186
|
+
function openStateKey(widgetId) {
|
|
2187
|
+
return `weld-widget-open-${widgetId}`;
|
|
2188
|
+
}
|
|
2199
2189
|
/**
|
|
2200
2190
|
* SDK initialization status
|
|
2201
2191
|
*/
|
|
@@ -2218,6 +2208,8 @@ class WeldSDK {
|
|
|
2218
2208
|
this.readyResolve = null;
|
|
2219
2209
|
// Subscription IDs for cleanup
|
|
2220
2210
|
this.subscriptionIds = [];
|
|
2211
|
+
// Page tracking cleanup
|
|
2212
|
+
this.pageTrackingCleanup = null;
|
|
2221
2213
|
/**
|
|
2222
2214
|
* Update user attributes (Intercom-style, with rate limiting)
|
|
2223
2215
|
* Limited to 20 calls per page load to prevent abuse
|
|
@@ -2256,6 +2248,13 @@ class WeldSDK {
|
|
|
2256
2248
|
console.log('[Weld SDK] Received message:', event.data.type);
|
|
2257
2249
|
}
|
|
2258
2250
|
if (event.data?.type === 'launcher:clicked') {
|
|
2251
|
+
if (this.status !== SDKStatus.READY) {
|
|
2252
|
+
console.log('[Weld SDK] Launcher clicked but SDK not ready yet — waiting...');
|
|
2253
|
+
this.readyPromise?.then(() => {
|
|
2254
|
+
this.handleLauncherClickMessage(event);
|
|
2255
|
+
});
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2259
2258
|
// Toggle behavior - if widget is open, close it; if closed, open it
|
|
2260
2259
|
const state = this.stateCoordinator.getState();
|
|
2261
2260
|
if (state.widget.isOpen) {
|
|
@@ -2268,9 +2267,260 @@ class WeldSDK {
|
|
|
2268
2267
|
}
|
|
2269
2268
|
}
|
|
2270
2269
|
if (event.data?.type === 'weld:close') {
|
|
2270
|
+
if (this.status !== SDKStatus.READY)
|
|
2271
|
+
return;
|
|
2271
2272
|
console.log('[Weld SDK] Widget close requested');
|
|
2272
2273
|
this.close();
|
|
2273
2274
|
}
|
|
2275
|
+
if (event.data?.type === 'weld:unread-count') {
|
|
2276
|
+
const count = event.data.count ?? 0;
|
|
2277
|
+
// Forward to launcher iframe
|
|
2278
|
+
const launcherIframe = this.iframeManager.getIframe(IframeType.LAUNCHER);
|
|
2279
|
+
if (launcherIframe?.element?.contentWindow) {
|
|
2280
|
+
launcherIframe.element.contentWindow.postMessage({
|
|
2281
|
+
type: 'weld:unread-count',
|
|
2282
|
+
count
|
|
2283
|
+
}, '*');
|
|
2284
|
+
}
|
|
2285
|
+
// Update state coordinator for external API consumers
|
|
2286
|
+
this.stateCoordinator.setBadgeCount(count);
|
|
2287
|
+
}
|
|
2288
|
+
if (event.data?.type === 'weld:image:open' && event.data?.url) {
|
|
2289
|
+
this.showImageLightbox(event.data.url);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Show fullscreen image lightbox on the parent page
|
|
2294
|
+
*/
|
|
2295
|
+
showImageLightbox(url) {
|
|
2296
|
+
// Remove existing lightbox if any
|
|
2297
|
+
const existing = document.getElementById('weld-image-lightbox');
|
|
2298
|
+
if (existing)
|
|
2299
|
+
existing.remove();
|
|
2300
|
+
// Zoom / pan state
|
|
2301
|
+
let scale = 1;
|
|
2302
|
+
let translateX = 0;
|
|
2303
|
+
let translateY = 0;
|
|
2304
|
+
let isDragging = false;
|
|
2305
|
+
let dragStartX = 0;
|
|
2306
|
+
let dragStartY = 0;
|
|
2307
|
+
let lastTranslateX = 0;
|
|
2308
|
+
let lastTranslateY = 0;
|
|
2309
|
+
const applyTransform = () => {
|
|
2310
|
+
img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
|
|
2311
|
+
};
|
|
2312
|
+
const resetTransform = () => {
|
|
2313
|
+
scale = 1;
|
|
2314
|
+
translateX = 0;
|
|
2315
|
+
translateY = 0;
|
|
2316
|
+
applyTransform();
|
|
2317
|
+
img.style.cursor = 'zoom-in';
|
|
2318
|
+
};
|
|
2319
|
+
const overlay = document.createElement('div');
|
|
2320
|
+
overlay.id = 'weld-image-lightbox';
|
|
2321
|
+
overlay.style.cssText = `
|
|
2322
|
+
position: fixed;
|
|
2323
|
+
inset: 0;
|
|
2324
|
+
z-index: 2147483647;
|
|
2325
|
+
background: rgba(0, 0, 0, 0.92);
|
|
2326
|
+
display: flex;
|
|
2327
|
+
align-items: center;
|
|
2328
|
+
justify-content: center;
|
|
2329
|
+
padding: 16px;
|
|
2330
|
+
cursor: pointer;
|
|
2331
|
+
overflow: hidden;
|
|
2332
|
+
`;
|
|
2333
|
+
// Close button
|
|
2334
|
+
const closeBtn = document.createElement('button');
|
|
2335
|
+
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>`;
|
|
2336
|
+
closeBtn.style.cssText = `
|
|
2337
|
+
position: absolute;
|
|
2338
|
+
top: 16px;
|
|
2339
|
+
right: 16px;
|
|
2340
|
+
width: 40px;
|
|
2341
|
+
height: 40px;
|
|
2342
|
+
border-radius: 50%;
|
|
2343
|
+
border: none;
|
|
2344
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2345
|
+
color: white;
|
|
2346
|
+
cursor: pointer;
|
|
2347
|
+
display: flex;
|
|
2348
|
+
align-items: center;
|
|
2349
|
+
justify-content: center;
|
|
2350
|
+
transition: background 0.15s;
|
|
2351
|
+
`;
|
|
2352
|
+
closeBtn.onmouseenter = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
|
|
2353
|
+
closeBtn.onmouseleave = () => { closeBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
|
|
2354
|
+
// Download button
|
|
2355
|
+
const downloadBtn = document.createElement('a');
|
|
2356
|
+
downloadBtn.href = url;
|
|
2357
|
+
downloadBtn.download = '';
|
|
2358
|
+
downloadBtn.target = '_blank';
|
|
2359
|
+
downloadBtn.rel = 'noopener noreferrer';
|
|
2360
|
+
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>`;
|
|
2361
|
+
downloadBtn.style.cssText = `
|
|
2362
|
+
position: absolute;
|
|
2363
|
+
top: 16px;
|
|
2364
|
+
right: 64px;
|
|
2365
|
+
width: 40px;
|
|
2366
|
+
height: 40px;
|
|
2367
|
+
border-radius: 50%;
|
|
2368
|
+
border: none;
|
|
2369
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2370
|
+
color: white;
|
|
2371
|
+
cursor: pointer;
|
|
2372
|
+
display: flex;
|
|
2373
|
+
align-items: center;
|
|
2374
|
+
justify-content: center;
|
|
2375
|
+
transition: background 0.15s;
|
|
2376
|
+
text-decoration: none;
|
|
2377
|
+
`;
|
|
2378
|
+
downloadBtn.onmouseenter = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.2)'; };
|
|
2379
|
+
downloadBtn.onmouseleave = () => { downloadBtn.style.background = 'rgba(255, 255, 255, 0.1)'; };
|
|
2380
|
+
// Image
|
|
2381
|
+
const img = document.createElement('img');
|
|
2382
|
+
img.src = url;
|
|
2383
|
+
img.alt = 'Full size';
|
|
2384
|
+
img.draggable = false;
|
|
2385
|
+
img.style.cssText = `
|
|
2386
|
+
max-width: 100%;
|
|
2387
|
+
max-height: 100%;
|
|
2388
|
+
object-fit: contain;
|
|
2389
|
+
border-radius: 8px;
|
|
2390
|
+
cursor: zoom-in;
|
|
2391
|
+
transition: transform 0.2s ease;
|
|
2392
|
+
user-select: none;
|
|
2393
|
+
`;
|
|
2394
|
+
// Click to toggle zoom
|
|
2395
|
+
img.addEventListener('click', (e) => {
|
|
2396
|
+
e.stopPropagation();
|
|
2397
|
+
if (scale === 1) {
|
|
2398
|
+
// Zoom in to 2.5x centered on click position
|
|
2399
|
+
const rect = img.getBoundingClientRect();
|
|
2400
|
+
const clickX = e.clientX - rect.left - rect.width / 2;
|
|
2401
|
+
const clickY = e.clientY - rect.top - rect.height / 2;
|
|
2402
|
+
scale = 2.5;
|
|
2403
|
+
translateX = -clickX * 1.5;
|
|
2404
|
+
translateY = -clickY * 1.5;
|
|
2405
|
+
applyTransform();
|
|
2406
|
+
img.style.cursor = 'zoom-out';
|
|
2407
|
+
}
|
|
2408
|
+
else {
|
|
2409
|
+
// Zoom out - reset
|
|
2410
|
+
resetTransform();
|
|
2411
|
+
}
|
|
2412
|
+
});
|
|
2413
|
+
// Mouse wheel zoom
|
|
2414
|
+
overlay.addEventListener('wheel', (e) => {
|
|
2415
|
+
e.preventDefault();
|
|
2416
|
+
const delta = e.deltaY > 0 ? -0.25 : 0.25;
|
|
2417
|
+
const newScale = Math.min(Math.max(scale + delta, 1), 5);
|
|
2418
|
+
if (newScale === 1) {
|
|
2419
|
+
resetTransform();
|
|
2420
|
+
}
|
|
2421
|
+
else {
|
|
2422
|
+
scale = newScale;
|
|
2423
|
+
applyTransform();
|
|
2424
|
+
img.style.cursor = 'zoom-out';
|
|
2425
|
+
}
|
|
2426
|
+
}, { passive: false });
|
|
2427
|
+
// Drag to pan when zoomed
|
|
2428
|
+
img.addEventListener('mousedown', (e) => {
|
|
2429
|
+
if (scale <= 1)
|
|
2430
|
+
return;
|
|
2431
|
+
e.preventDefault();
|
|
2432
|
+
isDragging = true;
|
|
2433
|
+
dragStartX = e.clientX;
|
|
2434
|
+
dragStartY = e.clientY;
|
|
2435
|
+
lastTranslateX = translateX;
|
|
2436
|
+
lastTranslateY = translateY;
|
|
2437
|
+
img.style.cursor = 'grabbing';
|
|
2438
|
+
img.style.transition = 'none';
|
|
2439
|
+
});
|
|
2440
|
+
window.addEventListener('mousemove', (e) => {
|
|
2441
|
+
if (!isDragging)
|
|
2442
|
+
return;
|
|
2443
|
+
translateX = lastTranslateX + (e.clientX - dragStartX);
|
|
2444
|
+
translateY = lastTranslateY + (e.clientY - dragStartY);
|
|
2445
|
+
applyTransform();
|
|
2446
|
+
});
|
|
2447
|
+
window.addEventListener('mouseup', () => {
|
|
2448
|
+
if (!isDragging)
|
|
2449
|
+
return;
|
|
2450
|
+
isDragging = false;
|
|
2451
|
+
img.style.cursor = scale > 1 ? 'zoom-out' : 'zoom-in';
|
|
2452
|
+
img.style.transition = 'transform 0.2s ease';
|
|
2453
|
+
});
|
|
2454
|
+
// Touch: pinch to zoom + drag to pan
|
|
2455
|
+
let lastTouchDist = 0;
|
|
2456
|
+
let lastTouchScale = 1;
|
|
2457
|
+
overlay.addEventListener('touchstart', (e) => {
|
|
2458
|
+
if (e.touches.length === 2) {
|
|
2459
|
+
e.preventDefault();
|
|
2460
|
+
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
|
2461
|
+
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
|
2462
|
+
lastTouchDist = Math.hypot(dx, dy);
|
|
2463
|
+
lastTouchScale = scale;
|
|
2464
|
+
}
|
|
2465
|
+
else if (e.touches.length === 1 && scale > 1) {
|
|
2466
|
+
isDragging = true;
|
|
2467
|
+
dragStartX = e.touches[0].clientX;
|
|
2468
|
+
dragStartY = e.touches[0].clientY;
|
|
2469
|
+
lastTranslateX = translateX;
|
|
2470
|
+
lastTranslateY = translateY;
|
|
2471
|
+
img.style.transition = 'none';
|
|
2472
|
+
}
|
|
2473
|
+
}, { passive: false });
|
|
2474
|
+
overlay.addEventListener('touchmove', (e) => {
|
|
2475
|
+
if (e.touches.length === 2) {
|
|
2476
|
+
e.preventDefault();
|
|
2477
|
+
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
|
2478
|
+
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
|
2479
|
+
const dist = Math.hypot(dx, dy);
|
|
2480
|
+
scale = Math.min(Math.max(lastTouchScale * (dist / lastTouchDist), 1), 5);
|
|
2481
|
+
if (scale === 1) {
|
|
2482
|
+
translateX = 0;
|
|
2483
|
+
translateY = 0;
|
|
2484
|
+
}
|
|
2485
|
+
applyTransform();
|
|
2486
|
+
}
|
|
2487
|
+
else if (e.touches.length === 1 && isDragging) {
|
|
2488
|
+
e.preventDefault();
|
|
2489
|
+
translateX = lastTranslateX + (e.touches[0].clientX - dragStartX);
|
|
2490
|
+
translateY = lastTranslateY + (e.touches[0].clientY - dragStartY);
|
|
2491
|
+
applyTransform();
|
|
2492
|
+
}
|
|
2493
|
+
}, { passive: false });
|
|
2494
|
+
overlay.addEventListener('touchend', (e) => {
|
|
2495
|
+
if (e.touches.length < 2) {
|
|
2496
|
+
lastTouchDist = 0;
|
|
2497
|
+
}
|
|
2498
|
+
if (e.touches.length === 0) {
|
|
2499
|
+
isDragging = false;
|
|
2500
|
+
img.style.transition = 'transform 0.2s ease';
|
|
2501
|
+
}
|
|
2502
|
+
});
|
|
2503
|
+
const close = () => {
|
|
2504
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
2505
|
+
overlay.remove();
|
|
2506
|
+
};
|
|
2507
|
+
// Only close on backdrop click when not zoomed (prevent accidental close while panning)
|
|
2508
|
+
overlay.addEventListener('click', (e) => {
|
|
2509
|
+
if (e.target === overlay && scale <= 1)
|
|
2510
|
+
close();
|
|
2511
|
+
});
|
|
2512
|
+
downloadBtn.addEventListener('click', (e) => e.stopPropagation());
|
|
2513
|
+
closeBtn.addEventListener('click', (e) => { e.stopPropagation(); close(); });
|
|
2514
|
+
// Close on Escape
|
|
2515
|
+
const handleKeyDown = (e) => {
|
|
2516
|
+
if (e.key === 'Escape')
|
|
2517
|
+
close();
|
|
2518
|
+
};
|
|
2519
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
2520
|
+
overlay.appendChild(closeBtn);
|
|
2521
|
+
overlay.appendChild(downloadBtn);
|
|
2522
|
+
overlay.appendChild(img);
|
|
2523
|
+
document.body.appendChild(overlay);
|
|
2274
2524
|
}
|
|
2275
2525
|
/**
|
|
2276
2526
|
* Initialize the SDK and render widget
|
|
@@ -2296,6 +2546,13 @@ class WeldSDK {
|
|
|
2296
2546
|
this.logger.info('WeldSDK ready');
|
|
2297
2547
|
// Call onReady callback
|
|
2298
2548
|
this.config.onReady?.();
|
|
2549
|
+
// Start tracking page URL changes
|
|
2550
|
+
this.startPageTracking();
|
|
2551
|
+
// Auto-open if widget was previously open (persisted in sessionStorage)
|
|
2552
|
+
if (this.wasOpen()) {
|
|
2553
|
+
this.logger.info('Restoring previously open widget from sessionStorage');
|
|
2554
|
+
this.open();
|
|
2555
|
+
}
|
|
2299
2556
|
}
|
|
2300
2557
|
catch (error) {
|
|
2301
2558
|
this.status = SDKStatus.ERROR;
|
|
@@ -2374,6 +2631,58 @@ class WeldSDK {
|
|
|
2374
2631
|
isReady() {
|
|
2375
2632
|
return this.status === SDKStatus.READY;
|
|
2376
2633
|
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Update callbacks on an existing instance (used by singleton reuse)
|
|
2636
|
+
*/
|
|
2637
|
+
updateCallbacks(config) {
|
|
2638
|
+
if (config.onReady !== undefined)
|
|
2639
|
+
this.config.onReady = config.onReady;
|
|
2640
|
+
if (config.onOpen !== undefined)
|
|
2641
|
+
this.config.onOpen = config.onOpen;
|
|
2642
|
+
if (config.onClose !== undefined)
|
|
2643
|
+
this.config.onClose = config.onClose;
|
|
2644
|
+
if (config.onError !== undefined)
|
|
2645
|
+
this.config.onError = config.onError;
|
|
2646
|
+
if (config.onDestroy !== undefined)
|
|
2647
|
+
this.config.onDestroy = config.onDestroy;
|
|
2648
|
+
if (config.onMinimize !== undefined)
|
|
2649
|
+
this.config.onMinimize = config.onMinimize;
|
|
2650
|
+
if (config.onMaximize !== undefined)
|
|
2651
|
+
this.config.onMaximize = config.onMaximize;
|
|
2652
|
+
}
|
|
2653
|
+
/**
|
|
2654
|
+
* Persist open/closed state to sessionStorage
|
|
2655
|
+
*/
|
|
2656
|
+
persistOpenState(isOpen) {
|
|
2657
|
+
try {
|
|
2658
|
+
sessionStorage.setItem(openStateKey(this.config.widgetId), isOpen ? 'true' : 'false');
|
|
2659
|
+
}
|
|
2660
|
+
catch {
|
|
2661
|
+
// sessionStorage might not be available
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Clear persisted state from sessionStorage
|
|
2666
|
+
*/
|
|
2667
|
+
clearPersistedState() {
|
|
2668
|
+
try {
|
|
2669
|
+
sessionStorage.removeItem(openStateKey(this.config.widgetId));
|
|
2670
|
+
}
|
|
2671
|
+
catch {
|
|
2672
|
+
// sessionStorage might not be available
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* Check if widget was previously open (from sessionStorage)
|
|
2677
|
+
*/
|
|
2678
|
+
wasOpen() {
|
|
2679
|
+
try {
|
|
2680
|
+
return sessionStorage.getItem(openStateKey(this.config.widgetId)) === 'true';
|
|
2681
|
+
}
|
|
2682
|
+
catch {
|
|
2683
|
+
return false;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2377
2686
|
/**
|
|
2378
2687
|
* Open the widget
|
|
2379
2688
|
*/
|
|
@@ -2382,8 +2691,6 @@ class WeldSDK {
|
|
|
2382
2691
|
console.log('[Weld SDK] Opening widget...');
|
|
2383
2692
|
this.stateCoordinator.openWidget();
|
|
2384
2693
|
this.iframeManager.showIframe(IframeType.WIDGET);
|
|
2385
|
-
this.iframeManager.showIframe(IframeType.BACKDROP);
|
|
2386
|
-
// Keep launcher visible so user can click it to close the widget
|
|
2387
2694
|
// Send open message to the widget iframe
|
|
2388
2695
|
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
2389
2696
|
if (widgetIframe?.element?.contentWindow) {
|
|
@@ -2394,6 +2701,7 @@ class WeldSDK {
|
|
|
2394
2701
|
if (launcherIframe?.element?.contentWindow) {
|
|
2395
2702
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-opened' }, '*');
|
|
2396
2703
|
}
|
|
2704
|
+
this.persistOpenState(true);
|
|
2397
2705
|
this.config.onOpen?.();
|
|
2398
2706
|
}
|
|
2399
2707
|
/**
|
|
@@ -2404,8 +2712,6 @@ class WeldSDK {
|
|
|
2404
2712
|
console.log('[Weld SDK] Closing widget...');
|
|
2405
2713
|
this.stateCoordinator.closeWidget();
|
|
2406
2714
|
this.iframeManager.hideIframe(IframeType.WIDGET);
|
|
2407
|
-
this.iframeManager.hideIframe(IframeType.BACKDROP);
|
|
2408
|
-
// Launcher stays visible
|
|
2409
2715
|
// Send close message to the widget iframe
|
|
2410
2716
|
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
2411
2717
|
if (widgetIframe?.element?.contentWindow) {
|
|
@@ -2416,6 +2722,7 @@ class WeldSDK {
|
|
|
2416
2722
|
if (launcherIframe?.element?.contentWindow) {
|
|
2417
2723
|
launcherIframe.element.contentWindow.postMessage({ type: 'weld:widget-closed' }, '*');
|
|
2418
2724
|
}
|
|
2725
|
+
this.persistOpenState(false);
|
|
2419
2726
|
this.config.onClose?.();
|
|
2420
2727
|
}
|
|
2421
2728
|
/**
|
|
@@ -2604,6 +2911,8 @@ class WeldSDK {
|
|
|
2604
2911
|
});
|
|
2605
2912
|
// Broadcast logout to iframes
|
|
2606
2913
|
this.messageBroker.broadcast('weld:auth:logout', {});
|
|
2914
|
+
// Clear persisted widget state
|
|
2915
|
+
this.clearPersistedState();
|
|
2607
2916
|
// Clear any stored session data
|
|
2608
2917
|
try {
|
|
2609
2918
|
const prefix = 'weld-';
|
|
@@ -2745,6 +3054,63 @@ class WeldSDK {
|
|
|
2745
3054
|
this.logger.setLevel('warn');
|
|
2746
3055
|
this.logger.info('Debug mode disabled');
|
|
2747
3056
|
}
|
|
3057
|
+
/**
|
|
3058
|
+
* Send a page change message to the widget iframe
|
|
3059
|
+
*/
|
|
3060
|
+
sendPageChange(url, title) {
|
|
3061
|
+
const widgetIframe = this.iframeManager.getIframe(IframeType.WIDGET);
|
|
3062
|
+
if (widgetIframe?.element?.contentWindow) {
|
|
3063
|
+
widgetIframe.element.contentWindow.postMessage({
|
|
3064
|
+
type: 'weld:page:change',
|
|
3065
|
+
url,
|
|
3066
|
+
title,
|
|
3067
|
+
timestamp: Date.now(),
|
|
3068
|
+
}, '*');
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Start tracking page URL changes (SPA navigations + popstate)
|
|
3073
|
+
*/
|
|
3074
|
+
startPageTracking() {
|
|
3075
|
+
let lastUrl = window.location.href;
|
|
3076
|
+
let debounceTimer = null;
|
|
3077
|
+
const notifyChange = () => {
|
|
3078
|
+
const currentUrl = window.location.href;
|
|
3079
|
+
if (currentUrl !== lastUrl) {
|
|
3080
|
+
lastUrl = currentUrl;
|
|
3081
|
+
this.sendPageChange(currentUrl, document.title);
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
const debouncedNotify = () => {
|
|
3085
|
+
if (debounceTimer)
|
|
3086
|
+
clearTimeout(debounceTimer);
|
|
3087
|
+
debounceTimer = setTimeout(notifyChange, 300);
|
|
3088
|
+
};
|
|
3089
|
+
// Send initial page
|
|
3090
|
+
this.sendPageChange(window.location.href, document.title);
|
|
3091
|
+
// Monkey-patch history.pushState and history.replaceState
|
|
3092
|
+
const origPushState = history.pushState.bind(history);
|
|
3093
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
3094
|
+
history.pushState = function (...args) {
|
|
3095
|
+
origPushState(...args);
|
|
3096
|
+
debouncedNotify();
|
|
3097
|
+
};
|
|
3098
|
+
history.replaceState = function (...args) {
|
|
3099
|
+
origReplaceState(...args);
|
|
3100
|
+
debouncedNotify();
|
|
3101
|
+
};
|
|
3102
|
+
// Listen for popstate (browser back/forward)
|
|
3103
|
+
const handlePopstate = () => debouncedNotify();
|
|
3104
|
+
window.addEventListener('popstate', handlePopstate);
|
|
3105
|
+
// Store cleanup
|
|
3106
|
+
this.pageTrackingCleanup = () => {
|
|
3107
|
+
if (debounceTimer)
|
|
3108
|
+
clearTimeout(debounceTimer);
|
|
3109
|
+
window.removeEventListener('popstate', handlePopstate);
|
|
3110
|
+
history.pushState = origPushState;
|
|
3111
|
+
history.replaceState = origReplaceState;
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
2748
3114
|
/**
|
|
2749
3115
|
* Ensure SDK is ready before operation
|
|
2750
3116
|
*/
|
|
@@ -2753,11 +3119,26 @@ class WeldSDK {
|
|
|
2753
3119
|
throw new Error('SDK not ready. Call init() first.');
|
|
2754
3120
|
}
|
|
2755
3121
|
}
|
|
3122
|
+
/**
|
|
3123
|
+
* Detach from the current component lifecycle without destroying the widget.
|
|
3124
|
+
* Use this as a React useEffect cleanup — the widget stays alive across navigations.
|
|
3125
|
+
*/
|
|
3126
|
+
detach() {
|
|
3127
|
+
// No-op: widget stays alive in the singleton registry
|
|
3128
|
+
this.logger.debug('WeldSDK detached (no-op, widget stays alive)');
|
|
3129
|
+
}
|
|
2756
3130
|
/**
|
|
2757
3131
|
* Destroy SDK and cleanup
|
|
2758
3132
|
*/
|
|
2759
3133
|
destroy() {
|
|
2760
3134
|
this.logger.info('Destroying WeldSDK');
|
|
3135
|
+
// Remove from singleton registry
|
|
3136
|
+
sdkRegistry.delete(this.config.widgetId);
|
|
3137
|
+
// Clear persisted state
|
|
3138
|
+
this.clearPersistedState();
|
|
3139
|
+
// Stop page tracking
|
|
3140
|
+
this.pageTrackingCleanup?.();
|
|
3141
|
+
this.pageTrackingCleanup = null;
|
|
2761
3142
|
// Remove event listener using bound handler
|
|
2762
3143
|
window.removeEventListener('message', this.boundHandleLauncherClick);
|
|
2763
3144
|
// Unsubscribe from all message broker subscriptions
|