function normalizeText(value: unknown): string | null { if (typeof value !== 'string') { return null } const trimmed = value.trim() return trimmed ? trimmed : null } function hashText(value: string): string { let hash = 0 for (let index = 0; index < value.length; index += 1) { hash = ((hash << 5) - hash + value.charCodeAt(index)) | 0 } return Math.abs(hash).toString(16).padStart(8, '0') } function boundedIdentity(prefix: string, raw: string): string { const normalizedPrefix = prefix.trim().replace(/:+$/, '') const normalizedRaw = raw.trim() if (!normalizedRaw) { return `${normalizedPrefix}:${hashText(prefix)}` } const candidate = `${normalizedPrefix}:${normalizedRaw}` if (candidate.length <= 120) { return candidate } return `${normalizedPrefix}:${hashText(normalizedRaw)}` } function normalizeURLPath(pathname: string): string { const normalized = pathname.replace(/\/+$/, '') return normalized === '/' ? '' : normalized } function safeParseURL(input: string): URL | null { try { return new URL(input) } catch { return null } } function genericAIConversationPath(pathname: string): boolean { return /^\/(?:chat|c|conversation)(?:\/|$)/i.test(pathname) } export interface GenericAIPageState { displayName: string | null avatarUrl: string | null fingerprint: string | null credentialFingerprint: string | null currentPath: string | null strongAuthSignalCount: number loginSignalCount: number loggedOutSignalCount: number challengeSignalCount: number } export function genericAIPageLooksAuthenticated( currentURL: string, pageState: GenericAIPageState | null, ): boolean { const current = safeParseURL(currentURL) const currentPath = normalizeURLPath(current?.pathname ?? pageState?.currentPath ?? '') const strongAuthSignalCount = pageState?.strongAuthSignalCount ?? 0 const loginSignalCount = pageState?.loginSignalCount ?? 0 const loggedOutSignalCount = pageState?.loggedOutSignalCount ?? 0 const challengeSignalCount = pageState?.challengeSignalCount ?? 0 const hasCredentialFingerprint = Boolean(normalizeText(pageState?.credentialFingerprint)) const onConversationPath = genericAIConversationPath(currentPath) if (challengeSignalCount > 0) { return false } if (loggedOutSignalCount > 0) { return false } if ( loginSignalCount > 0 && strongAuthSignalCount === 0 && !onConversationPath && !hasCredentialFingerprint ) { return false } if (loginSignalCount > 0) { return (strongAuthSignalCount >= 2 || hasCredentialFingerprint) && !onConversationPath } return strongAuthSignalCount > 0 || onConversationPath || hasCredentialFingerprint } export function buildGenericAIPageFingerprintFallback( platformId: string, pageState?: GenericAIPageState | null, ): string | null { const credentialFingerprint = normalizeText(pageState?.credentialFingerprint) if (credentialFingerprint) { return boundedIdentity(`${platformId}-session`, credentialFingerprint) } const fallbackFingerprint = normalizeText(pageState?.fingerprint) if (!fallbackFingerprint) { return null } const strongAuthSignalCount = pageState?.strongAuthSignalCount ?? 0 if (strongAuthSignalCount <= 0) { return null } return boundedIdentity(`${platformId}-page`, fallbackFingerprint) }