intercept.ts
1 /** 2 * Pipeline step: intercept — declarative XHR interception. 3 */ 4 5 import type { IPage } from '../../types.js'; 6 import { render, normalizeEvaluateSource } from '../template.js'; 7 8 interface InterceptParams { 9 trigger?: string; 10 capture?: string; 11 timeout?: number; 12 select?: string; 13 } 14 15 export async function stepIntercept( 16 page: IPage | null, 17 params: unknown, 18 data: unknown, 19 args: Record<string, unknown>, 20 ): Promise<unknown> { 21 const cfg: InterceptParams = typeof params === 'object' && params !== null ? (params as InterceptParams) : {}; 22 const trigger = cfg.trigger ?? ''; 23 const capturePattern = cfg.capture ?? ''; 24 const timeout = cfg.timeout ?? 8; 25 const selectPath = cfg.select ?? null; 26 27 if (!capturePattern) return data; 28 29 // Step 1: Inject fetch/XHR interceptor BEFORE trigger 30 await page!.installInterceptor(capturePattern); 31 32 // Step 2: Execute the trigger action 33 if (trigger.startsWith('navigate:')) { 34 const url = render(trigger.slice('navigate:'.length), { args, data }); 35 await page!.goto(String(url)); 36 } else if (trigger.startsWith('evaluate:')) { 37 const js = trigger.slice('evaluate:'.length); 38 await page!.evaluate(normalizeEvaluateSource(render(js, { args, data }) as string)); 39 } else if (trigger.startsWith('click:')) { 40 const ref = render(trigger.slice('click:'.length), { args, data }); 41 await page!.click(String(ref).replace(/^@/, '')); 42 } else if (trigger === 'scroll') { 43 await page!.scroll('down'); 44 } 45 46 // Step 3: Wait for network capture (event-driven, not fixed sleep) 47 await page!.waitForCapture(timeout); 48 49 // Step 4: Retrieve captured data 50 const matchingResponses = await page!.getInterceptedRequests(); 51 52 // Step 5: Select from response if specified 53 let result: unknown = matchingResponses.length === 1 ? matchingResponses[0] : 54 matchingResponses.length > 1 ? matchingResponses : data; 55 56 if (selectPath && result) { 57 let current: unknown = result; 58 for (const part of String(selectPath).split('.')) { 59 if (current && typeof current === 'object' && !Array.isArray(current)) { 60 current = (current as Record<string, unknown>)[part]; 61 } else break; 62 } 63 result = current ?? result; 64 } 65 66 return result; 67 }