fix: make login connection state observable
This commit is contained in:
+23
-5
@@ -77,12 +77,32 @@ function normalizeAPIAddress(value: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loginFailureMessage(reason: unknown): string {
|
function loginFailureMessage(reason: unknown): string {
|
||||||
|
if (reason instanceof Error && reason.message === "LINEUP_LOGIN_TIMEOUT") return "连接 AppServer 超时(10 秒)。请确认 Tailscale 已连接,并核对上方显示的服务地址。";
|
||||||
if (reason instanceof DOMException && reason.name === "AbortError") return "连接 AppServer 超时(10 秒)。请确认 Tailscale 已连接,且服务地址和端口可访问。";
|
if (reason instanceof DOMException && reason.name === "AbortError") return "连接 AppServer 超时(10 秒)。请确认 Tailscale 已连接,且服务地址和端口可访问。";
|
||||||
const message = reason instanceof Error ? reason.message : "";
|
const message = reason instanceof Error ? reason.message : "";
|
||||||
if (message === "Load failed" || message === "Failed to fetch") return "无法连接 AppServer。请确认 Tailscale 已连接,并检查地址是否为 http://100.121.118.116:8090。";
|
if (message === "Load failed" || message === "Failed to fetch") return "无法连接 AppServer。请确认 Tailscale 已连接,并检查地址是否为 http://100.121.118.116:8090。";
|
||||||
return message || "登录失败,请稍后重试。";
|
return message || "登录失败,请稍后重试。";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchLogin(endpoint: string, phone: string, code: string): Promise<Response> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
let timeoutID = 0;
|
||||||
|
const timeout = new Promise<never>((_, reject) => {
|
||||||
|
timeoutID = window.setTimeout(() => {
|
||||||
|
controller.abort();
|
||||||
|
reject(new Error("LINEUP_LOGIN_TIMEOUT"));
|
||||||
|
}, 10_000);
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
return await Promise.race([
|
||||||
|
fetch(`${endpoint}/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ phone, code }), signal: controller.signal }),
|
||||||
|
timeout,
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
window.clearTimeout(timeoutID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function appendBubble(role: "human" | "agent", content: string, markdown = false): HTMLElement {
|
function appendBubble(role: "human" | "agent", content: string, markdown = false): HTMLElement {
|
||||||
const row = document.createElement("article"); row.className = `message-row ${role}`;
|
const row = document.createElement("article"); row.className = `message-row ${role}`;
|
||||||
if (role === "agent") { const avatar = document.createElement("span"); avatar.className = "message-avatar"; avatar.textContent = "✦"; row.append(avatar); }
|
if (role === "agent") { const avatar = document.createElement("span"); avatar.className = "message-avatar"; avatar.textContent = "✦"; row.append(avatar); }
|
||||||
@@ -162,19 +182,17 @@ $<HTMLFormElement>("#login-form").addEventListener("submit", async event => {
|
|||||||
|
|
||||||
session.api = endpoint;
|
session.api = endpoint;
|
||||||
localStorage.setItem("lineup.api", session.api);
|
localStorage.setItem("lineup.api", session.api);
|
||||||
error.textContent = "正在连接 AppServer…";
|
error.textContent = `正在连接 ${session.api}…`;
|
||||||
submit.disabled = true;
|
submit.disabled = true;
|
||||||
submit.textContent = "正在进入…";
|
submit.textContent = "正在进入…";
|
||||||
const controller = new AbortController();
|
|
||||||
const timeout = window.setTimeout(() => controller.abort(), 10_000);
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(api("/login"), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ phone, code }), signal: controller.signal });
|
const response = await fetchLogin(session.api, phone, code);
|
||||||
const body = await response.json().catch(() => ({})) as LoginResponse & { error?: string };
|
const body = await response.json().catch(() => ({})) as LoginResponse & { error?: string };
|
||||||
if (!response.ok) throw new Error(body.error || "登录失败");
|
if (!response.ok) throw new Error(body.error || "登录失败");
|
||||||
session = { ...session, uid: body.uid, agentUID: body.agent_uid || session.agentUID, channelID: body.channel_id || session.channelID, channelType: body.channel_type || session.channelType, lastSeq: 0, active: true };
|
session = { ...session, uid: body.uid, agentUID: body.agent_uid || session.agentUID, channelID: body.channel_id || session.channelID, channelType: body.channel_type || session.channelType, lastSeq: 0, active: true };
|
||||||
loginView.hidden = true; chatView.hidden = false; addSystem("已进入与 AI Agent 的对话"); setPresence("正在同步消息…"); void syncLoop();
|
loginView.hidden = true; chatView.hidden = false; addSystem("已进入与 AI Agent 的对话"); setPresence("正在同步消息…"); void syncLoop();
|
||||||
} catch (reason) { error.textContent = loginFailureMessage(reason); }
|
} catch (reason) { error.textContent = loginFailureMessage(reason); }
|
||||||
finally { window.clearTimeout(timeout); submit.disabled = false; submit.textContent = "进入 LineUp"; }
|
finally { submit.disabled = false; submit.textContent = "进入 LineUp"; }
|
||||||
});
|
});
|
||||||
|
|
||||||
$<HTMLFormElement>("#message-form").addEventListener("submit", async event => {
|
$<HTMLFormElement>("#message-form").addEventListener("submit", async event => {
|
||||||
|
|||||||
Reference in New Issue
Block a user