MessageWrapper.jsx (3878B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import React, { useCallback, useEffect, useState } from "react"; 6 import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs"; 7 import { useSelector } from "react-redux"; 8 import { useIntersectionObserver } from "../../lib/utils"; 9 10 // Note: MessageWrapper emits events via submitGleanPingForPing() in the OMC messaging-system. 11 // If a feature is triggered outside of this flow (e.g., the Mobile Download QR Promo), 12 // it should emit New Tab-specific Glean events independently. 13 14 function MessageWrapper({ children, dispatch, hiddenOverride, onDismiss }) { 15 const message = useSelector(state => state.Messages); 16 const [isIntersecting, setIsIntersecting] = useState(false); 17 const [tabIsVisible, setTabIsVisible] = useState( 18 () => 19 typeof document !== "undefined" && document.visibilityState === "visible" 20 ); 21 const [hasRun, setHasRun] = useState(); 22 23 const handleIntersection = useCallback(() => { 24 setIsIntersecting(true); 25 // only send impression if messageId is defined and tab is visible 26 if (tabIsVisible && message.messageData.id && !hasRun) { 27 setHasRun(true); 28 dispatch( 29 ac.AlsoToMain({ 30 type: at.MESSAGE_IMPRESSION, 31 data: message.messageData, 32 }) 33 ); 34 } 35 }, [dispatch, message, tabIsVisible, hasRun]); 36 37 useEffect(() => { 38 // we dont want to dispatch this action unless the current tab is open and visible 39 if (message.isVisible && tabIsVisible) { 40 dispatch( 41 ac.AlsoToMain({ 42 type: at.MESSAGE_NOTIFY_VISIBILITY, 43 data: true, 44 }) 45 ); 46 } 47 }, [message, dispatch, tabIsVisible]); 48 49 useEffect(() => { 50 const handleVisibilityChange = () => { 51 setTabIsVisible(document.visibilityState === "visible"); 52 }; 53 54 document.addEventListener("visibilitychange", handleVisibilityChange); 55 return () => { 56 document.removeEventListener("visibilitychange", handleVisibilityChange); 57 }; 58 }, []); 59 60 const ref = useIntersectionObserver(handleIntersection); 61 62 const handleClose = useCallback(() => { 63 const action = { 64 type: at.MESSAGE_TOGGLE_VISIBILITY, 65 data: false, //isVisible 66 }; 67 if (message.portID) { 68 dispatch(ac.OnlyToOneContent(action, message.portID)); 69 } else { 70 dispatch(ac.AlsoToMain(action)); 71 } 72 dispatch( 73 ac.AlsoToMain({ 74 type: at.MESSAGE_NOTIFY_VISIBILITY, 75 data: false, 76 }) 77 ); 78 onDismiss?.(); 79 }, [dispatch, message, onDismiss]); 80 81 function handleDismiss() { 82 const { id } = message.messageData; 83 if (id) { 84 dispatch( 85 ac.OnlyToMain({ 86 type: at.MESSAGE_DISMISS, 87 data: { message: message.messageData }, 88 }) 89 ); 90 } 91 handleClose(); 92 } 93 94 function handleBlock() { 95 const { id } = message.messageData; 96 if (id) { 97 dispatch( 98 ac.OnlyToMain({ 99 type: at.MESSAGE_BLOCK, 100 data: id, 101 }) 102 ); 103 } 104 } 105 106 function handleClick(elementId) { 107 const { id } = message.messageData; 108 if (id) { 109 dispatch( 110 ac.OnlyToMain({ 111 type: at.MESSAGE_CLICK, 112 data: { message: message.messageData, source: elementId || "" }, 113 }) 114 ); 115 } 116 } 117 118 if (!message || (!hiddenOverride && !message.isVisible)) { 119 return null; 120 } 121 122 // only display the message if `isVisible` is true 123 return ( 124 <div 125 ref={el => { 126 ref.current = [el]; 127 }} 128 className="message-wrapper" 129 > 130 {React.cloneElement(children, { 131 isIntersecting, 132 handleDismiss, 133 handleClick, 134 handleBlock, 135 handleClose, 136 })} 137 </div> 138 ); 139 } 140 141 export { MessageWrapper };