Files
web/packages/tracking/lib/useTrackHardNavigation.ts
Linus Flood ffb88cf454 Merged in fix/sw-3591-pageview (pull request #3062)
Fix(SW-3591): fixed (?) race condition when tracking pageview on hard reload

* Added logging

* Fix


Approved-by: Hrishikesh Vaipurkar
2025-11-04 08:13:35 +00:00

169 lines
3.7 KiB
TypeScript

"use client"
import { useEffect } from "react"
import { useSessionId } from "@scandic-hotels/common/hooks/useSessionId"
import { logger } from "@scandic-hotels/common/logger"
import { promiseWithTimeout } from "@scandic-hotels/common/utils/promiseWithTimeout"
import { createSDKPageObject, trackPageView } from "./pageview"
import { useTrackingUserData } from "./useTrackingUserData"
import type {
TrackingSDKAncillaries,
TrackingSDKHotelInfo,
TrackingSDKPageData,
TrackingSDKPaymentInfo,
TrackingSDKUserData,
} from "./types"
type TrackingSDKProps = {
pageData: TrackingSDKPageData
hotelInfo?: TrackingSDKHotelInfo
paymentInfo?: TrackingSDKPaymentInfo
ancillaries?: TrackingSDKAncillaries
pathName: string
}
let hasTrackedHardNavigation = false
export const useTrackHardNavigation = ({
pageData,
hotelInfo,
paymentInfo,
ancillaries,
pathName,
}: TrackingSDKProps) => {
const sessionId = useSessionId()
const { userData, isPending } = useTrackingUserData()
useEffect(() => {
if (isPending) {
return
}
if (!userData) {
return
}
const track = () => {
if (hasTrackedHardNavigation) {
return
}
trackPerformance({
pathName,
sessionId,
paymentInfo,
hotelInfo,
userData,
pageData,
ancillaries,
})
hasTrackedHardNavigation = true
}
if (document.readyState === "complete") {
track()
return
}
window.addEventListener("load", track)
return () => window.removeEventListener("load", track)
}, [
pathName,
hotelInfo,
pageData,
sessionId,
paymentInfo,
userData,
ancillaries,
])
}
const trackPerformance = async ({
pathName,
sessionId,
paymentInfo,
hotelInfo,
userData,
pageData,
ancillaries,
}: {
pathName: string
sessionId: string | null
paymentInfo: TrackingSDKProps["paymentInfo"]
hotelInfo: TrackingSDKProps["hotelInfo"]
userData: TrackingSDKUserData
pageData: TrackingSDKProps["pageData"]
ancillaries: TrackingSDKProps["ancillaries"]
}) => {
let pageLoadTime: number | undefined = undefined
let lcpTime: number | undefined = undefined
try {
pageLoadTime = await promiseWithTimeout(getPageLoadTimeEntry(), 3000)
} catch (error) {
logger.error("Error obtaining pageLoadTime:", error)
}
try {
lcpTime = await promiseWithTimeout(getLCPTimeEntry(), 3000)
} catch (error) {
logger.error("Error obtaining lcpTime:", error)
}
const trackingData = {
...pageData,
sessionId,
pathName,
pageLoadTime,
lcpTime,
}
const pageObject = createSDKPageObject(trackingData)
trackPageView({
event: "pageView",
pageInfo: pageObject,
userInfo: userData,
hotelInfo,
paymentInfo,
ancillaries,
})
}
const getLCPTimeEntry = () => {
return new Promise<number | undefined>((resolve) => {
const observer = new PerformanceObserver((entries) => {
const lastEntry = entries.getEntries().at(-1)
if (lastEntry) {
observer.disconnect()
resolve(lastEntry.startTime / 1000)
}
})
const lcpSupported = PerformanceObserver.supportedEntryTypes?.includes(
"largest-contentful-paint"
)
if (lcpSupported) {
observer.observe({
type: "largest-contentful-paint",
buffered: true,
})
} else {
resolve(undefined)
}
})
}
const getPageLoadTimeEntry = () => {
return new Promise<number>((resolve) => {
const observer = new PerformanceObserver((entries) => {
const navEntry = entries.getEntriesByType("navigation")[0]
if (navEntry) {
observer.disconnect()
resolve(navEntry.duration / 1000)
}
})
observer.observe({ type: "navigation", buffered: true })
})
}