Files
geo/apps/desktop-client/src/main/generic-ai-auth.ts
T
root 162abdc97c
Backend CI / Backend (push) Has been cancelled
Frontend CI / Frontend (push) Failing after 1m39s
chore(frontend): introduce prettier + eslint and prune unused code
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports)
- Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier
- Add root scripts: format, format:check, lint, lint:fix
- Reformat 257 files across admin-web, ops-web, desktop-client, packages
- Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters
- Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex
- Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:39:09 +08:00

119 lines
3.3 KiB
TypeScript

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)
}