|
11 | 11 | const TOP_CONTAINER_NAME = "agentforce-messaging"; |
12 | 12 | const LWR_IFRAME_NAME = "agentforce-messaging-frame"; |
13 | 13 | const LWR_IFRAME_TITLE = "Chat Window"; |
14 | | - const PREVENT_SCROLLING_CLASS = "agentforceMessagingPreventScrolling"; |
| 14 | + const PREVENT_SCROLLING_CLASS = "embeddedMessagingPreventScrolling"; |
15 | 15 |
|
16 | 16 | /** |
17 | 17 | * Attributes required to construct SCRT 2.0 Service URL. |
|
45 | 45 | resolveAppReadyPromise = resolve; |
46 | 46 | }); |
47 | 47 |
|
48 | | - const hostEvents = { |
49 | | - ON_EMBEDDED_MESSAGING_WINDOW_MINIMIZED_EVENT_NAME: "onEmbeddedMessagingWindowMinimized", |
50 | | - ON_EMBEDDED_MESSAGING_WINDOW_MAXIMIZED_EVENT_NAME: "onEmbeddedMessagingWindowMaximized" |
51 | | - }; |
52 | | - |
53 | 48 | // ========================= |
54 | 49 | // Utils |
55 | 50 | // ========================= |
|
219 | 214 | function handleMinimize() { |
220 | 215 | const frame = getIframe(); |
221 | 216 |
|
222 | | - if (document.body.classList.contains(PREVENT_SCROLLING_CLASS)) { |
223 | | - // [Mobile] Remove class that prevents background clicking and scrolling. |
224 | | - // Restore document body's scroll position only for mobile devices |
225 | | - document.body.classList.remove(PREVENT_SCROLLING_CLASS); |
226 | | - if (agentforce_messaging.documentScrollPosition) { |
227 | | - window.scrollTo(0, agentforce_messaging.documentScrollPosition); |
228 | | - } |
229 | | - } |
| 217 | + // [Mobile] Remove class that prevents background clicking and scrolling. |
| 218 | + document.body.classList.remove(PREVENT_SCROLLING_CLASS); |
230 | 219 |
|
231 | 220 | if (frame) { |
232 | 221 | // Update width and height if options are provided |
|
239 | 228 | } |
240 | 229 | } |
241 | 230 |
|
242 | | - frame.classList.remove("initial"); |
243 | 231 | frame.classList.add("minimized"); |
244 | 232 | frame.classList.remove("maximized"); |
245 | | - |
246 | | - dispatchEventToHost(hostEvents.ON_EMBEDDED_MESSAGING_WINDOW_MINIMIZED_EVENT_NAME); |
247 | 233 | } |
248 | 234 | } |
249 | 235 |
|
250 | 236 | function handleMaximize() { |
251 | 237 | const frame = getIframe(); |
252 | 238 |
|
253 | | - if(!isDesktop() && !document.body.classList.contains(PREVENT_SCROLLING_CLASS)) { |
254 | | - if (document.scrollingElement) { |
255 | | - agentforce_messaging.documentScrollPosition = document.scrollingElement.scrollTop; |
256 | | - } else { |
257 | | - const docElementRect = document.documentElement.getBoundingClientRect(); |
258 | | - agentforce_messaging.documentScrollPosition = Math.abs(docElementRect.top); |
259 | | - } |
| 239 | + if(!isDesktop()) { |
| 240 | + // [Mobile] Add class that prevents background clicking and scrolling. |
260 | 241 | document.body.classList.add(PREVENT_SCROLLING_CLASS); |
261 | 242 | } |
262 | 243 |
|
|
271 | 252 | } |
272 | 253 | } |
273 | 254 |
|
274 | | - frame.classList.remove("initial"); |
275 | 255 | frame.classList.add("maximized"); |
276 | 256 | frame.classList.remove("minimized"); |
277 | | - |
278 | | - dispatchEventToHost(hostEvents.ON_EMBEDDED_MESSAGING_WINDOW_MAXIMIZED_EVENT_NAME); |
279 | 257 | } |
280 | 258 | } |
281 | 259 |
|
|
464 | 442 | shouldProcessApiCall(); |
465 | 443 |
|
466 | 444 | const iframe = getIframe(); |
467 | | - if (iframe && !iframe.classList.contains("maximized")) { |
| 445 | + if (iframe && iframe.classList.contains("minimized")) { |
468 | 446 | // Unhide iframe in case hideChatButton was previously called. |
469 | 447 | toggleChatFabVisibility(false); |
470 | 448 | return callRpcClient("launchChat") |
|
572 | 550 | }); |
573 | 551 | }; |
574 | 552 |
|
| 553 | + /** |
| 554 | + * Handle the onEmbeddedMessagingButtonCreated event. |
| 555 | + * Sets CSS variables for minimized iframe dimensions and unhides the iframe. |
| 556 | + * @param {CustomEvent} event - Button created event containing button dimensions |
| 557 | + */ |
| 558 | + function handleButtonCreatedEvent(event) { |
| 559 | + const buttonWidth = event?.detail?.buttonDimensions?.width; |
| 560 | + const buttonHeight = event?.detail?.buttonDimensions?.height; |
| 561 | + |
| 562 | + if (buttonWidth) { |
| 563 | + document.documentElement.style.setProperty('--minimized-iframe-width', buttonWidth); |
| 564 | + } |
| 565 | + if (buttonHeight) { |
| 566 | + document.documentElement.style.setProperty('--minimized-iframe-height', buttonHeight); |
| 567 | + } |
| 568 | + |
| 569 | + unhideIframe(); |
| 570 | + } |
| 571 | + |
| 572 | + /** |
| 573 | + * Adds event listeners on host window. |
| 574 | + */ |
| 575 | + function addEventHandlers() { |
| 576 | + window.addEventListener('onEmbeddedMessagingButtonCreated', handleButtonCreatedEvent); |
| 577 | + } |
| 578 | + |
575 | 579 | /** |
576 | 580 | * Load the bootstrap.css file for this static file. |
577 | 581 | */ |
|
671 | 675 | // Iframe app ready event handler |
672 | 676 | rpcManager.registerHandler("ESW_APP_READY_EVENT", async () => { |
673 | 677 | await appReadyPromise; |
674 | | - unhideIframe(); |
675 | 678 | const configuration = prepareConfigObject(); |
676 | 679 | return { configuration }; |
677 | 680 | }); |
|
793 | 796 | * @param {object} additionalSettings - A key-value mapping. |
794 | 797 | */ |
795 | 798 | function mergeObjects(targetObj, sourceObj) { |
| 799 | + /** |
| 800 | + * Helper function to create a map from array items based on key properties |
| 801 | + * @param {Array} array - The array to create a map from |
| 802 | + * @param {Function} keyExtractor - Function to extract key from array item |
| 803 | + * @returns {Map} Map of key to index |
| 804 | + */ |
| 805 | + function createArrayMap(array, keyExtractor) { |
| 806 | + const map = new Map(); |
| 807 | + array.forEach((item, index) => { |
| 808 | + if (typeof item === 'object' && item !== null) { |
| 809 | + const key = keyExtractor(item); |
| 810 | + if (key !== null) { |
| 811 | + map.set(key, index); |
| 812 | + } |
| 813 | + } |
| 814 | + }); |
| 815 | + return map; |
| 816 | + } |
| 817 | + |
| 818 | + /** |
| 819 | + * Helper function to merge arrays with custom key matching |
| 820 | + * @param {Array} targetArray - Target array to merge into |
| 821 | + * @param {Array} sourceArray - Source array to merge from |
| 822 | + * @param {Function} keyExtractor - Function to extract key from array item |
| 823 | + */ |
| 824 | + function mergeArraysWithKeyMatching(targetArray, sourceArray, keyExtractor) { |
| 825 | + const targetMap = createArrayMap(targetArray, keyExtractor); |
| 826 | + |
| 827 | + sourceArray.forEach((sourceItem) => { |
| 828 | + if (typeof sourceItem === 'object' && sourceItem !== null) { |
| 829 | + const key = keyExtractor(sourceItem); |
| 830 | + if (key !== null && !targetMap.has(key)) { |
| 831 | + targetArray.push(sourceItem); |
| 832 | + } |
| 833 | + } |
| 834 | + }); |
| 835 | + } |
| 836 | + |
796 | 837 | Object.keys(sourceObj).forEach((key) => { |
797 | 838 | if (Array.isArray(sourceObj[key])) { |
798 | 839 | // Handle array merging |
799 | 840 | if (Array.isArray(targetObj[key])) { |
800 | | - // If both are arrays, append items in source array to target array |
801 | | - targetObj[key].push(...sourceObj[key]); |
| 841 | + if (key === 'branding') { |
| 842 | + // For branding, merge arrays based on 'n' property |
| 843 | + mergeArraysWithKeyMatching(targetObj[key], sourceObj[key], (item) => |
| 844 | + Object.hasOwn(item, 'n') ? item.n : null |
| 845 | + ); |
| 846 | + } else if (key === 'customLabels') { |
| 847 | + // For custom labels, merge arrays based on 'sectionName' and 'labelName' properties |
| 848 | + mergeArraysWithKeyMatching(targetObj[key], sourceObj[key], (item) => |
| 849 | + (Object.hasOwn(item, 'sectionName') && Object.hasOwn(item, 'labelName')) |
| 850 | + ? item.sectionName + item.labelName |
| 851 | + : null |
| 852 | + ); |
| 853 | + } |
| 854 | + // For other array types, keep target array as is (no merging) |
802 | 855 | } else if (targetObj[key] === undefined) { |
803 | 856 | // If array exists in source but not target, just use it |
804 | 857 | targetObj[key] = sourceObj[key]; |
|
812 | 865 | } |
813 | 866 | }); |
814 | 867 | } |
| 868 | + |
815 | 869 | AgentforceMessaging.prototype.createIframe = function createIframe() { |
816 | 870 | return new Promise((resolve, reject) => { |
817 | 871 | try { |
|
872 | 926 | agentforce_messaging.settings.snippetConfig = snippetConfig; |
873 | 927 | mergeObjects(agentforce_messaging.settings, snippetConfig); |
874 | 928 |
|
| 929 | + // Add event listeners. |
| 930 | + addEventHandlers(); |
| 931 | + |
875 | 932 | // Load CSS file. |
876 | 933 | const cssPromise = loadCSS() |
877 | 934 | .then(() => { |
|
924 | 981 | } |
925 | 982 | }; |
926 | 983 |
|
| 984 | + /** |
| 985 | + * Remove event listeners on host window. |
| 986 | + */ |
| 987 | + AgentforceMessaging.prototype.removeEventHandlers = function removeEventHandlers() { |
| 988 | + window.removeEventListener('onEmbeddedMessagingButtonCreated', handleButtonCreatedEvent); |
| 989 | + }; |
| 990 | + |
927 | 991 | // Function to create a proxy that forwards to a target object |
928 | 992 | function createProxy(targetObject, objectName, functionMap = {}) { |
929 | 993 | return new Proxy({}, { |
|
0 commit comments