feat(login-ui): dedupe submissions and surface guard error codes
Collapse rapid double-submits into a single in-flight request across admin-web, ops-web and the desktop client so users cannot fire parallel login attempts and trip the new in-progress guard. Map the new login_rate_limited / login_locked / login_in_progress / login_guard_unavailable error codes to localized messages in every login surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { computed, readonly, shallowRef } from "vue";
|
||||
|
||||
import { createApiClient } from "@geo/http-client";
|
||||
import { ApiClientError, createApiClient } from "@geo/http-client";
|
||||
import type {
|
||||
AuthTokens,
|
||||
DesktopClientInfo,
|
||||
@@ -38,6 +38,7 @@ const session = shallowRef<DesktopSession | null>(readStoredSession());
|
||||
const apiBaseURL = shallowRef(readStoredApiBaseURL());
|
||||
const pending = shallowRef(false);
|
||||
const error = shallowRef<string | null>(null);
|
||||
let loginPromise: Promise<void> | null = null;
|
||||
|
||||
function readStoredSession(): DesktopSession | null {
|
||||
if (typeof window === "undefined") {
|
||||
@@ -336,7 +337,22 @@ async function syncRuntimeSessionToMain(next: DesktopSession | null = session.va
|
||||
await window.desktopBridge.app.syncRuntimeSession(buildRuntimeSessionPayload(next));
|
||||
}
|
||||
|
||||
async function login(payload: LoginRequest): Promise<void> {
|
||||
function formatLoginError(err: unknown): string {
|
||||
if (err instanceof ApiClientError) {
|
||||
const messages: Record<string, string> = {
|
||||
invalid_credentials: "邮箱或密码错误",
|
||||
login_rate_limited: "登录请求过于频繁",
|
||||
login_locked: "登录已被临时保护",
|
||||
login_in_progress: "登录请求正在处理中",
|
||||
login_guard_unavailable: "登录保护暂时不可用",
|
||||
};
|
||||
const message = messages[err.message] ?? err.message.replaceAll("_", " ");
|
||||
return err.detail ? `${message},${err.detail}` : message;
|
||||
}
|
||||
return err instanceof Error ? err.message : "登录失败,请稍后重试";
|
||||
}
|
||||
|
||||
async function performLogin(payload: LoginRequest): Promise<void> {
|
||||
pending.value = true;
|
||||
error.value = null;
|
||||
|
||||
@@ -368,7 +384,7 @@ async function login(payload: LoginRequest): Promise<void> {
|
||||
desktopClient: registered.client,
|
||||
});
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : "登录失败,请稍后重试";
|
||||
error.value = formatLoginError(err);
|
||||
persistSession(null);
|
||||
throw err;
|
||||
} finally {
|
||||
@@ -376,6 +392,17 @@ async function login(payload: LoginRequest): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function login(payload: LoginRequest): Promise<void> {
|
||||
if (loginPromise) {
|
||||
return loginPromise;
|
||||
}
|
||||
|
||||
loginPromise = performLogin({ ...payload }).finally(() => {
|
||||
loginPromise = null;
|
||||
});
|
||||
return loginPromise;
|
||||
}
|
||||
|
||||
async function enterPreview(): Promise<void> {
|
||||
error.value = null;
|
||||
persistSession({
|
||||
|
||||
Reference in New Issue
Block a user