openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
175 lines (174 loc) • 7.07 kB
JavaScript
//#region extensions/browser/src/browser/errors.ts
/**
* Browser domain errors.
*
* Provides HTTP-mappable error classes and stable blocked-policy messages used
* by route handlers, clients, and Gateway proxy code.
*/
/** Stable message for blocked CDP endpoint configuration. */
const BROWSER_ENDPOINT_BLOCKED_MESSAGE = "browser endpoint blocked by policy";
/** Stable message for blocked page navigation targets. */
const BROWSER_NAVIGATION_BLOCKED_MESSAGE = "browser navigation blocked by policy";
/** Stable machine-readable browser error reasons. */
const BROWSER_ERROR_REASONS = {
noDisplayForHeadedProfile: "no_display_for_headed_profile",
navigationBlocked: "navigation_blocked"
};
/** Stable machine-readable codes returned by browser action routes. */
const BROWSER_ACT_ERROR_CODES = {
kindRequired: "ACT_KIND_REQUIRED",
invalidRequest: "ACT_INVALID_REQUEST",
selectorUnsupported: "ACT_SELECTOR_UNSUPPORTED",
evaluateDisabled: "ACT_EVALUATE_DISABLED",
unsupportedForExistingSession: "ACT_EXISTING_SESSION_UNSUPPORTED",
targetIdMismatch: "ACT_TARGET_ID_MISMATCH"
};
const BROWSER_ACT_ERROR_CODE_VALUES = new Set(Object.values(BROWSER_ACT_ERROR_CODES));
function isBrowserActErrorCode(value) {
return typeof value === "string" && BROWSER_ACT_ERROR_CODE_VALUES.has(value);
}
const NO_DISPLAY_HEADLESS_SOURCES = [
"request",
"env",
"profile",
"config",
"default"
];
/** Base browser error carrying an HTTP status code. */
var BrowserError = class extends Error {
constructor(message, status = 500, options) {
super(message, options);
this.name = new.target.name;
this.status = status;
}
};
/**
* Raised when a browser CDP endpoint (the cdpUrl itself) fails the
* configured SSRF policy. Distinct from a blocked navigation target so
* callers see "fix your browser endpoint config" rather than "fix your
* navigation URL".
*/
var BrowserCdpEndpointBlockedError = class extends BrowserError {
constructor(options) {
super(BROWSER_ENDPOINT_BLOCKED_MESSAGE, 400, options);
}
};
/** Validation failure for browser route or config input. */
var BrowserValidationError = class extends BrowserError {
constructor(message, options) {
super(message, 400, options);
}
};
/** Raised when one tab reference matches multiple tabs. */
var BrowserTargetAmbiguousError = class extends BrowserError {
constructor(message = "ambiguous browser tab reference", options) {
super(message, 409, options);
}
};
/** Raised when a requested browser tab cannot be resolved. */
var BrowserTabNotFoundError = class extends BrowserError {
constructor(inputOrMessage, options) {
const input = typeof inputOrMessage === "object" ? inputOrMessage.input?.trim() : inputOrMessage?.trim();
const message = input ? /^\d+$/.test(input) ? `tab not found: browser tab "${input}" not found. Numeric values are not tab targets; use a stable tab id like "t1", a label, or a raw targetId. For positional selection, use "openclaw browser tab select ${input}".` : `tab not found: browser tab "${input}" not found. Use action=tabs and pass suggestedTargetId, tabId, label, or raw targetId.` : "tab not found";
super(message, 404, options);
}
};
/** Raised when a requested browser profile does not exist. */
var BrowserProfileNotFoundError = class extends BrowserError {
constructor(message, options) {
super(message, 404, options);
}
};
/** Raised when a browser config mutation conflicts with existing state. */
var BrowserConflictError = class extends BrowserError {
constructor(message, options) {
super(message, 409, options);
}
};
/** Raised when a browser profile cannot be reset by the current driver. */
var BrowserResetUnsupportedError = class extends BrowserError {
constructor(message, options) {
super(message, 400, options);
}
};
/** Raised when a profile is configured but not currently reachable. */
var BrowserProfileUnavailableError = class extends BrowserError {
constructor(message, options) {
super(message, 409, options);
this.metadata = options?.metadata;
}
};
/** Raised when browser resource allocation, such as CDP ports, is exhausted. */
var BrowserResourceExhaustedError = class extends BrowserError {
constructor(message, options) {
super(message, 507, options);
}
};
/** Map browser-domain errors to HTTP response details. */
function toBrowserErrorResponse(err) {
if (err instanceof BrowserProfileUnavailableError && err.metadata) return {
status: err.status,
message: err.message,
...err.metadata
};
if (err instanceof BrowserError) return {
status: err.status,
message: err.message
};
if (err instanceof Error && err.name === "BlockedBrowserTargetError") return {
status: 409,
message: err.message,
reason: BROWSER_ERROR_REASONS.navigationBlocked
};
if (err instanceof Error && err.name === "SsrFBlockedError") return {
status: 400,
message: BROWSER_NAVIGATION_BLOCKED_MESSAGE,
reason: BROWSER_ERROR_REASONS.navigationBlocked
};
if (err instanceof Error && err.name === "InvalidBrowserNavigationUrlError") return {
status: 400,
message: err.message,
reason: BROWSER_ERROR_REASONS.navigationBlocked
};
return null;
}
function parseNoDisplayDetails(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const details = value;
if (typeof details.profile !== "string" || details.profile.length === 0 || details.requestedHeadless !== false || !NO_DISPLAY_HEADLESS_SOURCES.includes(details.headlessSource) || details.displayPresent !== false) return null;
return {
profile: details.profile,
requestedHeadless: false,
headlessSource: details.headlessSource,
displayPresent: false
};
}
/** Parse only the closed browser error metadata contract from a route payload. */
function parseBrowserErrorPayload(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const body = value;
if (typeof body.error !== "string" || body.error.length === 0) return null;
const code = isBrowserActErrorCode(body.code) ? body.code : void 0;
const unrecognizedCode = body.unrecognizedCode === true || body.code !== void 0 && !code ? true : void 0;
const actionCode = code ? { code } : unrecognizedCode ? { unrecognizedCode: true } : {};
if (body.reason === BROWSER_ERROR_REASONS.navigationBlocked) return {
error: body.error,
...actionCode,
reason: body.reason
};
if (body.reason === BROWSER_ERROR_REASONS.noDisplayForHeadedProfile) {
const details = parseNoDisplayDetails(body.details);
if (details) return {
error: body.error,
...actionCode,
reason: body.reason,
details
};
}
return {
error: body.error,
...actionCode
};
}
//#endregion
export { BrowserError as a, BrowserResetUnsupportedError as c, BrowserTargetAmbiguousError as d, BrowserValidationError as f, BrowserConflictError as i, BrowserResourceExhaustedError as l, toBrowserErrorResponse as m, BROWSER_ERROR_REASONS as n, BrowserProfileNotFoundError as o, parseBrowserErrorPayload as p, BrowserCdpEndpointBlockedError as r, BrowserProfileUnavailableError as s, BROWSER_ACT_ERROR_CODES as t, BrowserTabNotFoundError as u };