49 lines
4.1 KiB
JavaScript
49 lines
4.1 KiB
JavaScript
(()=>{
|
|
'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 create({upstream,proxy,timeout=12000,fallbackTimeout=15000,writeTimeout=35000,cooldown=60000}){
|
|
const origin=new URL(upstream).origin;
|
|
let directUntil=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&&(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 directFirst=isBackend&&Date.now()<directUntil;
|
|
const first=directFirst?original.url:isBackend?proxy+url.pathname+url.search:original.url;
|
|
const second=directFirst?proxy+url.pathname+url.search:original.url;
|
|
try{
|
|
const response=await attempt(first,safeFallback?(directFirst?fallbackTimeout:timeout):writeTimeout);
|
|
if(!safeFallback||response.status<500)return response;
|
|
}catch(error){if(!safeFallback||original.signal.aborted)throw error}
|
|
// Same backend, same authorization, one SDK session. Never replay writes.
|
|
const response=await attempt(second,directFirst?timeout:fallbackTimeout);
|
|
if(response.ok)directUntil=directFirst?0:Date.now()+cooldown;
|
|
return response;
|
|
};
|
|
}
|
|
window.CateriumCloudTransport=Object.freeze({create,isTransient,errorMessage});
|
|
})();
|