/** * Internal dependencies */ import type { Session } from '../types'; export function isGdprAccepted( session: Session ): boolean { const { gdprCookie } = session; if ( ! gdprCookie.name ) { return true; } //end if const matches = ( pattern: string, value: string ) => 0 <= pattern .split( '*' ) .reduce( ( start, search ) => start < 0 ? start : value.indexOf( search, start ), 0 ); return document.cookie .split( ';' ) .map( ( c ) => c.trim() ) .map( ( c ) => c.split( '=' ) ) .map( ( [ n, ...v ] ) => [ n, v.join( '=' ) ] ) .filter( ( c ): c is [ string, string ] => c[ 0 ] !== undefined && c[ 1 ] !== undefined ) .filter( ( [ n ] ) => matches( gdprCookie.name, n ) ) .some( ( [ _, v ] ) => ! gdprCookie.value || matches( gdprCookie.value, v ) ); } //end isGdprAccepted() export function isValidScope( session: Session ): boolean { const { recordingsScope } = session; return ( ! recordingsScope.length || recordingsScope.some( ( s ) => matchesScope( window.location.href, s.trim() ) ) ); } //end isValidScope() function normalizePathname( pathname: string ): string { // Remove trailing slashes except for root "/" return pathname.length > 1 ? pathname.replace( /\/+$/, '' ) : pathname; } function normalizePort( url: URL ): string { // Treat default ports as empty so https://a.com and https://a.com:443 match if ( ( url.protocol === 'http:' && url.port === '80' ) || ( url.protocol === 'https:' && url.port === '443' ) ) { return ''; } return url.port; } type QueryRequirement = | { key: string; type: 'any' } | { key: string; type: 'exact'; value: string }; function parseQueryRequirements( search: string ): QueryRequirement[] { if ( ! search ) { return []; } const raw = search.startsWith( '?' ) ? search.slice( 1 ) : search; if ( ! raw ) { return []; } // Support: ?a=1 (exact), ?a (exists-any-value), ?a= (empty value) return raw .split( '&' ) .filter( Boolean ) .map( ( part ) => { const eq = part.indexOf( '=' ); if ( eq === -1 ) { return { key: decodeURIComponent( part ), type: 'any' as const, }; } return { key: decodeURIComponent( part.slice( 0, eq ) ), value: decodeURIComponent( part.slice( eq + 1 ) ), type: 'exact' as const, }; } ); } function matchesQuery( hrefSearch: string, targetSearch: string ): boolean { const hrefParams = new URLSearchParams( hrefSearch ); const requirements = parseQueryRequirements( targetSearch ); for ( const req of requirements ) { if ( ! hrefParams.has( req.key ) ) { return false; } if ( req.type === 'exact' && hrefParams.get( req.key ) !== req.value ) { return false; } } return true; } function matchesExactUrl( href: string, target: string ): boolean { let hrefUrl: URL; let targetUrl: URL; try { // Allow target to be absolute or relative (relative resolved against current origin) hrefUrl = new URL( href, window.location.origin ); targetUrl = new URL( target, window.location.origin ); } catch { return false; } // Scheme/host/port/path must match (path ignores trailing slash differences) if ( hrefUrl.protocol !== targetUrl.protocol ) { return false; } if ( hrefUrl.hostname !== targetUrl.hostname ) { return false; } if ( normalizePort( hrefUrl ) !== normalizePort( targetUrl ) ) { return false; } if ( normalizePathname( hrefUrl.pathname ) !== normalizePathname( targetUrl.pathname ) ) { return false; } // Query rules: // - If target has no query: ignore href query // - If target has query: // - ?a=1 requires exact value // - ?a requires that param exists (value ignored) // - Extra params in href are allowed if ( targetUrl.search ) { if ( ! matchesQuery( hrefUrl.search, targetUrl.search ) ) { return false; } } // Hash rules: // - If target has no hash: ignore href hash // - If target has hash: must match exactly (including leading '#') if ( targetUrl.hash ) { if ( hrefUrl.hash !== targetUrl.hash ) { return false; } } return true; } function matchesScope( href: string, raw: string ): boolean { const s = raw.trim(); if ( ! s ) { return false; } const isExact = s.startsWith( '"' ) && s.endsWith( '"' ) && s.length >= 2; if ( isExact ) { const target = s.slice( 1, -1 ).trim(); return !! target && matchesExactUrl( href, target ); } // Non-quoted entries behave as fragments: // Keep this simple, or you can normalize (e.g. ignore hash) if you want. return href.includes( s ); }