mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat(antigravity): 对齐 AM 客户端标识,前端 OAuth 增加 TOTP 支持
- User-Agent 改为 Electron 浏览器格式,新增 x-client-name/x-client-version/x-vscode-sessionid/x-goog-api-client header - 移除 MODEL_ALIAS_MAP 别名映射,改为仅剥离 -online 后缀 - thinking 自动注入关键字新增 gemini-3.1-pro - 新增定时 Antigravity UA 版本刷新任务(每 6 小时 + 启动时) - 前端 OAuth 设备授权新增 TOTP 验证码生成(otpauth),Region 改为按钮选择 - SSO OIDC 轮询日志 authorization_pending/slow_down 降级为 debug
This commit is contained in:
79
frontend/src/composables/useTotp.ts
Normal file
79
frontend/src/composables/useTotp.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { ref, computed, onBeforeUnmount } from 'vue'
|
||||
import { TOTP, Secret } from 'otpauth'
|
||||
|
||||
/**
|
||||
* TOTP composable: 给定 secret 持续生成验证码并显示剩余秒数。
|
||||
*/
|
||||
export function useTotp() {
|
||||
const secret = ref('')
|
||||
const code = ref('')
|
||||
const remaining = ref(0)
|
||||
const error = ref('')
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
let totp: TOTP | null = null
|
||||
|
||||
function parseSecret(raw: string): Secret | null {
|
||||
const cleaned = raw.replace(/[\s-]/g, '')
|
||||
if (!cleaned) return null
|
||||
try {
|
||||
return Secret.fromBase32(cleaned.toUpperCase())
|
||||
} catch {
|
||||
// 尝试 hex
|
||||
}
|
||||
try {
|
||||
return Secret.fromHex(cleaned)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function generate(): void {
|
||||
if (!totp) {
|
||||
code.value = ''
|
||||
remaining.value = 0
|
||||
return
|
||||
}
|
||||
code.value = totp.generate()
|
||||
const epoch = Math.floor(Date.now() / 1000)
|
||||
remaining.value = totp.period - (epoch % totp.period)
|
||||
}
|
||||
|
||||
function start(rawSecret: string): void {
|
||||
stop()
|
||||
error.value = ''
|
||||
secret.value = rawSecret
|
||||
|
||||
const parsed = parseSecret(rawSecret)
|
||||
if (!parsed) {
|
||||
error.value = rawSecret.trim() ? 'Secret 格式无效' : ''
|
||||
return
|
||||
}
|
||||
|
||||
totp = new TOTP({ secret: parsed, digits: 6, period: 30 })
|
||||
generate()
|
||||
timer = setInterval(generate, 1000)
|
||||
}
|
||||
|
||||
function stop(): void {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
totp = null
|
||||
code.value = ''
|
||||
remaining.value = 0
|
||||
error.value = ''
|
||||
}
|
||||
|
||||
onBeforeUnmount(stop)
|
||||
|
||||
return {
|
||||
secret,
|
||||
code: computed(() => code.value),
|
||||
remaining: computed(() => remaining.value),
|
||||
error: computed(() => error.value),
|
||||
start,
|
||||
stop,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user