import { display } from '../tools/display' import { ONE_MINUTE, ONE_SECOND } from '../tools/utils/timeUtils' import { findAllCommaSeparatedValues, findCommaSeparatedValue, findCommaSeparatedValues, generateUUID, } from '../tools/utils/stringUtils' import { buildUrl } from '../tools/utils/urlPolyfill' export interface CookieOptions { secure?: boolean crossSite?: boolean partitioned?: boolean domain?: string } export function setCookie(name: string, value: string, expireDelay: number = 0, options?: CookieOptions) { const date = new Date() date.setTime(date.getTime() + expireDelay) const expires = `expires=${date.toUTCString()}` const sameSite = options && options.crossSite ? 'none' : 'strict' const domain = options && options.domain ? `;domain=${options.domain}` : '' const secure = options && options.secure ? ';secure' : '' const partitioned = options && options.partitioned ? ';partitioned' : '' document.cookie = `${name}=${value};${expires};path=/;samesite=${sameSite}${domain}${secure}${partitioned}` } /** * Returns the value of the cookie with the given name * If there are multiple cookies with the same name, returns the first one */ export function getCookie(name: string) { return findCommaSeparatedValue(document.cookie, name) } /** * Returns all the values of the cookies with the given name */ export function getCookies(name: string): string[] { return findAllCommaSeparatedValues(document.cookie).get(name) || [] } let initCookieParsed: Map | undefined /** * Returns a cached value of the cookie. Use this during SDK initialization (and whenever possible) * to avoid accessing document.cookie multiple times. * * ⚠️ If there are multiple cookies with the same name, returns the LAST one (unlike `getCookie()`) */ export function getInitCookie(name: string) { if (!initCookieParsed) { initCookieParsed = findCommaSeparatedValues(document.cookie) } return initCookieParsed.get(name) } export function resetInitCookies() { initCookieParsed = undefined } export function deleteCookie(name: string, options?: CookieOptions) { setCookie(name, '', 0, options) } export function areCookiesAuthorized(options: CookieOptions): boolean { if (document.cookie === undefined || document.cookie === null) { return false } try { // Use a unique cookie name to avoid issues when the SDK is initialized multiple times during // the test cookie lifetime const testCookieName = `dd_cookie_test_${generateUUID()}` const testCookieValue = 'test' setCookie(testCookieName, testCookieValue, ONE_MINUTE, options) const isCookieCorrectlySet = getCookie(testCookieName) === testCookieValue deleteCookie(testCookieName, options) return isCookieCorrectlySet } catch (error) { display.error(error) return false } } /** * No API to retrieve it, number of levels for subdomain and suffix are unknown * strategy: find the minimal domain on which cookies are allowed to be set * https://web.dev/same-site-same-origin/#site */ let getCurrentSiteCache: string | undefined export function getCurrentSite(hostname = location.hostname, referrer = document.referrer): string | undefined { if (getCurrentSiteCache === undefined) { const defaultHostName = getCookieDefaultHostName(hostname, referrer) if (defaultHostName) { // Use a unique cookie name to avoid issues when the SDK is initialized multiple times during // the test cookie lifetime const testCookieName = `dd_site_test_${generateUUID()}` const testCookieValue = 'test' const domainLevels = defaultHostName.split('.') let candidateDomain = domainLevels.pop()! while (domainLevels.length && !getCookie(testCookieName)) { candidateDomain = `${domainLevels.pop()!}.${candidateDomain}` setCookie(testCookieName, testCookieValue, ONE_SECOND, { domain: candidateDomain }) } deleteCookie(testCookieName, { domain: candidateDomain }) getCurrentSiteCache = candidateDomain } } return getCurrentSiteCache } function getCookieDefaultHostName(hostname: string, referrer: string) { try { return hostname || buildUrl(referrer).hostname } catch { // Ignore } } export function resetGetCurrentSite() { getCurrentSiteCache = undefined }