mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(ui): land #28608 from @KimGLee
Landed from contributor PR #28608 by @KimGLee. Co-authored-by: Kim <150593189+KimGLee@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { GATEWAY_EVENT_UPDATE_AVAILABLE } from "../../../src/gateway/events.js";
|
||||
import { ConnectErrorDetailCodes } from "../../../src/gateway/protocol/connect-error-details.js";
|
||||
import { connectGateway, resolveControlUiClientVersion } from "./app-gateway.ts";
|
||||
|
||||
type GatewayClientMock = {
|
||||
@@ -209,6 +210,69 @@ describe("connectGateway", () => {
|
||||
expect(host.lastErrorCode).toBeNull();
|
||||
});
|
||||
|
||||
it("maps generic fetch-failed auth errors to actionable token mismatch message", () => {
|
||||
const host = createHost();
|
||||
|
||||
connectGateway(host);
|
||||
const client = gatewayClientInstances[0];
|
||||
expect(client).toBeDefined();
|
||||
|
||||
client.emitClose({
|
||||
code: 4008,
|
||||
reason: "connect failed",
|
||||
error: {
|
||||
code: "INVALID_REQUEST",
|
||||
message: "Fetch failed",
|
||||
details: { code: ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH },
|
||||
},
|
||||
});
|
||||
|
||||
expect(host.lastErrorCode).toBe(ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH);
|
||||
expect(host.lastError).toContain("gateway token mismatch");
|
||||
});
|
||||
|
||||
it("maps TypeError fetch failures to actionable auth rate-limit guidance", () => {
|
||||
const host = createHost();
|
||||
|
||||
connectGateway(host);
|
||||
const client = gatewayClientInstances[0];
|
||||
expect(client).toBeDefined();
|
||||
|
||||
client.emitClose({
|
||||
code: 4008,
|
||||
reason: "connect failed",
|
||||
error: {
|
||||
code: "INVALID_REQUEST",
|
||||
message: "TypeError: Failed to fetch",
|
||||
details: { code: ConnectErrorDetailCodes.AUTH_RATE_LIMITED },
|
||||
},
|
||||
});
|
||||
|
||||
expect(host.lastErrorCode).toBe(ConnectErrorDetailCodes.AUTH_RATE_LIMITED);
|
||||
expect(host.lastError).toContain("too many failed authentication attempts");
|
||||
});
|
||||
|
||||
it("preserves specific close errors even when auth detail codes are present", () => {
|
||||
const host = createHost();
|
||||
|
||||
connectGateway(host);
|
||||
const client = gatewayClientInstances[0];
|
||||
expect(client).toBeDefined();
|
||||
|
||||
client.emitClose({
|
||||
code: 4008,
|
||||
reason: "connect failed",
|
||||
error: {
|
||||
code: "INVALID_REQUEST",
|
||||
message: "Failed to fetch gateway metadata from ws://127.0.0.1:18789",
|
||||
details: { code: ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH },
|
||||
},
|
||||
});
|
||||
|
||||
expect(host.lastErrorCode).toBe(ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH);
|
||||
expect(host.lastError).toBe("Failed to fetch gateway metadata from ws://127.0.0.1:18789");
|
||||
});
|
||||
|
||||
it("prefers structured connect errors over close reason", () => {
|
||||
const host = createHost();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
GATEWAY_EVENT_UPDATE_AVAILABLE,
|
||||
type GatewayUpdateAvailableEventPayload,
|
||||
} from "../../../src/gateway/events.js";
|
||||
import { ConnectErrorDetailCodes } from "../../../src/gateway/protocol/connect-error-details.js";
|
||||
import { CHAT_SESSIONS_ACTIVE_MINUTES, flushChatQueueForEvent } from "./app-chat.ts";
|
||||
import type { EventLogEntry } from "./app-events.ts";
|
||||
import {
|
||||
@@ -43,6 +44,24 @@ import type {
|
||||
UpdateAvailable,
|
||||
} from "./types.ts";
|
||||
|
||||
function isGenericBrowserFetchFailure(message: string): boolean {
|
||||
return /^(?:typeerror:\s*)?(?:fetch failed|failed to fetch)$/i.test(message.trim());
|
||||
}
|
||||
|
||||
function formatAuthCloseErrorMessage(code: string | null, fallback: string): string {
|
||||
const resolvedCode = code ?? "";
|
||||
if (resolvedCode === ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH) {
|
||||
return "unauthorized: gateway token mismatch (open dashboard URL with current token)";
|
||||
}
|
||||
if (resolvedCode === ConnectErrorDetailCodes.AUTH_RATE_LIMITED) {
|
||||
return "unauthorized: too many failed authentication attempts (retry later)";
|
||||
}
|
||||
if (resolvedCode === ConnectErrorDetailCodes.AUTH_UNAUTHORIZED) {
|
||||
return "unauthorized: authentication failed";
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
type GatewayHost = {
|
||||
settings: UiSettings;
|
||||
password: string;
|
||||
@@ -218,7 +237,10 @@ export function connectGateway(host: GatewayHost) {
|
||||
(typeof error?.code === "string" ? error.code : null);
|
||||
if (code !== 1012) {
|
||||
if (error?.message) {
|
||||
host.lastError = error.message;
|
||||
host.lastError =
|
||||
host.lastErrorCode && isGenericBrowserFetchFailure(error.message)
|
||||
? formatAuthCloseErrorMessage(host.lastErrorCode, error.message)
|
||||
: error.message;
|
||||
return;
|
||||
}
|
||||
host.lastError = `disconnected (${code}): ${reason || "no reason"}`;
|
||||
|
||||
Reference in New Issue
Block a user