/** * Form Submission Learner * * Learns API patterns from form submissions to enable direct POST requests * without browser rendering on future submissions. * * Progressive optimization: * - First submission: Use browser, capture POST request * - Learn: Field mapping, CSRF patterns, validation rules * - Future submissions: Direct POST (~10-25x faster) * * Part of the "Browser Minimizer" philosophy - progressively eliminate * the need for rendering by learning the underlying API patterns. */ import type { Page } from 'playwright'; import { ApiPatternRegistry } from './api-pattern-learner.js'; /** * Form field detected in HTML */ export interface FormField { name: string; type: string; required: boolean; value?: string; selector: string; } /** * File upload field detected in HTML */ export interface FileField { name: string; required: boolean; accept?: string; multiple: boolean; selector: string; } /** * Detected form structure */ export interface DetectedForm { action?: string; method: string; encoding?: string; fields: FormField[]; fileFields: FileField[]; submitSelector: string; csrfFields: FormField[]; } /** * File upload data */ export interface FileUploadData { /** File path on local filesystem, OR */ filePath?: string; /** File contents as Buffer, OR */ buffer?: Buffer; /** File contents as base64 string */ base64?: string; /** Original filename */ filename: string; /** MIME type */ mimeType?: string; } /** * Form submission data provided by user */ export interface FormSubmissionData { url: string; fields: Record; files?: Record; formSelector?: string; isMultiStep?: boolean; stepNumber?: number; previousStepData?: Record; } /** * Result of a form submission */ export interface FormSubmissionResult { success: boolean; method: 'browser' | 'api'; responseUrl?: string; responseData?: any; duration: number; learned: boolean; error?: string; otpRequired?: boolean; otpChallenge?: OTPChallenge; } /** * OTP/2FA challenge detected during submission */ export interface OTPChallenge { type: 'sms' | 'email' | 'totp' | 'authenticator' | 'backup_code' | 'unknown'; message?: string; destination?: string; expiresIn?: number; retryAfter?: number; endpoint: string; codeLength?: number; } /** * Callback for prompting user for OTP code * Returns the OTP code entered by the user, or null if cancelled */ export type OTPPromptCallback = (challenge: OTPChallenge) => Promise; /** * Rate limit information for a domain */ export interface RateLimitInfo { /** Domain being rate limited */ domain: string; /** Rate limit quota (requests per period) */ limit?: number; /** Remaining requests in current period */ remaining?: number; /** Timestamp when rate limit resets (Unix timestamp in ms) */ resetAt?: number; /** Retry after N seconds (from Retry-After header) */ retryAfterSeconds?: number; /** Last time we hit a rate limit (for tracking) */ lastRateLimitTime?: number; /** Number of times we've been rate limited */ rateLimitCount: number; } /** * OAuth flow information */ export interface OAuthFlowInfo { /** OAuth flow type */ flowType: 'authorization_code' | 'implicit' | 'pkce'; /** Authorization endpoint URL */ authEndpoint: string; /** Token endpoint URL (for authorization code flow) */ tokenEndpoint?: string; /** Client ID */ clientId: string; /** Redirect URI */ redirectUri: string; /** Requested scopes */ scopes: string[]; /** State parameter (CSRF protection) */ state?: string; /** PKCE code challenge (if using PKCE) */ codeChallenge?: string; /** PKCE code challenge method */ codeChallengeMethod?: 'S256' | 'plain'; /** Response type (code, token, id_token) */ responseType: string; } /** * Learned OAuth flow pattern */ export interface LearnedOAuthFlow { id: string; domain: string; /** URL that triggers OAuth flow (e.g., /login, /connect) */ triggerUrl: string; /** OAuth provider (e.g., 'github', 'google', 'auth0') */ provider?: string; /** Flow information */ flow: OAuthFlowInfo; /** Whether this flow uses PKCE */ usesPKCE: boolean; /** Learned at timestamp */ learnedAt: number; /** Times this flow was used */ timesUsed: number; /** Success rate */ successRate: number; } /** * WebSocket message captured during form submission */ export interface WebSocketMessage { /** Event name (for Socket.IO-style APIs) or message type */ event?: string; /** Message payload */ payload: any; /** Timestamp when message was sent */ timestamp: number; /** WebSocket URL */ url: string; /** Direction: 'send' (client → server) or 'receive' (server → client) */ direction: 'send' | 'receive'; } /** * Learned WebSocket emission pattern for form submission */ export interface WebSocketPattern { /** WebSocket server URL */ wsUrl: string; /** Event name (e.g., 'form:submit', 'message', 'update') */ eventName?: string; /** Payload structure/template */ payloadTemplate: Record; /** Field mapping (formField → ws payload field) */ fieldMapping: Record; /** Whether this uses Socket.IO or raw WebSocket */ protocol: 'socket.io' | 'websocket' | 'sockjs'; /** Response event name to listen for (if any) */ responseEvent?: string; /** Expected response fields indicating success */ successFields?: string[]; } /** * Learned server action pattern (Next.js/Remix) */ export interface ServerActionPattern { /** Framework type */ framework: 'nextjs' | 'remix'; /** Action ID (Next.js only - from Next-Action header) */ actionId?: string; /** Action name (Remix only - from _action field) */ actionName?: string; /** Whether action ID is stable across builds (usually false for Next.js) */ isStableId: boolean; /** Field mapping (form field → server action param) */ fieldMapping: Record; /** Response type indicator */ responseType: 'redirect' | 'json' | 'flight-stream'; /** Expected redirect pattern (if responseType === 'redirect') */ redirectPattern?: string; } /** * Dynamic field that needs to be fetched before each submission */ export interface DynamicField { fieldName: string; valueType: 'user_id' | 'session_id' | 'nonce' | 'timestamp' | 'uuid' | 'csrf_token' | 'custom'; extractionStrategy: { type: 'dom' | 'api' | 'cookie' | 'url_param' | 'localStorage' | 'computed'; selector?: string; apiEndpoint?: string; cookieName?: string; paramName?: string; storageKey?: string; computeFn?: string; }; pattern?: RegExp; } /** * Learned form pattern that can be replayed */ export interface LearnedFormPattern { id: string; domain: string; formUrl: string; formSelector?: string; apiEndpoint: string; method: string; patternType?: 'rest' | 'graphql' | 'json-rpc' | 'websocket' | 'server-action'; encoding?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'application/json'; fieldMapping: Record; fileFields?: FileField[]; graphqlMutation?: { mutationName: string; query: string; variableMapping: Record; }; jsonRpcMethod?: { methodName: string; paramsMapping: Record; version: '1.0' | '2.0'; }; websocketPattern?: WebSocketPattern; serverActionPattern?: ServerActionPattern; dynamicFields: DynamicField[]; csrfTokenField?: string; csrfTokenSource?: 'meta' | 'hidden' | 'cookie'; csrfTokenSelector?: string; requiredFields: string[]; successIndicators: { statusCodes: number[]; responseFields?: string[]; }; requiresOTP?: boolean; otpPattern?: { detectionIndicators: { statusCodes?: number[]; responseFields?: string[]; responseValues?: Record; }; otpEndpoint: string; otpFieldName: string; otpMethod: 'POST' | 'PUT'; otpType: 'sms' | 'email' | 'totp' | 'authenticator' | 'backup_code' | 'unknown'; }; learnedAt: number; timesUsed: number; successRate: number; lastUsed?: number; } /** * Options for form submission */ export interface SubmitFormOptions { timeout?: number; waitForNavigation?: boolean; csrfToken?: string; onOTPRequired?: OTPPromptCallback; autoRetryOnOTP?: boolean; } export declare class FormSubmissionLearner { private patternRegistry; private formPatterns; private rateLimits; private oauthFlows; constructor(patternRegistry: ApiPatternRegistry); /** * Submit a form with progressive optimization * * First attempt: Check for learned pattern * Fallback: Use browser and learn the pattern */ submitForm(data: FormSubmissionData, page: Page, options?: SubmitFormOptions): Promise; /** * Submit form via learned API pattern */ private submitViaApi; /** * Submit multipart/form-data request with file uploads */ private submitMultipartForm; /** * Detect OTP challenge from API response */ private detectOTPChallenge; /** * Extract OTP type from response data */ private extractOTPType; /** * Submit OTP code to verification endpoint */ private submitOTP; /** * Learn OTP pattern from challenge and add it to form pattern */ private learnOTPPattern; /** * Enable WebSocket capture via Chrome DevTools Protocol */ private enableWebSocketCapture; /** * Analyze WebSocket messages to learn form submission pattern */ private analyzeWebSocketPattern; /** * Detect WebSocket protocol (Socket.IO, raw WebSocket, SockJS) */ private detectWebSocketProtocol; /** * Submit form via WebSocket using learned pattern * * Note: This is a basic implementation. For production use with Socket.IO or * other WebSocket libraries, you may need to install additional dependencies. */ private submitViaWebSocket; /** * Submit form via browser and learn the API pattern */ private submitViaBrowserAndLearn; /** * Detect form structure on the page */ private detectForm; /** * Analyze form submission to learn the API pattern */ private analyzeFormSubmission; /** * Extract field mapping from form and request */ private extractFieldMapping; /** * Detect CSRF token handling */ private detectCsrfHandling; /** * Extract CSRF token for learned pattern */ private extractCsrfToken; /** * Check if response indicates success */ private isSuccessResponse; /** * Find matching learned pattern for a form */ private findMatchingPattern; /** * Get all learned patterns for a domain */ getLearnedPatterns(domain: string): LearnedFormPattern[]; /** * Helper: Try to parse JSON safely */ private tryParseJson; /** * Helper: Convert to camelCase */ private toCamelCase; /** * Helper: Convert to snake_case */ private toSnakeCase; /** * Extract value for a dynamic field */ private extractDynamicFieldValue; /** * Extract value from DOM by fetching page and parsing HTML */ private extractFromDom; /** * Extract value from an API endpoint */ private extractFromApi; /** * Extract value from cookie */ private extractFromCookie; /** * Extract value from URL parameter */ private extractFromUrlParam; /** * Compute value using a function string */ private computeValue; /** * Generate a UUID v4 */ private generateUUID; /** * Detect if a value is dynamic (changes between submissions) */ private detectDynamicValue; /** * Infer extraction strategy for a dynamic field */ private inferExtractionStrategy; /** * Mask sensitive values for logging */ private maskSensitiveValue; /** * Analyze form to detect dynamic fields during learning */ private detectDynamicFields; /** * Detect if a network request is a GraphQL mutation */ private detectGraphQLMutation; /** * Create a GraphQL-specific learned pattern */ private createGraphQLPattern; /** * Detect server action pattern (Next.js Server Actions or Remix Actions) */ private detectServerAction; /** * Create a server action pattern */ private createServerActionPattern; /** * Detect JSON-RPC method call pattern */ private detectJsonRpc; /** * Create a JSON-RPC pattern */ private createJsonRpcPattern; /** * Detect and parse rate limit information from response */ private detectRateLimit; /** * Check if we should wait before making a request due to rate limiting * Returns wait time in milliseconds, or 0 if safe to proceed */ private checkRateLimitWait; /** * Retry a request with exponential backoff after rate limit */ private retryWithBackoff; /** * Update rate limit info after a successful or failed request */ private updateRateLimitInfo; /** * Detect OAuth authorization redirect from URL */ private detectOAuthRedirect; } //# sourceMappingURL=form-submission-learner.d.ts.map