mirror of
https://github.com/telemt/telemt.git
synced 2026-10-30 04:20:31 +03:00
WEB Session Lifecycle Observability + Bridge Recovery Drafts
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
(()=>{'use strict';
|
||||
function create(settings){
|
||||
const pause=(milliseconds,signal)=>new Promise((resolve,reject)=>{
|
||||
if(signal&&signal.aborted){reject(new Error('request aborted'));return}
|
||||
const timer=setTimeout(done,milliseconds);function done(){if(signal)signal.removeEventListener('abort',abort);resolve()}
|
||||
function abort(){clearTimeout(timer);signal.removeEventListener('abort',abort);reject(new Error('request aborted'))}
|
||||
if(signal)signal.addEventListener('abort',abort,{once:true});
|
||||
});
|
||||
const options=(method,token,body,headers,signal,keepalive)=>({
|
||||
method,body,signal,keepalive:!!keepalive,mode:'same-origin',credentials:'omit',cache:'no-store',redirect:'error',referrerPolicy:'no-referrer',
|
||||
headers:Object.assign(token?{Authorization:'Bearer '+token}:{},body?{'Content-Type':'application/octet-stream'}:{},headers||{})
|
||||
});
|
||||
function retryAfterMs(response){
|
||||
const header=response.headers.get('Retry-After');
|
||||
if(!header)return 0;
|
||||
const seconds=Number(header);
|
||||
if(Number.isFinite(seconds)&&seconds>=0)return Math.min(seconds*1000,30000);
|
||||
const when=Date.parse(header);
|
||||
if(Number.isFinite(when)){const delta=when-Date.now();return delta>0?Math.min(delta,30000):0}
|
||||
return 0;
|
||||
}
|
||||
function retryableStatus(status){return status===408||status===429||status===502||status===503||status===504}
|
||||
function responsePolicy(path,status){
|
||||
if(path==='/api/v1/session'&&status===200)return {limit:8,exact:true};
|
||||
if(path==='/api/v1/down'&&status===200)return {limit:settings.batchLimit(),exact:false};
|
||||
return {limit:0,exact:true};
|
||||
}
|
||||
async function send(path,frozenOptions,remainingBudget,maxAttempts){
|
||||
let delay=250,attempt=0,lastReason='network';maxAttempts=maxAttempts||9;
|
||||
const initialBudget=remainingBudget?Math.min(settings.retryMs(),remainingBudget()):settings.retryMs();
|
||||
const deadline=Date.now()+Math.max(0,initialBudget),external=frozenOptions.signal;
|
||||
const attemptLimit=path==='/api/v1/down'?settings.longPollMs()+settings.requestMs():settings.requestMs();
|
||||
while(attempt<maxAttempts){
|
||||
if(settings.closed()||(external&&external.aborted))throw new Error('request aborted');
|
||||
const remaining=Math.min(deadline-Date.now(),remainingBudget?remainingBudget():Infinity);if(remaining<=0)break;attempt++;
|
||||
const controller=new AbortController(),abort=()=>controller.abort();
|
||||
if(external)external.addEventListener('abort',abort,{once:true});
|
||||
const requestOptions=Object.assign({},frozenOptions,{signal:controller.signal});
|
||||
const timer=setTimeout(abort,Math.max(1,Math.min(attemptLimit,remaining)));
|
||||
let response=null,wait=0;
|
||||
try{
|
||||
const fetched=await fetch(settings.origin()+path,requestOptions);
|
||||
if(retryableStatus(fetched.status)){
|
||||
lastReason='http';wait=retryAfterMs(fetched);settings.cancel(fetched);
|
||||
}else{
|
||||
const policy=responsePolicy(path,fetched.status);let body;
|
||||
try{body=await settings.read(fetched,policy.limit,policy.exact,controller.signal)}
|
||||
catch(error){controller.abort();throw settings.failure('protocol',error&&error.message)}
|
||||
response={status:fetched.status,headers:fetched.headers,body};return response;
|
||||
}
|
||||
}catch(error){
|
||||
controller.abort();
|
||||
if(settings.closed()||(external&&external.aborted))throw error;
|
||||
if(settings.reason(error,'')==='protocol')throw error;
|
||||
}finally{clearTimeout(timer);if(external)external.removeEventListener('abort',abort)}
|
||||
const after=Math.min(deadline-Date.now(),remainingBudget?remainingBudget():Infinity);if(attempt>=maxAttempts||after<=0)break;
|
||||
settings.retrying();
|
||||
const backoff=wait||delay+Math.floor(Math.random()*Math.max(1,delay/4));
|
||||
await pause(Math.min(backoff,after),external);delay=Math.min(delay*2,2000);
|
||||
}
|
||||
throw settings.failure(lastReason,'carrier retry limit reached');
|
||||
}
|
||||
return Object.freeze({options,pause,send});
|
||||
}
|
||||
globalThis.TelemtBridgeRequest=Object.freeze({create});
|
||||
})();
|
||||
Reference in New Issue
Block a user