(()=>{ 'use strict'; const READ_RPCS=new Set(['sun_my_workspaces','sun_fetch_app_state','sun_is_platform_admin','caterium_trial_demo_status']); const isTransient=error=>/TimeoutError|AbortError|CATERIUM_TIMEOUT|Failed to fetch|fetch failed|NetworkError|Load failed|network request failed|превышено время ожидания/i.test(String(error?.message||error||'')); const errorMessage=error=>isTransient(error)?'Сервер временно не отвечает. Изменения остаются на этом устройстве. Проверьте соединение и повторите загрузку.':String(error?.message||error||'Неизвестная ошибка'); function routeUrl(base,url){ // Explicit PHP entry point also works for storage paths ending in .jpg: // Timeweb serves static extensions before Apache rewrite rules. return base.endsWith('.php')?base+'?__caterium_path='+encodeURIComponent(url.pathname)+(url.search?'&'+url.search.slice(1):''):base+url.pathname+url.search; } function mediaUrl(value){ if(!value)return value; const url=new URL(value,location.href); if(['https://usfjwhztqoopzzfmfbis.supabase.co','https://api.caterium.ru'].includes(url.origin)&&url.pathname.startsWith('/storage/v1/'))return routeUrl(location.origin+'/api/index.php',url); return value; } function create({upstream,proxy,fallbackProxy=proxy,timeout=12000,fallbackTimeout=15000,writeTimeout=35000,cooldown=60000}){ const origin=new URL(upstream).origin; proxy=proxy.replace(/\/$/,'');fallbackProxy=fallbackProxy.replace(/\/$/,''); let fallbackUntil=0; return async function(input,init={}){ const original=new Request(input,init),url=new URL(original.url),isBackend=url.origin===origin; // A Request used as RequestInit exposes its ReadableStream body. Safari // cannot upload that stream; buffer once and preserve bytes on fallback. const body=original.method==='GET'||original.method==='HEAD'?undefined:await original.clone().arrayBuffer(); const requestInit={method:original.method,headers:original.headers,body,credentials:original.credentials,mode:original.mode,cache:original.cache,redirect:original.redirect,referrer:original.referrer,referrerPolicy:original.referrerPolicy,integrity:original.integrity,keepalive:original.keepalive}; const read=original.method==='GET'||original.method==='HEAD'||(original.method==='POST'&&url.pathname.startsWith('/rest/v1/rpc/')&&READ_RPCS.has(url.pathname.slice('/rest/v1/rpc/'.length))); const passwordLogin=original.method==='POST'&&url.pathname==='/auth/v1/token'&&url.searchParams.get('grant_type')==='password'; const safeFallback=isBackend&&proxy!==fallbackProxy&&(read||passwordLogin); const expectJson=original.method!=='HEAD'&&(passwordLogin||url.pathname.startsWith('/rest/v1/rpc/')||(read&&(url.pathname.startsWith('/rest/v1/')||url.pathname.startsWith('/auth/v1/')))); async function attempt(target,limit){ const controller=new AbortController(),abort=()=>controller.abort(original.signal.reason); if(original.signal.aborted)abort();else original.signal.addEventListener('abort',abort,{once:true}); const timer=setTimeout(()=>controller.abort(new DOMException('Сервер не ответил вовремя. Проверьте соединение и повторите загрузку.','TimeoutError')),limit); try{ if(controller.signal.aborted)throw controller.signal.reason; const response=await fetch(new Request(target,requestInit),{signal:controller.signal}); if(expectJson&&response.ok&&response.status!==204){ const text=await response.clone().text(); try{if(!text.trim()||!response.headers.get('content-type')?.includes('json'))throw new Error();JSON.parse(text)} catch(_){throw new Error('Сервис вернул пустой или некорректный ответ. Повторите загрузку.')} } return response; }finally{clearTimeout(timer);original.signal.removeEventListener('abort',abort)} } const fallbackFirst=isBackend&&Date.now()