mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-22 10:25:20 -06:00
2829fe107c
* feat(ui): replace Overview page with Connection settings and sidebar attention chips * feat(ui): open new-session drafts on the chat start screen hero * refactor(ui): drop old overview-hints paths after rename * chore(i18n): sync control-ui locale bundles for connection/palette/attention keys * test(ui): expect attention slot above the sidebar update card * chore(i18n): re-sync locale bundles after rebase onto main * test(ui): stub sidebar-attention RPCs in app-sidebar unit tests * test(ui): use a non-secret-shaped token fixture in connection view test * refactor(ui): destructure gateway connection in connection settings draft * refactor(ui): keep connection settings code out of secret-scanner shapes * test(ui): expect Connection in the settings Connections group * improve(ui): idle-refresh sidebar attention chips for always-visible windows * chore(i18n): refresh raw-copy baseline after rebase * docs(ui): note the unknown-route chat fallback covers retired paths
121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
// Connection-failure hint classification shared by the login gate.
|
|
import {
|
|
ConnectErrorDetailCodes,
|
|
readConnectPairingRequiredMessage,
|
|
} from "../../../packages/gateway-protocol/src/connect-error-details.js";
|
|
import { normalizeLowercaseStringOrEmpty } from "../lib/string-coerce.ts";
|
|
|
|
const AUTH_REQUIRED_CODES = new Set<string>([
|
|
ConnectErrorDetailCodes.AUTH_REQUIRED,
|
|
ConnectErrorDetailCodes.AUTH_TOKEN_MISSING,
|
|
ConnectErrorDetailCodes.AUTH_PASSWORD_MISSING,
|
|
ConnectErrorDetailCodes.AUTH_TOKEN_NOT_CONFIGURED,
|
|
ConnectErrorDetailCodes.AUTH_PASSWORD_NOT_CONFIGURED,
|
|
]);
|
|
|
|
const AUTH_FAILURE_CODES = new Set<string>([
|
|
...AUTH_REQUIRED_CODES,
|
|
ConnectErrorDetailCodes.AUTH_UNAUTHORIZED,
|
|
ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH,
|
|
ConnectErrorDetailCodes.AUTH_PASSWORD_MISMATCH,
|
|
ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH,
|
|
ConnectErrorDetailCodes.AUTH_RATE_LIMITED,
|
|
ConnectErrorDetailCodes.AUTH_TAILSCALE_IDENTITY_MISSING,
|
|
ConnectErrorDetailCodes.AUTH_TAILSCALE_PROXY_MISSING,
|
|
ConnectErrorDetailCodes.AUTH_TAILSCALE_WHOIS_FAILED,
|
|
ConnectErrorDetailCodes.AUTH_TAILSCALE_IDENTITY_MISMATCH,
|
|
]);
|
|
|
|
const BROWSER_WEBSOCKET_SECURITY_ERROR_CODE = "BROWSER_WEBSOCKET_SECURITY_ERROR";
|
|
|
|
const INSECURE_CONTEXT_CODES = new Set<string>([
|
|
BROWSER_WEBSOCKET_SECURITY_ERROR_CODE,
|
|
ConnectErrorDetailCodes.CONTROL_UI_DEVICE_IDENTITY_REQUIRED,
|
|
ConnectErrorDetailCodes.DEVICE_IDENTITY_REQUIRED,
|
|
]);
|
|
|
|
type AuthHintKind = "required" | "failed";
|
|
|
|
type PairingHint =
|
|
| {
|
|
kind: "pairing-required";
|
|
requestId: string | null;
|
|
}
|
|
| {
|
|
kind: "scope-upgrade-pending" | "role-upgrade-pending" | "metadata-upgrade-pending";
|
|
requestId: string | null;
|
|
};
|
|
|
|
export function resolvePairingHint(
|
|
connected: boolean,
|
|
lastError: string | null,
|
|
lastErrorCode?: string | null,
|
|
): PairingHint | null {
|
|
if (connected || !lastError) {
|
|
return null;
|
|
}
|
|
const pairing = readConnectPairingRequiredMessage(lastError);
|
|
if (pairing) {
|
|
return {
|
|
kind:
|
|
pairing.reason === "scope-upgrade"
|
|
? "scope-upgrade-pending"
|
|
: pairing.reason === "role-upgrade"
|
|
? "role-upgrade-pending"
|
|
: pairing.reason === "metadata-upgrade"
|
|
? "metadata-upgrade-pending"
|
|
: "pairing-required",
|
|
requestId: pairing.requestId ?? null,
|
|
};
|
|
}
|
|
if (lastErrorCode === ConnectErrorDetailCodes.PAIRING_REQUIRED) {
|
|
return { kind: "pairing-required", requestId: null };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Return the connection auth hint to show, if any.
|
|
*
|
|
* Keep fallback string matching narrow so generic "connect failed" close reasons
|
|
* do not get misclassified as token/password problems.
|
|
*/
|
|
export function resolveAuthHintKind(params: {
|
|
connected: boolean;
|
|
lastError: string | null;
|
|
lastErrorCode?: string | null;
|
|
hasToken: boolean;
|
|
hasPassword: boolean;
|
|
}): AuthHintKind | null {
|
|
if (params.connected || !params.lastError) {
|
|
return null;
|
|
}
|
|
if (params.lastErrorCode) {
|
|
if (!AUTH_FAILURE_CODES.has(params.lastErrorCode)) {
|
|
return null;
|
|
}
|
|
return AUTH_REQUIRED_CODES.has(params.lastErrorCode) ? "required" : "failed";
|
|
}
|
|
|
|
const lower = normalizeLowercaseStringOrEmpty(params.lastError);
|
|
if (!lower.includes("unauthorized")) {
|
|
return null;
|
|
}
|
|
return !params.hasToken && !params.hasPassword ? "required" : "failed";
|
|
}
|
|
|
|
export function shouldShowInsecureContextHint(
|
|
connected: boolean,
|
|
lastError: string | null,
|
|
lastErrorCode?: string | null,
|
|
): boolean {
|
|
if (connected || !lastError) {
|
|
return false;
|
|
}
|
|
if (lastErrorCode) {
|
|
return INSECURE_CONTEXT_CODES.has(lastErrorCode);
|
|
}
|
|
const lower = normalizeLowercaseStringOrEmpty(lastError);
|
|
return lower.includes("secure context") || lower.includes("device identity required");
|
|
}
|