MCPcopy
hub / github.com/nesquena/hermes-webui / api

Function api

static/workspace.js:1–110  ·  view source on GitHub ↗
(path,opts={})

Source from the content-addressed store, hash-verified

1async function api(path,opts={}){
2 // Strip leading slash so URL resolves relative to location.href (supports subpath mounts)
3 const rel = path.startsWith('/') ? path.slice(1) : path;
4 const url=new URL(rel,document.baseURI||location.href);
5 const timeoutMs=Object.prototype.hasOwnProperty.call(opts,'timeoutMs')?opts.timeoutMs:30000;
6 const timeoutToast=opts.timeoutToast!==false;
7 const redirect401=opts.redirect401!==false;
8 const maxAttempts=Object.prototype.hasOwnProperty.call(opts,'retries')?Math.max(0,Number(opts.retries)||0)+1:3;
9 const retryTimeouts=opts.retryTimeouts===true;
10 const retryStatuses=Array.isArray(opts.retryStatuses)?opts.retryStatuses.map(Number).filter(Number.isFinite):[];
11 const retryDelayMs=Object.prototype.hasOwnProperty.call(opts,'retryDelayMs')?Math.max(0,Number(opts.retryDelayMs)||0):350;
12 // Retry up to 2 times on network errors (e.g. stale keep-alive after long idle).
13 // Callers may opt into retrying timeouts / transient server statuses for idempotent GETs.
14 let lastErr;
15 for(let attempt=0;attempt<maxAttempts;attempt++){
16 let controller=null;
17 let timeoutId=null;
18 let didTimeout=false;
19 let upstreamSignal=null;
20 let upstreamAbort=null;
21 try{
22 const fetchOpts={...opts};
23 delete fetchOpts.timeoutMs;
24 delete fetchOpts.timeoutToast;
25 delete fetchOpts.redirect401;
26 delete fetchOpts.retries;
27 delete fetchOpts.retryTimeouts;
28 delete fetchOpts.retryStatuses;
29 delete fetchOpts.retryDelayMs;
30
31 const useTimeout=Number.isFinite(Number(timeoutMs))&&Number(timeoutMs)>0;
32 if(useTimeout&&typeof AbortController!=='undefined'){
33 controller=new AbortController();
34 upstreamSignal=fetchOpts.signal||null;
35 if(upstreamSignal){
36 upstreamAbort=()=>controller.abort(upstreamSignal.reason);
37 if(upstreamSignal.aborted) upstreamAbort();
38 else upstreamSignal.addEventListener('abort',upstreamAbort,{once:true});
39 }
40 fetchOpts.signal=controller.signal;
41 }
42 const requestPromise=(async()=>{
43 const res=await fetch(url.href,{credentials:'include',headers:{'Content-Type':'application/json'},...fetchOpts});
44 if(!res.ok){
45 // 401 means the auth session expired. Redirect to login so the user can
46 // re-authenticate. This is especially important for iOS PWA (standalone mode)
47 // and for subpath mounts like /hermes/, where /login escapes to the site root.
48 if(res.status===401){
49 if(redirect401) window.location.href='login?next='+encodeURIComponent(window.location.pathname+window.location.search);
50 // Callers can opt out of navigation and handle the unauthenticated state themselves.
51 return;
52 }
53 const text=await res.text();
54 // Parse JSON error body and surface the human-readable message,
55 // rather than showing raw JSON like {"error":"Profile 'x' does not exist."}
56 let message=text;
57 try{const j=JSON.parse(text);message=j.error||j.message||text;}catch(e){}
58 // Attach the raw HTTP context so callers can branch on status (404 stale-session
59 // cleanup, 401 redirect, 503 retry, etc.) without re-parsing the message string.
60 const err=new Error(message);

Callers 15

_saveComposerDraftFunction · 0.85
_saveComposerDraftNowFunction · 0.85
_clearComposerDraftFunction · 0.85
newSessionFunction · 0.85
loadSessionFunction · 0.85
_checkAndShowHandoffHintFunction · 0.85
_generateHandoffSummaryFunction · 0.85
_ensureMessagesLoadedFunction · 0.85
_loadOlderMessagesFunction · 0.85

Calls 4

showToastFunction · 0.85
textMethod · 0.80
getMethod · 0.45
jsonMethod · 0.45

Tested by

no test coverage detected