@ably/ui 18.4.0 → 18.5.0

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/core/Header.js CHANGED
@@ -1,2 +1,2 @@
1
- import React,{useState,useEffect,useRef,useMemo,useCallback}from"react";import Icon from"./Icon";import cn from"./utils/cn";import Logo from"./Logo";import{componentMaxHeight,HEADER_BOTTOM_MARGIN,HEADER_HEIGHT}from"./utils/heights";import{HeaderLinks}from"./Header/HeaderLinks";import{throttle}from"es-toolkit/compat";import{COLLAPSE_TRIGGER_DISTANCE}from"./Notice/component";import{useThemedScrollpoints}from"./hooks/use-themed-scrollpoints";const FLEXIBLE_DESKTOP_CLASSES="hidden md:flex flex-1 items-center h-full";const MAX_MOBILE_MENU_WIDTH="560px";const Header=({className,isNoticeBannerEnabled=false,noticeHeight=0,searchBar,searchButton,logoHref,headerLinks,headerLinksClassName,headerCenterClassName,nav,mobileNav,sessionState,themedScrollpoints=[],searchButtonVisibility="all",location,logoBadge})=>{const[showMenu,setShowMenu]=useState(false);const[fadingOut,setFadingOut]=useState(false);const[noticeBannerVisible,setNoticeBannerVisible]=useState(isNoticeBannerEnabled);const menuRef=useRef(null);const scrollpointClasses=useThemedScrollpoints(themedScrollpoints);const headerStyle={height:HEADER_HEIGHT,top:noticeBannerVisible?`${noticeHeight}px`:"0"};const headerClassName=cn("fixed left-0 top-0 w-full z-50 bg-neutral-000 dark:bg-neutral-1300 border-b border-neutral-300 dark:border-neutral-1000 transition-all duration-300 ease-in-out px-6 lg:px-16",scrollpointClasses,{"md:top-auto":noticeBannerVisible});const closeMenu=()=>{setFadingOut(true)};useEffect(()=>{if(!fadingOut)return;const id=setTimeout(()=>{setShowMenu(false);setFadingOut(false)},150);return()=>clearTimeout(id)},[fadingOut]);const handleNoticeClose=useCallback(()=>{setNoticeBannerVisible(false)},[]);useEffect(()=>{document.addEventListener("notice-closed",handleNoticeClose);return()=>document.removeEventListener("notice-closed",handleNoticeClose)},[handleNoticeClose]);useEffect(()=>{if(!isNoticeBannerEnabled){return}const noticeElement=document.querySelector('[data-id="ui-notice"]');if(!noticeElement){console.warn("Header: Notice element not found");return}let previousVisibility=noticeBannerVisible;const handleScroll=()=>{const scrollY=window.scrollY;const isNoticeHidden=noticeElement.classList.contains("ui-announcement-hidden");const shouldBeVisible=scrollY<=COLLAPSE_TRIGGER_DISTANCE&&!isNoticeHidden;if(shouldBeVisible!==previousVisibility){previousVisibility=shouldBeVisible;setNoticeBannerVisible(shouldBeVisible)}};const throttledHandleScroll=throttle(handleScroll,100);handleScroll();window.addEventListener("scroll",throttledHandleScroll,{passive:true});return()=>{window.removeEventListener("scroll",throttledHandleScroll)}},[isNoticeBannerEnabled,noticeBannerVisible]);useEffect(()=>{const handleResize=()=>{if(window.innerWidth>=1040){setShowMenu(false)}};window.addEventListener("resize",handleResize);return()=>window.removeEventListener("resize",handleResize)},[]);useEffect(()=>{if(showMenu){document.body.classList.add("overflow-hidden")}else{document.body.classList.remove("overflow-hidden")}return()=>{document.body.classList.remove("overflow-hidden")}},[showMenu]);const[prevLocation,setPrevLocation]=useState(location);if(prevLocation!==location){setPrevLocation(location);if(location&&showMenu){closeMenu()}}const wrappedSearchButton=useMemo(()=>searchButton?React.createElement("div",{className:"text-neutral-1300 dark:text-neutral-000 flex items-center"},searchButton):null,[searchButton]);return React.createElement(React.Fragment,null,React.createElement("header",{role:"banner",style:headerStyle,className:headerClassName},React.createElement("div",{className:cn("flex items-center h-full",className)},React.createElement("nav",{className:"flex flex-1 h-full items-center"},["light","dark"].map(theme=>React.createElement(Logo,{key:theme,href:logoHref,theme:theme,badge:logoBadge,additionalLinkAttrs:{className:cn("h-full focus-base rounded mr-4 lg:mr-8",{"flex dark:hidden":theme==="light","hidden dark:flex":theme==="dark"})}})),React.createElement("div",{className:FLEXIBLE_DESKTOP_CLASSES},nav)),React.createElement("div",{className:"flex md:hidden flex-1 items-center justify-end gap-6 h-full"},searchButtonVisibility!=="desktop"?wrappedSearchButton:null,React.createElement("button",{className:"cursor-pointer focus-base rounded flex items-center p-0",onClick:()=>setShowMenu(!showMenu),"aria-expanded":showMenu,"aria-controls":"mobile-menu","aria-label":"Toggle menu"},React.createElement(Icon,{name:showMenu?"icon-gui-x-mark-outline":"icon-gui-bars-3-outline",additionalCSS:"text-neutral-1300 dark:text-neutral-000",size:"1.5rem"}))),searchBar?React.createElement("div",{className:cn(FLEXIBLE_DESKTOP_CLASSES,"justify-center",headerCenterClassName)},searchBar):null,React.createElement(HeaderLinks,{className:cn(FLEXIBLE_DESKTOP_CLASSES,headerLinksClassName),headerLinks:headerLinks,sessionState:sessionState,searchButton:wrappedSearchButton,searchButtonVisibility:searchButtonVisibility}))),showMenu?React.createElement(React.Fragment,null,React.createElement("div",{className:cn("fixed inset-0 bg-neutral-1300 dark:bg-neutral-1300 z-40",{"animate-[fade-in-ten-percent_150ms_ease-in-out_forwards]":!fadingOut,"animate-[fade-out-ten-percent_150ms_ease-in-out_forwards]":fadingOut}),onClick:closeMenu,onKeyDown:e=>e.key==="Escape"&&closeMenu(),role:"presentation"}),React.createElement("div",{id:"mobile-menu",className:"md:hidden fixed flex flex-col top-[4.75rem] overflow-y-hidden mx-3 right-0 w-[calc(100%-24px)] bg-neutral-000 dark:bg-neutral-1300 rounded-2xl ui-shadow-lg-medium z-50",style:{maxWidth:MAX_MOBILE_MENU_WIDTH,maxHeight:componentMaxHeight(HEADER_HEIGHT,HEADER_BOTTOM_MARGIN)},ref:menuRef,role:"navigation"},mobileNav,React.createElement(HeaderLinks,{headerLinks:headerLinks,sessionState:sessionState}))):null)};export default Header;
1
+ import React,{useState,useEffect,useRef,useMemo,useCallback}from"react";import Icon from"./Icon";import cn from"./utils/cn";import Logo from"./Logo";import{componentMaxHeight,HEADER_BOTTOM_MARGIN,HEADER_HEIGHT}from"./utils/heights";import{HeaderLinks}from"./Header/HeaderLinks";import{throttle}from"es-toolkit/compat";import{COLLAPSE_TRIGGER_DISTANCE}from"./Notice/component";import{useThemedScrollpoints}from"./hooks/use-themed-scrollpoints";const FLEXIBLE_DESKTOP_CLASSES="hidden md:flex flex-1 items-center h-full";const MAX_MOBILE_MENU_WIDTH="560px";const Header=({className,isNoticeBannerEnabled=false,noticeHeight=0,searchBar,searchButton,logoHref,headerLinks,headerLinksClassName,headerCenterClassName,nav,mobileNav,sessionState,themedScrollpoints=[],searchButtonVisibility="all",location,logoBadge})=>{const[showMenu,setShowMenu]=useState(false);const[fadingOut,setFadingOut]=useState(false);const[noticeBannerVisible,setNoticeBannerVisible]=useState(isNoticeBannerEnabled);const menuRef=useRef(null);const scrollpointClasses=useThemedScrollpoints(themedScrollpoints);const headerStyle={height:HEADER_HEIGHT,top:noticeBannerVisible?`${noticeHeight}px`:"0"};const headerClassName=cn("fixed left-0 top-0 w-full z-50 bg-neutral-000 dark:bg-neutral-1300 border-b border-neutral-300 dark:border-neutral-1000 transition-all duration-300 ease-in-out",scrollpointClasses,{"md:top-auto":noticeBannerVisible});const closeMenu=()=>{setFadingOut(true)};useEffect(()=>{if(!fadingOut)return;const id=setTimeout(()=>{setShowMenu(false);setFadingOut(false)},150);return()=>clearTimeout(id)},[fadingOut]);const handleNoticeClose=useCallback(()=>{setNoticeBannerVisible(false)},[]);useEffect(()=>{document.addEventListener("notice-closed",handleNoticeClose);return()=>document.removeEventListener("notice-closed",handleNoticeClose)},[handleNoticeClose]);useEffect(()=>{if(!isNoticeBannerEnabled){return}const noticeElement=document.querySelector('[data-id="ui-notice"]');if(!noticeElement){console.warn("Header: Notice element not found");return}let previousVisibility=noticeBannerVisible;const handleScroll=()=>{const scrollY=window.scrollY;const isNoticeHidden=noticeElement.classList.contains("ui-announcement-hidden");const shouldBeVisible=scrollY<=COLLAPSE_TRIGGER_DISTANCE&&!isNoticeHidden;if(shouldBeVisible!==previousVisibility){previousVisibility=shouldBeVisible;setNoticeBannerVisible(shouldBeVisible)}};const throttledHandleScroll=throttle(handleScroll,100);handleScroll();window.addEventListener("scroll",throttledHandleScroll,{passive:true});return()=>{window.removeEventListener("scroll",throttledHandleScroll)}},[isNoticeBannerEnabled,noticeBannerVisible]);useEffect(()=>{const handleResize=()=>{if(window.innerWidth>=1040){setShowMenu(false)}};window.addEventListener("resize",handleResize);return()=>window.removeEventListener("resize",handleResize)},[]);useEffect(()=>{if(showMenu){document.body.classList.add("overflow-hidden")}else{document.body.classList.remove("overflow-hidden")}return()=>{document.body.classList.remove("overflow-hidden")}},[showMenu]);const[prevLocation,setPrevLocation]=useState(location);if(prevLocation!==location){setPrevLocation(location);if(location&&showMenu){closeMenu()}}const wrappedSearchButton=useMemo(()=>searchButton?React.createElement("div",{className:"text-neutral-1300 dark:text-neutral-000 flex items-center"},searchButton):null,[searchButton]);return React.createElement(React.Fragment,null,React.createElement("header",{role:"banner",style:headerStyle,className:headerClassName},React.createElement("div",{className:cn("flex items-center h-full",className)},React.createElement("nav",{className:"flex flex-1 h-full items-center"},["light","dark"].map(theme=>React.createElement(Logo,{key:theme,href:logoHref,theme:theme,badge:logoBadge,additionalLinkAttrs:{className:cn("h-full focus-base rounded mr-4 lg:mr-8",{"flex dark:hidden":theme==="light","hidden dark:flex":theme==="dark"})}})),React.createElement("div",{className:FLEXIBLE_DESKTOP_CLASSES},nav)),React.createElement("div",{className:"flex md:hidden flex-1 items-center justify-end gap-6 h-full"},searchButtonVisibility!=="desktop"?wrappedSearchButton:null,React.createElement("button",{className:"cursor-pointer focus-base rounded flex items-center p-0",onClick:()=>setShowMenu(!showMenu),"aria-expanded":showMenu,"aria-controls":"mobile-menu","aria-label":"Toggle menu"},React.createElement(Icon,{name:showMenu?"icon-gui-x-mark-outline":"icon-gui-bars-3-outline",additionalCSS:"text-neutral-1300 dark:text-neutral-000",size:"1.5rem"}))),searchBar?React.createElement("div",{className:cn(FLEXIBLE_DESKTOP_CLASSES,"justify-center",headerCenterClassName)},searchBar):null,React.createElement(HeaderLinks,{className:cn(FLEXIBLE_DESKTOP_CLASSES,headerLinksClassName),headerLinks:headerLinks,sessionState:sessionState,searchButton:wrappedSearchButton,searchButtonVisibility:searchButtonVisibility}))),showMenu?React.createElement(React.Fragment,null,React.createElement("div",{className:cn("fixed inset-0 bg-neutral-1300 dark:bg-neutral-1300 z-40",{"animate-[fade-in-ten-percent_150ms_ease-in-out_forwards]":!fadingOut,"animate-[fade-out-ten-percent_150ms_ease-in-out_forwards]":fadingOut}),onClick:closeMenu,onKeyDown:e=>e.key==="Escape"&&closeMenu(),role:"presentation"}),React.createElement("div",{id:"mobile-menu",className:"md:hidden fixed flex flex-col top-[4.75rem] overflow-y-hidden mx-3 right-0 w-[calc(100%-24px)] bg-neutral-000 dark:bg-neutral-1300 rounded-2xl ui-shadow-lg-medium z-50",style:{maxWidth:MAX_MOBILE_MENU_WIDTH,maxHeight:componentMaxHeight(HEADER_HEIGHT,HEADER_BOTTOM_MARGIN)},ref:menuRef,role:"navigation"},mobileNav,React.createElement(HeaderLinks,{headerLinks:headerLinks,sessionState:sessionState}))):null)};export default Header;
2
2
  //# sourceMappingURL=Header.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/Header.tsx"],"sourcesContent":["import React, {\n useState,\n useEffect,\n useRef,\n ReactNode,\n useMemo,\n useCallback,\n} from \"react\";\nimport Icon from \"./Icon\";\nimport cn from \"./utils/cn\";\nimport Logo from \"./Logo\";\nimport {\n componentMaxHeight,\n HEADER_BOTTOM_MARGIN,\n HEADER_HEIGHT,\n} from \"./utils/heights\";\nimport { HeaderLinks } from \"./Header/HeaderLinks\";\nimport { throttle } from \"es-toolkit/compat\";\nimport { Theme } from \"./styles/colors/types\";\nimport { COLLAPSE_TRIGGER_DISTANCE } from \"./Notice/component\";\nimport { useThemedScrollpoints } from \"./hooks/use-themed-scrollpoints\";\nimport { ThemedScrollpoint } from \"./Header/types\";\n\nexport type { ThemedScrollpoint };\n\n/**\n * Represents the state of the user session in the header.\n */\nexport type HeaderSessionState = {\n /**\n * Indicates if the user is signed in.\n */\n signedIn: boolean;\n\n /**\n * Information required to log out the user.\n */\n logOut: {\n /**\n * Token used for logging out.\n */\n token: string;\n\n /**\n * URL to log out the user.\n */\n href: string;\n };\n\n /**\n * Name of the user's account.\n */\n accountName: string;\n};\n\n/**\n * Props for the Header component.\n */\nexport type HeaderProps = {\n /**\n * Optional classnames to add to the header\n */\n className?: string;\n /**\n * Indicates if the notice banner is enabled.\n */\n isNoticeBannerEnabled?: boolean;\n /**\n * Height of the notice banner in pixels.\n */\n noticeHeight?: number;\n /**\n * Optional search bar element.\n */\n searchBar?: ReactNode;\n\n /**\n * Optional search button element.\n */\n searchButton?: ReactNode;\n\n /**\n * URL for the logo link.\n */\n logoHref?: string;\n\n /**\n * Array of header links.\n */\n headerLinks?: {\n /**\n * URL for the link.\n */\n href: string;\n\n /**\n * Label for the link.\n */\n label: string;\n\n /**\n * Indicates if the link should open in a new tab.\n */\n external?: boolean;\n }[];\n\n /**\n * Optional classname for styling the header links container.\n */\n headerLinksClassName?: string;\n\n /**\n * Optional classname for styling the header center container.\n */\n headerCenterClassName?: string;\n\n /**\n * Optional desktop navigation element.\n */\n nav?: ReactNode;\n\n /**\n * Optional mobile navigation element.\n */\n mobileNav?: ReactNode;\n\n /**\n * State of the user session.\n */\n sessionState?: HeaderSessionState;\n\n /**\n * Array of themed scrollpoints. The header will change its appearance based on the scrollpoint in view.\n */\n themedScrollpoints?: ThemedScrollpoint[];\n\n /**\n * Visibility setting for the search button.\n * - \"all\": Visible on all devices.\n * - \"desktop\": Visible only on desktop devices.\n * - \"mobile\": Visible only on mobile devices.\n */\n searchButtonVisibility?: \"all\" | \"desktop\" | \"mobile\";\n\n /**\n * Optional location object to detect location changes.\n */\n location?: Location;\n\n /**\n * Optional badge text to display on the logo.\n */\n logoBadge?: string;\n};\n\nconst FLEXIBLE_DESKTOP_CLASSES = \"hidden md:flex flex-1 items-center h-full\";\n\n/**\n * Maximum width before the menu expanded into full width\n */\nconst MAX_MOBILE_MENU_WIDTH = \"560px\";\n\nconst Header: React.FC<HeaderProps> = ({\n className,\n isNoticeBannerEnabled = false,\n noticeHeight = 0,\n searchBar,\n searchButton,\n logoHref,\n headerLinks,\n headerLinksClassName,\n headerCenterClassName,\n nav,\n mobileNav,\n sessionState,\n themedScrollpoints = [],\n searchButtonVisibility = \"all\",\n location,\n logoBadge,\n}) => {\n const [showMenu, setShowMenu] = useState(false);\n const [fadingOut, setFadingOut] = useState(false);\n const [noticeBannerVisible, setNoticeBannerVisible] = useState(\n isNoticeBannerEnabled,\n );\n const menuRef = useRef<HTMLDivElement>(null);\n const scrollpointClasses = useThemedScrollpoints(themedScrollpoints);\n\n const headerStyle = {\n height: HEADER_HEIGHT,\n top: noticeBannerVisible ? `${noticeHeight}px` : \"0\",\n };\n\n const headerClassName = cn(\n \"fixed left-0 top-0 w-full z-50 bg-neutral-000 dark:bg-neutral-1300 border-b border-neutral-300 dark:border-neutral-1000 transition-all duration-300 ease-in-out px-6 lg:px-16\",\n scrollpointClasses,\n {\n \"md:top-auto\": noticeBannerVisible,\n },\n );\n\n const closeMenu = () => {\n setFadingOut(true);\n };\n\n useEffect(() => {\n if (!fadingOut) return;\n const id = setTimeout(() => {\n setShowMenu(false);\n setFadingOut(false);\n }, 150);\n return () => clearTimeout(id);\n }, [fadingOut]);\n\n const handleNoticeClose = useCallback(() => {\n setNoticeBannerVisible(false);\n }, []);\n\n useEffect(() => {\n document.addEventListener(\"notice-closed\", handleNoticeClose);\n return () =>\n document.removeEventListener(\"notice-closed\", handleNoticeClose);\n }, [handleNoticeClose]);\n\n useEffect(() => {\n if (!isNoticeBannerEnabled) {\n return;\n }\n\n const noticeElement = document.querySelector('[data-id=\"ui-notice\"]');\n\n if (!noticeElement) {\n console.warn(\"Header: Notice element not found\");\n return;\n }\n\n let previousVisibility = noticeBannerVisible;\n\n const handleScroll = () => {\n const scrollY = window.scrollY;\n const isNoticeHidden = noticeElement.classList.contains(\n \"ui-announcement-hidden\",\n );\n\n const shouldBeVisible =\n scrollY <= COLLAPSE_TRIGGER_DISTANCE && !isNoticeHidden;\n\n if (shouldBeVisible !== previousVisibility) {\n previousVisibility = shouldBeVisible;\n setNoticeBannerVisible(shouldBeVisible);\n }\n };\n\n const throttledHandleScroll = throttle(handleScroll, 100);\n\n handleScroll();\n\n window.addEventListener(\"scroll\", throttledHandleScroll, { passive: true });\n\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n };\n }, [isNoticeBannerEnabled, noticeBannerVisible]);\n\n useEffect(() => {\n const handleResize = () => {\n if (window.innerWidth >= 1040) {\n setShowMenu(false);\n }\n };\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n useEffect(() => {\n if (showMenu) {\n document.body.classList.add(\"overflow-hidden\");\n } else {\n document.body.classList.remove(\"overflow-hidden\");\n }\n\n // Cleanup on unmount\n return () => {\n document.body.classList.remove(\"overflow-hidden\");\n };\n }, [showMenu]);\n\n // Close menu when location changes\n const [prevLocation, setPrevLocation] = useState(location);\n if (prevLocation !== location) {\n setPrevLocation(location);\n if (location && showMenu) {\n closeMenu();\n }\n }\n\n const wrappedSearchButton = useMemo(\n () =>\n searchButton ? (\n <div className=\"text-neutral-1300 dark:text-neutral-000 flex items-center\">\n {searchButton}\n </div>\n ) : null,\n [searchButton],\n );\n\n return (\n <>\n <header role=\"banner\" style={headerStyle} className={headerClassName}>\n <div className={cn(\"flex items-center h-full\", className)}>\n <nav className=\"flex flex-1 h-full items-center\">\n {([\"light\", \"dark\"] as Theme[]).map((theme) => (\n <Logo\n key={theme}\n href={logoHref}\n theme={theme}\n badge={logoBadge}\n additionalLinkAttrs={{\n className: cn(\"h-full focus-base rounded mr-4 lg:mr-8\", {\n \"flex dark:hidden\": theme === \"light\",\n \"hidden dark:flex\": theme === \"dark\",\n }),\n }}\n />\n ))}\n <div className={FLEXIBLE_DESKTOP_CLASSES}>{nav}</div>\n </nav>\n <div className=\"flex md:hidden flex-1 items-center justify-end gap-6 h-full\">\n {searchButtonVisibility !== \"desktop\" ? wrappedSearchButton : null}\n <button\n className=\"cursor-pointer focus-base rounded flex items-center p-0\"\n onClick={() => setShowMenu(!showMenu)}\n aria-expanded={showMenu}\n aria-controls=\"mobile-menu\"\n aria-label=\"Toggle menu\"\n >\n <Icon\n name={\n showMenu\n ? \"icon-gui-x-mark-outline\"\n : \"icon-gui-bars-3-outline\"\n }\n additionalCSS=\"text-neutral-1300 dark:text-neutral-000\"\n size=\"1.5rem\"\n />\n </button>\n </div>\n {searchBar ? (\n <div\n className={cn(\n FLEXIBLE_DESKTOP_CLASSES,\n \"justify-center\",\n headerCenterClassName,\n )}\n >\n {searchBar}\n </div>\n ) : null}\n <HeaderLinks\n className={cn(FLEXIBLE_DESKTOP_CLASSES, headerLinksClassName)}\n headerLinks={headerLinks}\n sessionState={sessionState}\n searchButton={wrappedSearchButton}\n searchButtonVisibility={searchButtonVisibility}\n />\n </div>\n </header>\n {showMenu ? (\n <>\n <div\n className={cn(\n \"fixed inset-0 bg-neutral-1300 dark:bg-neutral-1300 z-40\",\n {\n \"animate-[fade-in-ten-percent_150ms_ease-in-out_forwards]\":\n !fadingOut,\n \"animate-[fade-out-ten-percent_150ms_ease-in-out_forwards]\":\n fadingOut,\n },\n )}\n onClick={closeMenu}\n onKeyDown={(e) => e.key === \"Escape\" && closeMenu()}\n role=\"presentation\"\n />\n <div\n id=\"mobile-menu\"\n className=\"md:hidden fixed flex flex-col top-[4.75rem] overflow-y-hidden mx-3 right-0 w-[calc(100%-24px)] bg-neutral-000 dark:bg-neutral-1300 rounded-2xl ui-shadow-lg-medium z-50\"\n style={{\n maxWidth: MAX_MOBILE_MENU_WIDTH,\n maxHeight: componentMaxHeight(\n HEADER_HEIGHT,\n HEADER_BOTTOM_MARGIN,\n ),\n }}\n ref={menuRef}\n role=\"navigation\"\n >\n {mobileNav}\n <HeaderLinks\n headerLinks={headerLinks}\n sessionState={sessionState}\n />\n </div>\n </>\n ) : null}\n </>\n );\n};\n\nexport default Header;\n"],"names":["React","useState","useEffect","useRef","useMemo","useCallback","Icon","cn","Logo","componentMaxHeight","HEADER_BOTTOM_MARGIN","HEADER_HEIGHT","HeaderLinks","throttle","COLLAPSE_TRIGGER_DISTANCE","useThemedScrollpoints","FLEXIBLE_DESKTOP_CLASSES","MAX_MOBILE_MENU_WIDTH","Header","className","isNoticeBannerEnabled","noticeHeight","searchBar","searchButton","logoHref","headerLinks","headerLinksClassName","headerCenterClassName","nav","mobileNav","sessionState","themedScrollpoints","searchButtonVisibility","location","logoBadge","showMenu","setShowMenu","fadingOut","setFadingOut","noticeBannerVisible","setNoticeBannerVisible","menuRef","scrollpointClasses","headerStyle","height","top","headerClassName","closeMenu","id","setTimeout","clearTimeout","handleNoticeClose","document","addEventListener","removeEventListener","noticeElement","querySelector","console","warn","previousVisibility","handleScroll","scrollY","window","isNoticeHidden","classList","contains","shouldBeVisible","throttledHandleScroll","passive","handleResize","innerWidth","body","add","remove","prevLocation","setPrevLocation","wrappedSearchButton","div","header","role","style","map","theme","key","href","badge","additionalLinkAttrs","button","onClick","aria-expanded","aria-controls","aria-label","name","additionalCSS","size","onKeyDown","e","maxWidth","maxHeight","ref"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/Header.tsx"],"sourcesContent":["import React, {\n useState,\n useEffect,\n useRef,\n ReactNode,\n useMemo,\n useCallback,\n} from \"react\";\nimport Icon from \"./Icon\";\nimport cn from \"./utils/cn\";\nimport Logo from \"./Logo\";\nimport {\n componentMaxHeight,\n HEADER_BOTTOM_MARGIN,\n HEADER_HEIGHT,\n} from \"./utils/heights\";\nimport { HeaderLinks } from \"./Header/HeaderLinks\";\nimport { throttle } from \"es-toolkit/compat\";\nimport { Theme } from \"./styles/colors/types\";\nimport { COLLAPSE_TRIGGER_DISTANCE } from \"./Notice/component\";\nimport { useThemedScrollpoints } from \"./hooks/use-themed-scrollpoints\";\nimport { ThemedScrollpoint } from \"./Header/types\";\n\nexport type { ThemedScrollpoint };\n\n/**\n * Represents the state of the user session in the header.\n */\nexport type HeaderSessionState = {\n /**\n * Indicates if the user is signed in.\n */\n signedIn: boolean;\n\n /**\n * Information required to log out the user.\n */\n logOut: {\n /**\n * Token used for logging out.\n */\n token: string;\n\n /**\n * URL to log out the user.\n */\n href: string;\n };\n\n /**\n * Name of the user's account.\n */\n accountName: string;\n};\n\n/**\n * Props for the Header component.\n */\nexport type HeaderProps = {\n /**\n * Optional classnames to add to the header\n */\n className?: string;\n /**\n * Indicates if the notice banner is enabled.\n */\n isNoticeBannerEnabled?: boolean;\n /**\n * Height of the notice banner in pixels.\n */\n noticeHeight?: number;\n /**\n * Optional search bar element.\n */\n searchBar?: ReactNode;\n\n /**\n * Optional search button element.\n */\n searchButton?: ReactNode;\n\n /**\n * URL for the logo link.\n */\n logoHref?: string;\n\n /**\n * Array of header links.\n */\n headerLinks?: {\n /**\n * URL for the link.\n */\n href: string;\n\n /**\n * Label for the link.\n */\n label: string;\n\n /**\n * Indicates if the link should open in a new tab.\n */\n external?: boolean;\n }[];\n\n /**\n * Optional classname for styling the header links container.\n */\n headerLinksClassName?: string;\n\n /**\n * Optional classname for styling the header center container.\n */\n headerCenterClassName?: string;\n\n /**\n * Optional desktop navigation element.\n */\n nav?: ReactNode;\n\n /**\n * Optional mobile navigation element.\n */\n mobileNav?: ReactNode;\n\n /**\n * State of the user session.\n */\n sessionState?: HeaderSessionState;\n\n /**\n * Array of themed scrollpoints. The header will change its appearance based on the scrollpoint in view.\n */\n themedScrollpoints?: ThemedScrollpoint[];\n\n /**\n * Visibility setting for the search button.\n * - \"all\": Visible on all devices.\n * - \"desktop\": Visible only on desktop devices.\n * - \"mobile\": Visible only on mobile devices.\n */\n searchButtonVisibility?: \"all\" | \"desktop\" | \"mobile\";\n\n /**\n * Optional location object to detect location changes.\n */\n location?: Location;\n\n /**\n * Optional badge text to display on the logo.\n */\n logoBadge?: string;\n};\n\nconst FLEXIBLE_DESKTOP_CLASSES = \"hidden md:flex flex-1 items-center h-full\";\n\n/**\n * Maximum width before the menu expanded into full width\n */\nconst MAX_MOBILE_MENU_WIDTH = \"560px\";\n\nconst Header: React.FC<HeaderProps> = ({\n className,\n isNoticeBannerEnabled = false,\n noticeHeight = 0,\n searchBar,\n searchButton,\n logoHref,\n headerLinks,\n headerLinksClassName,\n headerCenterClassName,\n nav,\n mobileNav,\n sessionState,\n themedScrollpoints = [],\n searchButtonVisibility = \"all\",\n location,\n logoBadge,\n}) => {\n const [showMenu, setShowMenu] = useState(false);\n const [fadingOut, setFadingOut] = useState(false);\n const [noticeBannerVisible, setNoticeBannerVisible] = useState(\n isNoticeBannerEnabled,\n );\n const menuRef = useRef<HTMLDivElement>(null);\n const scrollpointClasses = useThemedScrollpoints(themedScrollpoints);\n\n const headerStyle = {\n height: HEADER_HEIGHT,\n top: noticeBannerVisible ? `${noticeHeight}px` : \"0\",\n };\n\n const headerClassName = cn(\n \"fixed left-0 top-0 w-full z-50 bg-neutral-000 dark:bg-neutral-1300 border-b border-neutral-300 dark:border-neutral-1000 transition-all duration-300 ease-in-out\",\n scrollpointClasses,\n {\n \"md:top-auto\": noticeBannerVisible,\n },\n );\n\n const closeMenu = () => {\n setFadingOut(true);\n };\n\n useEffect(() => {\n if (!fadingOut) return;\n const id = setTimeout(() => {\n setShowMenu(false);\n setFadingOut(false);\n }, 150);\n return () => clearTimeout(id);\n }, [fadingOut]);\n\n const handleNoticeClose = useCallback(() => {\n setNoticeBannerVisible(false);\n }, []);\n\n useEffect(() => {\n document.addEventListener(\"notice-closed\", handleNoticeClose);\n return () =>\n document.removeEventListener(\"notice-closed\", handleNoticeClose);\n }, [handleNoticeClose]);\n\n useEffect(() => {\n if (!isNoticeBannerEnabled) {\n return;\n }\n\n const noticeElement = document.querySelector('[data-id=\"ui-notice\"]');\n\n if (!noticeElement) {\n console.warn(\"Header: Notice element not found\");\n return;\n }\n\n let previousVisibility = noticeBannerVisible;\n\n const handleScroll = () => {\n const scrollY = window.scrollY;\n const isNoticeHidden = noticeElement.classList.contains(\n \"ui-announcement-hidden\",\n );\n\n const shouldBeVisible =\n scrollY <= COLLAPSE_TRIGGER_DISTANCE && !isNoticeHidden;\n\n if (shouldBeVisible !== previousVisibility) {\n previousVisibility = shouldBeVisible;\n setNoticeBannerVisible(shouldBeVisible);\n }\n };\n\n const throttledHandleScroll = throttle(handleScroll, 100);\n\n handleScroll();\n\n window.addEventListener(\"scroll\", throttledHandleScroll, { passive: true });\n\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n };\n }, [isNoticeBannerEnabled, noticeBannerVisible]);\n\n useEffect(() => {\n const handleResize = () => {\n if (window.innerWidth >= 1040) {\n setShowMenu(false);\n }\n };\n window.addEventListener(\"resize\", handleResize);\n return () => window.removeEventListener(\"resize\", handleResize);\n }, []);\n\n useEffect(() => {\n if (showMenu) {\n document.body.classList.add(\"overflow-hidden\");\n } else {\n document.body.classList.remove(\"overflow-hidden\");\n }\n\n // Cleanup on unmount\n return () => {\n document.body.classList.remove(\"overflow-hidden\");\n };\n }, [showMenu]);\n\n // Close menu when location changes\n const [prevLocation, setPrevLocation] = useState(location);\n if (prevLocation !== location) {\n setPrevLocation(location);\n if (location && showMenu) {\n closeMenu();\n }\n }\n\n const wrappedSearchButton = useMemo(\n () =>\n searchButton ? (\n <div className=\"text-neutral-1300 dark:text-neutral-000 flex items-center\">\n {searchButton}\n </div>\n ) : null,\n [searchButton],\n );\n\n return (\n <>\n <header role=\"banner\" style={headerStyle} className={headerClassName}>\n <div className={cn(\"flex items-center h-full\", className)}>\n <nav className=\"flex flex-1 h-full items-center\">\n {([\"light\", \"dark\"] as Theme[]).map((theme) => (\n <Logo\n key={theme}\n href={logoHref}\n theme={theme}\n badge={logoBadge}\n additionalLinkAttrs={{\n className: cn(\"h-full focus-base rounded mr-4 lg:mr-8\", {\n \"flex dark:hidden\": theme === \"light\",\n \"hidden dark:flex\": theme === \"dark\",\n }),\n }}\n />\n ))}\n <div className={FLEXIBLE_DESKTOP_CLASSES}>{nav}</div>\n </nav>\n <div className=\"flex md:hidden flex-1 items-center justify-end gap-6 h-full\">\n {searchButtonVisibility !== \"desktop\" ? wrappedSearchButton : null}\n <button\n className=\"cursor-pointer focus-base rounded flex items-center p-0\"\n onClick={() => setShowMenu(!showMenu)}\n aria-expanded={showMenu}\n aria-controls=\"mobile-menu\"\n aria-label=\"Toggle menu\"\n >\n <Icon\n name={\n showMenu\n ? \"icon-gui-x-mark-outline\"\n : \"icon-gui-bars-3-outline\"\n }\n additionalCSS=\"text-neutral-1300 dark:text-neutral-000\"\n size=\"1.5rem\"\n />\n </button>\n </div>\n {searchBar ? (\n <div\n className={cn(\n FLEXIBLE_DESKTOP_CLASSES,\n \"justify-center\",\n headerCenterClassName,\n )}\n >\n {searchBar}\n </div>\n ) : null}\n <HeaderLinks\n className={cn(FLEXIBLE_DESKTOP_CLASSES, headerLinksClassName)}\n headerLinks={headerLinks}\n sessionState={sessionState}\n searchButton={wrappedSearchButton}\n searchButtonVisibility={searchButtonVisibility}\n />\n </div>\n </header>\n {showMenu ? (\n <>\n <div\n className={cn(\n \"fixed inset-0 bg-neutral-1300 dark:bg-neutral-1300 z-40\",\n {\n \"animate-[fade-in-ten-percent_150ms_ease-in-out_forwards]\":\n !fadingOut,\n \"animate-[fade-out-ten-percent_150ms_ease-in-out_forwards]\":\n fadingOut,\n },\n )}\n onClick={closeMenu}\n onKeyDown={(e) => e.key === \"Escape\" && closeMenu()}\n role=\"presentation\"\n />\n <div\n id=\"mobile-menu\"\n className=\"md:hidden fixed flex flex-col top-[4.75rem] overflow-y-hidden mx-3 right-0 w-[calc(100%-24px)] bg-neutral-000 dark:bg-neutral-1300 rounded-2xl ui-shadow-lg-medium z-50\"\n style={{\n maxWidth: MAX_MOBILE_MENU_WIDTH,\n maxHeight: componentMaxHeight(\n HEADER_HEIGHT,\n HEADER_BOTTOM_MARGIN,\n ),\n }}\n ref={menuRef}\n role=\"navigation\"\n >\n {mobileNav}\n <HeaderLinks\n headerLinks={headerLinks}\n sessionState={sessionState}\n />\n </div>\n </>\n ) : null}\n </>\n );\n};\n\nexport default Header;\n"],"names":["React","useState","useEffect","useRef","useMemo","useCallback","Icon","cn","Logo","componentMaxHeight","HEADER_BOTTOM_MARGIN","HEADER_HEIGHT","HeaderLinks","throttle","COLLAPSE_TRIGGER_DISTANCE","useThemedScrollpoints","FLEXIBLE_DESKTOP_CLASSES","MAX_MOBILE_MENU_WIDTH","Header","className","isNoticeBannerEnabled","noticeHeight","searchBar","searchButton","logoHref","headerLinks","headerLinksClassName","headerCenterClassName","nav","mobileNav","sessionState","themedScrollpoints","searchButtonVisibility","location","logoBadge","showMenu","setShowMenu","fadingOut","setFadingOut","noticeBannerVisible","setNoticeBannerVisible","menuRef","scrollpointClasses","headerStyle","height","top","headerClassName","closeMenu","id","setTimeout","clearTimeout","handleNoticeClose","document","addEventListener","removeEventListener","noticeElement","querySelector","console","warn","previousVisibility","handleScroll","scrollY","window","isNoticeHidden","classList","contains","shouldBeVisible","throttledHandleScroll","passive","handleResize","innerWidth","body","add","remove","prevLocation","setPrevLocation","wrappedSearchButton","div","header","role","style","map","theme","key","href","badge","additionalLinkAttrs","button","onClick","aria-expanded","aria-controls","aria-label","name","additionalCSS","size","onKeyDown","e","maxWidth","maxHeight","ref"],"mappings":"AAAA,OAAOA,OACLC,QAAQ,CACRC,SAAS,CACTC,MAAM,CAENC,OAAO,CACPC,WAAW,KACN,OAAQ,AACf,QAAOC,SAAU,QAAS,AAC1B,QAAOC,OAAQ,YAAa,AAC5B,QAAOC,SAAU,QAAS,AAC1B,QACEC,kBAAkB,CAClBC,oBAAoB,CACpBC,aAAa,KACR,iBAAkB,AACzB,QAASC,WAAW,KAAQ,sBAAuB,AACnD,QAASC,QAAQ,KAAQ,mBAAoB,AAE7C,QAASC,yBAAyB,KAAQ,oBAAqB,AAC/D,QAASC,qBAAqB,KAAQ,iCAAkC,CAuIxE,MAAMC,yBAA2B,4CAKjC,MAAMC,sBAAwB,QAE9B,MAAMC,OAAgC,CAAC,CACrCC,SAAS,CACTC,sBAAwB,KAAK,CAC7BC,aAAe,CAAC,CAChBC,SAAS,CACTC,YAAY,CACZC,QAAQ,CACRC,WAAW,CACXC,oBAAoB,CACpBC,qBAAqB,CACrBC,GAAG,CACHC,SAAS,CACTC,YAAY,CACZC,mBAAqB,EAAE,CACvBC,uBAAyB,KAAK,CAC9BC,QAAQ,CACRC,SAAS,CACV,IACC,KAAM,CAACC,SAAUC,YAAY,CAAGnC,SAAS,OACzC,KAAM,CAACoC,UAAWC,aAAa,CAAGrC,SAAS,OAC3C,KAAM,CAACsC,oBAAqBC,uBAAuB,CAAGvC,SACpDmB,uBAEF,MAAMqB,QAAUtC,OAAuB,MACvC,MAAMuC,mBAAqB3B,sBAAsBgB,oBAEjD,MAAMY,YAAc,CAClBC,OAAQjC,cACRkC,IAAKN,oBAAsB,CAAC,EAAElB,aAAa,EAAE,CAAC,CAAG,GACnD,EAEA,MAAMyB,gBAAkBvC,GACtB,kKACAmC,mBACA,CACE,cAAeH,mBACjB,GAGF,MAAMQ,UAAY,KAChBT,aAAa,KACf,EAEApC,UAAU,KACR,GAAI,CAACmC,UAAW,OAChB,MAAMW,GAAKC,WAAW,KACpBb,YAAY,OACZE,aAAa,MACf,EAAG,KACH,MAAO,IAAMY,aAAaF,GAC5B,EAAG,CAACX,UAAU,EAEd,MAAMc,kBAAoB9C,YAAY,KACpCmC,uBAAuB,MACzB,EAAG,EAAE,EAELtC,UAAU,KACRkD,SAASC,gBAAgB,CAAC,gBAAiBF,mBAC3C,MAAO,IACLC,SAASE,mBAAmB,CAAC,gBAAiBH,kBAClD,EAAG,CAACA,kBAAkB,EAEtBjD,UAAU,KACR,GAAI,CAACkB,sBAAuB,CAC1B,MACF,CAEA,MAAMmC,cAAgBH,SAASI,aAAa,CAAC,yBAE7C,GAAI,CAACD,cAAe,CAClBE,QAAQC,IAAI,CAAC,oCACb,MACF,CAEA,IAAIC,mBAAqBpB,oBAEzB,MAAMqB,aAAe,KACnB,MAAMC,QAAUC,OAAOD,OAAO,CAC9B,MAAME,eAAiBR,cAAcS,SAAS,CAACC,QAAQ,CACrD,0BAGF,MAAMC,gBACJL,SAAW/C,2BAA6B,CAACiD,eAE3C,GAAIG,kBAAoBP,mBAAoB,CAC1CA,mBAAqBO,gBACrB1B,uBAAuB0B,gBACzB,CACF,EAEA,MAAMC,sBAAwBtD,SAAS+C,aAAc,KAErDA,eAEAE,OAAOT,gBAAgB,CAAC,SAAUc,sBAAuB,CAAEC,QAAS,IAAK,GAEzE,MAAO,KACLN,OAAOR,mBAAmB,CAAC,SAAUa,sBACvC,CACF,EAAG,CAAC/C,sBAAuBmB,oBAAoB,EAE/CrC,UAAU,KACR,MAAMmE,aAAe,KACnB,GAAIP,OAAOQ,UAAU,EAAI,KAAM,CAC7BlC,YAAY,MACd,CACF,EACA0B,OAAOT,gBAAgB,CAAC,SAAUgB,cAClC,MAAO,IAAMP,OAAOR,mBAAmB,CAAC,SAAUe,aACpD,EAAG,EAAE,EAELnE,UAAU,KACR,GAAIiC,SAAU,CACZiB,SAASmB,IAAI,CAACP,SAAS,CAACQ,GAAG,CAAC,kBAC9B,KAAO,CACLpB,SAASmB,IAAI,CAACP,SAAS,CAACS,MAAM,CAAC,kBACjC,CAGA,MAAO,KACLrB,SAASmB,IAAI,CAACP,SAAS,CAACS,MAAM,CAAC,kBACjC,CACF,EAAG,CAACtC,SAAS,EAGb,KAAM,CAACuC,aAAcC,gBAAgB,CAAG1E,SAASgC,UACjD,GAAIyC,eAAiBzC,SAAU,CAC7B0C,gBAAgB1C,UAChB,GAAIA,UAAYE,SAAU,CACxBY,WACF,CACF,CAEA,MAAM6B,oBAAsBxE,QAC1B,IACEmB,aACE,oBAACsD,OAAI1D,UAAU,6DACZI,cAED,KACN,CAACA,aAAa,EAGhB,OACE,wCACE,oBAACuD,UAAOC,KAAK,SAASC,MAAOrC,YAAaxB,UAAW2B,iBACnD,oBAAC+B,OAAI1D,UAAWZ,GAAG,2BAA4BY,YAC7C,oBAACS,OAAIT,UAAU,mCACZ,AAAC,CAAC,QAAS,OAAO,CAAa8D,GAAG,CAAC,AAACC,OACnC,oBAAC1E,MACC2E,IAAKD,MACLE,KAAM5D,SACN0D,MAAOA,MACPG,MAAOnD,UACPoD,oBAAqB,CACnBnE,UAAWZ,GAAG,yCAA0C,CACtD,mBAAoB2E,QAAU,QAC9B,mBAAoBA,QAAU,MAChC,EACF,KAGJ,oBAACL,OAAI1D,UAAWH,0BAA2BY,MAE7C,oBAACiD,OAAI1D,UAAU,+DACZa,yBAA2B,UAAY4C,oBAAsB,KAC9D,oBAACW,UACCpE,UAAU,0DACVqE,QAAS,IAAMpD,YAAY,CAACD,UAC5BsD,gBAAetD,SACfuD,gBAAc,cACdC,aAAW,eAEX,oBAACrF,MACCsF,KACEzD,SACI,0BACA,0BAEN0D,cAAc,0CACdC,KAAK,aAIVxE,UACC,oBAACuD,OACC1D,UAAWZ,GACTS,yBACA,iBACAW,wBAGDL,WAED,KACJ,oBAACV,aACCO,UAAWZ,GAAGS,yBAA0BU,sBACxCD,YAAaA,YACbK,aAAcA,aACdP,aAAcqD,oBACd5C,uBAAwBA,2BAI7BG,SACC,wCACE,oBAAC0C,OACC1D,UAAWZ,GACT,0DACA,CACE,2DACE,CAAC8B,UACH,4DACEA,SACJ,GAEFmD,QAASzC,UACTgD,UAAW,AAACC,GAAMA,EAAEb,GAAG,GAAK,UAAYpC,YACxCgC,KAAK,iBAEP,oBAACF,OACC7B,GAAG,cACH7B,UAAU,0KACV6D,MAAO,CACLiB,SAAUhF,sBACViF,UAAWzF,mBACTE,cACAD,qBAEJ,EACAyF,IAAK1D,QACLsC,KAAK,cAEJlD,UACD,oBAACjB,aACCa,YAAaA,YACbK,aAAcA,iBAIlB,KAGV,CAEA,gBAAeZ,MAAO"}
@@ -1,2 +1,2 @@
1
- import React from"react";import G2BestMeetsRequirementsSpring2025 from"../images/badges/g2-best-meets-requirements-spring-2025.svg";import G2BestResultsSpring2025 from"../images/badges/g2-best-results-spring-2025.svg";import G2BestSupportSpring2025 from"../images/badges/g2-best-support-spring-2025.svg";import{products}from"../ProductTile/data";import DialpadLogo from"../Meganav/images/cust-logo-dialpad-light.png";import DialpadLogoDark from"../Meganav/images/cust-logo-dialpad-dark.png";export const productsMenu=[{label:"Architecture",icon:"icon-gui-squares-2-x-2-outline",link:"/platform"},{label:"Integrations",icon:"icon-gui-puzzle-piece-outline",link:"/docs/platform/integrations"},{label:"SDKs",icon:"icon-gui-cube-transparent-outline",link:"/docs/sdks"},{label:"Security & Compliance",icon:"icon-gui-shield-check-outline",link:"/security-and-compliance"}];export const compareMenu=[{label:"Ably vs Pusher",link:"/compare/ably-vs-pusher"},{label:"Ably vs PubNub",link:"/compare/ably-vs-pubnub"},{label:"Ably vs Socket.io",link:"/compare/ably-vs-socketio"}];export const solutionsMenu=[{label:"Fan Engagement",icon:"icon-gui-hand-thumb-up-outline",link:"/fan-engagement",description:"Enhance every moment with live, interactive fan experiences."},{label:"FinTech",icon:"icon-gui-currency-dollar-outline",link:"/fin-tech",description:"Speed, reliability, and confidence in every user interaction."},{label:"EdTech",icon:"icon-gui-academic-cap-outline",link:"/ed-tech",description:"Power collaborative, interactive learning environments."},{label:"CXTech",icon:"icon-gui-face-smile-outline",link:"/cx-tech",description:"Deliver fast support, strong relationships, and high retention."},{label:"HealthTech",icon:"icon-gui-heartbeat-outline",link:"/health-tech",description:"Reliable tools with full data privacy and compliance."}];export const customerStoriesHighlight={companyLogo:DialpadLogo,companyLogoDark:DialpadLogoDark,companyLink:"/case-studies/dialpad",companyName:"Dialpad",companyDesc:"are sending 60B+ messages per month across 20+ critical use cases with zero downtime since migrating to Ably."};export const companyMenu=[{label:"Our story",icon:"icon-gui-ably-badge",link:"/about"},{label:"Careers",icon:"icon-gui-briefcase-outline",link:"/careers",badge:"WE'RE HIRING"}];export const ablyAwards=[{image:G2BestSupportSpring2025,desc:"G2 Best Support Spring 2025"},{image:G2BestMeetsRequirementsSpring2025,desc:"G2 Best Meets Requirements Spring 2025"},{image:G2BestResultsSpring2025,desc:"G2 Best Results Spring 2025"}];export const menuItemLinks=[{name:"Pricing",link:"/pricing",isHiddenMobile:true},{name:"Docs",link:"/docs",isHiddenMobile:true}];export const defaultBlogPosts=[{title:"Ably AI Transport: keep your agents connected and stateful across devices",link:"/blog/ably-ai-transport",categories:["New Release","AI Transport","New Feature","RealTime Experiences","Engineering","Ably News"],pubDate:"Dec 9, 2025"},{title:"Live chat at unlimited scale: What it takes to support stadium-sized audiences",link:"/blog/live-chat-at-unlimited-scale",categories:["Chat"],pubDate:"Nov 18, 2025"}];export const productsForNav={...products,pubsub:{...products.pubsub,link:"/pubsub"},liveSync:{...products.liveSync,link:"/livesync"},chat:{...products.chat,link:"/chat"},spaces:{...products.spaces,link:"/spaces"},aiTransport:{...products.aiTransport,link:"/ai-transport"},liveObjects:{...products.liveObjects,link:"/liveobjects"}};
1
+ import React from"react";import G2BestMeetsRequirementsSpring2025 from"../images/badges/g2-best-meets-requirements-spring-2025.svg";import G2BestResultsSpring2025 from"../images/badges/g2-best-results-spring-2025.svg";import G2BestSupportSpring2025 from"../images/badges/g2-best-support-spring-2025.svg";import{products}from"../ProductTile/data";import FinLogoLight from"../Meganav/images/cust-logo-fin-light.svg";import FinLogoDark from"../Meganav/images/cust-logo-fin-dark.svg";export const productsMenu=[{label:"Architecture",icon:"icon-gui-squares-2-x-2-outline",link:"/platform"},{label:"Integrations",icon:"icon-gui-puzzle-piece-outline",link:"/docs/platform/integrations"},{label:"SDKs",icon:"icon-gui-cube-transparent-outline",link:"/docs/sdks"},{label:"Security & Compliance",icon:"icon-gui-shield-check-outline",link:"/security-and-compliance"}];export const compareMenu=[{label:"Ably vs Pusher",link:"/compare/ably-vs-pusher"},{label:"Ably vs PubNub",link:"/compare/ably-vs-pubnub"},{label:"Ably vs Socket.io",link:"/compare/ably-vs-socketio"}];export const solutionsMenu=[{label:"Fan Engagement",icon:"icon-gui-hand-thumb-up-outline",link:"/fan-engagement",description:"Enhance every moment with live, interactive fan experiences."},{label:"FinTech",icon:"icon-gui-currency-dollar-outline",link:"/fin-tech",description:"Speed, reliability, and confidence in every user interaction."},{label:"EdTech",icon:"icon-gui-academic-cap-outline",link:"/ed-tech",description:"Power collaborative, interactive learning environments."},{label:"CXTech",icon:"icon-gui-face-smile-outline",link:"/cx-tech",description:"Deliver fast support, strong relationships, and high retention."},{label:"HealthTech",icon:"icon-gui-heartbeat-outline",link:"/health-tech",description:"Reliable tools with full data privacy and compliance."}];export const customerStoriesHighlight={companyLogo:FinLogoLight,companyLogoDark:FinLogoDark,companyLink:"/case-studies/fin-intercom",companyName:"Fin",companyDesc:"drove 80% more efficiency in realtime delivery for AI chat by replacing its in-house realtime platform with Ably."};export const companyMenu=[{label:"Our story",icon:"icon-gui-ably-badge",link:"/about"},{label:"Careers",icon:"icon-gui-briefcase-outline",link:"/careers",badge:"WE'RE HIRING"}];export const ablyAwards=[{image:G2BestSupportSpring2025,desc:"G2 Best Support Spring 2025"},{image:G2BestMeetsRequirementsSpring2025,desc:"G2 Best Meets Requirements Spring 2025"},{image:G2BestResultsSpring2025,desc:"G2 Best Results Spring 2025"}];export const menuItemLinks=[{name:"Pricing",link:"/pricing",isHiddenMobile:true},{name:"Docs",link:"/docs",isHiddenMobile:true}];export const defaultBlogPosts=[{title:"Ably AI Transport: keep your agents connected and stateful across devices",link:"/blog/ably-ai-transport",categories:["New Release","AI Transport","New Feature","RealTime Experiences","Engineering","Ably News"],pubDate:"Dec 9, 2025"},{title:"Live chat at unlimited scale: What it takes to support stadium-sized audiences",link:"/blog/live-chat-at-unlimited-scale",categories:["Chat"],pubDate:"Nov 18, 2025"}];export const productsForNav={...products,pubsub:{...products.pubsub,link:"/pubsub"},liveSync:{...products.liveSync,link:"/livesync"},chat:{...products.chat,link:"/chat"},spaces:{...products.spaces,link:"/spaces"},aiTransport:{...products.aiTransport,link:"/ai-transport"},liveObjects:{...products.liveObjects,link:"/liveobjects"}};
2
2
  //# sourceMappingURL=data.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/Meganav/data.tsx"],"sourcesContent":["import React from \"react\";\nimport { IconName } from \"../Icon/types\";\nimport { CustomerStoryHighlight } from \"./MeganavCustomerStories\";\nimport { BlogPost } from \"./MeganavBlog\";\nimport G2BestMeetsRequirementsSpring2025 from \"../images/badges/g2-best-meets-requirements-spring-2025.svg\";\nimport G2BestResultsSpring2025 from \"../images/badges/g2-best-results-spring-2025.svg\";\nimport G2BestSupportSpring2025 from \"../images/badges/g2-best-support-spring-2025.svg\";\nimport { products } from \"../ProductTile/data\";\nimport DialpadLogo from \"../Meganav/images/cust-logo-dialpad-light.png\";\nimport DialpadLogoDark from \"../Meganav/images/cust-logo-dialpad-dark.png\";\n\nexport type FlyoutPanelList = {\n label: string;\n icon?: IconName;\n link: string;\n isMobile?: boolean;\n description?: string;\n badge?: string;\n};\n\nexport type MenuItem = {\n name: string;\n link?: string;\n isHiddenMobile?: boolean;\n content?: React.ReactNode;\n panelClassName?: string;\n};\n\nexport const productsMenu: FlyoutPanelList[] = [\n {\n label: \"Architecture\",\n icon: \"icon-gui-squares-2-x-2-outline\",\n link: \"/platform\",\n },\n {\n label: \"Integrations\",\n icon: \"icon-gui-puzzle-piece-outline\",\n link: \"/docs/platform/integrations\",\n },\n {\n label: \"SDKs\",\n icon: \"icon-gui-cube-transparent-outline\",\n link: \"/docs/sdks\",\n },\n {\n label: \"Security & Compliance\",\n icon: \"icon-gui-shield-check-outline\",\n link: \"/security-and-compliance\",\n },\n];\n\nexport const compareMenu: FlyoutPanelList[] = [\n {\n label: \"Ably vs Pusher\",\n link: \"/compare/ably-vs-pusher\",\n },\n {\n label: \"Ably vs PubNub\",\n link: \"/compare/ably-vs-pubnub\",\n },\n {\n label: \"Ably vs Socket.io\",\n link: \"/compare/ably-vs-socketio\",\n },\n];\n\nexport const solutionsMenu: FlyoutPanelList[] = [\n {\n label: \"Fan Engagement\",\n icon: \"icon-gui-hand-thumb-up-outline\",\n link: \"/fan-engagement\",\n description: \"Enhance every moment with live, interactive fan experiences.\",\n },\n {\n label: \"FinTech\",\n icon: \"icon-gui-currency-dollar-outline\",\n link: \"/fin-tech\",\n description:\n \"Speed, reliability, and confidence in every user interaction.\",\n },\n {\n label: \"EdTech\",\n icon: \"icon-gui-academic-cap-outline\",\n link: \"/ed-tech\",\n description: \"Power collaborative, interactive learning environments.\",\n },\n {\n label: \"CXTech\",\n icon: \"icon-gui-face-smile-outline\",\n link: \"/cx-tech\",\n description:\n \"Deliver fast support, strong relationships, and high retention.\",\n },\n\n {\n label: \"HealthTech\",\n icon: \"icon-gui-heartbeat-outline\",\n link: \"/health-tech\",\n description: \"Reliable tools with full data privacy and compliance.\",\n },\n];\n\nexport const customerStoriesHighlight: CustomerStoryHighlight = {\n companyLogo: DialpadLogo,\n companyLogoDark: DialpadLogoDark,\n companyLink: \"/case-studies/dialpad\",\n companyName: \"Dialpad\",\n companyDesc:\n \"are sending 60B+ messages per month across 20+ critical use cases with zero downtime since migrating to Ably.\",\n};\n\nexport const companyMenu: FlyoutPanelList[] = [\n {\n label: \"Our story\",\n icon: \"icon-gui-ably-badge\",\n link: \"/about\",\n },\n {\n label: \"Careers\",\n icon: \"icon-gui-briefcase-outline\",\n link: \"/careers\",\n badge: \"WE'RE HIRING\",\n },\n];\n\nexport const ablyAwards = [\n {\n image: G2BestSupportSpring2025,\n desc: \"G2 Best Support Spring 2025\",\n },\n {\n image: G2BestMeetsRequirementsSpring2025,\n desc: \"G2 Best Meets Requirements Spring 2025\",\n },\n {\n image: G2BestResultsSpring2025,\n desc: \"G2 Best Results Spring 2025\",\n },\n];\n\nexport const menuItemLinks = [\n { name: \"Pricing\", link: \"/pricing\", isHiddenMobile: true },\n { name: \"Docs\", link: \"/docs\", isHiddenMobile: true },\n];\n\nexport const defaultBlogPosts: BlogPost[] = [\n {\n title:\n \"Ably AI Transport: keep your agents connected and stateful across devices\",\n link: \"/blog/ably-ai-transport\",\n categories: [\n \"New Release\",\n \"AI Transport\",\n \"New Feature\",\n \"RealTime Experiences\",\n \"Engineering\",\n \"Ably News\",\n ],\n pubDate: \"Dec 9, 2025\",\n },\n {\n title:\n \"Live chat at unlimited scale: What it takes to support stadium-sized audiences\",\n link: \"/blog/live-chat-at-unlimited-scale\",\n categories: [\"Chat\"],\n pubDate: \"Nov 18, 2025\",\n },\n];\n\nexport const productsForNav = {\n ...products,\n pubsub: { ...products.pubsub, link: \"/pubsub\" },\n liveSync: { ...products.liveSync, link: \"/livesync\" },\n chat: { ...products.chat, link: \"/chat\" },\n spaces: { ...products.spaces, link: \"/spaces\" },\n aiTransport: {\n ...products.aiTransport,\n link: \"/ai-transport\",\n },\n liveObjects: { ...products.liveObjects, link: \"/liveobjects\" },\n};\n"],"names":["React","G2BestMeetsRequirementsSpring2025","G2BestResultsSpring2025","G2BestSupportSpring2025","products","DialpadLogo","DialpadLogoDark","productsMenu","label","icon","link","compareMenu","solutionsMenu","description","customerStoriesHighlight","companyLogo","companyLogoDark","companyLink","companyName","companyDesc","companyMenu","badge","ablyAwards","image","desc","menuItemLinks","name","isHiddenMobile","defaultBlogPosts","title","categories","pubDate","productsForNav","pubsub","liveSync","chat","spaces","aiTransport","liveObjects"],"mappings":"AAAA,OAAOA,UAAW,OAAQ,AAI1B,QAAOC,sCAAuC,6DAA8D,AAC5G,QAAOC,4BAA6B,kDAAmD,AACvF,QAAOC,4BAA6B,kDAAmD,AACvF,QAASC,QAAQ,KAAQ,qBAAsB,AAC/C,QAAOC,gBAAiB,+CAAgD,AACxE,QAAOC,oBAAqB,8CAA+C,AAmB3E,QAAO,MAAMC,aAAkC,CAC7C,CACEC,MAAO,eACPC,KAAM,iCACNC,KAAM,WACR,EACA,CACEF,MAAO,eACPC,KAAM,gCACNC,KAAM,6BACR,EACA,CACEF,MAAO,OACPC,KAAM,oCACNC,KAAM,YACR,EACA,CACEF,MAAO,wBACPC,KAAM,gCACNC,KAAM,0BACR,EACD,AAAC,AAEF,QAAO,MAAMC,YAAiC,CAC5C,CACEH,MAAO,iBACPE,KAAM,yBACR,EACA,CACEF,MAAO,iBACPE,KAAM,yBACR,EACA,CACEF,MAAO,oBACPE,KAAM,2BACR,EACD,AAAC,AAEF,QAAO,MAAME,cAAmC,CAC9C,CACEJ,MAAO,iBACPC,KAAM,iCACNC,KAAM,kBACNG,YAAa,8DACf,EACA,CACEL,MAAO,UACPC,KAAM,mCACNC,KAAM,YACNG,YACE,+DACJ,EACA,CACEL,MAAO,SACPC,KAAM,gCACNC,KAAM,WACNG,YAAa,yDACf,EACA,CACEL,MAAO,SACPC,KAAM,8BACNC,KAAM,WACNG,YACE,iEACJ,EAEA,CACEL,MAAO,aACPC,KAAM,6BACNC,KAAM,eACNG,YAAa,uDACf,EACD,AAAC,AAEF,QAAO,MAAMC,yBAAmD,CAC9DC,YAAaV,YACbW,gBAAiBV,gBACjBW,YAAa,wBACbC,YAAa,UACbC,YACE,+GACJ,CAAE,AAEF,QAAO,MAAMC,YAAiC,CAC5C,CACEZ,MAAO,YACPC,KAAM,sBACNC,KAAM,QACR,EACA,CACEF,MAAO,UACPC,KAAM,6BACNC,KAAM,WACNW,MAAO,cACT,EACD,AAAC,AAEF,QAAO,MAAMC,WAAa,CACxB,CACEC,MAAOpB,wBACPqB,KAAM,6BACR,EACA,CACED,MAAOtB,kCACPuB,KAAM,wCACR,EACA,CACED,MAAOrB,wBACPsB,KAAM,6BACR,EACD,AAAC,AAEF,QAAO,MAAMC,cAAgB,CAC3B,CAAEC,KAAM,UAAWhB,KAAM,WAAYiB,eAAgB,IAAK,EAC1D,CAAED,KAAM,OAAQhB,KAAM,QAASiB,eAAgB,IAAK,EACrD,AAAC,AAEF,QAAO,MAAMC,iBAA+B,CAC1C,CACEC,MACE,4EACFnB,KAAM,0BACNoB,WAAY,CACV,cACA,eACA,cACA,uBACA,cACA,YACD,CACDC,QAAS,aACX,EACA,CACEF,MACE,iFACFnB,KAAM,qCACNoB,WAAY,CAAC,OAAO,CACpBC,QAAS,cACX,EACD,AAAC,AAEF,QAAO,MAAMC,eAAiB,CAC5B,GAAG5B,QAAQ,CACX6B,OAAQ,CAAE,GAAG7B,SAAS6B,MAAM,CAAEvB,KAAM,SAAU,EAC9CwB,SAAU,CAAE,GAAG9B,SAAS8B,QAAQ,CAAExB,KAAM,WAAY,EACpDyB,KAAM,CAAE,GAAG/B,SAAS+B,IAAI,CAAEzB,KAAM,OAAQ,EACxC0B,OAAQ,CAAE,GAAGhC,SAASgC,MAAM,CAAE1B,KAAM,SAAU,EAC9C2B,YAAa,CACX,GAAGjC,SAASiC,WAAW,CACvB3B,KAAM,eACR,EACA4B,YAAa,CAAE,GAAGlC,SAASkC,WAAW,CAAE5B,KAAM,cAAe,CAC/D,CAAE"}
1
+ {"version":3,"sources":["../../../src/core/Meganav/data.tsx"],"sourcesContent":["import React from \"react\";\nimport { IconName } from \"../Icon/types\";\nimport { CustomerStoryHighlight } from \"./MeganavCustomerStories\";\nimport { BlogPost } from \"./MeganavBlog\";\nimport G2BestMeetsRequirementsSpring2025 from \"../images/badges/g2-best-meets-requirements-spring-2025.svg\";\nimport G2BestResultsSpring2025 from \"../images/badges/g2-best-results-spring-2025.svg\";\nimport G2BestSupportSpring2025 from \"../images/badges/g2-best-support-spring-2025.svg\";\nimport { products } from \"../ProductTile/data\";\nimport FinLogoLight from \"../Meganav/images/cust-logo-fin-light.svg\";\nimport FinLogoDark from \"../Meganav/images/cust-logo-fin-dark.svg\";\n\nexport type FlyoutPanelList = {\n label: string;\n icon?: IconName;\n link: string;\n isMobile?: boolean;\n description?: string;\n badge?: string;\n};\n\nexport type MenuItem = {\n name: string;\n link?: string;\n isHiddenMobile?: boolean;\n content?: React.ReactNode;\n panelClassName?: string;\n};\n\nexport const productsMenu: FlyoutPanelList[] = [\n {\n label: \"Architecture\",\n icon: \"icon-gui-squares-2-x-2-outline\",\n link: \"/platform\",\n },\n {\n label: \"Integrations\",\n icon: \"icon-gui-puzzle-piece-outline\",\n link: \"/docs/platform/integrations\",\n },\n {\n label: \"SDKs\",\n icon: \"icon-gui-cube-transparent-outline\",\n link: \"/docs/sdks\",\n },\n {\n label: \"Security & Compliance\",\n icon: \"icon-gui-shield-check-outline\",\n link: \"/security-and-compliance\",\n },\n];\n\nexport const compareMenu: FlyoutPanelList[] = [\n {\n label: \"Ably vs Pusher\",\n link: \"/compare/ably-vs-pusher\",\n },\n {\n label: \"Ably vs PubNub\",\n link: \"/compare/ably-vs-pubnub\",\n },\n {\n label: \"Ably vs Socket.io\",\n link: \"/compare/ably-vs-socketio\",\n },\n];\n\nexport const solutionsMenu: FlyoutPanelList[] = [\n {\n label: \"Fan Engagement\",\n icon: \"icon-gui-hand-thumb-up-outline\",\n link: \"/fan-engagement\",\n description: \"Enhance every moment with live, interactive fan experiences.\",\n },\n {\n label: \"FinTech\",\n icon: \"icon-gui-currency-dollar-outline\",\n link: \"/fin-tech\",\n description:\n \"Speed, reliability, and confidence in every user interaction.\",\n },\n {\n label: \"EdTech\",\n icon: \"icon-gui-academic-cap-outline\",\n link: \"/ed-tech\",\n description: \"Power collaborative, interactive learning environments.\",\n },\n {\n label: \"CXTech\",\n icon: \"icon-gui-face-smile-outline\",\n link: \"/cx-tech\",\n description:\n \"Deliver fast support, strong relationships, and high retention.\",\n },\n\n {\n label: \"HealthTech\",\n icon: \"icon-gui-heartbeat-outline\",\n link: \"/health-tech\",\n description: \"Reliable tools with full data privacy and compliance.\",\n },\n];\n\nexport const customerStoriesHighlight: CustomerStoryHighlight = {\n companyLogo: FinLogoLight,\n companyLogoDark: FinLogoDark,\n companyLink: \"/case-studies/fin-intercom\",\n companyName: \"Fin\",\n companyDesc:\n \"drove 80% more efficiency in realtime delivery for AI chat by replacing its in-house realtime platform with Ably.\",\n};\n\nexport const companyMenu: FlyoutPanelList[] = [\n {\n label: \"Our story\",\n icon: \"icon-gui-ably-badge\",\n link: \"/about\",\n },\n {\n label: \"Careers\",\n icon: \"icon-gui-briefcase-outline\",\n link: \"/careers\",\n badge: \"WE'RE HIRING\",\n },\n];\n\nexport const ablyAwards = [\n {\n image: G2BestSupportSpring2025,\n desc: \"G2 Best Support Spring 2025\",\n },\n {\n image: G2BestMeetsRequirementsSpring2025,\n desc: \"G2 Best Meets Requirements Spring 2025\",\n },\n {\n image: G2BestResultsSpring2025,\n desc: \"G2 Best Results Spring 2025\",\n },\n];\n\nexport const menuItemLinks = [\n { name: \"Pricing\", link: \"/pricing\", isHiddenMobile: true },\n { name: \"Docs\", link: \"/docs\", isHiddenMobile: true },\n];\n\nexport const defaultBlogPosts: BlogPost[] = [\n {\n title:\n \"Ably AI Transport: keep your agents connected and stateful across devices\",\n link: \"/blog/ably-ai-transport\",\n categories: [\n \"New Release\",\n \"AI Transport\",\n \"New Feature\",\n \"RealTime Experiences\",\n \"Engineering\",\n \"Ably News\",\n ],\n pubDate: \"Dec 9, 2025\",\n },\n {\n title:\n \"Live chat at unlimited scale: What it takes to support stadium-sized audiences\",\n link: \"/blog/live-chat-at-unlimited-scale\",\n categories: [\"Chat\"],\n pubDate: \"Nov 18, 2025\",\n },\n];\n\nexport const productsForNav = {\n ...products,\n pubsub: { ...products.pubsub, link: \"/pubsub\" },\n liveSync: { ...products.liveSync, link: \"/livesync\" },\n chat: { ...products.chat, link: \"/chat\" },\n spaces: { ...products.spaces, link: \"/spaces\" },\n aiTransport: {\n ...products.aiTransport,\n link: \"/ai-transport\",\n },\n liveObjects: { ...products.liveObjects, link: \"/liveobjects\" },\n};\n"],"names":["React","G2BestMeetsRequirementsSpring2025","G2BestResultsSpring2025","G2BestSupportSpring2025","products","FinLogoLight","FinLogoDark","productsMenu","label","icon","link","compareMenu","solutionsMenu","description","customerStoriesHighlight","companyLogo","companyLogoDark","companyLink","companyName","companyDesc","companyMenu","badge","ablyAwards","image","desc","menuItemLinks","name","isHiddenMobile","defaultBlogPosts","title","categories","pubDate","productsForNav","pubsub","liveSync","chat","spaces","aiTransport","liveObjects"],"mappings":"AAAA,OAAOA,UAAW,OAAQ,AAI1B,QAAOC,sCAAuC,6DAA8D,AAC5G,QAAOC,4BAA6B,kDAAmD,AACvF,QAAOC,4BAA6B,kDAAmD,AACvF,QAASC,QAAQ,KAAQ,qBAAsB,AAC/C,QAAOC,iBAAkB,2CAA4C,AACrE,QAAOC,gBAAiB,0CAA2C,AAmBnE,QAAO,MAAMC,aAAkC,CAC7C,CACEC,MAAO,eACPC,KAAM,iCACNC,KAAM,WACR,EACA,CACEF,MAAO,eACPC,KAAM,gCACNC,KAAM,6BACR,EACA,CACEF,MAAO,OACPC,KAAM,oCACNC,KAAM,YACR,EACA,CACEF,MAAO,wBACPC,KAAM,gCACNC,KAAM,0BACR,EACD,AAAC,AAEF,QAAO,MAAMC,YAAiC,CAC5C,CACEH,MAAO,iBACPE,KAAM,yBACR,EACA,CACEF,MAAO,iBACPE,KAAM,yBACR,EACA,CACEF,MAAO,oBACPE,KAAM,2BACR,EACD,AAAC,AAEF,QAAO,MAAME,cAAmC,CAC9C,CACEJ,MAAO,iBACPC,KAAM,iCACNC,KAAM,kBACNG,YAAa,8DACf,EACA,CACEL,MAAO,UACPC,KAAM,mCACNC,KAAM,YACNG,YACE,+DACJ,EACA,CACEL,MAAO,SACPC,KAAM,gCACNC,KAAM,WACNG,YAAa,yDACf,EACA,CACEL,MAAO,SACPC,KAAM,8BACNC,KAAM,WACNG,YACE,iEACJ,EAEA,CACEL,MAAO,aACPC,KAAM,6BACNC,KAAM,eACNG,YAAa,uDACf,EACD,AAAC,AAEF,QAAO,MAAMC,yBAAmD,CAC9DC,YAAaV,aACbW,gBAAiBV,YACjBW,YAAa,6BACbC,YAAa,MACbC,YACE,mHACJ,CAAE,AAEF,QAAO,MAAMC,YAAiC,CAC5C,CACEZ,MAAO,YACPC,KAAM,sBACNC,KAAM,QACR,EACA,CACEF,MAAO,UACPC,KAAM,6BACNC,KAAM,WACNW,MAAO,cACT,EACD,AAAC,AAEF,QAAO,MAAMC,WAAa,CACxB,CACEC,MAAOpB,wBACPqB,KAAM,6BACR,EACA,CACED,MAAOtB,kCACPuB,KAAM,wCACR,EACA,CACED,MAAOrB,wBACPsB,KAAM,6BACR,EACD,AAAC,AAEF,QAAO,MAAMC,cAAgB,CAC3B,CAAEC,KAAM,UAAWhB,KAAM,WAAYiB,eAAgB,IAAK,EAC1D,CAAED,KAAM,OAAQhB,KAAM,QAASiB,eAAgB,IAAK,EACrD,AAAC,AAEF,QAAO,MAAMC,iBAA+B,CAC1C,CACEC,MACE,4EACFnB,KAAM,0BACNoB,WAAY,CACV,cACA,eACA,cACA,uBACA,cACA,YACD,CACDC,QAAS,aACX,EACA,CACEF,MACE,iFACFnB,KAAM,qCACNoB,WAAY,CAAC,OAAO,CACpBC,QAAS,cACX,EACD,AAAC,AAEF,QAAO,MAAMC,eAAiB,CAC5B,GAAG5B,QAAQ,CACX6B,OAAQ,CAAE,GAAG7B,SAAS6B,MAAM,CAAEvB,KAAM,SAAU,EAC9CwB,SAAU,CAAE,GAAG9B,SAAS8B,QAAQ,CAAExB,KAAM,WAAY,EACpDyB,KAAM,CAAE,GAAG/B,SAAS+B,IAAI,CAAEzB,KAAM,OAAQ,EACxC0B,OAAQ,CAAE,GAAGhC,SAASgC,MAAM,CAAE1B,KAAM,SAAU,EAC9C2B,YAAa,CACX,GAAGjC,SAASiC,WAAW,CACvB3B,KAAM,eACR,EACA4B,YAAa,CAAE,GAAGlC,SAASkC,WAAW,CAAE5B,KAAM,cAAe,CAC/D,CAAE"}
@@ -0,0 +1,5 @@
1
+ <svg width="244" height="122" viewBox="0 0 244 122" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M153.262 81.5712H146.407V50.7141H153.262V81.5712ZM153.262 47.286H146.407V40.4287H153.262V47.286ZM124.127 81.5712H117.271V40.4287H142.979V47.286H124.126V57.5718H142.979V64.4291H124.126V81.5717L124.127 81.5712Z" fill="white"/>
3
+ <path d="M163.545 54.5714V50.7142H156.69V81.5713L163.545 81.5665V65.5711C163.545 59.2495 166.902 55.9994 171.686 55.9994C176.47 55.9994 178.434 59.8922 178.399 65.6066V81.5708H185.254V64.2851C185.29 54.1778 180.006 50.1809 173.361 50.1089C169.643 50.1089 165.676 51.4284 163.545 54.5709V54.5714Z" fill="white"/>
4
+ <path d="M100.324 67.6661C104.042 67.6661 107.05 64.6196 106.987 60.8866C106.926 57.231 103.868 54.3337 100.213 54.3337H97.6566C95.7453 54.3337 93.9922 53.6617 92.6169 52.5428C92.4835 52.4348 92.4835 52.2323 92.6169 52.1243C93.9922 51.0059 95.7453 50.3339 97.6566 50.3339H100.205C103.924 50.3339 107.014 47.3392 106.987 43.6197C106.961 39.8997 104.013 37.0264 100.37 37.001C96.6518 36.9741 93.6578 40.0662 93.6578 43.7848V46.334C93.6578 48.2459 92.9859 49.9993 91.8678 51.375C91.7598 51.5084 91.5573 51.5084 91.4494 51.375C90.3312 49.9988 89.6594 48.2459 89.6594 46.334V43.6677C89.6594 39.9477 86.6136 36.9381 82.8815 37.001C79.2268 37.0624 76.3302 40.121 76.3302 43.7766V46.334C76.3302 48.2459 75.6584 49.9993 74.5402 51.375C74.4323 51.5084 74.2297 51.5084 74.1218 51.375C73.0036 49.9988 72.3318 48.2459 72.3318 46.334V43.6677C72.3313 39.8997 69.2059 36.8608 65.4071 37.0062C61.9534 37.1368 59.1384 39.953 59.0065 43.4075C58.8625 47.2072 61.8987 50.3339 65.6658 50.3339H68.3315C70.2429 50.3339 71.9959 51.0059 73.3712 52.1243C73.5046 52.2323 73.5046 52.4348 73.3712 52.5428C71.9959 53.6617 70.2429 54.3337 68.3315 54.3337H65.7752C62.1199 54.3337 59.0621 57.2305 59.0012 60.8866C58.9397 64.5423 61.9457 67.6661 65.6643 67.6661H68.3301C70.2414 67.6661 71.9945 68.3381 73.3698 69.4565C73.5032 69.5645 73.5032 69.7671 73.3698 69.8751C71.994 70.9939 70.2414 71.6659 68.3301 71.6659H65.6643C61.8973 71.6659 58.8596 74.7926 59.005 78.5923C59.1355 82.0468 61.9505 84.863 65.4057 84.9935C69.2044 85.1375 72.3304 82.1006 72.3304 78.3326V75.6662C72.3304 73.7544 73.0022 72.001 74.1203 70.6253C74.2283 70.4923 74.4308 70.4923 74.5388 70.6253C75.6569 72.0014 76.3287 73.7544 76.3287 75.6662V78.2232C76.3287 81.8788 79.2253 84.9374 82.8801 84.9988C86.6136 85.0617 89.658 82.0536 89.658 78.3341V75.6677C89.658 73.7558 90.3298 72.0024 91.4479 70.6267C91.5559 70.4933 91.7584 70.4933 91.8664 70.6267C92.9845 72.0024 93.6563 73.7558 93.6563 75.6677V78.3341C93.6563 82.102 96.7823 85.1404 100.581 84.995C104.035 84.8644 106.85 82.0483 106.98 78.5923C107.124 74.7926 104.088 71.6659 100.321 71.6659H97.6552C95.7438 71.6659 93.9908 70.9939 92.6155 69.8751C92.4821 69.7671 92.4821 69.5645 92.6155 69.4565C93.9908 68.3381 95.7438 67.6661 97.6552 67.6661H100.324ZM80.3314 50.3339H85.6629C87.5743 50.3339 89.3273 51.0059 90.7027 52.1243C90.8361 52.2323 90.8361 52.4348 90.7027 52.5428C89.3273 53.6617 87.5743 54.3337 85.6629 54.3337H80.3314C78.4201 54.3337 76.6671 53.6617 75.2917 52.5428C75.1583 52.4348 75.1583 52.2323 75.2917 52.1243C76.6671 51.0059 78.4201 50.3339 80.3314 50.3339ZM72.3342 60.9999V58.3335C72.3342 56.4217 73.006 54.6682 74.1242 53.2926C74.2321 53.1591 74.4347 53.1591 74.5426 53.2926C75.6612 54.6687 76.3326 56.4217 76.3326 58.3335V63.6653C76.3326 65.5772 75.6608 67.3306 74.5426 68.7063C74.4347 68.8392 74.2321 68.8392 74.1242 68.7063C73.006 67.3301 72.3342 65.5772 72.3342 63.6653V60.9989V61.0004V60.9999ZM85.6634 71.6659H80.3319C78.4206 71.6659 76.6675 70.9939 75.2922 69.8751C75.1588 69.7671 75.1588 69.5645 75.2922 69.4565C76.6675 68.3381 78.4206 67.6661 80.3319 67.6661H85.6634C87.5748 67.6661 89.3278 68.3381 90.7031 69.4565C90.8365 69.5645 90.8365 69.7671 90.7031 69.8751C89.3278 70.9939 87.5748 71.6659 85.6634 71.6659ZM93.6606 60.9999V63.6663C93.6606 65.5781 92.9888 67.3315 91.8707 68.7072C91.7627 68.8407 91.5602 68.8407 91.4522 68.7072C90.3341 67.3315 89.6623 65.5781 89.6623 63.6663V58.333C89.6623 56.4212 90.3341 54.6678 91.4522 53.2921C91.5602 53.1587 91.7627 53.1587 91.8707 53.2921C92.9888 54.6682 93.6606 56.4212 93.6606 58.333V60.9994V60.9999Z" fill="white"/>
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="244" height="122" viewBox="0 0 244 122" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M153.262 81.5712H146.407V50.7141H153.262V81.5712ZM153.262 47.286H146.407V40.4287H153.262V47.286ZM124.127 81.5712H117.271V40.4287H142.979V47.286H124.126V57.5718H142.979V64.4291H124.126V81.5717L124.127 81.5712Z" fill="#03020D"/>
3
+ <path d="M163.545 54.5714V50.7142H156.69V81.5713L163.545 81.5665V65.5711C163.545 59.2495 166.902 55.9994 171.686 55.9994C176.471 55.9994 178.434 59.8922 178.399 65.6066V81.5708H185.254V64.2851C185.29 54.1778 180.006 50.1809 173.361 50.1089C169.643 50.1089 165.676 51.4284 163.545 54.5709V54.5714Z" fill="#03020D"/>
4
+ <path d="M100.324 67.6661C104.042 67.6661 107.05 64.6196 106.987 60.8866C106.926 57.231 103.868 54.3337 100.213 54.3337H97.6566C95.7453 54.3337 93.9922 53.6617 92.6169 52.5428C92.4835 52.4348 92.4835 52.2323 92.6169 52.1243C93.9922 51.0059 95.7453 50.3339 97.6566 50.3339H100.205C103.924 50.3339 107.014 47.3392 106.987 43.6197C106.961 39.8997 104.013 37.0264 100.37 37.001C96.6518 36.9741 93.6578 40.0662 93.6578 43.7848V46.334C93.6578 48.2459 92.9859 49.9993 91.8678 51.375C91.7598 51.5084 91.5573 51.5084 91.4494 51.375C90.3312 49.9988 89.6594 48.2459 89.6594 46.334V43.6677C89.6594 39.9477 86.6136 36.9381 82.8815 37.001C79.2268 37.0624 76.3302 40.121 76.3302 43.7766V46.334C76.3302 48.2459 75.6584 49.9993 74.5402 51.375C74.4323 51.5084 74.2297 51.5084 74.1218 51.375C73.0036 49.9988 72.3318 48.2459 72.3318 46.334V43.6677C72.3313 39.8997 69.2059 36.8608 65.4071 37.0062C61.9534 37.1368 59.1384 39.953 59.0065 43.4075C58.8625 47.2072 61.8987 50.3339 65.6658 50.3339H68.3315C70.2429 50.3339 71.9959 51.0059 73.3712 52.1243C73.5046 52.2323 73.5046 52.4348 73.3712 52.5428C71.9959 53.6617 70.2429 54.3337 68.3315 54.3337H65.7752C62.1199 54.3337 59.0621 57.2305 59.0012 60.8866C58.9397 64.5423 61.9457 67.6661 65.6643 67.6661H68.3301C70.2414 67.6661 71.9945 68.3381 73.3698 69.4565C73.5032 69.5645 73.5032 69.7671 73.3698 69.8751C71.994 70.9939 70.2414 71.6659 68.3301 71.6659H65.6643C61.8973 71.6659 58.8596 74.7926 59.005 78.5923C59.1355 82.0468 61.9505 84.863 65.4057 84.9935C69.2044 85.1375 72.3304 82.1006 72.3304 78.3326V75.6662C72.3304 73.7544 73.0022 72.001 74.1203 70.6253C74.2283 70.4923 74.4308 70.4923 74.5388 70.6253C75.6569 72.0014 76.3287 73.7544 76.3287 75.6662V78.2232C76.3287 81.8788 79.2253 84.9374 82.8801 84.9988C86.6136 85.0617 89.658 82.0536 89.658 78.3341V75.6677C89.658 73.7558 90.3298 72.0024 91.4479 70.6267C91.5559 70.4933 91.7584 70.4933 91.8664 70.6267C92.9845 72.0024 93.6563 73.7558 93.6563 75.6677V78.3341C93.6563 82.102 96.7823 85.1404 100.581 84.995C104.035 84.8644 106.85 82.0483 106.98 78.5923C107.124 74.7926 104.088 71.6659 100.321 71.6659H97.6552C95.7438 71.6659 93.9908 70.9939 92.6155 69.8751C92.4821 69.7671 92.4821 69.5645 92.6155 69.4565C93.9908 68.3381 95.7438 67.6661 97.6552 67.6661H100.324ZM80.3314 50.3339H85.6629C87.5743 50.3339 89.3273 51.0059 90.7027 52.1243C90.8361 52.2323 90.8361 52.4348 90.7027 52.5428C89.3273 53.6617 87.5743 54.3337 85.6629 54.3337H80.3314C78.4201 54.3337 76.6671 53.6617 75.2917 52.5428C75.1583 52.4348 75.1583 52.2323 75.2917 52.1243C76.6671 51.0059 78.4201 50.3339 80.3314 50.3339ZM72.3342 60.9999V58.3335C72.3342 56.4217 73.006 54.6682 74.1242 53.2926C74.2321 53.1591 74.4347 53.1591 74.5426 53.2926C75.6612 54.6687 76.3326 56.4217 76.3326 58.3335V63.6653C76.3326 65.5772 75.6608 67.3306 74.5426 68.7063C74.4347 68.8392 74.2321 68.8392 74.1242 68.7063C73.006 67.3301 72.3342 65.5772 72.3342 63.6653V60.9989V61.0004V60.9999ZM85.6634 71.6659H80.3319C78.4206 71.6659 76.6675 70.9939 75.2922 69.8751C75.1588 69.7671 75.1588 69.5645 75.2922 69.4565C76.6675 68.3381 78.4206 67.6661 80.3319 67.6661H85.6634C87.5748 67.6661 89.3278 68.3381 90.7031 69.4565C90.8365 69.5645 90.8365 69.7671 90.7031 69.8751C89.3278 70.9939 87.5748 71.6659 85.6634 71.6659ZM93.6606 60.9999V63.6663C93.6606 65.5781 92.9888 67.3315 91.8707 68.7072C91.7627 68.8407 91.5602 68.8407 91.4522 68.7072C90.3341 67.3315 89.6623 65.5781 89.6623 63.6663V58.333C89.6623 56.4212 90.3341 54.6678 91.4522 53.2921C91.5602 53.1587 91.7627 53.1587 91.8707 53.2921C92.9888 54.6682 93.6606 56.4212 93.6606 58.333V60.9994V60.9999Z" fill="#03020D"/>
5
+ </svg>
package/core/Meganav.js CHANGED
@@ -1,2 +1,2 @@
1
- import React,{useEffect,useMemo}from"react";import Header from"./Header";import Flyout from"./Flyout";import{MeganavMobile}from"./Meganav/MeganavMobile";import Notice from"./Notice";import{HEADER_HEIGHT}from"./utils/heights";import{getMenuItemsForHeader}from"./Meganav/utils/getMenuItemsForHeader";const Meganav=({sessionState,notice,theme,themedScrollpoints,onNoticeClose,blogPosts})=>{const[noticeHeight,setNoticeHeight]=React.useState(0);const finalNoticeHeight=notice?noticeHeight:0;const headerMenuItems=useMemo(()=>getMenuItemsForHeader(blogPosts),[blogPosts]);const mobileNavItems=useMemo(()=>headerMenuItems.filter(item=>!item.isHiddenMobile).map(({name,link,content})=>({name,link,content})),[headerMenuItems]);const defaultThemedScrollpoints=[{id:"meganav",className:"ui-theme-light !bg-transparent !border-none"},{id:"meganav-theme-dark",className:"ui-theme-dark !bg-transparent !border-none"},{id:"main",className:"ui-theme-light bg-neutral-000 dark:bg-neutral-1300 border-b"},{id:"main-theme-dark",className:"ui-theme-dark bg-neutral-000 dark:bg-neutral-1300 border-b"}];useEffect(()=>{if(!notice)return;const noticeElement=document.querySelector('[data-id="ui-notice"]');if(!noticeElement)return;const updateNoticeHeight=()=>{setNoticeHeight(noticeElement.getBoundingClientRect().height)};const observer=new ResizeObserver(updateNoticeHeight);observer.observe(noticeElement);const timeoutId=setTimeout(updateNoticeHeight,0);window.addEventListener("resize",updateNoticeHeight);return()=>{clearTimeout(timeoutId);observer.disconnect();window.removeEventListener("resize",updateNoticeHeight)}},[notice]);return React.createElement(React.Fragment,null,React.createElement("div",{className:"absolute top-0 left-0 right-0 w-full z-50",id:theme==="dark"?"meganav-theme-dark":"meganav","data-testid":"meganav",style:{height:HEADER_HEIGHT+finalNoticeHeight}},notice&&React.createElement(Notice,{...notice.props,config:notice.config,onClose:onNoticeClose}),React.createElement(Header,{className:"max-w-screen-xl mx-auto",isNoticeBannerEnabled:!!notice,noticeHeight:finalNoticeHeight,nav:React.createElement(Flyout,{menuItems:headerMenuItems,className:"justify-left z-40",flyOutClassName:"flex justify-left",viewPortClassName:"ui-shadow-lg-medium border border-neutral-200 dark:border-neutral-1100 rounded-2xl -mt-1 bg-neutral-000 dark:bg-neutral-1300"}),mobileNav:React.createElement(MeganavMobile,{navItems:mobileNavItems}),headerLinks:[{href:"/contact",label:"Contact us"}],headerLinksClassName:"md:gap-x-6 ",sessionState:sessionState,themedScrollpoints:themedScrollpoints??defaultThemedScrollpoints})))};export default Meganav;
1
+ import React,{useEffect,useMemo}from"react";import Header from"./Header";import Flyout from"./Flyout";import{MeganavMobile}from"./Meganav/MeganavMobile";import Notice from"./Notice";import{HEADER_HEIGHT}from"./utils/heights";import{getMenuItemsForHeader}from"./Meganav/utils/getMenuItemsForHeader";const Meganav=({sessionState,notice,theme,themedScrollpoints,onNoticeClose,blogPosts})=>{const[noticeHeight,setNoticeHeight]=React.useState(0);const finalNoticeHeight=notice?noticeHeight:0;const headerMenuItems=useMemo(()=>getMenuItemsForHeader(blogPosts),[blogPosts]);const mobileNavItems=useMemo(()=>headerMenuItems.filter(item=>!item.isHiddenMobile).map(({name,link,content})=>({name,link,content})),[headerMenuItems]);const defaultThemedScrollpoints=[{id:"meganav",className:"ui-theme-light !bg-transparent !border-none"},{id:"meganav-theme-dark",className:"ui-theme-dark !bg-transparent !border-none"},{id:"main",className:"ui-theme-light bg-neutral-000 dark:bg-neutral-1300 border-b"},{id:"main-theme-dark",className:"ui-theme-dark bg-neutral-000 dark:bg-neutral-1300 border-b"}];useEffect(()=>{if(!notice)return;const noticeElement=document.querySelector('[data-id="ui-notice"]');if(!noticeElement)return;const updateNoticeHeight=()=>{setNoticeHeight(noticeElement.getBoundingClientRect().height)};const observer=new ResizeObserver(updateNoticeHeight);observer.observe(noticeElement);const timeoutId=setTimeout(updateNoticeHeight,0);window.addEventListener("resize",updateNoticeHeight);return()=>{clearTimeout(timeoutId);observer.disconnect();window.removeEventListener("resize",updateNoticeHeight)}},[notice]);return React.createElement(React.Fragment,null,React.createElement("div",{className:"absolute top-0 left-0 right-0 w-full z-50",id:theme==="dark"?"meganav-theme-dark":"meganav","data-testid":"meganav",style:{height:HEADER_HEIGHT+finalNoticeHeight}},notice&&React.createElement(Notice,{...notice.props,config:notice.config,onClose:onNoticeClose}),React.createElement(Header,{className:"max-w-screen-xl mx-auto ui-grid-px",isNoticeBannerEnabled:!!notice,noticeHeight:finalNoticeHeight,nav:React.createElement(Flyout,{menuItems:headerMenuItems,className:"justify-left z-40",flyOutClassName:"flex justify-left",viewPortClassName:"ui-shadow-lg-medium border border-neutral-200 dark:border-neutral-1100 rounded-2xl -mt-1 bg-neutral-000 dark:bg-neutral-1300"}),mobileNav:React.createElement(MeganavMobile,{navItems:mobileNavItems}),headerLinks:[{href:"/contact",label:"Contact us"}],headerLinksClassName:"md:gap-x-6 ",sessionState:sessionState,themedScrollpoints:themedScrollpoints??defaultThemedScrollpoints})))};export default Meganav;
2
2
  //# sourceMappingURL=Meganav.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/Meganav.tsx"],"sourcesContent":["import React, { useEffect, useMemo } from \"react\";\nimport Header, { HeaderSessionState, ThemedScrollpoint } from \"./Header\";\nimport Flyout from \"./Flyout\";\nimport { MeganavMobile } from \"./Meganav/MeganavMobile\";\nimport Notice from \"./Notice\";\nimport { HEADER_HEIGHT } from \"./utils/heights\";\nimport { BlogPost } from \"./Meganav/MeganavBlog\";\nimport { getMenuItemsForHeader } from \"./Meganav/utils/getMenuItemsForHeader\";\n\nexport type MeganavNoticeBannerProps = {\n props: {\n title: string;\n bodyText: string;\n buttonLink: string;\n buttonLabel: string;\n closeBtn: boolean;\n };\n config: {\n cookieId: string;\n noticeId: string | number;\n options: {\n collapse: boolean;\n };\n };\n};\n\nexport type MeganavProps = {\n sessionState: HeaderSessionState;\n blogPosts: BlogPost[];\n notice?: MeganavNoticeBannerProps;\n theme?: string;\n themedScrollpoints?: ThemedScrollpoint[];\n onNoticeClose?: () => void;\n};\n\nconst Meganav = ({\n sessionState,\n notice,\n theme,\n themedScrollpoints,\n onNoticeClose,\n blogPosts,\n}: MeganavProps) => {\n const [noticeHeight, setNoticeHeight] = React.useState(0);\n\n const finalNoticeHeight = notice ? noticeHeight : 0;\n\n const headerMenuItems = useMemo(\n () => getMenuItemsForHeader(blogPosts),\n [blogPosts],\n );\n\n const mobileNavItems = useMemo(\n () =>\n headerMenuItems\n .filter((item) => !item.isHiddenMobile)\n .map(({ name, link, content }) => ({ name, link, content })),\n [headerMenuItems],\n );\n\n const defaultThemedScrollpoints = [\n {\n id: \"meganav\",\n className: \"ui-theme-light !bg-transparent !border-none\",\n },\n {\n id: \"meganav-theme-dark\",\n className: \"ui-theme-dark !bg-transparent !border-none\",\n },\n {\n id: \"main\",\n className: \"ui-theme-light bg-neutral-000 dark:bg-neutral-1300 border-b\",\n },\n {\n id: \"main-theme-dark\",\n className: \"ui-theme-dark bg-neutral-000 dark:bg-neutral-1300 border-b\",\n },\n ];\n\n useEffect(() => {\n if (!notice) return;\n\n const noticeElement = document.querySelector('[data-id=\"ui-notice\"]');\n if (!noticeElement) return;\n\n const updateNoticeHeight = () => {\n setNoticeHeight(noticeElement.getBoundingClientRect().height);\n };\n\n const observer = new ResizeObserver(updateNoticeHeight);\n observer.observe(noticeElement);\n\n const timeoutId = setTimeout(updateNoticeHeight, 0);\n window.addEventListener(\"resize\", updateNoticeHeight);\n\n return () => {\n clearTimeout(timeoutId);\n observer.disconnect();\n window.removeEventListener(\"resize\", updateNoticeHeight);\n };\n }, [notice]);\n\n return (\n <>\n <div\n className=\"absolute top-0 left-0 right-0 w-full z-50\"\n id={theme === \"dark\" ? \"meganav-theme-dark\" : \"meganav\"}\n data-testid=\"meganav\"\n style={{ height: HEADER_HEIGHT + finalNoticeHeight }}\n >\n {notice && (\n <Notice\n {...notice.props}\n config={notice.config}\n onClose={onNoticeClose}\n />\n )}\n <Header\n className=\"max-w-screen-xl mx-auto\"\n isNoticeBannerEnabled={!!notice}\n noticeHeight={finalNoticeHeight}\n nav={\n <Flyout\n menuItems={headerMenuItems}\n className=\"justify-left z-40\"\n flyOutClassName=\"flex justify-left\"\n viewPortClassName=\"ui-shadow-lg-medium border border-neutral-200 dark:border-neutral-1100 rounded-2xl -mt-1 bg-neutral-000 dark:bg-neutral-1300\"\n />\n }\n mobileNav={<MeganavMobile navItems={mobileNavItems} />}\n headerLinks={[{ href: \"/contact\", label: \"Contact us\" }]}\n headerLinksClassName=\"md:gap-x-6 \"\n sessionState={sessionState}\n themedScrollpoints={themedScrollpoints ?? defaultThemedScrollpoints}\n />\n </div>\n </>\n );\n};\n\nexport default Meganav;\n"],"names":["React","useEffect","useMemo","Header","Flyout","MeganavMobile","Notice","HEADER_HEIGHT","getMenuItemsForHeader","Meganav","sessionState","notice","theme","themedScrollpoints","onNoticeClose","blogPosts","noticeHeight","setNoticeHeight","useState","finalNoticeHeight","headerMenuItems","mobileNavItems","filter","item","isHiddenMobile","map","name","link","content","defaultThemedScrollpoints","id","className","noticeElement","document","querySelector","updateNoticeHeight","getBoundingClientRect","height","observer","ResizeObserver","observe","timeoutId","setTimeout","window","addEventListener","clearTimeout","disconnect","removeEventListener","div","data-testid","style","props","config","onClose","isNoticeBannerEnabled","nav","menuItems","flyOutClassName","viewPortClassName","mobileNav","navItems","headerLinks","href","label","headerLinksClassName"],"mappings":"AAAA,OAAOA,OAASC,SAAS,CAAEC,OAAO,KAAQ,OAAQ,AAClD,QAAOC,WAAuD,UAAW,AACzE,QAAOC,WAAY,UAAW,AAC9B,QAASC,aAAa,KAAQ,yBAA0B,AACxD,QAAOC,WAAY,UAAW,AAC9B,QAASC,aAAa,KAAQ,iBAAkB,AAEhD,QAASC,qBAAqB,KAAQ,uCAAwC,CA4B9E,MAAMC,QAAU,CAAC,CACfC,YAAY,CACZC,MAAM,CACNC,KAAK,CACLC,kBAAkB,CAClBC,aAAa,CACbC,SAAS,CACI,IACb,KAAM,CAACC,aAAcC,gBAAgB,CAAGjB,MAAMkB,QAAQ,CAAC,GAEvD,MAAMC,kBAAoBR,OAASK,aAAe,EAElD,MAAMI,gBAAkBlB,QACtB,IAAMM,sBAAsBO,WAC5B,CAACA,UAAU,EAGb,MAAMM,eAAiBnB,QACrB,IACEkB,gBACGE,MAAM,CAAC,AAACC,MAAS,CAACA,KAAKC,cAAc,EACrCC,GAAG,CAAC,CAAC,CAAEC,IAAI,CAAEC,IAAI,CAAEC,OAAO,CAAE,GAAM,CAAA,CAAEF,KAAMC,KAAMC,OAAQ,CAAA,GAC7D,CAACR,gBAAgB,EAGnB,MAAMS,0BAA4B,CAChC,CACEC,GAAI,UACJC,UAAW,6CACb,EACA,CACED,GAAI,qBACJC,UAAW,4CACb,EACA,CACED,GAAI,OACJC,UAAW,6DACb,EACA,CACED,GAAI,kBACJC,UAAW,4DACb,EACD,CAED9B,UAAU,KACR,GAAI,CAACU,OAAQ,OAEb,MAAMqB,cAAgBC,SAASC,aAAa,CAAC,yBAC7C,GAAI,CAACF,cAAe,OAEpB,MAAMG,mBAAqB,KACzBlB,gBAAgBe,cAAcI,qBAAqB,GAAGC,MAAM,CAC9D,EAEA,MAAMC,SAAW,IAAIC,eAAeJ,oBACpCG,SAASE,OAAO,CAACR,eAEjB,MAAMS,UAAYC,WAAWP,mBAAoB,GACjDQ,OAAOC,gBAAgB,CAAC,SAAUT,oBAElC,MAAO,KACLU,aAAaJ,WACbH,SAASQ,UAAU,GACnBH,OAAOI,mBAAmB,CAAC,SAAUZ,mBACvC,CACF,EAAG,CAACxB,OAAO,EAEX,OACE,wCACE,oBAACqC,OACCjB,UAAU,4CACVD,GAAIlB,QAAU,OAAS,qBAAuB,UAC9CqC,cAAY,UACZC,MAAO,CAAEb,OAAQ9B,cAAgBY,iBAAkB,GAElDR,QACC,oBAACL,QACE,GAAGK,OAAOwC,KAAK,CAChBC,OAAQzC,OAAOyC,MAAM,CACrBC,QAASvC,gBAGb,oBAACX,QACC4B,UAAU,0BACVuB,sBAAuB,CAAC,CAAC3C,OACzBK,aAAcG,kBACdoC,IACE,oBAACnD,QACCoD,UAAWpC,gBACXW,UAAU,oBACV0B,gBAAgB,oBAChBC,kBAAkB,iIAGtBC,UAAW,oBAACtD,eAAcuD,SAAUvC,iBACpCwC,YAAa,CAAC,CAAEC,KAAM,WAAYC,MAAO,YAAa,EAAE,CACxDC,qBAAqB,cACrBtD,aAAcA,aACdG,mBAAoBA,oBAAsBgB,6BAKpD,CAEA,gBAAepB,OAAQ"}
1
+ {"version":3,"sources":["../../src/core/Meganav.tsx"],"sourcesContent":["import React, { useEffect, useMemo } from \"react\";\nimport Header, { HeaderSessionState, ThemedScrollpoint } from \"./Header\";\nimport Flyout from \"./Flyout\";\nimport { MeganavMobile } from \"./Meganav/MeganavMobile\";\nimport Notice from \"./Notice\";\nimport { HEADER_HEIGHT } from \"./utils/heights\";\nimport { BlogPost } from \"./Meganav/MeganavBlog\";\nimport { getMenuItemsForHeader } from \"./Meganav/utils/getMenuItemsForHeader\";\n\nexport type MeganavNoticeBannerProps = {\n props: {\n title: string;\n bodyText: string;\n buttonLink: string;\n buttonLabel: string;\n closeBtn: boolean;\n };\n config: {\n cookieId: string;\n noticeId: string | number;\n options: {\n collapse: boolean;\n };\n };\n};\n\nexport type MeganavProps = {\n sessionState: HeaderSessionState;\n blogPosts: BlogPost[];\n notice?: MeganavNoticeBannerProps;\n theme?: string;\n themedScrollpoints?: ThemedScrollpoint[];\n onNoticeClose?: () => void;\n};\n\nconst Meganav = ({\n sessionState,\n notice,\n theme,\n themedScrollpoints,\n onNoticeClose,\n blogPosts,\n}: MeganavProps) => {\n const [noticeHeight, setNoticeHeight] = React.useState(0);\n\n const finalNoticeHeight = notice ? noticeHeight : 0;\n\n const headerMenuItems = useMemo(\n () => getMenuItemsForHeader(blogPosts),\n [blogPosts],\n );\n\n const mobileNavItems = useMemo(\n () =>\n headerMenuItems\n .filter((item) => !item.isHiddenMobile)\n .map(({ name, link, content }) => ({ name, link, content })),\n [headerMenuItems],\n );\n\n const defaultThemedScrollpoints = [\n {\n id: \"meganav\",\n className: \"ui-theme-light !bg-transparent !border-none\",\n },\n {\n id: \"meganav-theme-dark\",\n className: \"ui-theme-dark !bg-transparent !border-none\",\n },\n {\n id: \"main\",\n className: \"ui-theme-light bg-neutral-000 dark:bg-neutral-1300 border-b\",\n },\n {\n id: \"main-theme-dark\",\n className: \"ui-theme-dark bg-neutral-000 dark:bg-neutral-1300 border-b\",\n },\n ];\n\n useEffect(() => {\n if (!notice) return;\n\n const noticeElement = document.querySelector('[data-id=\"ui-notice\"]');\n if (!noticeElement) return;\n\n const updateNoticeHeight = () => {\n setNoticeHeight(noticeElement.getBoundingClientRect().height);\n };\n\n const observer = new ResizeObserver(updateNoticeHeight);\n observer.observe(noticeElement);\n\n const timeoutId = setTimeout(updateNoticeHeight, 0);\n window.addEventListener(\"resize\", updateNoticeHeight);\n\n return () => {\n clearTimeout(timeoutId);\n observer.disconnect();\n window.removeEventListener(\"resize\", updateNoticeHeight);\n };\n }, [notice]);\n\n return (\n <>\n <div\n className=\"absolute top-0 left-0 right-0 w-full z-50\"\n id={theme === \"dark\" ? \"meganav-theme-dark\" : \"meganav\"}\n data-testid=\"meganav\"\n style={{ height: HEADER_HEIGHT + finalNoticeHeight }}\n >\n {notice && (\n <Notice\n {...notice.props}\n config={notice.config}\n onClose={onNoticeClose}\n />\n )}\n <Header\n className=\"max-w-screen-xl mx-auto ui-grid-px\"\n isNoticeBannerEnabled={!!notice}\n noticeHeight={finalNoticeHeight}\n nav={\n <Flyout\n menuItems={headerMenuItems}\n className=\"justify-left z-40\"\n flyOutClassName=\"flex justify-left\"\n viewPortClassName=\"ui-shadow-lg-medium border border-neutral-200 dark:border-neutral-1100 rounded-2xl -mt-1 bg-neutral-000 dark:bg-neutral-1300\"\n />\n }\n mobileNav={<MeganavMobile navItems={mobileNavItems} />}\n headerLinks={[{ href: \"/contact\", label: \"Contact us\" }]}\n headerLinksClassName=\"md:gap-x-6 \"\n sessionState={sessionState}\n themedScrollpoints={themedScrollpoints ?? defaultThemedScrollpoints}\n />\n </div>\n </>\n );\n};\n\nexport default Meganav;\n"],"names":["React","useEffect","useMemo","Header","Flyout","MeganavMobile","Notice","HEADER_HEIGHT","getMenuItemsForHeader","Meganav","sessionState","notice","theme","themedScrollpoints","onNoticeClose","blogPosts","noticeHeight","setNoticeHeight","useState","finalNoticeHeight","headerMenuItems","mobileNavItems","filter","item","isHiddenMobile","map","name","link","content","defaultThemedScrollpoints","id","className","noticeElement","document","querySelector","updateNoticeHeight","getBoundingClientRect","height","observer","ResizeObserver","observe","timeoutId","setTimeout","window","addEventListener","clearTimeout","disconnect","removeEventListener","div","data-testid","style","props","config","onClose","isNoticeBannerEnabled","nav","menuItems","flyOutClassName","viewPortClassName","mobileNav","navItems","headerLinks","href","label","headerLinksClassName"],"mappings":"AAAA,OAAOA,OAASC,SAAS,CAAEC,OAAO,KAAQ,OAAQ,AAClD,QAAOC,WAAuD,UAAW,AACzE,QAAOC,WAAY,UAAW,AAC9B,QAASC,aAAa,KAAQ,yBAA0B,AACxD,QAAOC,WAAY,UAAW,AAC9B,QAASC,aAAa,KAAQ,iBAAkB,AAEhD,QAASC,qBAAqB,KAAQ,uCAAwC,CA4B9E,MAAMC,QAAU,CAAC,CACfC,YAAY,CACZC,MAAM,CACNC,KAAK,CACLC,kBAAkB,CAClBC,aAAa,CACbC,SAAS,CACI,IACb,KAAM,CAACC,aAAcC,gBAAgB,CAAGjB,MAAMkB,QAAQ,CAAC,GAEvD,MAAMC,kBAAoBR,OAASK,aAAe,EAElD,MAAMI,gBAAkBlB,QACtB,IAAMM,sBAAsBO,WAC5B,CAACA,UAAU,EAGb,MAAMM,eAAiBnB,QACrB,IACEkB,gBACGE,MAAM,CAAC,AAACC,MAAS,CAACA,KAAKC,cAAc,EACrCC,GAAG,CAAC,CAAC,CAAEC,IAAI,CAAEC,IAAI,CAAEC,OAAO,CAAE,GAAM,CAAA,CAAEF,KAAMC,KAAMC,OAAQ,CAAA,GAC7D,CAACR,gBAAgB,EAGnB,MAAMS,0BAA4B,CAChC,CACEC,GAAI,UACJC,UAAW,6CACb,EACA,CACED,GAAI,qBACJC,UAAW,4CACb,EACA,CACED,GAAI,OACJC,UAAW,6DACb,EACA,CACED,GAAI,kBACJC,UAAW,4DACb,EACD,CAED9B,UAAU,KACR,GAAI,CAACU,OAAQ,OAEb,MAAMqB,cAAgBC,SAASC,aAAa,CAAC,yBAC7C,GAAI,CAACF,cAAe,OAEpB,MAAMG,mBAAqB,KACzBlB,gBAAgBe,cAAcI,qBAAqB,GAAGC,MAAM,CAC9D,EAEA,MAAMC,SAAW,IAAIC,eAAeJ,oBACpCG,SAASE,OAAO,CAACR,eAEjB,MAAMS,UAAYC,WAAWP,mBAAoB,GACjDQ,OAAOC,gBAAgB,CAAC,SAAUT,oBAElC,MAAO,KACLU,aAAaJ,WACbH,SAASQ,UAAU,GACnBH,OAAOI,mBAAmB,CAAC,SAAUZ,mBACvC,CACF,EAAG,CAACxB,OAAO,EAEX,OACE,wCACE,oBAACqC,OACCjB,UAAU,4CACVD,GAAIlB,QAAU,OAAS,qBAAuB,UAC9CqC,cAAY,UACZC,MAAO,CAAEb,OAAQ9B,cAAgBY,iBAAkB,GAElDR,QACC,oBAACL,QACE,GAAGK,OAAOwC,KAAK,CAChBC,OAAQzC,OAAOyC,MAAM,CACrBC,QAASvC,gBAGb,oBAACX,QACC4B,UAAU,qCACVuB,sBAAuB,CAAC,CAAC3C,OACzBK,aAAcG,kBACdoC,IACE,oBAACnD,QACCoD,UAAWpC,gBACXW,UAAU,oBACV0B,gBAAgB,oBAChBC,kBAAkB,iIAGtBC,UAAW,oBAACtD,eAAcuD,SAAUvC,iBACpCwC,YAAa,CAAC,CAAEC,KAAM,WAAYC,MAAO,YAAa,EAAE,CACxDC,qBAAqB,cACrBtD,aAAcA,aACdG,mBAAoBA,oBAAsBgB,6BAKpD,CAEA,gBAAepB,OAAQ"}
@@ -0,0 +1,2 @@
1
+ import{isbot}from"isbot";import*as logger from"./logger";const FORGED_UA_PATTERN=/Version\/[\d.]+.*Firefox\//;export const isLikelyAutomated=()=>{if(typeof navigator==="undefined"){return true}if(navigator.webdriver){return true}try{const userAgent=navigator.userAgent??"";return isbot(userAgent)||FORGED_UA_PATTERN.test(userAgent)}catch(e){logger.error("Bot detection failed, failing closed",e);return true}};
2
+ //# sourceMappingURL=bot-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/insights/bot-detection.ts"],"sourcesContent":["import { isbot } from \"isbot\";\n\nimport * as logger from \"./logger\";\n\n// Automated clients (crawlers, headless browsers, scraper fleets) execute our\n// analytics bundles and inflate per-event billing without producing usable\n// insights, so we skip initialization for them entirely.\n\n// Real Firefox never carries a Safari-style \"Version/x\" token; UAs with both\n// are fabricated by scraper UA generators.\nconst FORGED_UA_PATTERN = /Version\\/[\\d.]+.*Firefox\\//;\n\nexport const isLikelyAutomated = (): boolean => {\n // Non-browser environment: nothing worth tracking\n if (typeof navigator === \"undefined\") {\n return true;\n }\n\n // Set by WebDriver-based automation (Puppeteer, Playwright, Selenium)\n // even when the user agent has been disguised\n if (navigator.webdriver) {\n return true;\n }\n\n try {\n const userAgent = navigator.userAgent ?? \"\";\n return isbot(userAgent) || FORGED_UA_PATTERN.test(userAgent);\n } catch (e) {\n logger.error(\"Bot detection failed, failing closed\", e);\n return true;\n }\n};\n"],"names":["isbot","logger","FORGED_UA_PATTERN","isLikelyAutomated","navigator","webdriver","userAgent","test","e","error"],"mappings":"AAAA,OAASA,KAAK,KAAQ,OAAQ,AAE9B,WAAYC,WAAY,UAAW,CAQnC,MAAMC,kBAAoB,4BAE1B,QAAO,MAAMC,kBAAoB,KAE/B,GAAI,OAAOC,YAAc,YAAa,CACpC,OAAO,IACT,CAIA,GAAIA,UAAUC,SAAS,CAAE,CACvB,OAAO,IACT,CAEA,GAAI,CACF,MAAMC,UAAYF,UAAUE,SAAS,EAAI,GACzC,OAAON,MAAMM,YAAcJ,kBAAkBK,IAAI,CAACD,UACpD,CAAE,MAAOE,EAAG,CACVP,OAAOQ,KAAK,CAAC,uCAAwCD,GACrD,OAAO,IACT,CACF,CAAE"}
@@ -0,0 +1,2 @@
1
+ import{describe,expect,afterEach,it,vi}from"vitest";import{isbot}from"isbot";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import{isLikelyAutomated}from"./bot-detection";import{InsightsService}from"./service";import{InsightsCommandQueue}from"./command-queue";vi.mock("isbot",async importOriginal=>{const actual=await importOriginal();return{...actual,isbot:vi.fn(actual.isbot)}});vi.mock("./mixpanel",()=>({initMixpanel:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./posthog",()=>({initPosthog:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./datalayer",()=>({track:vi.fn(),trackPageView:vi.fn()}));vi.mock("./logger",()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}));const CHROME_UA="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";const stubNavigator=overrides=>{vi.stubGlobal("navigator",{webdriver:false,userAgent:CHROME_UA,...overrides})};describe("isLikelyAutomated",()=>{afterEach(()=>{vi.unstubAllGlobals()});it("passes a regular Chrome browser",()=>{stubNavigator({});expect(isLikelyAutomated()).toBe(false)});it("passes real Firefox",()=>{stubNavigator({userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:130.0) Gecko/20100101 Firefox/130.0"});expect(isLikelyAutomated()).toBe(false)});it("passes real Safari despite its Version/x token",()=>{stubNavigator({userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"});expect(isLikelyAutomated()).toBe(false)});it("flags webdriver automation regardless of user agent",()=>{stubNavigator({webdriver:true});expect(isLikelyAutomated()).toBe(true)});it("flags headless browsers",()=>{stubNavigator({userAgent:"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/147.0.7727.15 Safari/537.36"});expect(isLikelyAutomated()).toBe(true)});it("flags self-identified crawlers",()=>{stubNavigator({userAgent:"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.0; +https://openai.com/gptbot"});expect(isLikelyAutomated()).toBe(true)});it("treats environments without navigator as automated",()=>{vi.stubGlobal("navigator",undefined);expect(isLikelyAutomated()).toBe(true)});it("flags forged Safari/Firefox hybrid user agents",()=>{stubNavigator({userAgent:"Mozilla/5.0 (Macintosh; Intel Mac OS X 12) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Firefox/99.0"});expect(isLikelyAutomated()).toBe(true)});it("flags SEO/monitoring crawlers via isbot",()=>{stubNavigator({userAgent:"Mozilla/5.0 (compatible; ContentKing/1.0; +https://www.contentkingapp.com)"});expect(isLikelyAutomated()).toBe(true)});it("flags self-declared indexing crawlers via isbot",()=>{stubNavigator({userAgent:"ExoPriorsSiteClosure/1.0 (content indexing; contact@exopriors.com)"});expect(isLikelyAutomated()).toBe(true)});it("flags LG webOS smart TVs — a known, accepted isbot false positive",()=>{stubNavigator({userAgent:"Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) 85.0.4183.93/STR Large Screen WebAppManager Safari/537.36"});expect(isLikelyAutomated()).toBe(true)});it("fails closed when bot detection itself throws",()=>{stubNavigator({});vi.mocked(isbot).mockImplementationOnce(()=>{throw new Error("pattern explosion")});expect(isLikelyAutomated()).toBe(true)})});describe("InsightsService bot gating",()=>{afterEach(()=>{vi.unstubAllGlobals();vi.clearAllMocks()});const config={mixpanelToken:"test-token",mixpanelAutoCapture:true,posthogApiKey:"test-key",posthogApiHost:"test-host"};it("skips all analytics initialization for automated clients",()=>{stubNavigator({webdriver:true});new InsightsService().initInsights(config);expect(mixpanel.initMixpanel).not.toHaveBeenCalled();expect(posthog.initPosthog).not.toHaveBeenCalled()});it("initializes analytics for regular browsers",()=>{stubNavigator({});new InsightsService().initInsights(config);expect(mixpanel.initMixpanel).toHaveBeenCalled();expect(posthog.initPosthog).toHaveBeenCalled()})});describe("InsightsCommandQueue bot gating",()=>{afterEach(()=>{vi.unstubAllGlobals();vi.clearAllMocks()});const config={mixpanelToken:"test-token",mixpanelAutoCapture:true,posthogApiKey:"test-key",posthogApiHost:"test-host"};it("keeps post-init calls away from the SDKs for automated clients",()=>{stubNavigator({webdriver:true});const queue=new InsightsCommandQueue;queue.initInsights(config);queue.trackPageView();queue.track("some_event",{});expect(mixpanel.initMixpanel).not.toHaveBeenCalled();expect(mixpanel.trackPageView).not.toHaveBeenCalled();expect(mixpanel.track).not.toHaveBeenCalled();expect(posthog.initPosthog).not.toHaveBeenCalled();expect(posthog.trackPageView).not.toHaveBeenCalled()});it("dispatches post-init calls normally for regular browsers",()=>{stubNavigator({});const queue=new InsightsCommandQueue;queue.initInsights(config);queue.trackPageView();expect(mixpanel.initMixpanel).toHaveBeenCalled();expect(mixpanel.trackPageView).toHaveBeenCalled()})});
2
+ //# sourceMappingURL=bot-detection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/insights/bot-detection.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, expect, afterEach, it, vi } from \"vitest\";\nimport { isbot } from \"isbot\";\n\nimport * as mixpanel from \"./mixpanel\";\nimport * as posthog from \"./posthog\";\nimport { isLikelyAutomated } from \"./bot-detection\";\nimport { InsightsService } from \"./service\";\nimport { InsightsCommandQueue } from \"./command-queue\";\n\n// Real implementation by default; individual tests can override to simulate\n// library failure\nvi.mock(\"isbot\", async (importOriginal) => {\n const actual = await importOriginal<typeof import(\"isbot\")>();\n return { ...actual, isbot: vi.fn(actual.isbot) };\n});\n\nvi.mock(\"./mixpanel\", () => ({\n initMixpanel: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./posthog\", () => ({\n initPosthog: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./datalayer\", () => ({\n track: vi.fn(),\n trackPageView: vi.fn(),\n}));\n\nvi.mock(\"./logger\", () => ({\n debug: vi.fn(),\n info: vi.fn(),\n warn: vi.fn(),\n error: vi.fn(),\n}));\n\nconst CHROME_UA =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\";\n\nconst stubNavigator = (overrides: {\n webdriver?: boolean;\n userAgent?: string;\n}) => {\n vi.stubGlobal(\"navigator\", {\n webdriver: false,\n userAgent: CHROME_UA,\n ...overrides,\n });\n};\n\ndescribe(\"isLikelyAutomated\", () => {\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n it(\"passes a regular Chrome browser\", () => {\n stubNavigator({});\n expect(isLikelyAutomated()).toBe(false);\n });\n\n it(\"passes real Firefox\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:130.0) Gecko/20100101 Firefox/130.0\",\n });\n expect(isLikelyAutomated()).toBe(false);\n });\n\n it(\"passes real Safari despite its Version/x token\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15\",\n });\n expect(isLikelyAutomated()).toBe(false);\n });\n\n it(\"flags webdriver automation regardless of user agent\", () => {\n stubNavigator({ webdriver: true });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags headless browsers\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/147.0.7727.15 Safari/537.36\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags self-identified crawlers\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.0; +https://openai.com/gptbot\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"treats environments without navigator as automated\", () => {\n vi.stubGlobal(\"navigator\", undefined);\n expect(isLikelyAutomated()).toBe(true);\n });\n\n // Verbatim UA of the scraper fleet observed hammering ably.com — isbot\n // cannot catch it (browser-shaped), so the forged-UA heuristic must\n // catch it instead\n it(\"flags forged Safari/Firefox hybrid user agents\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 12) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Firefox/99.0\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags SEO/monitoring crawlers via isbot\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (compatible; ContentKing/1.0; +https://www.contentkingapp.com)\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags self-declared indexing crawlers via isbot\", () => {\n stubNavigator({\n userAgent:\n \"ExoPriorsSiteClosure/1.0 (content indexing; contact@exopriors.com)\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"flags LG webOS smart TVs — a known, accepted isbot false positive\", () => {\n stubNavigator({\n userAgent:\n \"Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) 85.0.4183.93/STR Large Screen WebAppManager Safari/537.36\",\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n\n it(\"fails closed when bot detection itself throws\", () => {\n stubNavigator({});\n vi.mocked(isbot).mockImplementationOnce(() => {\n throw new Error(\"pattern explosion\");\n });\n expect(isLikelyAutomated()).toBe(true);\n });\n});\n\ndescribe(\"InsightsService bot gating\", () => {\n afterEach(() => {\n vi.unstubAllGlobals();\n vi.clearAllMocks();\n });\n\n const config = {\n mixpanelToken: \"test-token\",\n mixpanelAutoCapture: true,\n posthogApiKey: \"test-key\",\n posthogApiHost: \"test-host\",\n };\n\n it(\"skips all analytics initialization for automated clients\", () => {\n stubNavigator({ webdriver: true });\n\n new InsightsService().initInsights(config);\n\n expect(mixpanel.initMixpanel).not.toHaveBeenCalled();\n expect(posthog.initPosthog).not.toHaveBeenCalled();\n });\n\n it(\"initializes analytics for regular browsers\", () => {\n stubNavigator({});\n\n new InsightsService().initInsights(config);\n\n expect(mixpanel.initMixpanel).toHaveBeenCalled();\n expect(posthog.initPosthog).toHaveBeenCalled();\n });\n});\n\ndescribe(\"InsightsCommandQueue bot gating\", () => {\n afterEach(() => {\n vi.unstubAllGlobals();\n vi.clearAllMocks();\n });\n\n const config = {\n mixpanelToken: \"test-token\",\n mixpanelAutoCapture: true,\n posthogApiKey: \"test-key\",\n posthogApiHost: \"test-host\",\n };\n\n // Pins the guarantee that gated clients can't reach the SDKs through\n // calls made after init (route-change page views etc.) — we must not\n // depend on Mixpanel/PostHog's own pre-init guards for that\n it(\"keeps post-init calls away from the SDKs for automated clients\", () => {\n stubNavigator({ webdriver: true });\n\n const queue = new InsightsCommandQueue();\n queue.initInsights(config);\n queue.trackPageView();\n queue.track(\"some_event\", {});\n\n expect(mixpanel.initMixpanel).not.toHaveBeenCalled();\n expect(mixpanel.trackPageView).not.toHaveBeenCalled();\n expect(mixpanel.track).not.toHaveBeenCalled();\n expect(posthog.initPosthog).not.toHaveBeenCalled();\n expect(posthog.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"dispatches post-init calls normally for regular browsers\", () => {\n stubNavigator({});\n\n const queue = new InsightsCommandQueue();\n queue.initInsights(config);\n queue.trackPageView();\n\n expect(mixpanel.initMixpanel).toHaveBeenCalled();\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n });\n});\n"],"names":["describe","expect","afterEach","it","vi","isbot","mixpanel","posthog","isLikelyAutomated","InsightsService","InsightsCommandQueue","mock","importOriginal","actual","fn","initMixpanel","trackPageView","track","initPosthog","debug","info","warn","error","CHROME_UA","stubNavigator","overrides","stubGlobal","webdriver","userAgent","unstubAllGlobals","toBe","undefined","mocked","mockImplementationOnce","Error","clearAllMocks","config","mixpanelToken","mixpanelAutoCapture","posthogApiKey","posthogApiHost","initInsights","not","toHaveBeenCalled","queue"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import{InsightsService}from"./service";import*as logger from"./logger";export class InsightsCommandQueue{executeInitInsights(config){this.debugMode=!!config.debug;if(this.debugMode){logger.debug("Initializing insights")}this.realImplementation=new InsightsService;this.realImplementation.initInsights(config);this.isInitialized=true;this.processQueue()}processQueue(){if(this.debugMode){logger.debug(`Processing ${this.queue.length} queued commands`)}while(this.queue.length>0){const cmd=this.queue.shift();if(cmd&&this.realImplementation&&typeof this.realImplementation[cmd.methodName]==="function"){try{if(this.debugMode){logger.debug(`Executing queued command: ${cmd.methodName}`,cmd.args)}const fn=this.realImplementation[cmd.methodName];fn.apply(this.realImplementation,cmd.args)}catch(e){if(this.debugMode){logger.error(`Error executing queued command: ${cmd.methodName}`,e)}}}}}initInsights(_config){}enableDebugMode(){}disableDebugMode(){}identify(_identity){}trackPageView(_options){}track(_event,_properties){}setupObserver(){return()=>{}}constructor(){_define_property(this,"isInitialized",false);_define_property(this,"queue",[]);_define_property(this,"debugMode",false);_define_property(this,"realImplementation",null);return new Proxy(this,{get:(target,prop)=>{if(prop in target&&typeof target[prop]!=="function"){return target[prop]}return(...args)=>{if(!target.isInitialized||!target.realImplementation){if(prop==="initInsights"){target.executeInitInsights(args[0]);return}if(target.debugMode){logger.debug(`Queuing method call: ${String(prop)}`,args)}target.queue.push({methodName:prop,args});return}if(typeof target.realImplementation[prop]==="function"){return target.realImplementation[prop](...args)}}}})}}
1
+ function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import{InsightsService}from"./service";import{isLikelyAutomated}from"./bot-detection";import*as logger from"./logger";class NoopAnalyticsService{initInsights(_config){}enableDebugMode(){}disableDebugMode(){}identify(_identity){}trackPageView(_options){}track(_event,_properties){}setupObserver(){return()=>{}}}export class InsightsCommandQueue{executeInitInsights(config){this.debugMode=!!config.debug;if(isLikelyAutomated()){if(this.debugMode){logger.debug("Automated client detected, installing no-op analytics")}this.realImplementation=new NoopAnalyticsService;this.isInitialized=true;this.queue=[];return}if(this.debugMode){logger.debug("Initializing insights")}this.realImplementation=new InsightsService;this.realImplementation.initInsights(config);this.isInitialized=true;this.processQueue()}processQueue(){if(this.debugMode){logger.debug(`Processing ${this.queue.length} queued commands`)}while(this.queue.length>0){const cmd=this.queue.shift();if(cmd&&this.realImplementation&&typeof this.realImplementation[cmd.methodName]==="function"){try{if(this.debugMode){logger.debug(`Executing queued command: ${cmd.methodName}`,cmd.args)}const fn=this.realImplementation[cmd.methodName];fn.apply(this.realImplementation,cmd.args)}catch(e){if(this.debugMode){logger.error(`Error executing queued command: ${cmd.methodName}`,e)}}}}}initInsights(_config){}enableDebugMode(){}disableDebugMode(){}identify(_identity){}trackPageView(_options){}track(_event,_properties){}setupObserver(){return()=>{}}constructor(){_define_property(this,"isInitialized",false);_define_property(this,"queue",[]);_define_property(this,"debugMode",false);_define_property(this,"realImplementation",null);return new Proxy(this,{get:(target,prop)=>{if(prop in target&&typeof target[prop]!=="function"){return target[prop]}return(...args)=>{if(!target.isInitialized||!target.realImplementation){if(prop==="initInsights"){target.executeInitInsights(args[0]);return}if(target.debugMode){logger.debug(`Queuing method call: ${String(prop)}`,args)}target.queue.push({methodName:prop,args});return}if(typeof target.realImplementation[prop]==="function"){return target.realImplementation[prop](...args)}}}})}}
2
2
  //# sourceMappingURL=command-queue.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/insights/command-queue.ts"],"sourcesContent":["import {\n AnalyticsService,\n Command,\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport { InsightsService } from \"./service\";\nimport * as logger from \"./logger\";\n\n// Queue handler that will collect commands before initialization\nexport class InsightsCommandQueue implements AnalyticsService {\n private isInitialized: boolean = false;\n private queue: Command[] = [];\n private debugMode: boolean = false;\n private realImplementation: InsightsService | null = null;\n\n constructor() {\n // Create a proxy that will either queue commands or execute them directly\n return new Proxy(this, {\n get: (target: InsightsCommandQueue, prop: keyof InsightsCommandQueue) => {\n // Return actual properties of the queue\n if (prop in target && typeof target[prop] !== \"function\") {\n return target[prop];\n }\n\n // Return a function that either queues or executes the method call\n return (...args: unknown[]) => {\n if (!target.isInitialized || !target.realImplementation) {\n // Queue the command for later execution\n if (prop === \"initInsights\") {\n // Special handling for initInsights - execute it right away\n target.executeInitInsights(args[0] as InsightsConfig);\n return;\n }\n\n // For debug logging\n if (target.debugMode) {\n logger.debug(`Queuing method call: ${String(prop)}`, args);\n }\n\n target.queue.push({\n methodName: prop,\n args,\n });\n\n return;\n }\n\n // Execute the command immediately on the real implementation\n if (typeof target.realImplementation[prop] === \"function\") {\n return (\n target.realImplementation[prop] as (...args: unknown[]) => unknown\n )(...args);\n }\n };\n },\n });\n }\n\n // Special handling for init since it needs to happen right away\n private executeInitInsights(config: InsightsConfig): void {\n this.debugMode = !!config.debug;\n\n if (this.debugMode) {\n logger.debug(\"Initializing insights\");\n }\n\n // Create and initialize the real implementation\n this.realImplementation = new InsightsService();\n this.realImplementation.initInsights(config);\n\n // Mark as initialized and process the queue\n this.isInitialized = true;\n this.processQueue();\n }\n\n // Process all queued commands\n private processQueue(): void {\n if (this.debugMode) {\n logger.debug(`Processing ${this.queue.length} queued commands`);\n }\n\n while (this.queue.length > 0) {\n const cmd = this.queue.shift();\n if (\n cmd &&\n this.realImplementation &&\n typeof this.realImplementation[cmd.methodName] === \"function\"\n ) {\n try {\n if (this.debugMode) {\n logger.debug(\n `Executing queued command: ${cmd.methodName}`,\n cmd.args,\n );\n }\n\n const fn = this.realImplementation[cmd.methodName] as (\n ...args: unknown[]\n ) => unknown;\n\n // Execute the command with the real implementation\n fn.apply(this.realImplementation, cmd.args as unknown[]);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\n `Error executing queued command: ${cmd.methodName}`,\n e,\n );\n }\n }\n }\n }\n }\n\n // Implement all methods required by AnalyticsService to satisfy TypeScript\n // (These won't be called directly due to the proxy)\n initInsights(_config: InsightsConfig): void {}\n enableDebugMode(): void {}\n disableDebugMode(): void {}\n identify(_identity: InsightsIdentity): void {}\n trackPageView(_options?: TrackPageViewOptions): void {}\n track(_event: string, _properties?: Record<string, unknown>): void {}\n setupObserver(): () => void {\n return () => {};\n }\n}\n"],"names":["InsightsService","logger","InsightsCommandQueue","executeInitInsights","config","debugMode","debug","realImplementation","initInsights","isInitialized","processQueue","queue","length","cmd","shift","methodName","args","fn","apply","e","error","_config","enableDebugMode","disableDebugMode","identify","_identity","trackPageView","_options","track","_event","_properties","setupObserver","Proxy","get","target","prop","String","push"],"mappings":"oLAOA,OAASA,eAAe,KAAQ,WAAY,AAC5C,WAAYC,WAAY,UAAW,AAGnC,QAAO,MAAMC,qBAkDX,AAAQC,oBAAoBC,MAAsB,CAAQ,CACxD,IAAI,CAACC,SAAS,CAAG,CAAC,CAACD,OAAOE,KAAK,CAE/B,GAAI,IAAI,CAACD,SAAS,CAAE,CAClBJ,OAAOK,KAAK,CAAC,wBACf,CAGA,IAAI,CAACC,kBAAkB,CAAG,IAAIP,gBAC9B,IAAI,CAACO,kBAAkB,CAACC,YAAY,CAACJ,OAGrC,CAAA,IAAI,CAACK,aAAa,CAAG,KACrB,IAAI,CAACC,YAAY,EACnB,CAGA,AAAQA,cAAqB,CAC3B,GAAI,IAAI,CAACL,SAAS,CAAE,CAClBJ,OAAOK,KAAK,CAAC,CAAC,WAAW,EAAE,IAAI,CAACK,KAAK,CAACC,MAAM,CAAC,gBAAgB,CAAC,CAChE,CAEA,MAAO,IAAI,CAACD,KAAK,CAACC,MAAM,CAAG,EAAG,CAC5B,MAAMC,IAAM,IAAI,CAACF,KAAK,CAACG,KAAK,GAC5B,GACED,KACA,IAAI,CAACN,kBAAkB,EACvB,OAAO,IAAI,CAACA,kBAAkB,CAACM,IAAIE,UAAU,CAAC,GAAK,WACnD,CACA,GAAI,CACF,GAAI,IAAI,CAACV,SAAS,CAAE,CAClBJ,OAAOK,KAAK,CACV,CAAC,0BAA0B,EAAEO,IAAIE,UAAU,CAAC,CAAC,CAC7CF,IAAIG,IAAI,CAEZ,CAEA,MAAMC,GAAK,IAAI,CAACV,kBAAkB,CAACM,IAAIE,UAAU,CAAC,CAKlDE,GAAGC,KAAK,CAAC,IAAI,CAACX,kBAAkB,CAAEM,IAAIG,IAAI,CAC5C,CAAE,MAAOG,EAAG,CACV,GAAI,IAAI,CAACd,SAAS,CAAE,CAClBJ,OAAOmB,KAAK,CACV,CAAC,gCAAgC,EAAEP,IAAIE,UAAU,CAAC,CAAC,CACnDI,EAEJ,CACF,CACF,CACF,CACF,CAIAX,aAAaa,OAAuB,CAAQ,CAAC,CAC7CC,iBAAwB,CAAC,CACzBC,kBAAyB,CAAC,CAC1BC,SAASC,SAA2B,CAAQ,CAAC,CAC7CC,cAAcC,QAA+B,CAAQ,CAAC,CACtDC,MAAMC,MAAc,CAAEC,WAAqC,CAAQ,CAAC,CACpEC,eAA4B,CAC1B,MAAO,KAAO,CAChB,CA7GA,aAAc,CALd,sBAAQtB,gBAAyB,OACjC,sBAAQE,QAAmB,EAAE,EAC7B,sBAAQN,YAAqB,OAC7B,sBAAQE,qBAA6C,MAInD,OAAO,IAAIyB,MAAM,IAAI,CAAE,CACrBC,IAAK,CAACC,OAA8BC,QAElC,GAAIA,QAAQD,QAAU,OAAOA,MAAM,CAACC,KAAK,GAAK,WAAY,CACxD,OAAOD,MAAM,CAACC,KAAK,AACrB,CAGA,MAAO,CAAC,GAAGnB,QACT,GAAI,CAACkB,OAAOzB,aAAa,EAAI,CAACyB,OAAO3B,kBAAkB,CAAE,CAEvD,GAAI4B,OAAS,eAAgB,CAE3BD,OAAO/B,mBAAmB,CAACa,IAAI,CAAC,EAAE,EAClC,MACF,CAGA,GAAIkB,OAAO7B,SAAS,CAAE,CACpBJ,OAAOK,KAAK,CAAC,CAAC,qBAAqB,EAAE8B,OAAOD,MAAM,CAAC,CAAEnB,KACvD,CAEAkB,OAAOvB,KAAK,CAAC0B,IAAI,CAAC,CAChBtB,WAAYoB,KACZnB,IACF,GAEA,MACF,CAGA,GAAI,OAAOkB,OAAO3B,kBAAkB,CAAC4B,KAAK,GAAK,WAAY,CACzD,OAAO,AACLD,OAAO3B,kBAAkB,CAAC4B,KAAK,IAC5BnB,KACP,CACF,CACF,CACF,EACF,CAqEF"}
1
+ {"version":3,"sources":["../../../src/core/insights/command-queue.ts"],"sourcesContent":["import {\n AnalyticsService,\n Command,\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport { InsightsService } from \"./service\";\nimport { isLikelyAutomated } from \"./bot-detection\";\nimport * as logger from \"./logger\";\n\n// Inert implementation installed for automated clients: the analytics SDKs\n// are never initialized and no call can ever reach them — a guarantee we own,\n// rather than relying on the SDKs' own pre-init behaviour.\nclass NoopAnalyticsService implements AnalyticsService {\n initInsights(_config: InsightsConfig): void {}\n enableDebugMode(): void {}\n disableDebugMode(): void {}\n identify(_identity: InsightsIdentity): void {}\n trackPageView(_options?: TrackPageViewOptions): void {}\n track(_event: string, _properties?: Record<string, unknown>): void {}\n setupObserver(): () => void {\n return () => {};\n }\n}\n\n// Queue handler that will collect commands before initialization\nexport class InsightsCommandQueue implements AnalyticsService {\n private isInitialized: boolean = false;\n private queue: Command[] = [];\n private debugMode: boolean = false;\n private realImplementation: AnalyticsService | null = null;\n\n constructor() {\n // Create a proxy that will either queue commands or execute them directly\n return new Proxy(this, {\n get: (target: InsightsCommandQueue, prop: keyof InsightsCommandQueue) => {\n // Return actual properties of the queue\n if (prop in target && typeof target[prop] !== \"function\") {\n return target[prop];\n }\n\n // Return a function that either queues or executes the method call\n return (...args: unknown[]) => {\n if (!target.isInitialized || !target.realImplementation) {\n // Queue the command for later execution\n if (prop === \"initInsights\") {\n // Special handling for initInsights - execute it right away\n target.executeInitInsights(args[0] as InsightsConfig);\n return;\n }\n\n // For debug logging\n if (target.debugMode) {\n logger.debug(`Queuing method call: ${String(prop)}`, args);\n }\n\n target.queue.push({\n methodName: prop,\n args,\n });\n\n return;\n }\n\n // Execute the command immediately on the real implementation\n if (typeof target.realImplementation[prop] === \"function\") {\n return (\n target.realImplementation[prop] as (...args: unknown[]) => unknown\n )(...args);\n }\n };\n },\n });\n }\n\n // Special handling for init since it needs to happen right away\n private executeInitInsights(config: InsightsConfig): void {\n this.debugMode = !!config.debug;\n\n if (isLikelyAutomated()) {\n if (this.debugMode) {\n logger.debug(\"Automated client detected, installing no-op analytics\");\n }\n\n this.realImplementation = new NoopAnalyticsService();\n this.isInitialized = true;\n this.queue = [];\n return;\n }\n\n if (this.debugMode) {\n logger.debug(\"Initializing insights\");\n }\n\n // Create and initialize the real implementation\n this.realImplementation = new InsightsService();\n this.realImplementation.initInsights(config);\n\n // Mark as initialized and process the queue\n this.isInitialized = true;\n this.processQueue();\n }\n\n // Process all queued commands\n private processQueue(): void {\n if (this.debugMode) {\n logger.debug(`Processing ${this.queue.length} queued commands`);\n }\n\n while (this.queue.length > 0) {\n const cmd = this.queue.shift();\n if (\n cmd &&\n this.realImplementation &&\n typeof this.realImplementation[cmd.methodName] === \"function\"\n ) {\n try {\n if (this.debugMode) {\n logger.debug(\n `Executing queued command: ${cmd.methodName}`,\n cmd.args,\n );\n }\n\n const fn = this.realImplementation[cmd.methodName] as (\n ...args: unknown[]\n ) => unknown;\n\n // Execute the command with the real implementation\n fn.apply(this.realImplementation, cmd.args as unknown[]);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\n `Error executing queued command: ${cmd.methodName}`,\n e,\n );\n }\n }\n }\n }\n }\n\n // Implement all methods required by AnalyticsService to satisfy TypeScript\n // (These won't be called directly due to the proxy)\n initInsights(_config: InsightsConfig): void {}\n enableDebugMode(): void {}\n disableDebugMode(): void {}\n identify(_identity: InsightsIdentity): void {}\n trackPageView(_options?: TrackPageViewOptions): void {}\n track(_event: string, _properties?: Record<string, unknown>): void {}\n setupObserver(): () => void {\n return () => {};\n }\n}\n"],"names":["InsightsService","isLikelyAutomated","logger","NoopAnalyticsService","initInsights","_config","enableDebugMode","disableDebugMode","identify","_identity","trackPageView","_options","track","_event","_properties","setupObserver","InsightsCommandQueue","executeInitInsights","config","debugMode","debug","realImplementation","isInitialized","queue","processQueue","length","cmd","shift","methodName","args","fn","apply","e","error","Proxy","get","target","prop","String","push"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{describe,expect,beforeEach,afterEach,it,vi}from"vitest";import*as datalayer from"./datalayer";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import*as logger from"./logger";import*as insights from"./index";vi.mock("./datalayer",()=>({track:vi.fn(),trackPageView:vi.fn()}));vi.mock("./mixpanel",()=>({initMixpanel:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./posthog",()=>({initPosthog:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./logger",()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}));describe("Insights Command Queue",()=>{const testConfig={debug:true,mixpanelToken:"test-token",mixpanelAutoCapture:false,posthogApiKey:"test-key",posthogApiHost:"test-host"};const testIdentity={userId:"user-123",accountId:"account-456",organisationId:"org-789",email:"test@example.com",name:"Test User"};beforeEach(()=>{vi.clearAllMocks();vi.resetModules()});afterEach(()=>{document.body.replaceWith(document.body.cloneNode(true))});describe("Pre-initialization Queueing",()=>{it("should queue methods called before initialization",async()=>{insights.track("early_event",{early:true});insights.identify(testIdentity);insights.trackPageView();expect(mixpanel.track).not.toHaveBeenCalled();expect(posthog.track).not.toHaveBeenCalled();expect(datalayer.track).not.toHaveBeenCalled();expect(mixpanel.identify).not.toHaveBeenCalled();expect(posthog.identify).not.toHaveBeenCalled();expect(mixpanel.trackPageView).not.toHaveBeenCalled();expect(posthog.trackPageView).not.toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.initInsights(testConfig);expect(mixpanel.initMixpanel).toHaveBeenCalledWith(testConfig.mixpanelToken,testConfig.mixpanelAutoCapture,testConfig.debug,undefined);expect(posthog.initPosthog).toHaveBeenCalledWith(testConfig.posthogApiKey,testConfig.posthogApiHost);expect(mixpanel.track).toHaveBeenCalledWith("early_event",{early:true});expect(posthog.track).toHaveBeenCalledWith("early_event",{early:true});expect(datalayer.track).toHaveBeenCalledWith("early_event",{early:true});expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should handle errors in queued methods gracefully",async()=>{mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel error")});insights.track("error_event",{error:true});insights.trackPageView();insights.initInsights(testConfig);expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(posthog.track).toHaveBeenCalledWith("error_event",{error:true});expect(datalayer.track).toHaveBeenCalledWith("error_event",{error:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Post-initialization Direct Execution",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should directly call methods after initialization",()=>{insights.track("post_init_event",{post:true});expect(mixpanel.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(posthog.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(datalayer.track).toHaveBeenCalledWith("post_init_event",{post:true})});it("should handle all exported methods correctly",()=>{insights.identify(testIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);insights.trackPageView();expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.enableDebugMode();expect(mixpanel.enableDebugMode).toHaveBeenCalled();expect(posthog.enableDebugMode).toHaveBeenCalled();insights.disableDebugMode();expect(mixpanel.disableDebugMode).toHaveBeenCalled();expect(posthog.disableDebugMode).toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Observer Setup",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should set up click event observer and track clicks",()=>{const cleanup=insights.setupObserver();const testElement=document.createElement("button");testElement.setAttribute("data-insight-event","button_clicked");testElement.setAttribute("data-insight-button-id","test-123");document.body.appendChild(testElement);testElement.click();expect(mixpanel.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(posthog.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(datalayer.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});cleanup();vi.clearAllMocks();testElement.click();expect(mixpanel.track).not.toHaveBeenCalled()});it("should handle nested elements correctly",()=>{insights.setupObserver();const parentElement=document.createElement("div");parentElement.setAttribute("data-insight-event","container_clicked");parentElement.setAttribute("data-insight-container-id","parent-container");const childElement=document.createElement("span");childElement.textContent="Click me";parentElement.appendChild(childElement);document.body.appendChild(parentElement);childElement.click();expect(mixpanel.track).toHaveBeenCalledWith("container_clicked",{containerId:"parent-container"})})});describe("Error Handling",()=>{it("should handle initialization errors gracefully",()=>{mixpanel.initMixpanel.mockImplementationOnce(()=>{throw new Error("Mixpanel init error")});expect(()=>{insights.initInsights(testConfig)}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to initialize Mixpanel"),expect.any(Error))});it("should handle runtime errors in methods",()=>{insights.initInsights(testConfig);vi.clearAllMocks();mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel track error")});posthog.track.mockImplementationOnce(()=>{throw new Error("Posthog track error")});expect(()=>{insights.track("error_test",{test:true})}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Posthog"),expect.any(Error));expect(datalayer.track).toHaveBeenCalledWith("error_test",{test:true})})});describe("Debug Mode",()=>{it("should respect debug flag in config",()=>{insights.initInsights(testConfig);expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Initializing insights"));vi.clearAllMocks();insights.track("debug_test",{debug:true});expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("Tracking event"),expect.objectContaining({event:"debug_test",properties:{debug:true}}))});it("should not log debug info when debug is false",()=>{insights.initInsights({...testConfig,debug:false});vi.clearAllMocks();insights.track("no_debug_test",{debug:false});expect(logger.info).not.toHaveBeenCalled()})});describe("Arbitrary Properties",()=>{const customPageViewProperties={customProperty:"custom-value",anotherProperty:123,nestedObject:{foo:"bar"}};const customPageViewPropertiesWithDataLayer={customProperty:"custom-value",anotherProperty:456};const customPageViewPropertiesWithExcludeIds={customProperty:"test",count:789};const customIdentityProperties={customProperty:"user-custom-value",userSegment:"premium",signupDate:"2024-01-01"};const customMinimalIdentityProperties={customField1:"value1",customField2:999,customField3:{nested:true}};beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should pass arbitrary properties through trackPageView",()=>{insights.trackPageView(customPageViewProperties);expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should pass arbitrary properties through trackPageView with includeDataLayer",()=>{insights.trackPageView({includeDataLayer:true,...customPageViewPropertiesWithDataLayer});expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(datalayer.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer)});it("should pass arbitrary properties through trackPageView with excludeIds",()=>{const excludeIds=["id1","id2"];insights.trackPageView({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(mixpanel.trackPageView).toHaveBeenCalledWith({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithExcludeIds)});it("should pass arbitrary properties through identify",()=>{const identityWithCustomProps={...testIdentity,...customIdentityProperties};insights.identify(identityWithCustomProps);expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps)});it("should pass arbitrary properties through identify with minimal identity",()=>{const minimalIdentity={userId:"minimal-user",accountId:"minimal-account",...customMinimalIdentityProperties};insights.identify(minimalIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity)})})});
1
+ import{describe,expect,beforeEach,afterEach,it,vi}from"vitest";import*as datalayer from"./datalayer";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import*as logger from"./logger";import*as insights from"./index";vi.mock("./datalayer",()=>({track:vi.fn(),trackPageView:vi.fn()}));vi.mock("./mixpanel",()=>({initMixpanel:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./posthog",()=>({initPosthog:vi.fn(),enableDebugMode:vi.fn(),disableDebugMode:vi.fn(),identify:vi.fn(),trackPageView:vi.fn(),track:vi.fn()}));vi.mock("./logger",()=>({debug:vi.fn(),info:vi.fn(),warn:vi.fn(),error:vi.fn()}));vi.mock("./bot-detection",()=>({isLikelyAutomated:vi.fn().mockReturnValue(false)}));describe("Insights Command Queue",()=>{const testConfig={debug:true,mixpanelToken:"test-token",mixpanelAutoCapture:false,posthogApiKey:"test-key",posthogApiHost:"test-host"};const testIdentity={userId:"user-123",accountId:"account-456",organisationId:"org-789",email:"test@example.com",name:"Test User"};beforeEach(()=>{vi.clearAllMocks();vi.resetModules()});afterEach(()=>{document.body.replaceWith(document.body.cloneNode(true))});describe("Pre-initialization Queueing",()=>{it("should queue methods called before initialization",async()=>{insights.track("early_event",{early:true});insights.identify(testIdentity);insights.trackPageView();expect(mixpanel.track).not.toHaveBeenCalled();expect(posthog.track).not.toHaveBeenCalled();expect(datalayer.track).not.toHaveBeenCalled();expect(mixpanel.identify).not.toHaveBeenCalled();expect(posthog.identify).not.toHaveBeenCalled();expect(mixpanel.trackPageView).not.toHaveBeenCalled();expect(posthog.trackPageView).not.toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.initInsights(testConfig);expect(mixpanel.initMixpanel).toHaveBeenCalledWith(testConfig.mixpanelToken,testConfig.mixpanelAutoCapture,testConfig.debug,undefined);expect(posthog.initPosthog).toHaveBeenCalledWith(testConfig.posthogApiKey,testConfig.posthogApiHost);expect(mixpanel.track).toHaveBeenCalledWith("early_event",{early:true});expect(posthog.track).toHaveBeenCalledWith("early_event",{early:true});expect(datalayer.track).toHaveBeenCalledWith("early_event",{early:true});expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should handle errors in queued methods gracefully",async()=>{mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel error")});insights.track("error_event",{error:true});insights.trackPageView();insights.initInsights(testConfig);expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(posthog.track).toHaveBeenCalledWith("error_event",{error:true});expect(datalayer.track).toHaveBeenCalledWith("error_event",{error:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Post-initialization Direct Execution",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should directly call methods after initialization",()=>{insights.track("post_init_event",{post:true});expect(mixpanel.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(posthog.track).toHaveBeenCalledWith("post_init_event",{post:true});expect(datalayer.track).toHaveBeenCalledWith("post_init_event",{post:true})});it("should handle all exported methods correctly",()=>{insights.identify(testIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);expect(posthog.identify).toHaveBeenCalledWith(testIdentity);insights.trackPageView();expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).not.toHaveBeenCalled();insights.enableDebugMode();expect(mixpanel.enableDebugMode).toHaveBeenCalled();expect(posthog.enableDebugMode).toHaveBeenCalled();insights.disableDebugMode();expect(mixpanel.disableDebugMode).toHaveBeenCalled();expect(posthog.disableDebugMode).toHaveBeenCalled()});it("should report page view to GTM as well when includeDataLayer is true",()=>{insights.trackPageView({includeDataLayer:true});expect(mixpanel.trackPageView).toHaveBeenCalled();expect(posthog.trackPageView).toHaveBeenCalled();expect(datalayer.trackPageView).toHaveBeenCalled()})});describe("Observer Setup",()=>{beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should set up click event observer and track clicks",()=>{const cleanup=insights.setupObserver();const testElement=document.createElement("button");testElement.setAttribute("data-insight-event","button_clicked");testElement.setAttribute("data-insight-button-id","test-123");document.body.appendChild(testElement);testElement.click();expect(mixpanel.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(posthog.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});expect(datalayer.track).toHaveBeenCalledWith("button_clicked",{buttonId:"test-123"});cleanup();vi.clearAllMocks();testElement.click();expect(mixpanel.track).not.toHaveBeenCalled()});it("should handle nested elements correctly",()=>{insights.setupObserver();const parentElement=document.createElement("div");parentElement.setAttribute("data-insight-event","container_clicked");parentElement.setAttribute("data-insight-container-id","parent-container");const childElement=document.createElement("span");childElement.textContent="Click me";parentElement.appendChild(childElement);document.body.appendChild(parentElement);childElement.click();expect(mixpanel.track).toHaveBeenCalledWith("container_clicked",{containerId:"parent-container"})})});describe("Error Handling",()=>{it("should handle initialization errors gracefully",()=>{mixpanel.initMixpanel.mockImplementationOnce(()=>{throw new Error("Mixpanel init error")});expect(()=>{insights.initInsights(testConfig)}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to initialize Mixpanel"),expect.any(Error))});it("should handle runtime errors in methods",()=>{insights.initInsights(testConfig);vi.clearAllMocks();mixpanel.track.mockImplementationOnce(()=>{throw new Error("Mixpanel track error")});posthog.track.mockImplementationOnce(()=>{throw new Error("Posthog track error")});expect(()=>{insights.track("error_test",{test:true})}).not.toThrow();expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Mixpanel"),expect.any(Error));expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to track event in Posthog"),expect.any(Error));expect(datalayer.track).toHaveBeenCalledWith("error_test",{test:true})})});describe("Debug Mode",()=>{it("should respect debug flag in config",()=>{insights.initInsights(testConfig);expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Initializing insights"));vi.clearAllMocks();insights.track("debug_test",{debug:true});expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("Tracking event"),expect.objectContaining({event:"debug_test",properties:{debug:true}}))});it("should not log debug info when debug is false",()=>{insights.initInsights({...testConfig,debug:false});vi.clearAllMocks();insights.track("no_debug_test",{debug:false});expect(logger.info).not.toHaveBeenCalled()})});describe("Arbitrary Properties",()=>{const customPageViewProperties={customProperty:"custom-value",anotherProperty:123,nestedObject:{foo:"bar"}};const customPageViewPropertiesWithDataLayer={customProperty:"custom-value",anotherProperty:456};const customPageViewPropertiesWithExcludeIds={customProperty:"test",count:789};const customIdentityProperties={customProperty:"user-custom-value",userSegment:"premium",signupDate:"2024-01-01"};const customMinimalIdentityProperties={customField1:"value1",customField2:999,customField3:{nested:true}};beforeEach(()=>{insights.initInsights(testConfig);vi.clearAllMocks()});it("should pass arbitrary properties through trackPageView",()=>{insights.trackPageView(customPageViewProperties);expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewProperties);expect(datalayer.trackPageView).not.toHaveBeenCalled()});it("should pass arbitrary properties through trackPageView with includeDataLayer",()=>{insights.trackPageView({includeDataLayer:true,...customPageViewPropertiesWithDataLayer});expect(mixpanel.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer);expect(datalayer.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithDataLayer)});it("should pass arbitrary properties through trackPageView with excludeIds",()=>{const excludeIds=["id1","id2"];insights.trackPageView({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(mixpanel.trackPageView).toHaveBeenCalledWith({excludeIds,...customPageViewPropertiesWithExcludeIds});expect(posthog.trackPageView).toHaveBeenCalledWith(customPageViewPropertiesWithExcludeIds)});it("should pass arbitrary properties through identify",()=>{const identityWithCustomProps={...testIdentity,...customIdentityProperties};insights.identify(identityWithCustomProps);expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps)});it("should pass arbitrary properties through identify with minimal identity",()=>{const minimalIdentity={userId:"minimal-user",accountId:"minimal-account",...customMinimalIdentityProperties};insights.identify(minimalIdentity);expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity)})})});
2
2
  //# sourceMappingURL=index.test.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/insights/index.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, expect, beforeEach, afterEach, it, vi, Mock } from \"vitest\";\n\nimport * as datalayer from \"./datalayer\";\nimport * as mixpanel from \"./mixpanel\";\nimport * as posthog from \"./posthog\";\nimport * as logger from \"./logger\";\nimport * as insights from \"./index\";\n\n// Mock the dependencies\nvi.mock(\"./datalayer\", () => ({\n track: vi.fn(),\n trackPageView: vi.fn(),\n}));\n\nvi.mock(\"./mixpanel\", () => ({\n initMixpanel: vi.fn(),\n enableDebugMode: vi.fn(),\n disableDebugMode: vi.fn(),\n identify: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./posthog\", () => ({\n initPosthog: vi.fn(),\n enableDebugMode: vi.fn(),\n disableDebugMode: vi.fn(),\n identify: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./logger\", () => ({\n debug: vi.fn(),\n info: vi.fn(),\n warn: vi.fn(),\n error: vi.fn(),\n}));\n\ndescribe(\"Insights Command Queue\", () => {\n const testConfig = {\n debug: true,\n mixpanelToken: \"test-token\",\n mixpanelAutoCapture: false,\n posthogApiKey: \"test-key\",\n posthogApiHost: \"test-host\",\n };\n\n const testIdentity = {\n userId: \"user-123\",\n accountId: \"account-456\",\n organisationId: \"org-789\",\n email: \"test@example.com\",\n name: \"Test User\",\n };\n\n beforeEach(() => {\n // Clear all mocks before each test\n vi.clearAllMocks();\n\n // Reset the module to clear any internal state\n vi.resetModules();\n });\n\n afterEach(() => {\n // Cleanup document event listeners\n document.body.replaceWith(document.body.cloneNode(true));\n });\n\n describe(\"Pre-initialization Queueing\", () => {\n it(\"should queue methods called before initialization\", async () => {\n // Call methods before initialization\n insights.track(\"early_event\", { early: true });\n insights.identify(testIdentity);\n insights.trackPageView();\n\n // Verify nothing has been called yet on the underlying services\n expect(mixpanel.track).not.toHaveBeenCalled();\n expect(posthog.track).not.toHaveBeenCalled();\n expect(datalayer.track).not.toHaveBeenCalled();\n expect(mixpanel.identify).not.toHaveBeenCalled();\n expect(posthog.identify).not.toHaveBeenCalled();\n expect(mixpanel.trackPageView).not.toHaveBeenCalled();\n expect(posthog.trackPageView).not.toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n\n // Now initialize\n insights.initInsights(testConfig);\n\n // Initialize should be called immediately\n expect(mixpanel.initMixpanel).toHaveBeenCalledWith(\n testConfig.mixpanelToken,\n testConfig.mixpanelAutoCapture,\n testConfig.debug,\n undefined,\n );\n expect(posthog.initPosthog).toHaveBeenCalledWith(\n testConfig.posthogApiKey,\n testConfig.posthogApiHost,\n );\n\n // Queued methods should now be called in the correct order\n expect(mixpanel.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n expect(posthog.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n\n expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);\n expect(posthog.identify).toHaveBeenCalledWith(testIdentity);\n\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should handle errors in queued methods gracefully\", async () => {\n // Setup an error for one of the methods\n (mixpanel.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel error\");\n });\n\n // Call methods before initialization\n insights.track(\"error_event\", { error: true });\n insights.trackPageView();\n\n // Now initialize\n insights.initInsights(testConfig);\n\n // Should have logged the error but continued processing the queue\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Mixpanel\"),\n expect.any(Error),\n );\n\n // The other methods should still be called\n expect(posthog.track).toHaveBeenCalledWith(\"error_event\", {\n error: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"error_event\", {\n error: true,\n });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should report page view to GTM as well when includeDataLayer is true\", () => {\n insights.trackPageView({ includeDataLayer: true });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).toHaveBeenCalled();\n });\n });\n\n describe(\"Post-initialization Direct Execution\", () => {\n beforeEach(() => {\n // Initialize first\n insights.initInsights(testConfig);\n // Clear the mocks to focus on post-init behavior\n vi.clearAllMocks();\n });\n\n it(\"should directly call methods after initialization\", () => {\n // Call methods after initialization\n insights.track(\"post_init_event\", { post: true });\n\n // Should be called immediately\n expect(mixpanel.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n expect(posthog.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n });\n\n it(\"should handle all exported methods correctly\", () => {\n // Test each exported method\n insights.identify(testIdentity);\n expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);\n expect(posthog.identify).toHaveBeenCalledWith(testIdentity);\n\n insights.trackPageView();\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n\n insights.enableDebugMode();\n expect(mixpanel.enableDebugMode).toHaveBeenCalled();\n expect(posthog.enableDebugMode).toHaveBeenCalled();\n\n insights.disableDebugMode();\n expect(mixpanel.disableDebugMode).toHaveBeenCalled();\n expect(posthog.disableDebugMode).toHaveBeenCalled();\n });\n\n it(\"should report page view to GTM as well when includeDataLayer is true\", () => {\n insights.trackPageView({ includeDataLayer: true });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).toHaveBeenCalled();\n });\n });\n\n describe(\"Observer Setup\", () => {\n beforeEach(() => {\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n });\n\n it(\"should set up click event observer and track clicks\", () => {\n // Setup observer\n const cleanup = insights.setupObserver();\n\n // Create a test element with insight attributes\n const testElement = document.createElement(\"button\");\n testElement.setAttribute(\"data-insight-event\", \"button_clicked\");\n testElement.setAttribute(\"data-insight-button-id\", \"test-123\");\n document.body.appendChild(testElement);\n\n // Simulate click\n testElement.click();\n\n // Should track the event\n expect(mixpanel.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n expect(posthog.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n\n // Test cleanup\n cleanup();\n\n // Reset tracking mocks\n vi.clearAllMocks();\n\n // Click again - should not track\n testElement.click();\n expect(mixpanel.track).not.toHaveBeenCalled();\n });\n\n it(\"should handle nested elements correctly\", () => {\n // Setup observer\n insights.setupObserver();\n\n // Create parent element with insight attributes\n const parentElement = document.createElement(\"div\");\n parentElement.setAttribute(\"data-insight-event\", \"container_clicked\");\n parentElement.setAttribute(\n \"data-insight-container-id\",\n \"parent-container\",\n );\n\n // Create child element without insights\n const childElement = document.createElement(\"span\");\n childElement.textContent = \"Click me\";\n\n // Nest elements\n parentElement.appendChild(childElement);\n document.body.appendChild(parentElement);\n\n // Click the child element\n childElement.click();\n\n // Should find and use the parent's insight attributes\n expect(mixpanel.track).toHaveBeenCalledWith(\"container_clicked\", {\n containerId: \"parent-container\",\n });\n });\n });\n\n describe(\"Error Handling\", () => {\n it(\"should handle initialization errors gracefully\", () => {\n // Setup an error in initialization\n (mixpanel.initMixpanel as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel init error\");\n });\n\n // Should not throw when initializing\n expect(() => {\n insights.initInsights(testConfig);\n }).not.toThrow();\n\n // Should log the error\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to initialize Mixpanel\"),\n expect.any(Error),\n );\n });\n\n it(\"should handle runtime errors in methods\", () => {\n // Initialize first\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n\n // Setup errors in tracking\n (mixpanel.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel track error\");\n });\n\n (posthog.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Posthog track error\");\n });\n\n // Should not throw when tracking\n expect(() => {\n insights.track(\"error_test\", { test: true });\n }).not.toThrow();\n\n // Should log the errors\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Mixpanel\"),\n expect.any(Error),\n );\n\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Posthog\"),\n expect.any(Error),\n );\n\n // Should still try to track with datalayer\n expect(datalayer.track).toHaveBeenCalledWith(\"error_test\", {\n test: true,\n });\n });\n });\n\n describe(\"Debug Mode\", () => {\n it(\"should respect debug flag in config\", () => {\n // Initialize with debug: true\n insights.initInsights(testConfig);\n\n // Should log debug messages\n expect(logger.debug).toHaveBeenCalledWith(\n expect.stringContaining(\"Initializing insights\"),\n );\n\n // Clear mocks and test tracking\n vi.clearAllMocks();\n insights.track(\"debug_test\", { debug: true });\n\n // Should log info about tracking\n expect(logger.info).toHaveBeenCalledWith(\n expect.stringContaining(\"Tracking event\"),\n expect.objectContaining({\n event: \"debug_test\",\n properties: { debug: true },\n }),\n );\n });\n\n it(\"should not log debug info when debug is false\", () => {\n // Initialize with debug: false\n insights.initInsights({\n ...testConfig,\n debug: false,\n });\n\n // Clear mocks and test tracking\n vi.clearAllMocks();\n insights.track(\"no_debug_test\", { debug: false });\n\n // Should not log info about tracking\n expect(logger.info).not.toHaveBeenCalled();\n });\n });\n\n describe(\"Arbitrary Properties\", () => {\n const customPageViewProperties = {\n customProperty: \"custom-value\",\n anotherProperty: 123,\n nestedObject: { foo: \"bar\" },\n };\n\n const customPageViewPropertiesWithDataLayer = {\n customProperty: \"custom-value\",\n anotherProperty: 456,\n };\n\n const customPageViewPropertiesWithExcludeIds = {\n customProperty: \"test\",\n count: 789,\n };\n\n const customIdentityProperties = {\n customProperty: \"user-custom-value\",\n userSegment: \"premium\",\n signupDate: \"2024-01-01\",\n };\n\n const customMinimalIdentityProperties = {\n customField1: \"value1\",\n customField2: 999,\n customField3: { nested: true },\n };\n\n beforeEach(() => {\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n });\n\n it(\"should pass arbitrary properties through trackPageView\", () => {\n // Call trackPageView with custom properties\n insights.trackPageView(customPageViewProperties);\n\n // Verify mixpanel received all properties\n expect(mixpanel.trackPageView).toHaveBeenCalledWith(\n customPageViewProperties,\n );\n\n // Verify posthog received all properties\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewProperties,\n );\n\n // Verify datalayer did not receive anything (includeDataLayer not set)\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should pass arbitrary properties through trackPageView with includeDataLayer\", () => {\n // Call trackPageView with custom properties and includeDataLayer\n insights.trackPageView({\n includeDataLayer: true,\n ...customPageViewPropertiesWithDataLayer,\n });\n\n // Verify mixpanel received custom properties (includeDataLayer filtered out)\n expect(mixpanel.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n\n // Verify posthog received custom properties (includeDataLayer filtered out)\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n\n // Verify datalayer received custom properties (includeDataLayer filtered out)\n expect(datalayer.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n });\n\n it(\"should pass arbitrary properties through trackPageView with excludeIds\", () => {\n const excludeIds = [\"id1\", \"id2\"];\n\n // Call trackPageView with custom properties and excludeIds\n insights.trackPageView({\n excludeIds,\n ...customPageViewPropertiesWithExcludeIds,\n });\n\n // Verify mixpanel received excludeIds and custom properties\n expect(mixpanel.trackPageView).toHaveBeenCalledWith({\n excludeIds,\n ...customPageViewPropertiesWithExcludeIds,\n });\n\n // Verify posthog received only custom properties (excludeIds filtered out)\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithExcludeIds,\n );\n });\n\n it(\"should pass arbitrary properties through identify\", () => {\n const identityWithCustomProps = {\n ...testIdentity,\n ...customIdentityProperties,\n };\n\n // Call identify with custom properties\n insights.identify(identityWithCustomProps);\n\n // Verify mixpanel received all properties\n expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);\n\n // Verify posthog received all properties\n expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps);\n });\n\n it(\"should pass arbitrary properties through identify with minimal identity\", () => {\n const minimalIdentity = {\n userId: \"minimal-user\",\n accountId: \"minimal-account\",\n ...customMinimalIdentityProperties,\n };\n\n // Call identify with only required fields plus custom properties\n insights.identify(minimalIdentity);\n\n // Verify mixpanel received all properties\n expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);\n\n // Verify posthog received all properties\n expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity);\n });\n });\n});\n"],"names":["describe","expect","beforeEach","afterEach","it","vi","datalayer","mixpanel","posthog","logger","insights","mock","track","fn","trackPageView","initMixpanel","enableDebugMode","disableDebugMode","identify","initPosthog","debug","info","warn","error","testConfig","mixpanelToken","mixpanelAutoCapture","posthogApiKey","posthogApiHost","testIdentity","userId","accountId","organisationId","email","name","clearAllMocks","resetModules","document","body","replaceWith","cloneNode","early","not","toHaveBeenCalled","initInsights","toHaveBeenCalledWith","undefined","mockImplementationOnce","Error","stringContaining","any","includeDataLayer","post","cleanup","setupObserver","testElement","createElement","setAttribute","appendChild","click","buttonId","parentElement","childElement","textContent","containerId","toThrow","test","objectContaining","event","properties","customPageViewProperties","customProperty","anotherProperty","nestedObject","foo","customPageViewPropertiesWithDataLayer","customPageViewPropertiesWithExcludeIds","count","customIdentityProperties","userSegment","signupDate","customMinimalIdentityProperties","customField1","customField2","customField3","nested","excludeIds","identityWithCustomProps","minimalIdentity"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/core/insights/index.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, expect, beforeEach, afterEach, it, vi, Mock } from \"vitest\";\n\nimport * as datalayer from \"./datalayer\";\nimport * as mixpanel from \"./mixpanel\";\nimport * as posthog from \"./posthog\";\nimport * as logger from \"./logger\";\nimport * as insights from \"./index\";\n\n// Mock the dependencies\nvi.mock(\"./datalayer\", () => ({\n track: vi.fn(),\n trackPageView: vi.fn(),\n}));\n\nvi.mock(\"./mixpanel\", () => ({\n initMixpanel: vi.fn(),\n enableDebugMode: vi.fn(),\n disableDebugMode: vi.fn(),\n identify: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./posthog\", () => ({\n initPosthog: vi.fn(),\n enableDebugMode: vi.fn(),\n disableDebugMode: vi.fn(),\n identify: vi.fn(),\n trackPageView: vi.fn(),\n track: vi.fn(),\n}));\n\nvi.mock(\"./logger\", () => ({\n debug: vi.fn(),\n info: vi.fn(),\n warn: vi.fn(),\n error: vi.fn(),\n}));\n\n// Isolate these tests from the real isbot pattern list — a future isbot\n// update matching the test environment's UA would otherwise gate every init\n// here and silently invert the call-expectation assertions\nvi.mock(\"./bot-detection\", () => ({\n isLikelyAutomated: vi.fn().mockReturnValue(false),\n}));\n\ndescribe(\"Insights Command Queue\", () => {\n const testConfig = {\n debug: true,\n mixpanelToken: \"test-token\",\n mixpanelAutoCapture: false,\n posthogApiKey: \"test-key\",\n posthogApiHost: \"test-host\",\n };\n\n const testIdentity = {\n userId: \"user-123\",\n accountId: \"account-456\",\n organisationId: \"org-789\",\n email: \"test@example.com\",\n name: \"Test User\",\n };\n\n beforeEach(() => {\n // Clear all mocks before each test\n vi.clearAllMocks();\n\n // Reset the module to clear any internal state\n vi.resetModules();\n });\n\n afterEach(() => {\n // Cleanup document event listeners\n document.body.replaceWith(document.body.cloneNode(true));\n });\n\n describe(\"Pre-initialization Queueing\", () => {\n it(\"should queue methods called before initialization\", async () => {\n // Call methods before initialization\n insights.track(\"early_event\", { early: true });\n insights.identify(testIdentity);\n insights.trackPageView();\n\n // Verify nothing has been called yet on the underlying services\n expect(mixpanel.track).not.toHaveBeenCalled();\n expect(posthog.track).not.toHaveBeenCalled();\n expect(datalayer.track).not.toHaveBeenCalled();\n expect(mixpanel.identify).not.toHaveBeenCalled();\n expect(posthog.identify).not.toHaveBeenCalled();\n expect(mixpanel.trackPageView).not.toHaveBeenCalled();\n expect(posthog.trackPageView).not.toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n\n // Now initialize\n insights.initInsights(testConfig);\n\n // Initialize should be called immediately\n expect(mixpanel.initMixpanel).toHaveBeenCalledWith(\n testConfig.mixpanelToken,\n testConfig.mixpanelAutoCapture,\n testConfig.debug,\n undefined,\n );\n expect(posthog.initPosthog).toHaveBeenCalledWith(\n testConfig.posthogApiKey,\n testConfig.posthogApiHost,\n );\n\n // Queued methods should now be called in the correct order\n expect(mixpanel.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n expect(posthog.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"early_event\", {\n early: true,\n });\n\n expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);\n expect(posthog.identify).toHaveBeenCalledWith(testIdentity);\n\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should handle errors in queued methods gracefully\", async () => {\n // Setup an error for one of the methods\n (mixpanel.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel error\");\n });\n\n // Call methods before initialization\n insights.track(\"error_event\", { error: true });\n insights.trackPageView();\n\n // Now initialize\n insights.initInsights(testConfig);\n\n // Should have logged the error but continued processing the queue\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Mixpanel\"),\n expect.any(Error),\n );\n\n // The other methods should still be called\n expect(posthog.track).toHaveBeenCalledWith(\"error_event\", {\n error: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"error_event\", {\n error: true,\n });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should report page view to GTM as well when includeDataLayer is true\", () => {\n insights.trackPageView({ includeDataLayer: true });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).toHaveBeenCalled();\n });\n });\n\n describe(\"Post-initialization Direct Execution\", () => {\n beforeEach(() => {\n // Initialize first\n insights.initInsights(testConfig);\n // Clear the mocks to focus on post-init behavior\n vi.clearAllMocks();\n });\n\n it(\"should directly call methods after initialization\", () => {\n // Call methods after initialization\n insights.track(\"post_init_event\", { post: true });\n\n // Should be called immediately\n expect(mixpanel.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n expect(posthog.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"post_init_event\", {\n post: true,\n });\n });\n\n it(\"should handle all exported methods correctly\", () => {\n // Test each exported method\n insights.identify(testIdentity);\n expect(mixpanel.identify).toHaveBeenCalledWith(testIdentity);\n expect(posthog.identify).toHaveBeenCalledWith(testIdentity);\n\n insights.trackPageView();\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n\n insights.enableDebugMode();\n expect(mixpanel.enableDebugMode).toHaveBeenCalled();\n expect(posthog.enableDebugMode).toHaveBeenCalled();\n\n insights.disableDebugMode();\n expect(mixpanel.disableDebugMode).toHaveBeenCalled();\n expect(posthog.disableDebugMode).toHaveBeenCalled();\n });\n\n it(\"should report page view to GTM as well when includeDataLayer is true\", () => {\n insights.trackPageView({ includeDataLayer: true });\n expect(mixpanel.trackPageView).toHaveBeenCalled();\n expect(posthog.trackPageView).toHaveBeenCalled();\n expect(datalayer.trackPageView).toHaveBeenCalled();\n });\n });\n\n describe(\"Observer Setup\", () => {\n beforeEach(() => {\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n });\n\n it(\"should set up click event observer and track clicks\", () => {\n // Setup observer\n const cleanup = insights.setupObserver();\n\n // Create a test element with insight attributes\n const testElement = document.createElement(\"button\");\n testElement.setAttribute(\"data-insight-event\", \"button_clicked\");\n testElement.setAttribute(\"data-insight-button-id\", \"test-123\");\n document.body.appendChild(testElement);\n\n // Simulate click\n testElement.click();\n\n // Should track the event\n expect(mixpanel.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n expect(posthog.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n expect(datalayer.track).toHaveBeenCalledWith(\"button_clicked\", {\n buttonId: \"test-123\",\n });\n\n // Test cleanup\n cleanup();\n\n // Reset tracking mocks\n vi.clearAllMocks();\n\n // Click again - should not track\n testElement.click();\n expect(mixpanel.track).not.toHaveBeenCalled();\n });\n\n it(\"should handle nested elements correctly\", () => {\n // Setup observer\n insights.setupObserver();\n\n // Create parent element with insight attributes\n const parentElement = document.createElement(\"div\");\n parentElement.setAttribute(\"data-insight-event\", \"container_clicked\");\n parentElement.setAttribute(\n \"data-insight-container-id\",\n \"parent-container\",\n );\n\n // Create child element without insights\n const childElement = document.createElement(\"span\");\n childElement.textContent = \"Click me\";\n\n // Nest elements\n parentElement.appendChild(childElement);\n document.body.appendChild(parentElement);\n\n // Click the child element\n childElement.click();\n\n // Should find and use the parent's insight attributes\n expect(mixpanel.track).toHaveBeenCalledWith(\"container_clicked\", {\n containerId: \"parent-container\",\n });\n });\n });\n\n describe(\"Error Handling\", () => {\n it(\"should handle initialization errors gracefully\", () => {\n // Setup an error in initialization\n (mixpanel.initMixpanel as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel init error\");\n });\n\n // Should not throw when initializing\n expect(() => {\n insights.initInsights(testConfig);\n }).not.toThrow();\n\n // Should log the error\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to initialize Mixpanel\"),\n expect.any(Error),\n );\n });\n\n it(\"should handle runtime errors in methods\", () => {\n // Initialize first\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n\n // Setup errors in tracking\n (mixpanel.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Mixpanel track error\");\n });\n\n (posthog.track as Mock).mockImplementationOnce(() => {\n throw new Error(\"Posthog track error\");\n });\n\n // Should not throw when tracking\n expect(() => {\n insights.track(\"error_test\", { test: true });\n }).not.toThrow();\n\n // Should log the errors\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Mixpanel\"),\n expect.any(Error),\n );\n\n expect(logger.error).toHaveBeenCalledWith(\n expect.stringContaining(\"Failed to track event in Posthog\"),\n expect.any(Error),\n );\n\n // Should still try to track with datalayer\n expect(datalayer.track).toHaveBeenCalledWith(\"error_test\", {\n test: true,\n });\n });\n });\n\n describe(\"Debug Mode\", () => {\n it(\"should respect debug flag in config\", () => {\n // Initialize with debug: true\n insights.initInsights(testConfig);\n\n // Should log debug messages\n expect(logger.debug).toHaveBeenCalledWith(\n expect.stringContaining(\"Initializing insights\"),\n );\n\n // Clear mocks and test tracking\n vi.clearAllMocks();\n insights.track(\"debug_test\", { debug: true });\n\n // Should log info about tracking\n expect(logger.info).toHaveBeenCalledWith(\n expect.stringContaining(\"Tracking event\"),\n expect.objectContaining({\n event: \"debug_test\",\n properties: { debug: true },\n }),\n );\n });\n\n it(\"should not log debug info when debug is false\", () => {\n // Initialize with debug: false\n insights.initInsights({\n ...testConfig,\n debug: false,\n });\n\n // Clear mocks and test tracking\n vi.clearAllMocks();\n insights.track(\"no_debug_test\", { debug: false });\n\n // Should not log info about tracking\n expect(logger.info).not.toHaveBeenCalled();\n });\n });\n\n describe(\"Arbitrary Properties\", () => {\n const customPageViewProperties = {\n customProperty: \"custom-value\",\n anotherProperty: 123,\n nestedObject: { foo: \"bar\" },\n };\n\n const customPageViewPropertiesWithDataLayer = {\n customProperty: \"custom-value\",\n anotherProperty: 456,\n };\n\n const customPageViewPropertiesWithExcludeIds = {\n customProperty: \"test\",\n count: 789,\n };\n\n const customIdentityProperties = {\n customProperty: \"user-custom-value\",\n userSegment: \"premium\",\n signupDate: \"2024-01-01\",\n };\n\n const customMinimalIdentityProperties = {\n customField1: \"value1\",\n customField2: 999,\n customField3: { nested: true },\n };\n\n beforeEach(() => {\n insights.initInsights(testConfig);\n vi.clearAllMocks();\n });\n\n it(\"should pass arbitrary properties through trackPageView\", () => {\n // Call trackPageView with custom properties\n insights.trackPageView(customPageViewProperties);\n\n // Verify mixpanel received all properties\n expect(mixpanel.trackPageView).toHaveBeenCalledWith(\n customPageViewProperties,\n );\n\n // Verify posthog received all properties\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewProperties,\n );\n\n // Verify datalayer did not receive anything (includeDataLayer not set)\n expect(datalayer.trackPageView).not.toHaveBeenCalled();\n });\n\n it(\"should pass arbitrary properties through trackPageView with includeDataLayer\", () => {\n // Call trackPageView with custom properties and includeDataLayer\n insights.trackPageView({\n includeDataLayer: true,\n ...customPageViewPropertiesWithDataLayer,\n });\n\n // Verify mixpanel received custom properties (includeDataLayer filtered out)\n expect(mixpanel.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n\n // Verify posthog received custom properties (includeDataLayer filtered out)\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n\n // Verify datalayer received custom properties (includeDataLayer filtered out)\n expect(datalayer.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithDataLayer,\n );\n });\n\n it(\"should pass arbitrary properties through trackPageView with excludeIds\", () => {\n const excludeIds = [\"id1\", \"id2\"];\n\n // Call trackPageView with custom properties and excludeIds\n insights.trackPageView({\n excludeIds,\n ...customPageViewPropertiesWithExcludeIds,\n });\n\n // Verify mixpanel received excludeIds and custom properties\n expect(mixpanel.trackPageView).toHaveBeenCalledWith({\n excludeIds,\n ...customPageViewPropertiesWithExcludeIds,\n });\n\n // Verify posthog received only custom properties (excludeIds filtered out)\n expect(posthog.trackPageView).toHaveBeenCalledWith(\n customPageViewPropertiesWithExcludeIds,\n );\n });\n\n it(\"should pass arbitrary properties through identify\", () => {\n const identityWithCustomProps = {\n ...testIdentity,\n ...customIdentityProperties,\n };\n\n // Call identify with custom properties\n insights.identify(identityWithCustomProps);\n\n // Verify mixpanel received all properties\n expect(mixpanel.identify).toHaveBeenCalledWith(identityWithCustomProps);\n\n // Verify posthog received all properties\n expect(posthog.identify).toHaveBeenCalledWith(identityWithCustomProps);\n });\n\n it(\"should pass arbitrary properties through identify with minimal identity\", () => {\n const minimalIdentity = {\n userId: \"minimal-user\",\n accountId: \"minimal-account\",\n ...customMinimalIdentityProperties,\n };\n\n // Call identify with only required fields plus custom properties\n insights.identify(minimalIdentity);\n\n // Verify mixpanel received all properties\n expect(mixpanel.identify).toHaveBeenCalledWith(minimalIdentity);\n\n // Verify posthog received all properties\n expect(posthog.identify).toHaveBeenCalledWith(minimalIdentity);\n });\n });\n});\n"],"names":["describe","expect","beforeEach","afterEach","it","vi","datalayer","mixpanel","posthog","logger","insights","mock","track","fn","trackPageView","initMixpanel","enableDebugMode","disableDebugMode","identify","initPosthog","debug","info","warn","error","isLikelyAutomated","mockReturnValue","testConfig","mixpanelToken","mixpanelAutoCapture","posthogApiKey","posthogApiHost","testIdentity","userId","accountId","organisationId","email","name","clearAllMocks","resetModules","document","body","replaceWith","cloneNode","early","not","toHaveBeenCalled","initInsights","toHaveBeenCalledWith","undefined","mockImplementationOnce","Error","stringContaining","any","includeDataLayer","post","cleanup","setupObserver","testElement","createElement","setAttribute","appendChild","click","buttonId","parentElement","childElement","textContent","containerId","toThrow","test","objectContaining","event","properties","customPageViewProperties","customProperty","anotherProperty","nestedObject","foo","customPageViewPropertiesWithDataLayer","customPageViewPropertiesWithExcludeIds","count","customIdentityProperties","userSegment","signupDate","customMinimalIdentityProperties","customField1","customField2","customField3","nested","excludeIds","identityWithCustomProps","minimalIdentity"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import mixpanel from"mixpanel-browser";export const MIXPANEL_EU_API_HOST="https://api-eu.mixpanel.com";export const initMixpanel=(token,autoCapture=false,debug=false,apiHost=MIXPANEL_EU_API_HOST)=>{const blockSelectors=["[data-mask]"];if(!token){console.warn("Mixpanel token not provided, skipping initialization");return}mixpanel.init(token,{api_host:apiHost,debug:debug,persistence:"localStorage",autocapture:autoCapture?{block_selectors:blockSelectors,capture_text_content:true,pageview:false}:false,track_pageview:false,record_sessions_percent:0})};export const enableDebugMode=()=>{mixpanel.set_config({debug:true})};export const disableDebugMode=()=>{mixpanel.set_config({debug:false})};export const identify=({userId,accountId,organisationId,email,name,...properties})=>{if(!userId){return}mixpanel.identify(userId.toString());const peopleProperties={...properties};if(email||name){peopleProperties.$email=email;peopleProperties.$name=name}if(organisationId){peopleProperties.organization_id=[organisationId.toString()]}if(Object.keys(peopleProperties).length>0){mixpanel.people.set(peopleProperties)}if(accountId){mixpanel.people.union({accounts:[accountId.toString()]})}};const redactUrlSegments=excludeIds=>{const pathSegments=window.location.pathname.split("/");const redactedSegments=pathSegments.map(segment=>{if(/^\d+$/.test(segment)||excludeIds.some(id=>id&&id!==""&&segment===id)){return"{redacted}"}return segment});const url=new URL(window.location.href);url.pathname=redactedSegments.join("/");return decodeURI(url.toString()).toLowerCase()};export const trackPageView=properties=>{const{excludeIds,...rest}=properties??{};mixpanel.track_pageview({redacted_path:redactUrlSegments(excludeIds??[]),...rest})};export const track=(event,properties)=>{mixpanel.track(event,properties)};
1
+ import mixpanel from"mixpanel-browser";export const MIXPANEL_EU_API_HOST="https://api-eu.mixpanel.com";export const initMixpanel=(token,autoCapture=false,debug=false,apiHost=MIXPANEL_EU_API_HOST)=>{const blockSelectors=["[data-mask]"];if(!token){console.warn("Mixpanel token not provided, skipping initialization");return}mixpanel.init(token,{api_host:apiHost,debug:debug,persistence:"localStorage",autocapture:autoCapture?{block_selectors:blockSelectors,capture_text_content:true,pageview:false,scroll:false,input:false,dead_click:false,rage_click:false}:false,track_pageview:false,record_sessions_percent:0})};export const enableDebugMode=()=>{mixpanel.set_config({debug:true})};export const disableDebugMode=()=>{mixpanel.set_config({debug:false})};export const identify=({userId,accountId,organisationId,email,name,...properties})=>{if(!userId){return}mixpanel.identify(userId.toString());const peopleProperties={...properties};if(email||name){peopleProperties.$email=email;peopleProperties.$name=name}if(organisationId){peopleProperties.organization_id=[organisationId.toString()]}if(Object.keys(peopleProperties).length>0){mixpanel.people.set(peopleProperties)}if(accountId){mixpanel.people.union({accounts:[accountId.toString()]})}};const redactUrlSegments=excludeIds=>{const pathSegments=window.location.pathname.split("/");const redactedSegments=pathSegments.map(segment=>{if(/^\d+$/.test(segment)||excludeIds.some(id=>id&&id!==""&&segment===id)){return"{redacted}"}return segment});const url=new URL(window.location.href);url.pathname=redactedSegments.join("/");return decodeURI(url.toString()).toLowerCase()};export const trackPageView=properties=>{const{excludeIds,...rest}=properties??{};mixpanel.track_pageview({redacted_path:redactUrlSegments(excludeIds??[]),...rest})};export const track=(event,properties)=>{mixpanel.track(event,properties)};
2
2
  //# sourceMappingURL=mixpanel.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/insights/mixpanel.ts"],"sourcesContent":["import mixpanel from \"mixpanel-browser\";\n\nimport { InsightsIdentity, TrackPageViewOptions } from \"./types\";\n\nexport const MIXPANEL_EU_API_HOST = \"https://api-eu.mixpanel.com\";\n\nexport const initMixpanel = (\n token: string,\n autoCapture: boolean = false,\n debug: boolean = false,\n apiHost: string = MIXPANEL_EU_API_HOST,\n) => {\n const blockSelectors = [\"[data-mask]\"];\n if (!token) {\n console.warn(\"Mixpanel token not provided, skipping initialization\");\n return;\n }\n\n mixpanel.init(token, {\n api_host: apiHost,\n debug: debug,\n persistence: \"localStorage\",\n autocapture: autoCapture\n ? {\n block_selectors: blockSelectors,\n capture_text_content: true,\n pageview: false, // We'll track page views manually\n }\n : false,\n track_pageview: false, // We'll track page views manually\n record_sessions_percent: 0,\n });\n};\n\nexport const enableDebugMode = () => {\n mixpanel.set_config({ debug: true });\n};\n\nexport const disableDebugMode = () => {\n mixpanel.set_config({ debug: false });\n};\n\nexport const identify = ({\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties\n}: InsightsIdentity) => {\n // In very rare cases we might have a user without an account, so we'll\n // let null/undefined/blank strings through on that one\n if (!userId) {\n return;\n }\n\n mixpanel.identify(userId.toString());\n\n const peopleProperties: Record<string, unknown> = { ...properties };\n\n if (email || name) {\n peopleProperties.$email = email;\n peopleProperties.$name = name;\n }\n\n if (organisationId) {\n peopleProperties.organization_id = [organisationId.toString()];\n }\n\n if (Object.keys(peopleProperties).length > 0) {\n mixpanel.people.set(peopleProperties);\n }\n\n if (accountId) {\n mixpanel.people.union({ accounts: [accountId.toString()] });\n }\n};\n\n// Simple function to replace all digits and IDs in a URL path with {redacted},\n// purely to make reporting based on aggregates easier\nconst redactUrlSegments = (excludeIds: string[]) => {\n const pathSegments = window.location.pathname.split(\"/\");\n\n const redactedSegments = pathSegments.map((segment) => {\n // Redact if the segment contains only digits or matches any of the excluded IDs\n if (\n /^\\d+$/.test(segment) ||\n excludeIds.some((id) => id && id !== \"\" && segment === id)\n ) {\n return \"{redacted}\";\n }\n\n return segment;\n });\n\n // Join the segments back together\n const url = new URL(window.location.href);\n url.pathname = redactedSegments.join(\"/\");\n\n return decodeURI(url.toString()).toLowerCase();\n};\n\nexport const trackPageView = (properties?: TrackPageViewOptions) => {\n const { excludeIds, ...rest } = properties ?? {};\n\n // Add the redacted URL to the page view event for reporting\n mixpanel.track_pageview({\n redacted_path: redactUrlSegments(excludeIds ?? []),\n ...rest,\n });\n};\n\nexport const track = (event: string, properties?: Record<string, unknown>) => {\n mixpanel.track(event, properties);\n};\n"],"names":["mixpanel","MIXPANEL_EU_API_HOST","initMixpanel","token","autoCapture","debug","apiHost","blockSelectors","console","warn","init","api_host","persistence","autocapture","block_selectors","capture_text_content","pageview","track_pageview","record_sessions_percent","enableDebugMode","set_config","disableDebugMode","identify","userId","accountId","organisationId","email","name","properties","toString","peopleProperties","$email","$name","organization_id","Object","keys","length","people","set","union","accounts","redactUrlSegments","excludeIds","pathSegments","window","location","pathname","split","redactedSegments","map","segment","test","some","id","url","URL","href","join","decodeURI","toLowerCase","trackPageView","rest","redacted_path","track","event"],"mappings":"AAAA,OAAOA,aAAc,kBAAmB,AAIxC,QAAO,MAAMC,qBAAuB,6BAA8B,AAElE,QAAO,MAAMC,aAAe,CAC1BC,MACAC,YAAuB,KAAK,CAC5BC,MAAiB,KAAK,CACtBC,QAAkBL,oBAAoB,IAEtC,MAAMM,eAAiB,CAAC,cAAc,CACtC,GAAI,CAACJ,MAAO,CACVK,QAAQC,IAAI,CAAC,wDACb,MACF,CAEAT,SAASU,IAAI,CAACP,MAAO,CACnBQ,SAAUL,QACVD,MAAOA,MACPO,YAAa,eACbC,YAAaT,YACT,CACEU,gBAAiBP,eACjBQ,qBAAsB,KACtBC,SAAU,KACZ,EACA,MACJC,eAAgB,MAChBC,wBAAyB,CAC3B,EACF,CAAE,AAEF,QAAO,MAAMC,gBAAkB,KAC7BnB,SAASoB,UAAU,CAAC,CAAEf,MAAO,IAAK,EACpC,CAAE,AAEF,QAAO,MAAMgB,iBAAmB,KAC9BrB,SAASoB,UAAU,CAAC,CAAEf,MAAO,KAAM,EACrC,CAAE,AAEF,QAAO,MAAMiB,SAAW,CAAC,CACvBC,MAAM,CACNC,SAAS,CACTC,cAAc,CACdC,KAAK,CACLC,IAAI,CACJ,GAAGC,WACc,IAGjB,GAAI,CAACL,OAAQ,CACX,MACF,CAEAvB,SAASsB,QAAQ,CAACC,OAAOM,QAAQ,IAEjC,MAAMC,iBAA4C,CAAE,GAAGF,UAAU,AAAC,EAElE,GAAIF,OAASC,KAAM,CACjBG,iBAAiBC,MAAM,CAAGL,KAC1BI,CAAAA,iBAAiBE,KAAK,CAAGL,IAC3B,CAEA,GAAIF,eAAgB,CAClBK,iBAAiBG,eAAe,CAAG,CAACR,eAAeI,QAAQ,GAAG,AAChE,CAEA,GAAIK,OAAOC,IAAI,CAACL,kBAAkBM,MAAM,CAAG,EAAG,CAC5CpC,SAASqC,MAAM,CAACC,GAAG,CAACR,iBACtB,CAEA,GAAIN,UAAW,CACbxB,SAASqC,MAAM,CAACE,KAAK,CAAC,CAAEC,SAAU,CAAChB,UAAUK,QAAQ,GAAG,AAAC,EAC3D,CACF,CAAE,CAIF,MAAMY,kBAAoB,AAACC,aACzB,MAAMC,aAAeC,OAAOC,QAAQ,CAACC,QAAQ,CAACC,KAAK,CAAC,KAEpD,MAAMC,iBAAmBL,aAAaM,GAAG,CAAC,AAACC,UAEzC,GACE,QAAQC,IAAI,CAACD,UACbR,WAAWU,IAAI,CAAC,AAACC,IAAOA,IAAMA,KAAO,IAAMH,UAAYG,IACvD,CACA,MAAO,YACT,CAEA,OAAOH,OACT,GAGA,MAAMI,IAAM,IAAIC,IAAIX,OAAOC,QAAQ,CAACW,IAAI,CACxCF,CAAAA,IAAIR,QAAQ,CAAGE,iBAAiBS,IAAI,CAAC,KAErC,OAAOC,UAAUJ,IAAIzB,QAAQ,IAAI8B,WAAW,EAC9C,CAEA,QAAO,MAAMC,cAAgB,AAAChC,aAC5B,KAAM,CAAEc,UAAU,CAAE,GAAGmB,KAAM,CAAGjC,YAAc,CAAC,EAG/C5B,SAASiB,cAAc,CAAC,CACtB6C,cAAerB,kBAAkBC,YAAc,EAAE,EACjD,GAAGmB,IAAI,AACT,EACF,CAAE,AAEF,QAAO,MAAME,MAAQ,CAACC,MAAepC,cACnC5B,SAAS+D,KAAK,CAACC,MAAOpC,WACxB,CAAE"}
1
+ {"version":3,"sources":["../../../src/core/insights/mixpanel.ts"],"sourcesContent":["import mixpanel from \"mixpanel-browser\";\n\nimport { InsightsIdentity, TrackPageViewOptions } from \"./types\";\n\nexport const MIXPANEL_EU_API_HOST = \"https://api-eu.mixpanel.com\";\n\nexport const initMixpanel = (\n token: string,\n autoCapture: boolean = false,\n debug: boolean = false,\n apiHost: string = MIXPANEL_EU_API_HOST,\n) => {\n const blockSelectors = [\"[data-mask]\"];\n if (!token) {\n console.warn(\"Mixpanel token not provided, skipping initialization\");\n return;\n }\n\n mixpanel.init(token, {\n api_host: apiHost,\n debug: debug,\n persistence: \"localStorage\",\n autocapture: autoCapture\n ? {\n block_selectors: blockSelectors,\n capture_text_content: true,\n pageview: false, // We'll track page views manually\n // Keep clicks and submits: the other interaction events are noise\n // that dominates our Mixpanel event volume (and therefore billing)\n scroll: false,\n input: false,\n dead_click: false,\n rage_click: false,\n }\n : false,\n track_pageview: false, // We'll track page views manually\n record_sessions_percent: 0,\n });\n};\n\nexport const enableDebugMode = () => {\n mixpanel.set_config({ debug: true });\n};\n\nexport const disableDebugMode = () => {\n mixpanel.set_config({ debug: false });\n};\n\nexport const identify = ({\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties\n}: InsightsIdentity) => {\n // In very rare cases we might have a user without an account, so we'll\n // let null/undefined/blank strings through on that one\n if (!userId) {\n return;\n }\n\n mixpanel.identify(userId.toString());\n\n const peopleProperties: Record<string, unknown> = { ...properties };\n\n if (email || name) {\n peopleProperties.$email = email;\n peopleProperties.$name = name;\n }\n\n if (organisationId) {\n peopleProperties.organization_id = [organisationId.toString()];\n }\n\n if (Object.keys(peopleProperties).length > 0) {\n mixpanel.people.set(peopleProperties);\n }\n\n if (accountId) {\n mixpanel.people.union({ accounts: [accountId.toString()] });\n }\n};\n\n// Simple function to replace all digits and IDs in a URL path with {redacted},\n// purely to make reporting based on aggregates easier\nconst redactUrlSegments = (excludeIds: string[]) => {\n const pathSegments = window.location.pathname.split(\"/\");\n\n const redactedSegments = pathSegments.map((segment) => {\n // Redact if the segment contains only digits or matches any of the excluded IDs\n if (\n /^\\d+$/.test(segment) ||\n excludeIds.some((id) => id && id !== \"\" && segment === id)\n ) {\n return \"{redacted}\";\n }\n\n return segment;\n });\n\n // Join the segments back together\n const url = new URL(window.location.href);\n url.pathname = redactedSegments.join(\"/\");\n\n return decodeURI(url.toString()).toLowerCase();\n};\n\nexport const trackPageView = (properties?: TrackPageViewOptions) => {\n const { excludeIds, ...rest } = properties ?? {};\n\n // Add the redacted URL to the page view event for reporting\n mixpanel.track_pageview({\n redacted_path: redactUrlSegments(excludeIds ?? []),\n ...rest,\n });\n};\n\nexport const track = (event: string, properties?: Record<string, unknown>) => {\n mixpanel.track(event, properties);\n};\n"],"names":["mixpanel","MIXPANEL_EU_API_HOST","initMixpanel","token","autoCapture","debug","apiHost","blockSelectors","console","warn","init","api_host","persistence","autocapture","block_selectors","capture_text_content","pageview","scroll","input","dead_click","rage_click","track_pageview","record_sessions_percent","enableDebugMode","set_config","disableDebugMode","identify","userId","accountId","organisationId","email","name","properties","toString","peopleProperties","$email","$name","organization_id","Object","keys","length","people","set","union","accounts","redactUrlSegments","excludeIds","pathSegments","window","location","pathname","split","redactedSegments","map","segment","test","some","id","url","URL","href","join","decodeURI","toLowerCase","trackPageView","rest","redacted_path","track","event"],"mappings":"AAAA,OAAOA,aAAc,kBAAmB,AAIxC,QAAO,MAAMC,qBAAuB,6BAA8B,AAElE,QAAO,MAAMC,aAAe,CAC1BC,MACAC,YAAuB,KAAK,CAC5BC,MAAiB,KAAK,CACtBC,QAAkBL,oBAAoB,IAEtC,MAAMM,eAAiB,CAAC,cAAc,CACtC,GAAI,CAACJ,MAAO,CACVK,QAAQC,IAAI,CAAC,wDACb,MACF,CAEAT,SAASU,IAAI,CAACP,MAAO,CACnBQ,SAAUL,QACVD,MAAOA,MACPO,YAAa,eACbC,YAAaT,YACT,CACEU,gBAAiBP,eACjBQ,qBAAsB,KACtBC,SAAU,MAGVC,OAAQ,MACRC,MAAO,MACPC,WAAY,MACZC,WAAY,KACd,EACA,MACJC,eAAgB,MAChBC,wBAAyB,CAC3B,EACF,CAAE,AAEF,QAAO,MAAMC,gBAAkB,KAC7BvB,SAASwB,UAAU,CAAC,CAAEnB,MAAO,IAAK,EACpC,CAAE,AAEF,QAAO,MAAMoB,iBAAmB,KAC9BzB,SAASwB,UAAU,CAAC,CAAEnB,MAAO,KAAM,EACrC,CAAE,AAEF,QAAO,MAAMqB,SAAW,CAAC,CACvBC,MAAM,CACNC,SAAS,CACTC,cAAc,CACdC,KAAK,CACLC,IAAI,CACJ,GAAGC,WACc,IAGjB,GAAI,CAACL,OAAQ,CACX,MACF,CAEA3B,SAAS0B,QAAQ,CAACC,OAAOM,QAAQ,IAEjC,MAAMC,iBAA4C,CAAE,GAAGF,UAAU,AAAC,EAElE,GAAIF,OAASC,KAAM,CACjBG,iBAAiBC,MAAM,CAAGL,KAC1BI,CAAAA,iBAAiBE,KAAK,CAAGL,IAC3B,CAEA,GAAIF,eAAgB,CAClBK,iBAAiBG,eAAe,CAAG,CAACR,eAAeI,QAAQ,GAAG,AAChE,CAEA,GAAIK,OAAOC,IAAI,CAACL,kBAAkBM,MAAM,CAAG,EAAG,CAC5CxC,SAASyC,MAAM,CAACC,GAAG,CAACR,iBACtB,CAEA,GAAIN,UAAW,CACb5B,SAASyC,MAAM,CAACE,KAAK,CAAC,CAAEC,SAAU,CAAChB,UAAUK,QAAQ,GAAG,AAAC,EAC3D,CACF,CAAE,CAIF,MAAMY,kBAAoB,AAACC,aACzB,MAAMC,aAAeC,OAAOC,QAAQ,CAACC,QAAQ,CAACC,KAAK,CAAC,KAEpD,MAAMC,iBAAmBL,aAAaM,GAAG,CAAC,AAACC,UAEzC,GACE,QAAQC,IAAI,CAACD,UACbR,WAAWU,IAAI,CAAC,AAACC,IAAOA,IAAMA,KAAO,IAAMH,UAAYG,IACvD,CACA,MAAO,YACT,CAEA,OAAOH,OACT,GAGA,MAAMI,IAAM,IAAIC,IAAIX,OAAOC,QAAQ,CAACW,IAAI,CACxCF,CAAAA,IAAIR,QAAQ,CAAGE,iBAAiBS,IAAI,CAAC,KAErC,OAAOC,UAAUJ,IAAIzB,QAAQ,IAAI8B,WAAW,EAC9C,CAEA,QAAO,MAAMC,cAAgB,AAAChC,aAC5B,KAAM,CAAEc,UAAU,CAAE,GAAGmB,KAAM,CAAGjC,YAAc,CAAC,EAG/ChC,SAASqB,cAAc,CAAC,CACtB6C,cAAerB,kBAAkBC,YAAc,EAAE,EACjD,GAAGmB,IAAI,AACT,EACF,CAAE,AAEF,QAAO,MAAME,MAAQ,CAACC,MAAepC,cACnChC,SAASmE,KAAK,CAACC,MAAOpC,WACxB,CAAE"}
@@ -1,2 +1,2 @@
1
- import{describe,expect,beforeEach,it,vi}from"vitest";import mixpanel from"mixpanel-browser";import{initMixpanel,MIXPANEL_EU_API_HOST}from"./mixpanel";vi.mock("mixpanel-browser",()=>({default:{init:vi.fn()}}));describe("initMixpanel",()=>{beforeEach(()=>{vi.clearAllMocks()});it("does not initialize without a token",()=>{const consoleSpy=vi.spyOn(console,"warn").mockImplementation(()=>{});initMixpanel("");expect(mixpanel.init).not.toHaveBeenCalled();consoleSpy.mockRestore()});it("blocks [data-mask] elements in autocapture",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.autocapture.block_selectors).toEqual(["[data-mask]"])});it("does not record sessions",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.record_sessions_percent).toBe(0)});it("defaults api_host to the EU ingestion endpoint",()=>{initMixpanel("test-token");const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe(MIXPANEL_EU_API_HOST);expect(MIXPANEL_EU_API_HOST).toBe("https://api-eu.mixpanel.com")});it("allows overriding api_host",()=>{initMixpanel("test-token",false,false,"https://api.mixpanel.com");const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe("https://api.mixpanel.com")});it("falls back to the EU default when api_host is undefined",()=>{initMixpanel("test-token",false,false,undefined);const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe(MIXPANEL_EU_API_HOST)})});
1
+ import{describe,expect,beforeEach,it,vi}from"vitest";import mixpanel from"mixpanel-browser";import{initMixpanel,MIXPANEL_EU_API_HOST}from"./mixpanel";vi.mock("mixpanel-browser",()=>({default:{init:vi.fn()}}));describe("initMixpanel",()=>{beforeEach(()=>{vi.clearAllMocks()});it("does not initialize without a token",()=>{const consoleSpy=vi.spyOn(console,"warn").mockImplementation(()=>{});initMixpanel("");expect(mixpanel.init).not.toHaveBeenCalled();consoleSpy.mockRestore()});it("blocks [data-mask] elements in autocapture",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.autocapture.block_selectors).toEqual(["[data-mask]"])});it("captures clicks and submits, with noisy interaction events disabled",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.autocapture).toMatchObject({scroll:false,input:false,dead_click:false,rage_click:false});expect(config.autocapture.click).not.toBe(false);expect(config.autocapture.submit).not.toBe(false)});it("does not record sessions",()=>{initMixpanel("test-token",true);const config=mixpanel.init.mock.calls[0][1];expect(config.record_sessions_percent).toBe(0)});it("defaults api_host to the EU ingestion endpoint",()=>{initMixpanel("test-token");const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe(MIXPANEL_EU_API_HOST);expect(MIXPANEL_EU_API_HOST).toBe("https://api-eu.mixpanel.com")});it("allows overriding api_host",()=>{initMixpanel("test-token",false,false,"https://api.mixpanel.com");const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe("https://api.mixpanel.com")});it("falls back to the EU default when api_host is undefined",()=>{initMixpanel("test-token",false,false,undefined);const config=mixpanel.init.mock.calls[0][1];expect(config.api_host).toBe(MIXPANEL_EU_API_HOST)})});
2
2
  //# sourceMappingURL=mixpanel.test.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/insights/mixpanel.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, expect, beforeEach, it, vi } from \"vitest\";\nimport mixpanel from \"mixpanel-browser\";\n\nimport { initMixpanel, MIXPANEL_EU_API_HOST } from \"./mixpanel\";\n\nvi.mock(\"mixpanel-browser\", () => ({\n default: {\n init: vi.fn(),\n },\n}));\n\ndescribe(\"initMixpanel\", () => {\n beforeEach(() => {\n vi.clearAllMocks();\n });\n\n it(\"does not initialize without a token\", () => {\n const consoleSpy = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n initMixpanel(\"\");\n expect(mixpanel.init).not.toHaveBeenCalled();\n consoleSpy.mockRestore();\n });\n\n it(\"blocks [data-mask] elements in autocapture\", () => {\n initMixpanel(\"test-token\", true);\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.autocapture.block_selectors).toEqual([\"[data-mask]\"]);\n });\n\n it(\"does not record sessions\", () => {\n initMixpanel(\"test-token\", true);\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.record_sessions_percent).toBe(0);\n });\n\n it(\"defaults api_host to the EU ingestion endpoint\", () => {\n initMixpanel(\"test-token\");\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.api_host).toBe(MIXPANEL_EU_API_HOST);\n expect(MIXPANEL_EU_API_HOST).toBe(\"https://api-eu.mixpanel.com\");\n });\n\n it(\"allows overriding api_host\", () => {\n initMixpanel(\"test-token\", false, false, \"https://api.mixpanel.com\");\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.api_host).toBe(\"https://api.mixpanel.com\");\n });\n\n it(\"falls back to the EU default when api_host is undefined\", () => {\n initMixpanel(\"test-token\", false, false, undefined);\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.api_host).toBe(MIXPANEL_EU_API_HOST);\n });\n});\n"],"names":["describe","expect","beforeEach","it","vi","mixpanel","initMixpanel","MIXPANEL_EU_API_HOST","mock","default","init","fn","clearAllMocks","consoleSpy","spyOn","console","mockImplementation","not","toHaveBeenCalled","mockRestore","config","calls","autocapture","block_selectors","toEqual","record_sessions_percent","toBe","api_host","undefined"],"mappings":"AAIA,OAASA,QAAQ,CAAEC,MAAM,CAAEC,UAAU,CAAEC,EAAE,CAAEC,EAAE,KAAQ,QAAS,AAC9D,QAAOC,aAAc,kBAAmB,AAExC,QAASC,YAAY,CAAEC,oBAAoB,KAAQ,YAAa,CAEhEH,GAAGI,IAAI,CAAC,mBAAoB,IAAO,CAAA,CACjCC,QAAS,CACPC,KAAMN,GAAGO,EAAE,EACb,CACF,CAAA,GAEAX,SAAS,eAAgB,KACvBE,WAAW,KACTE,GAAGQ,aAAa,EAClB,GAEAT,GAAG,sCAAuC,KACxC,MAAMU,WAAaT,GAAGU,KAAK,CAACC,QAAS,QAAQC,kBAAkB,CAAC,KAAO,GACvEV,aAAa,IACbL,OAAOI,SAASK,IAAI,EAAEO,GAAG,CAACC,gBAAgB,GAC1CL,WAAWM,WAAW,EACxB,GAEAhB,GAAG,6CAA8C,KAC/CG,aAAa,aAAc,MAE3B,MAAMc,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOE,WAAW,CAACC,eAAe,EAAEC,OAAO,CAAC,CAAC,cAAc,CACpE,GAEArB,GAAG,2BAA4B,KAC7BG,aAAa,aAAc,MAE3B,MAAMc,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOK,uBAAuB,EAAEC,IAAI,CAAC,EAC9C,GAEAvB,GAAG,iDAAkD,KACnDG,aAAa,cAEb,MAAMc,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOO,QAAQ,EAAED,IAAI,CAACnB,sBAC7BN,OAAOM,sBAAsBmB,IAAI,CAAC,8BACpC,GAEAvB,GAAG,6BAA8B,KAC/BG,aAAa,aAAc,MAAO,MAAO,4BAEzC,MAAMc,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOO,QAAQ,EAAED,IAAI,CAAC,2BAC/B,GAEAvB,GAAG,0DAA2D,KAC5DG,aAAa,aAAc,MAAO,MAAOsB,WAEzC,MAAMR,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOO,QAAQ,EAAED,IAAI,CAACnB,qBAC/B,EACF"}
1
+ {"version":3,"sources":["../../../src/core/insights/mixpanel.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n */\n\nimport { describe, expect, beforeEach, it, vi } from \"vitest\";\nimport mixpanel from \"mixpanel-browser\";\n\nimport { initMixpanel, MIXPANEL_EU_API_HOST } from \"./mixpanel\";\n\nvi.mock(\"mixpanel-browser\", () => ({\n default: {\n init: vi.fn(),\n },\n}));\n\ndescribe(\"initMixpanel\", () => {\n beforeEach(() => {\n vi.clearAllMocks();\n });\n\n it(\"does not initialize without a token\", () => {\n const consoleSpy = vi.spyOn(console, \"warn\").mockImplementation(() => {});\n initMixpanel(\"\");\n expect(mixpanel.init).not.toHaveBeenCalled();\n consoleSpy.mockRestore();\n });\n\n it(\"blocks [data-mask] elements in autocapture\", () => {\n initMixpanel(\"test-token\", true);\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.autocapture.block_selectors).toEqual([\"[data-mask]\"]);\n });\n\n it(\"captures clicks and submits, with noisy interaction events disabled\", () => {\n initMixpanel(\"test-token\", true);\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.autocapture).toMatchObject({\n scroll: false,\n input: false,\n dead_click: false,\n rage_click: false,\n });\n // click and submit deliberately stay on (SDK default) — an explicit\n // `false` here would silently defeat the intent of keeping them\n expect(config.autocapture.click).not.toBe(false);\n expect(config.autocapture.submit).not.toBe(false);\n });\n\n it(\"does not record sessions\", () => {\n initMixpanel(\"test-token\", true);\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.record_sessions_percent).toBe(0);\n });\n\n it(\"defaults api_host to the EU ingestion endpoint\", () => {\n initMixpanel(\"test-token\");\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.api_host).toBe(MIXPANEL_EU_API_HOST);\n expect(MIXPANEL_EU_API_HOST).toBe(\"https://api-eu.mixpanel.com\");\n });\n\n it(\"allows overriding api_host\", () => {\n initMixpanel(\"test-token\", false, false, \"https://api.mixpanel.com\");\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.api_host).toBe(\"https://api.mixpanel.com\");\n });\n\n it(\"falls back to the EU default when api_host is undefined\", () => {\n initMixpanel(\"test-token\", false, false, undefined);\n\n const config = (mixpanel.init as ReturnType<typeof vi.fn>).mock.calls[0][1];\n expect(config.api_host).toBe(MIXPANEL_EU_API_HOST);\n });\n});\n"],"names":["describe","expect","beforeEach","it","vi","mixpanel","initMixpanel","MIXPANEL_EU_API_HOST","mock","default","init","fn","clearAllMocks","consoleSpy","spyOn","console","mockImplementation","not","toHaveBeenCalled","mockRestore","config","calls","autocapture","block_selectors","toEqual","toMatchObject","scroll","input","dead_click","rage_click","click","toBe","submit","record_sessions_percent","api_host","undefined"],"mappings":"AAIA,OAASA,QAAQ,CAAEC,MAAM,CAAEC,UAAU,CAAEC,EAAE,CAAEC,EAAE,KAAQ,QAAS,AAC9D,QAAOC,aAAc,kBAAmB,AAExC,QAASC,YAAY,CAAEC,oBAAoB,KAAQ,YAAa,CAEhEH,GAAGI,IAAI,CAAC,mBAAoB,IAAO,CAAA,CACjCC,QAAS,CACPC,KAAMN,GAAGO,EAAE,EACb,CACF,CAAA,GAEAX,SAAS,eAAgB,KACvBE,WAAW,KACTE,GAAGQ,aAAa,EAClB,GAEAT,GAAG,sCAAuC,KACxC,MAAMU,WAAaT,GAAGU,KAAK,CAACC,QAAS,QAAQC,kBAAkB,CAAC,KAAO,GACvEV,aAAa,IACbL,OAAOI,SAASK,IAAI,EAAEO,GAAG,CAACC,gBAAgB,GAC1CL,WAAWM,WAAW,EACxB,GAEAhB,GAAG,6CAA8C,KAC/CG,aAAa,aAAc,MAE3B,MAAMc,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOE,WAAW,CAACC,eAAe,EAAEC,OAAO,CAAC,CAAC,cAAc,CACpE,GAEArB,GAAG,sEAAuE,KACxEG,aAAa,aAAc,MAE3B,MAAMc,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOE,WAAW,EAAEG,aAAa,CAAC,CACvCC,OAAQ,MACRC,MAAO,MACPC,WAAY,MACZC,WAAY,KACd,GAGA5B,OAAOmB,OAAOE,WAAW,CAACQ,KAAK,EAAEb,GAAG,CAACc,IAAI,CAAC,OAC1C9B,OAAOmB,OAAOE,WAAW,CAACU,MAAM,EAAEf,GAAG,CAACc,IAAI,CAAC,MAC7C,GAEA5B,GAAG,2BAA4B,KAC7BG,aAAa,aAAc,MAE3B,MAAMc,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOa,uBAAuB,EAAEF,IAAI,CAAC,EAC9C,GAEA5B,GAAG,iDAAkD,KACnDG,aAAa,cAEb,MAAMc,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOc,QAAQ,EAAEH,IAAI,CAACxB,sBAC7BN,OAAOM,sBAAsBwB,IAAI,CAAC,8BACpC,GAEA5B,GAAG,6BAA8B,KAC/BG,aAAa,aAAc,MAAO,MAAO,4BAEzC,MAAMc,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOc,QAAQ,EAAEH,IAAI,CAAC,2BAC/B,GAEA5B,GAAG,0DAA2D,KAC5DG,aAAa,aAAc,MAAO,MAAO6B,WAEzC,MAAMf,OAAS,AAACf,SAASK,IAAI,CAA8BF,IAAI,CAACa,KAAK,CAAC,EAAE,CAAC,EAAE,CAC3EpB,OAAOmB,OAAOc,QAAQ,EAAEH,IAAI,CAACxB,qBAC/B,EACF"}
@@ -1,2 +1,2 @@
1
- function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import*as datalayer from"./datalayer";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import*as logger from"./logger";export class InsightsService{initInsights({mixpanelToken,mixpanelAutoCapture,mixpanelApiHost,posthogApiKey,posthogApiHost,debug=false}){this.debugMode=!!debug;if(this.debugMode){logger.debug("InsightService: Initializing insights")}try{mixpanel.initMixpanel(mixpanelToken,mixpanelAutoCapture,this.debugMode,mixpanelApiHost)}catch(e){if(this.debugMode){logger.error("Failed to initialize Mixpanel",e)}}try{posthog.initPosthog(posthogApiKey,posthogApiHost)}catch(e){if(this.debugMode){logger.error("Failed to initialize Posthog",e)}}}enableDebugMode(){this.debugMode=true;logger.debug("Enabling debug mode");try{mixpanel.enableDebugMode();posthog.enableDebugMode()}catch(e){logger.error("Failed to enable debug mode",e)}}disableDebugMode(){this.debugMode=false;logger.debug("Disabling debug mode");try{mixpanel.disableDebugMode();posthog.disableDebugMode()}catch(e){logger.error("Failed to disable debug mode",e)}}identify(identity){const{userId,accountId,organisationId,email,name,...properties}=identity;if(!userId){if(this.debugMode){logger.warn("User ID not provided, skipping identify")}return}if(this.debugMode){logger.info("Identifying user",{userId,accountId,organisationId,email,name,...properties})}try{mixpanel.identify({userId,accountId,organisationId,email,name,...properties})}catch(e){if(this.debugMode){logger.error("Failed to identify user in Mixpanel",e)}}try{posthog.identify({userId,accountId,organisationId,email,name,...properties})}catch(e){if(this.debugMode){logger.error("Failed to identify user in Posthog",e)}}}trackPageView(options){const{excludeIds,includeDataLayer,...properties}=options??{};if(this.debugMode){logger.info("Tracking page view")}try{mixpanel.trackPageView({excludeIds,...properties})}catch(e){if(this.debugMode){logger.error("Failed to track page view in Mixpanel",e)}}try{posthog.trackPageView(properties)}catch(e){if(this.debugMode){logger.error("Failed to track page view in Posthog",e)}}if(includeDataLayer){try{datalayer.trackPageView(properties)}catch(e){if(this.debugMode){logger.error("Failed to track page view in GTM",e)}}}}track(event,properties){if(this.debugMode){logger.info("Tracking event",{event,properties})}try{mixpanel.track(event,properties)}catch(e){if(this.debugMode){logger.error("Failed to track event in Mixpanel",e)}}try{posthog.track(event,properties)}catch(e){if(this.debugMode){logger.error("Failed to track event in Posthog",e)}}try{datalayer.track(event,properties)}catch(e){if(this.debugMode){logger.error("Failed to track event in Datalayer",e)}}}setupObserver(){const getInsightAttributes=element=>{const MAX_ATTRIBUTES=10;let count=0;const attributes={};for(const attr of Array.from(element.attributes)){if(count>=MAX_ATTRIBUTES)break;if(attr.name.startsWith("data-insight-")){if(!/^data-insight-[a-zA-Z0-9-]+$/.test(attr.name))continue;if(typeof attr.value!=="string"||attr.value.length>100)continue;const key=attr.name.replace("data-insight-","").split("-").map((part,index)=>index===0?part:part.charAt(0).toUpperCase()+part.slice(1)).join("");attributes[key]=attr.value;count++}}return attributes};const findClosestElementWithInsights=element=>{let current=element;while(current&&current!==document.body){const insights=getInsightAttributes(current);if(Object.keys(insights).length>0){return insights}current=current.parentElement}return null};const handleClick=event=>{if(!(event.target instanceof HTMLElement))return;const insights=findClosestElementWithInsights(event.target);if(insights){const{event:eventName,...properties}=insights;this.track(eventName||"element_clicked",properties)}};document.body.addEventListener("click",handleClick);return()=>{document.body.removeEventListener("click",handleClick)}}constructor(){_define_property(this,"debugMode",false)}}
1
+ function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import*as datalayer from"./datalayer";import*as mixpanel from"./mixpanel";import*as posthog from"./posthog";import*as logger from"./logger";import{isLikelyAutomated}from"./bot-detection";export class InsightsService{initInsights({mixpanelToken,mixpanelAutoCapture,mixpanelApiHost,posthogApiKey,posthogApiHost,debug=false}){this.debugMode=!!debug;if(isLikelyAutomated()){if(this.debugMode){logger.debug("InsightService: automated client detected, skipping insights initialization")}return}if(this.debugMode){logger.debug("InsightService: Initializing insights")}try{mixpanel.initMixpanel(mixpanelToken,mixpanelAutoCapture,this.debugMode,mixpanelApiHost)}catch(e){if(this.debugMode){logger.error("Failed to initialize Mixpanel",e)}}try{posthog.initPosthog(posthogApiKey,posthogApiHost)}catch(e){if(this.debugMode){logger.error("Failed to initialize Posthog",e)}}}enableDebugMode(){this.debugMode=true;logger.debug("Enabling debug mode");try{mixpanel.enableDebugMode();posthog.enableDebugMode()}catch(e){logger.error("Failed to enable debug mode",e)}}disableDebugMode(){this.debugMode=false;logger.debug("Disabling debug mode");try{mixpanel.disableDebugMode();posthog.disableDebugMode()}catch(e){logger.error("Failed to disable debug mode",e)}}identify(identity){const{userId,accountId,organisationId,email,name,...properties}=identity;if(!userId){if(this.debugMode){logger.warn("User ID not provided, skipping identify")}return}if(this.debugMode){logger.info("Identifying user",{userId,accountId,organisationId,email,name,...properties})}try{mixpanel.identify({userId,accountId,organisationId,email,name,...properties})}catch(e){if(this.debugMode){logger.error("Failed to identify user in Mixpanel",e)}}try{posthog.identify({userId,accountId,organisationId,email,name,...properties})}catch(e){if(this.debugMode){logger.error("Failed to identify user in Posthog",e)}}}trackPageView(options){const{excludeIds,includeDataLayer,...properties}=options??{};if(this.debugMode){logger.info("Tracking page view")}try{mixpanel.trackPageView({excludeIds,...properties})}catch(e){if(this.debugMode){logger.error("Failed to track page view in Mixpanel",e)}}try{posthog.trackPageView(properties)}catch(e){if(this.debugMode){logger.error("Failed to track page view in Posthog",e)}}if(includeDataLayer){try{datalayer.trackPageView(properties)}catch(e){if(this.debugMode){logger.error("Failed to track page view in GTM",e)}}}}track(event,properties){if(this.debugMode){logger.info("Tracking event",{event,properties})}try{mixpanel.track(event,properties)}catch(e){if(this.debugMode){logger.error("Failed to track event in Mixpanel",e)}}try{posthog.track(event,properties)}catch(e){if(this.debugMode){logger.error("Failed to track event in Posthog",e)}}try{datalayer.track(event,properties)}catch(e){if(this.debugMode){logger.error("Failed to track event in Datalayer",e)}}}setupObserver(){const getInsightAttributes=element=>{const MAX_ATTRIBUTES=10;let count=0;const attributes={};for(const attr of Array.from(element.attributes)){if(count>=MAX_ATTRIBUTES)break;if(attr.name.startsWith("data-insight-")){if(!/^data-insight-[a-zA-Z0-9-]+$/.test(attr.name))continue;if(typeof attr.value!=="string"||attr.value.length>100)continue;const key=attr.name.replace("data-insight-","").split("-").map((part,index)=>index===0?part:part.charAt(0).toUpperCase()+part.slice(1)).join("");attributes[key]=attr.value;count++}}return attributes};const findClosestElementWithInsights=element=>{let current=element;while(current&&current!==document.body){const insights=getInsightAttributes(current);if(Object.keys(insights).length>0){return insights}current=current.parentElement}return null};const handleClick=event=>{if(!(event.target instanceof HTMLElement))return;const insights=findClosestElementWithInsights(event.target);if(insights){const{event:eventName,...properties}=insights;this.track(eventName||"element_clicked",properties)}};document.body.addEventListener("click",handleClick);return()=>{document.body.removeEventListener("click",handleClick)}}constructor(){_define_property(this,"debugMode",false)}}
2
2
  //# sourceMappingURL=service.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/insights/service.ts"],"sourcesContent":["import type {\n AnalyticsService,\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport * as datalayer from \"./datalayer\";\nimport * as mixpanel from \"./mixpanel\";\nimport * as posthog from \"./posthog\";\nimport * as logger from \"./logger\";\n\n// The real implementation that will be used after initialization\nexport class InsightsService implements AnalyticsService {\n private debugMode: boolean = false;\n\n initInsights({\n mixpanelToken,\n mixpanelAutoCapture,\n mixpanelApiHost,\n posthogApiKey,\n posthogApiHost,\n debug = false,\n }: InsightsConfig): void {\n this.debugMode = !!debug;\n\n if (this.debugMode) {\n logger.debug(\"InsightService: Initializing insights\");\n }\n\n try {\n mixpanel.initMixpanel(\n mixpanelToken,\n mixpanelAutoCapture,\n this.debugMode,\n mixpanelApiHost,\n );\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to initialize Mixpanel\", e);\n }\n }\n\n try {\n posthog.initPosthog(posthogApiKey, posthogApiHost);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to initialize Posthog\", e);\n }\n }\n }\n\n enableDebugMode(): void {\n this.debugMode = true;\n logger.debug(\"Enabling debug mode\");\n\n try {\n mixpanel.enableDebugMode();\n posthog.enableDebugMode();\n } catch (e) {\n logger.error(\"Failed to enable debug mode\", e);\n }\n }\n\n disableDebugMode(): void {\n this.debugMode = false;\n logger.debug(\"Disabling debug mode\");\n\n try {\n mixpanel.disableDebugMode();\n posthog.disableDebugMode();\n } catch (e) {\n logger.error(\"Failed to disable debug mode\", e);\n }\n }\n\n identify(identity: InsightsIdentity): void {\n const { userId, accountId, organisationId, email, name, ...properties } =\n identity;\n\n // In very rare cases we might have a user without an account, so we'll\n // let null/undefined/blank strings through on that one\n if (!userId) {\n if (this.debugMode) {\n logger.warn(\"User ID not provided, skipping identify\");\n }\n return;\n }\n\n if (this.debugMode) {\n logger.info(\"Identifying user\", {\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties,\n });\n }\n\n try {\n mixpanel.identify({\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties,\n });\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to identify user in Mixpanel\", e);\n }\n }\n\n try {\n posthog.identify({\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties,\n });\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to identify user in Posthog\", e);\n }\n }\n }\n\n trackPageView(options?: TrackPageViewOptions): void {\n const { excludeIds, includeDataLayer, ...properties } = options ?? {};\n\n if (this.debugMode) {\n logger.info(\"Tracking page view\");\n }\n\n try {\n mixpanel.trackPageView({ excludeIds, ...properties });\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track page view in Mixpanel\", e);\n }\n }\n\n try {\n posthog.trackPageView(properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track page view in Posthog\", e);\n }\n }\n\n if (includeDataLayer) {\n try {\n datalayer.trackPageView(properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track page view in GTM\", e);\n }\n }\n }\n }\n\n track(event: string, properties?: Record<string, unknown>): void {\n if (this.debugMode) {\n logger.info(\"Tracking event\", { event, properties });\n }\n\n try {\n mixpanel.track(event, properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track event in Mixpanel\", e);\n }\n }\n\n try {\n posthog.track(event, properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track event in Posthog\", e);\n }\n }\n\n try {\n datalayer.track(event, properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track event in Datalayer\", e);\n }\n }\n }\n\n setupObserver(): () => void {\n // Helper to get all data-insight-* attributes from an element\n const getInsightAttributes = (\n element: HTMLElement,\n ): { event?: string; [key: string]: string | undefined } => {\n // limit how many data attributes we'll process\n const MAX_ATTRIBUTES = 10;\n let count = 0;\n\n const attributes: { event?: string; [key: string]: string | undefined } =\n {};\n\n for (const attr of Array.from(element.attributes)) {\n if (count >= MAX_ATTRIBUTES) break;\n if (attr.name.startsWith(\"data-insight-\")) {\n // Validate attribute name format\n if (!/^data-insight-[a-zA-Z0-9-]+$/.test(attr.name)) continue;\n\n // Sanitize attribute value\n if (typeof attr.value !== \"string\" || attr.value.length > 100)\n continue;\n\n // Convert data-insight-event-name to eventName\n const key = attr.name\n .replace(\"data-insight-\", \"\")\n .split(\"-\")\n .map((part: string, index: number) =>\n index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1),\n )\n .join(\"\");\n attributes[key] = attr.value;\n count++;\n }\n }\n return attributes;\n };\n\n // Helper to find closest element with data-insight attributes\n const findClosestElementWithInsights = (element: HTMLElement) => {\n let current = element;\n while (current && current !== document.body) {\n const insights = getInsightAttributes(current);\n if (Object.keys(insights).length > 0) {\n return insights;\n }\n\n current = current.parentElement as HTMLElement;\n }\n return null;\n };\n\n // Global click handler\n const handleClick = (event: MouseEvent): void => {\n if (!(event.target instanceof HTMLElement)) return;\n const insights = findClosestElementWithInsights(event.target);\n if (insights) {\n // Extract special properties if they exist\n const { event: eventName, ...properties } = insights;\n this.track(eventName || \"element_clicked\", properties);\n }\n };\n\n // Add listener to document body to catch all clicks\n document.body.addEventListener(\"click\", handleClick);\n\n // Return cleanup function in case it's needed\n return () => {\n document.body.removeEventListener(\"click\", handleClick);\n };\n }\n}\n"],"names":["datalayer","mixpanel","posthog","logger","InsightsService","initInsights","mixpanelToken","mixpanelAutoCapture","mixpanelApiHost","posthogApiKey","posthogApiHost","debug","debugMode","initMixpanel","e","error","initPosthog","enableDebugMode","disableDebugMode","identify","identity","userId","accountId","organisationId","email","name","properties","warn","info","trackPageView","options","excludeIds","includeDataLayer","track","event","setupObserver","getInsightAttributes","element","MAX_ATTRIBUTES","count","attributes","attr","Array","from","startsWith","test","value","length","key","replace","split","map","part","index","charAt","toUpperCase","slice","join","findClosestElementWithInsights","current","document","body","insights","Object","keys","parentElement","handleClick","target","HTMLElement","eventName","addEventListener","removeEventListener"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/core/insights/service.ts"],"sourcesContent":["import type {\n AnalyticsService,\n InsightsConfig,\n InsightsIdentity,\n TrackPageViewOptions,\n} from \"./types\";\nimport * as datalayer from \"./datalayer\";\nimport * as mixpanel from \"./mixpanel\";\nimport * as posthog from \"./posthog\";\nimport * as logger from \"./logger\";\nimport { isLikelyAutomated } from \"./bot-detection\";\n\n// The real implementation that will be used after initialization\nexport class InsightsService implements AnalyticsService {\n private debugMode: boolean = false;\n\n initInsights({\n mixpanelToken,\n mixpanelAutoCapture,\n mixpanelApiHost,\n posthogApiKey,\n posthogApiHost,\n debug = false,\n }: InsightsConfig): void {\n this.debugMode = !!debug;\n\n if (isLikelyAutomated()) {\n if (this.debugMode) {\n logger.debug(\n \"InsightService: automated client detected, skipping insights initialization\",\n );\n }\n return;\n }\n\n if (this.debugMode) {\n logger.debug(\"InsightService: Initializing insights\");\n }\n\n try {\n mixpanel.initMixpanel(\n mixpanelToken,\n mixpanelAutoCapture,\n this.debugMode,\n mixpanelApiHost,\n );\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to initialize Mixpanel\", e);\n }\n }\n\n try {\n posthog.initPosthog(posthogApiKey, posthogApiHost);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to initialize Posthog\", e);\n }\n }\n }\n\n enableDebugMode(): void {\n this.debugMode = true;\n logger.debug(\"Enabling debug mode\");\n\n try {\n mixpanel.enableDebugMode();\n posthog.enableDebugMode();\n } catch (e) {\n logger.error(\"Failed to enable debug mode\", e);\n }\n }\n\n disableDebugMode(): void {\n this.debugMode = false;\n logger.debug(\"Disabling debug mode\");\n\n try {\n mixpanel.disableDebugMode();\n posthog.disableDebugMode();\n } catch (e) {\n logger.error(\"Failed to disable debug mode\", e);\n }\n }\n\n identify(identity: InsightsIdentity): void {\n const { userId, accountId, organisationId, email, name, ...properties } =\n identity;\n\n // In very rare cases we might have a user without an account, so we'll\n // let null/undefined/blank strings through on that one\n if (!userId) {\n if (this.debugMode) {\n logger.warn(\"User ID not provided, skipping identify\");\n }\n return;\n }\n\n if (this.debugMode) {\n logger.info(\"Identifying user\", {\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties,\n });\n }\n\n try {\n mixpanel.identify({\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties,\n });\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to identify user in Mixpanel\", e);\n }\n }\n\n try {\n posthog.identify({\n userId,\n accountId,\n organisationId,\n email,\n name,\n ...properties,\n });\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to identify user in Posthog\", e);\n }\n }\n }\n\n trackPageView(options?: TrackPageViewOptions): void {\n const { excludeIds, includeDataLayer, ...properties } = options ?? {};\n\n if (this.debugMode) {\n logger.info(\"Tracking page view\");\n }\n\n try {\n mixpanel.trackPageView({ excludeIds, ...properties });\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track page view in Mixpanel\", e);\n }\n }\n\n try {\n posthog.trackPageView(properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track page view in Posthog\", e);\n }\n }\n\n if (includeDataLayer) {\n try {\n datalayer.trackPageView(properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track page view in GTM\", e);\n }\n }\n }\n }\n\n track(event: string, properties?: Record<string, unknown>): void {\n if (this.debugMode) {\n logger.info(\"Tracking event\", { event, properties });\n }\n\n try {\n mixpanel.track(event, properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track event in Mixpanel\", e);\n }\n }\n\n try {\n posthog.track(event, properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track event in Posthog\", e);\n }\n }\n\n try {\n datalayer.track(event, properties);\n } catch (e) {\n if (this.debugMode) {\n logger.error(\"Failed to track event in Datalayer\", e);\n }\n }\n }\n\n setupObserver(): () => void {\n // Helper to get all data-insight-* attributes from an element\n const getInsightAttributes = (\n element: HTMLElement,\n ): { event?: string; [key: string]: string | undefined } => {\n // limit how many data attributes we'll process\n const MAX_ATTRIBUTES = 10;\n let count = 0;\n\n const attributes: { event?: string; [key: string]: string | undefined } =\n {};\n\n for (const attr of Array.from(element.attributes)) {\n if (count >= MAX_ATTRIBUTES) break;\n if (attr.name.startsWith(\"data-insight-\")) {\n // Validate attribute name format\n if (!/^data-insight-[a-zA-Z0-9-]+$/.test(attr.name)) continue;\n\n // Sanitize attribute value\n if (typeof attr.value !== \"string\" || attr.value.length > 100)\n continue;\n\n // Convert data-insight-event-name to eventName\n const key = attr.name\n .replace(\"data-insight-\", \"\")\n .split(\"-\")\n .map((part: string, index: number) =>\n index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1),\n )\n .join(\"\");\n attributes[key] = attr.value;\n count++;\n }\n }\n return attributes;\n };\n\n // Helper to find closest element with data-insight attributes\n const findClosestElementWithInsights = (element: HTMLElement) => {\n let current = element;\n while (current && current !== document.body) {\n const insights = getInsightAttributes(current);\n if (Object.keys(insights).length > 0) {\n return insights;\n }\n\n current = current.parentElement as HTMLElement;\n }\n return null;\n };\n\n // Global click handler\n const handleClick = (event: MouseEvent): void => {\n if (!(event.target instanceof HTMLElement)) return;\n const insights = findClosestElementWithInsights(event.target);\n if (insights) {\n // Extract special properties if they exist\n const { event: eventName, ...properties } = insights;\n this.track(eventName || \"element_clicked\", properties);\n }\n };\n\n // Add listener to document body to catch all clicks\n document.body.addEventListener(\"click\", handleClick);\n\n // Return cleanup function in case it's needed\n return () => {\n document.body.removeEventListener(\"click\", handleClick);\n };\n }\n}\n"],"names":["datalayer","mixpanel","posthog","logger","isLikelyAutomated","InsightsService","initInsights","mixpanelToken","mixpanelAutoCapture","mixpanelApiHost","posthogApiKey","posthogApiHost","debug","debugMode","initMixpanel","e","error","initPosthog","enableDebugMode","disableDebugMode","identify","identity","userId","accountId","organisationId","email","name","properties","warn","info","trackPageView","options","excludeIds","includeDataLayer","track","event","setupObserver","getInsightAttributes","element","MAX_ATTRIBUTES","count","attributes","attr","Array","from","startsWith","test","value","length","key","replace","split","map","part","index","charAt","toUpperCase","slice","join","findClosestElementWithInsights","current","document","body","insights","Object","keys","parentElement","handleClick","target","HTMLElement","eventName","addEventListener","removeEventListener"],"mappings":"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"}
package/index.d.ts CHANGED
@@ -6329,6 +6329,11 @@ export function useThemedScrollpoints(scrollpoints: ThemedScrollpoint[]): string
6329
6329
  //# sourceMappingURL=use-themed-scrollpoints.d.ts.map
6330
6330
  }
6331
6331
 
6332
+ declare module '@ably/ui/core/insights/bot-detection' {
6333
+ export const isLikelyAutomated: () => boolean;
6334
+ //# sourceMappingURL=bot-detection.d.ts.map
6335
+ }
6336
+
6332
6337
  declare module '@ably/ui/core/insights/command-queue' {
6333
6338
  import { AnalyticsService, InsightsConfig, InsightsIdentity, TrackPageViewOptions } from "@ably/ui/core/types";
6334
6339
  export class InsightsCommandQueue implements AnalyticsService {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ably/ui",
3
- "version": "18.4.0",
3
+ "version": "18.5.0",
4
4
  "description": "Home of the Ably design system library ([design.ably.com](https://design.ably.com)). It provides a showcase, development/test environment and a publishing pipeline for different distributables.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,8 +37,8 @@
37
37
  "@types/react": "^18.3.1",
38
38
  "@types/react-dom": "^18.3.0",
39
39
  "@types/svg-sprite": "^0.0.39",
40
- "@typescript-eslint/eslint-plugin": "^8.64.0",
41
- "@typescript-eslint/parser": "^8.64.0",
40
+ "@typescript-eslint/eslint-plugin": "^8.66.0",
41
+ "@typescript-eslint/parser": "^8.66.0",
42
42
  "@vitejs/plugin-react-swc": "^4.3.0",
43
43
  "@vitest/browser": "^4.1.10",
44
44
  "@vitest/browser-playwright": "^4.1.10",
@@ -58,7 +58,7 @@
58
58
  "msw": "2.15.0",
59
59
  "msw-storybook-addon": "^2.0.7",
60
60
  "playwright": "^1.61.1",
61
- "posthog-js": "1.403.0",
61
+ "posthog-js": "1.413.2",
62
62
  "prettier": "^3.9.4",
63
63
  "storybook": "^10.5.0",
64
64
  "svg-sprite": "^2.0.4",
@@ -80,13 +80,14 @@
80
80
  "@radix-ui/react-tooltip": "^1.2.8",
81
81
  "array-flat-polyfill": "^1.0.1",
82
82
  "clsx": "^2.1.1",
83
- "dompurify": "^3.4.12",
83
+ "dompurify": "^3.4.13",
84
84
  "embla-carousel": "^8.6.0",
85
85
  "embla-carousel-autoplay": "^8.6.0",
86
86
  "embla-carousel-react": "^8.6.0",
87
87
  "es-toolkit": "^1.49.0",
88
88
  "highlight.js": "^11.11.1",
89
89
  "highlightjs-curl": "^1.3.0",
90
+ "isbot": "5.2.1",
90
91
  "js-cookie": "^3.0.7",
91
92
  "react": "^18.2.0",
92
93
  "react-dom": "^18.3.1",