mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
feat(login): 添加手动点击Passkey按钮的 AbortController 以防止重复点击
This commit is contained in:
+45
-17
@@ -77,6 +77,9 @@ const passkeyLoading = ref(false)
|
|||||||
// Conditional UI 的 AbortController
|
// Conditional UI 的 AbortController
|
||||||
let conditionalAbortController: AbortController | null = null
|
let conditionalAbortController: AbortController | null = null
|
||||||
|
|
||||||
|
// 手动模式的 AbortController(用于防止重复点击)
|
||||||
|
let manualAbortController: AbortController | null = null
|
||||||
|
|
||||||
// 标记当前是否有手动模式的 PassKey 请求正在进行
|
// 标记当前是否有手动模式的 PassKey 请求正在进行
|
||||||
let isManualPassKeyActive = false
|
let isManualPassKeyActive = false
|
||||||
|
|
||||||
@@ -158,17 +161,17 @@ async function authenticateWithPassKey(options: PassKeyAuthOptions = {}): Promis
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 转换credential为可传输格式
|
// 3. 转换credential为可传输格式
|
||||||
|
const publicKeyCredential = credential as PublicKeyCredential
|
||||||
|
const assertionResponse = publicKeyCredential.response as AuthenticatorAssertionResponse
|
||||||
const credentialJSON = {
|
const credentialJSON = {
|
||||||
id: credential.id,
|
id: publicKeyCredential.id,
|
||||||
rawId: bufferToBase64Url((credential as any).rawId),
|
rawId: bufferToBase64Url(publicKeyCredential.rawId),
|
||||||
type: credential.type,
|
type: publicKeyCredential.type,
|
||||||
response: {
|
response: {
|
||||||
authenticatorData: bufferToBase64Url((credential as any).response.authenticatorData),
|
authenticatorData: bufferToBase64Url(assertionResponse.authenticatorData),
|
||||||
clientDataJSON: bufferToBase64Url((credential as any).response.clientDataJSON),
|
clientDataJSON: bufferToBase64Url(assertionResponse.clientDataJSON),
|
||||||
signature: bufferToBase64Url((credential as any).response.signature),
|
signature: bufferToBase64Url(assertionResponse.signature),
|
||||||
userHandle: (credential as any).response.userHandle
|
userHandle: assertionResponse.userHandle ? bufferToBase64Url(assertionResponse.userHandle) : null,
|
||||||
? bufferToBase64Url((credential as any).response.userHandle)
|
|
||||||
: null,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,14 +188,23 @@ async function authenticateWithPassKey(options: PassKeyAuthOptions = {}): Promis
|
|||||||
async function loginWithPassKey(isConditional = false) {
|
async function loginWithPassKey(isConditional = false) {
|
||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
// 如果是手动触发(非 Conditional UI),先取消 Conditional UI 请求
|
// 如果是手动触发(非 Conditional UI)
|
||||||
if (!isConditional && conditionalAbortController) {
|
|
||||||
conditionalAbortController.abort()
|
|
||||||
conditionalAbortController = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 手动模式下,标记手动请求为活跃状态,并立即设置 loading
|
|
||||||
if (!isConditional) {
|
if (!isConditional) {
|
||||||
|
// 取消之前的 Conditional UI 请求
|
||||||
|
if (conditionalAbortController) {
|
||||||
|
conditionalAbortController.abort()
|
||||||
|
conditionalAbortController = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消之前的手动请求(防止重复点击)
|
||||||
|
if (manualAbortController) {
|
||||||
|
manualAbortController.abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的 AbortController
|
||||||
|
manualAbortController = new AbortController()
|
||||||
|
|
||||||
|
// 标记手动请求为活跃状态,并立即设置 loading
|
||||||
isManualPassKeyActive = true
|
isManualPassKeyActive = true
|
||||||
passkeyLoading.value = true
|
passkeyLoading.value = true
|
||||||
}
|
}
|
||||||
@@ -200,7 +212,12 @@ async function loginWithPassKey(isConditional = false) {
|
|||||||
try {
|
try {
|
||||||
const finishResponse = await authenticateWithPassKey({
|
const finishResponse = await authenticateWithPassKey({
|
||||||
isConditional,
|
isConditional,
|
||||||
signal: isConditional && conditionalAbortController ? conditionalAbortController.signal : undefined,
|
signal:
|
||||||
|
isConditional && conditionalAbortController
|
||||||
|
? conditionalAbortController.signal
|
||||||
|
: !isConditional && manualAbortController
|
||||||
|
? manualAbortController.signal
|
||||||
|
: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
await handleLoginSuccess(finishResponse)
|
await handleLoginSuccess(finishResponse)
|
||||||
@@ -213,6 +230,12 @@ async function loginWithPassKey(isConditional = false) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 手动模式下的 AbortError 也应该静默(用户重复点击导致)
|
||||||
|
if (!isConditional && error.name === 'AbortError') {
|
||||||
|
console.warn('[PassKey] Manual request aborted (likely due to rapid clicking):', error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 设置错误信息
|
// 设置错误信息
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
errorMessage.value = error.response.data?.detail || t('login.passkeyLoginFailed')
|
errorMessage.value = error.response.data?.detail || t('login.passkeyLoginFailed')
|
||||||
@@ -227,6 +250,7 @@ async function loginWithPassKey(isConditional = false) {
|
|||||||
// 手动模式:始终清除,并取消手动活跃标记
|
// 手动模式:始终清除,并取消手动活跃标记
|
||||||
isManualPassKeyActive = false
|
isManualPassKeyActive = false
|
||||||
passkeyLoading.value = false
|
passkeyLoading.value = false
|
||||||
|
manualAbortController = null
|
||||||
} else {
|
} else {
|
||||||
// Conditional UI 模式:只有在没有手动请求活跃时才清除
|
// Conditional UI 模式:只有在没有手动请求活跃时才清除
|
||||||
if (!isManualPassKeyActive && passkeyLoading.value) {
|
if (!isManualPassKeyActive && passkeyLoading.value) {
|
||||||
@@ -482,6 +506,10 @@ onUnmounted(() => {
|
|||||||
conditionalAbortController.abort()
|
conditionalAbortController.abort()
|
||||||
conditionalAbortController = null
|
conditionalAbortController = null
|
||||||
}
|
}
|
||||||
|
if (manualAbortController) {
|
||||||
|
manualAbortController.abort()
|
||||||
|
manualAbortController = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user