import {scenarios} from './scenarios.js'; import {boundedFetch,loadReference} from './scalar-config.js'; const $=id=>document.getElementById(id); let controller=null,generation=0,lastResult=null; function download(name,text,type='application/json'){ const href=URL.createObjectURL(new Blob([text],{type}));const a=document.createElement('a');a.href=href;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(href),1000); } function option(value,text){const o=document.createElement('option');o.value=value;o.textContent=text;return o;} scenarios.forEach((s,i)=>$('scenario').append(option(String(i),s.title))); function selected(){return scenarios[Number($('scenario').value)];} function variant(){return selected().variants[Number($('variant').value)];} function cancel(){generation++;controller?.abort();controller=null;$('send').disabled=false;$('cancel').disabled=true;} function clearResult(){lastResult=null;$('download').disabled=true;$('status').textContent='Готово к отправке';$('status').dataset.state='';$('http').textContent='—';$('duration').textContent='—';$('response').textContent='Нет ответа';$('headers').textContent='—';$('result-help').textContent='Ответ появится после отправки. Здесь не показана заранее сохранённая запись.';} function updateVariant(){ cancel();clearResult();const v=variant(); $('why').textContent=selected().why;$('learn').textContent=selected().learn; $('method').textContent=v.method;$('endpoint').textContent='api/'+v.path; $('query-panel').hidden=!v.query;$('query').value=JSON.stringify(v.query??{},null,2); $('body-panel').hidden=!['POST','PATCH'].includes(v.method);$('body').value=v.raw??JSON.stringify(v.body??{},null,2); $('token-panel').hidden=selected().id!=='auth';$('token').value=v.token??''; $('file-panel').hidden=selected().id!=='upload';$('file').value=''; $('body-type').textContent='Content-Type: '+(v.contentType??'application/json');$('expected').textContent=String(v.expect); $('input-note').textContent='';updateCurl(); } function updateScenario(){const s=selected();$('variant').replaceChildren(...s.variants.map((v,i)=>option(String(i),v.title)));updateVariant();} function requestData(){ const v=variant();const url=new URL('./api/'+v.path,location.href); if(v.query){let q;try{q=JSON.parse($('query').value);}catch{throw new Error('Параметры URL должны быть JSON-объектом.');} if(!q||Array.isArray(q)||typeof q!=='object')throw new Error('Параметры URL должны быть объектом.'); for(const [key,value]of Object.entries(q)){if(!['string','number','boolean'].includes(typeof value))throw new Error('Значения параметров должны быть строками или числами.');url.searchParams.set(key,String(value));}} const headers={Accept:'application/json'};let body; if(['POST','PATCH'].includes(v.method)){body=$('body').value;headers['Content-Type']=v.contentType??'application/json';} if(selected().id==='auth'&&$('token').value)headers.Authorization='Bearer '+$('token').value; return {url:url.href,method:v.method,headers,body}; } const shellQuote=value=>"'"+value.replaceAll("'","'\\''")+"'"; function curlOf(r){return ['curl -i -X '+r.method+' '+shellQuote(r.url),...Object.entries(r.headers).map(([k,v])=>'-H '+shellQuote(k+': '+v)),...(r.body!==undefined?['--data-binary '+shellQuote(r.body)]:[])].join(' \\\n ');} function updateCurl(){try{$('curl').textContent=curlOf(requestData());}catch(e){$('curl').textContent=e.message;}} $('scenario').addEventListener('change',updateScenario);$('variant').addEventListener('change',updateVariant);$('reset').addEventListener('click',updateVariant); for(const id of ['query','body','token'])$(id).addEventListener('input',updateCurl); $('cancel').addEventListener('click',()=>{cancel();$('status').textContent='Запрос отменён';$('status').dataset.state='';$('result-help').textContent='Можно изменить данные и отправить снова.';}); $('send').addEventListener('click',async()=>{ cancel();clearResult();let r; try{r=requestData();}catch(e){$('status').textContent=e.message;$('status').dataset.state='error';return;} const ticket=generation;controller=new AbortController();$('send').disabled=true;$('cancel').disabled=false;$('status').textContent='Отправляем запрос…';$('status').dataset.state='loading'; const started=performance.now();const activeController=controller;const timeout=setTimeout(()=>activeController.abort(),10000); try{ const response=await boundedFetch(r.url,{method:r.method,headers:r.headers,body:r.body,signal:controller.signal}); const raw=await response.text();if(ticket!==generation)return; const ms=Math.round(performance.now()-started);let data;try{data=JSON.parse(raw);}catch{data={message:'Сервер вернул не JSON',preview:raw.slice(0,1000)};} const headers=Object.fromEntries(['content-type','x-request-id','x-aen-demo','retry-after','allow','www-authenticate'].filter(k=>response.headers.has(k)).map(k=>[k,response.headers.get(k)])); lastResult={scenario:selected().id,request:r,status:response.status,durationMs:ms,headers,response:data}; $('status').textContent='HTTP '+response.status+' · ответ получен';$('status').dataset.state=response.ok?'success':'error';$('http').textContent=String(response.status);$('duration').textContent=ms+' мс';$('response').textContent=JSON.stringify(data,null,2);$('headers').textContent=JSON.stringify(headers,null,2);$('download').disabled=false; $('result-help').textContent=response.status===variant().expect?'Ответ совпал с исходным примером. После ваших изменений статус может быть другим.':'Статус отличается от исходного примера: изучите ответ и изменённые поля.'; }catch(e){if(ticket!==generation)return;$('status').textContent=e.name==='AbortError'?'Превышено ожидание 10 секунд':'Запрос не завершён';$('status').dataset.state='error';$('response').textContent=e.message;$('result-help').textContent='Проверьте PHP-сервер и соединение. Затем можно повторить запрос.'; }finally{clearTimeout(timeout);if(ticket===generation){controller=null;$('send').disabled=false;$('cancel').disabled=true;}} }); $('file').addEventListener('change',async()=>{const f=$('file').files[0];if(!f)return;const ticket=generation;if(f.size>4096){$('input-note').textContent='Файл больше 4096 байт. Выберите небольшой учебный TXT.';$('file').value='';return;}const text=await f.text();if(ticket!==generation)return;$('body').value=text;updateCurl();$('input-note').textContent='Файл прочитан локально. Нажмите «Отправить запрос», чтобы передать текст.';}); $('download').addEventListener('click',()=>{if(lastResult)download('scalar-'+lastResult.scenario+'-result.json',JSON.stringify(lastResult,null,2));}); $('copy-curl').addEventListener('click',async()=>{try{await navigator.clipboard.writeText($('curl').textContent);$('input-note').textContent='Команда скопирована. Для PowerShell используйте curl.exe и проверьте кавычки; надёжнее передать тело через файл.';}catch{$('input-note').textContent='Копирование недоступно. Выделите команду в блоке выше.';}}); $('load-scalar').addEventListener('click',async()=>{const button=$('load-scalar');button.disabled=true;$('scalar-host').hidden=false;$('scalar-note').textContent='Загружаем локальную библиотеку и контракт…';try{await loadReference('#scalar');$('scalar-note').textContent='Scalar загружен. Откройте операцию и нажмите Test Request для настоящего запроса.';button.textContent='Scalar открыт';}catch(e){$('scalar-note').textContent=e.message;button.disabled=false;}}); updateScenario();