export interface AuthCheckProfile { loggedInSelector: string | null; loginUrlPatterns: string[]; } export interface AuthCheckInput { profile: AuthCheckProfile; finalUrl: string; statusCode: number; loggedInSelectorPresent: boolean; } export type AuthExpiry = { expired: false } | { expired: true; reason: string }; function urlMatches(url: string, glob: string): boolean { // Minimal glob: '**' = '.*', '*' = '[^/]*' // Use a sentinel for '**' so the second '*' substitution doesn't clobber the '.*'. const DOUBLE = '\x00DOUBLE\x00'; const escaped = glob .replace(/\*\*/g, DOUBLE) .replace(/[.+?^${}()|[\]\\]/g, '\\$&') .replace(/\*/g, '[^/]*') .split(DOUBLE).join('.*'); return new RegExp('^' + escaped + '$').test(url); } export function detectAuthExpiry(input: AuthCheckInput): AuthExpiry { if (input.statusCode === 401 || input.statusCode === 403) { return { expired: true, reason: `HTTP ${input.statusCode}` }; } for (const pattern of input.profile.loginUrlPatterns) { if (urlMatches(input.finalUrl, pattern)) { return { expired: true, reason: 'redirected to login URL' }; } } if (input.profile.loggedInSelector && !input.loggedInSelectorPresent) { return { expired: true, reason: 'logged-in selector not found' }; } return { expired: false }; }