Steel / api /src /services /cdp /errors /launch-errors.ts
supernovagateway's picture
Upload folder using huggingface_hub
fb38ec5 verified
/**
* Custom error classes for categorizing CDPService launch failures
* These allow for slightly more intelligent error handling and recovery strategies
*/
export enum LaunchErrorType {
TIMEOUT = "TIMEOUT",
CONFIGURATION = "CONFIGURATION",
RESOURCE = "RESOURCE",
SYSTEM = "SYSTEM",
NETWORK = "NETWORK",
FINGERPRINT = "FINGERPRINT",
PLUGIN = "PLUGIN",
CLEANUP = "CLEANUP",
BROWSER_PROCESS = "BROWSER_PROCESS",
SESSION_CONTEXT = "SESSION_CONTEXT",
}
export enum BrowserProcessState {
PAGE_REFRESH = "page_refresh",
LAUNCH_FAILED = "launch_failed",
PAGE_ACCESS = "page_access",
TARGET_SETUP = "target_setup",
UNKNOWN = "unknown",
}
export enum PluginName {
LAUNCH_MUTATOR = "launch_mutator",
PLUGIN_MANAGER = "plugin_manager",
UNKNOWN = "unknown",
}
export enum PluginOperation {
PRE_LAUNCH_HOOK = "pre-launch hook",
BROWSER_LAUNCH_NOTIFICATION = "browser launch notification",
LAUNCH = "launch",
}
export enum CleanupType {
PRE_LAUNCH_FILE_CLEANUP = "pre-launch file cleanup",
GENERAL = "general",
}
export enum SessionContextType {
CONTEXT_INJECTION = "context injection",
}
export enum FingerprintStage {
GENERATION = "generation",
INJECTION = "injection",
}
export enum ResourceType {
EXTENSIONS = "extensions",
FILE = "file",
}
export enum NetworkOperation {
WEBSOCKET_SETUP = "websocket setup",
PORT_BINDING = "port binding",
NETWORK_SETUP = "network setup",
}
export enum SystemOperation {
FILE_ACCESS = "file access",
UNKNOWN_OPERATION = "unknown operation",
}
export enum ConfigurationField {
DIMENSIONS = "dimensions",
TIMEZONE = "timezone",
PROXY_URL = "proxyUrl",
}
export enum ErrorCategories {}
export abstract class BaseLaunchError extends Error {
public readonly type: LaunchErrorType;
public readonly isRetryable: boolean;
public readonly context?: Record<string, any>;
constructor(
type: LaunchErrorType,
message: string,
isRetryable: boolean = false,
context?: Record<string, any>,
cause?: unknown,
) {
super(message, { cause });
this.name = this.constructor.name;
this.type = type;
this.isRetryable = isRetryable;
this.context = context;
// Maintains proper stack trace for where error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
/**
* Thrown when browser launch times out after 30 seconds
* This is typically retryable as it may be a temporary resource issue
*/
export class LaunchTimeoutError extends BaseLaunchError {
constructor(timeoutMs: number = 30000, cause?: unknown) {
super(
LaunchErrorType.TIMEOUT,
`Browser launch timeout after ${timeoutMs}ms`,
true,
{
timeoutMs,
},
cause,
);
}
}
/**
* Thrown when configuration parameters are invalid or incompatible
* These are typically not retryable without fixing the configuration
*/
export class ConfigurationError extends BaseLaunchError {
constructor(
message: string,
configField?: ConfigurationField,
configValue?: any,
cause?: unknown,
) {
super(
LaunchErrorType.CONFIGURATION,
`Configuration error: ${message}`,
false,
{
configField,
configValue,
},
cause,
);
}
}
/**
* Thrown when required system resources are unavailable
* Some may be retryable (temporary disk space), others not (missing Chrome binary)
*/
export class ResourceError extends BaseLaunchError {
constructor(
message: string,
resourceType: ResourceType,
isRetryable: boolean = false,
cause?: unknown,
) {
super(
LaunchErrorType.RESOURCE,
`Resource error: ${message}`,
isRetryable,
{ resourceType },
cause,
);
}
}
/**
* Thrown when system-level operations fail
* Usually retryable as they may be temporary system issues
*/
export class SystemError extends BaseLaunchError {
constructor(message: string, operation: SystemOperation, originalError?: Error) {
super(
LaunchErrorType.SYSTEM,
`System error during ${operation}: ${message}`,
true,
{
operation,
originalError: originalError?.message,
},
originalError,
);
}
}
/**
* Thrown when network-related operations fail (proxy, WebSocket setup)
* Usually retryable as network issues are often temporary
*/
export class NetworkError extends BaseLaunchError {
constructor(message: string, networkOperation: NetworkOperation, cause?: unknown) {
super(
LaunchErrorType.NETWORK,
`Network error during ${networkOperation}: ${message}`,
true,
{
networkOperation,
},
cause,
);
}
}
/**
* Thrown when fingerprint generation or injection fails
* Usually retryable, can fall back to no fingerprint
*/
export class FingerprintError extends BaseLaunchError {
constructor(message: string, stage: FingerprintStage, cause?: unknown) {
super(
LaunchErrorType.FINGERPRINT,
`Fingerprint error during ${stage}: ${message}`,
true,
{
stage,
},
cause,
);
}
}
/**
* Thrown when plugin operations fail during launch
* May or may not be retryable depending on the plugin
*/
export class PluginError extends BaseLaunchError {
constructor(
message: string,
pluginName: PluginName,
operation: PluginOperation,
isRetryable: boolean = true,
cause?: unknown,
) {
super(
LaunchErrorType.PLUGIN,
`Plugin error in ${pluginName} during ${operation}: ${message}`,
isRetryable,
{
pluginName,
operation,
},
cause,
);
}
}
/**
* Thrown when file cleanup operations fail
* Usually retryable and non-critical to browser launch
*/
export class CleanupError extends BaseLaunchError {
constructor(message: string, cleanupType: CleanupType, cause?: unknown) {
super(
LaunchErrorType.CLEANUP,
`Cleanup error during ${cleanupType}: ${message}`,
true,
{
cleanupType,
},
cause,
);
}
}
/**
* Thrown when the browser process fails to start or crashes immediately
* Usually retryable as it may be a temporary issue
*/
export class BrowserProcessError extends BaseLaunchError {
constructor(
message: string,
processState: BrowserProcessState,
cause?: unknown,
exitCode?: number,
) {
super(
LaunchErrorType.BROWSER_PROCESS,
`Browser process error (${processState}): ${message}`,
true,
{
processState,
exitCode,
},
cause,
);
}
}
/**
* Thrown when session context injection fails
* Usually retryable, can fall back to launching without context
*/
export class SessionContextError extends BaseLaunchError {
constructor(message: string, contextType: SessionContextType, cause?: unknown) {
super(
LaunchErrorType.SESSION_CONTEXT,
`Session context error with ${contextType}: ${message}`,
true,
{
contextType,
},
cause,
);
}
}
/**
* Utility function to categorize unknown errors
*/
export function categorizeError(error: unknown, context?: string): BaseLaunchError {
if (error instanceof BaseLaunchError) {
return error;
}
const errorMessage = error instanceof Error ? error.message : String(error);
const errorStack = error instanceof Error ? error.stack : undefined;
// Analyze error message patterns to categorize
const lowerMessage = errorMessage.toLowerCase();
if (lowerMessage.includes("timeout") || lowerMessage.includes("timed out")) {
return new LaunchTimeoutError();
}
if (
lowerMessage.includes("enoent") ||
lowerMessage.includes("not found") ||
lowerMessage.includes("no such file")
) {
return new ResourceError(errorMessage, ResourceType.FILE, false);
}
if (lowerMessage.includes("eacces") || lowerMessage.includes("permission denied")) {
return new SystemError(
errorMessage,
context ? SystemOperation.UNKNOWN_OPERATION : SystemOperation.FILE_ACCESS,
);
}
if (lowerMessage.includes("eaddrinuse") || lowerMessage.includes("address already in use")) {
return new NetworkError(errorMessage, NetworkOperation.PORT_BINDING);
}
if (lowerMessage.includes("proxy") || lowerMessage.includes("websocket")) {
return new NetworkError(
errorMessage,
context ? NetworkOperation.NETWORK_SETUP : NetworkOperation.NETWORK_SETUP,
);
}
if (lowerMessage.includes("fingerprint")) {
return new FingerprintError(errorMessage, FingerprintStage.GENERATION);
}
if (lowerMessage.includes("plugin")) {
return new PluginError(
errorMessage,
PluginName.UNKNOWN,
context ? PluginOperation.LAUNCH : PluginOperation.LAUNCH,
);
}
if (lowerMessage.includes("cleanup") || lowerMessage.includes("clean")) {
return new CleanupError(errorMessage, context ? CleanupType.GENERAL : CleanupType.GENERAL);
}
if (
lowerMessage.includes("chrome") ||
lowerMessage.includes("browser") ||
lowerMessage.includes("process")
) {
return new BrowserProcessError(errorMessage, BrowserProcessState.UNKNOWN);
}
// Default to system error for unrecognized errors
return new SystemError(
errorMessage,
context ? SystemOperation.UNKNOWN_OPERATION : SystemOperation.UNKNOWN_OPERATION,
error instanceof Error ? error : undefined,
);
}