/** Bounded protocol calls for isolated browser fixtures, never product code. */ export function createCdpCalls(socket,{timeoutMs=30000,onEvent=()=>{},setTimer=setTimeout,clearTimer=clearTimeout}={}){ let serial=0,failure;const pending=new Map(); const fail=error=>{failure??=error;for(const row of pending.values()){clearTimer(row.timer);row.reject(failure);}pending.clear();}; socket.addEventListener('close',()=>fail(Error('CDP connection closed'))); socket.addEventListener('error',()=>fail(Error('CDP connection failed'))); socket.addEventListener('message',event=>{ try{ const message=JSON.parse(event.data); if(!message.id){onEvent(message);if(['Inspector.targetCrashed','Inspector.detached','Target.targetCrashed','Page.javascriptDialogOpening'].includes(message.method))fail(Error('CDP target unavailable: '+message.method+' '+JSON.stringify(message.params??{})));return;} const row=pending.get(message.id);if(!row)return;pending.delete(message.id);clearTimer(row.timer);message.error?row.reject(Error(JSON.stringify(message.error))):row.resolve(message.result); }catch(error){fail(error);} }); return(method,params={})=>new Promise((resolve,reject)=>{ if(failure){reject(failure);return;} const id=++serial,row={resolve,reject,timer:undefined};pending.set(id,row); row.timer=setTimer(()=>fail(Error(`CDP ${method} timed out after ${timeoutMs}ms`)),timeoutMs); try{socket.send(JSON.stringify({id,method,params}));}catch(error){fail(error);} }); }