[object Object]

← back to Dw Pitch Followup

Frontend: build API URLs from location.host so fetches work even when page opened with inline credentials

8a5b1ac4cd1ffc67c14b41fbc5837ae6e368c421 · 2026-07-07 13:35:12 -0700 · Steve Abrams

Files touched

Diff

commit 8a5b1ac4cd1ffc67c14b41fbc5837ae6e368c421
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 7 13:35:12 2026 -0700

    Frontend: build API URLs from location.host so fetches work even when page opened with inline credentials
---
 public/index.html | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/public/index.html b/public/index.html
index deb9ddd..4901d40 100644
--- a/public/index.html
+++ b/public/index.html
@@ -83,6 +83,10 @@
 
 <script>
 const LISTS = ['list1','list2','list3'];
+// Build fetch URLs from host only — never inherit credentials from the page URL.
+// If the tab was opened as http://user:pass@host/, a relative fetch('/api/…')
+// throws "URL that includes credentials"; rebuilding from location.host strips them.
+const API = p => `${location.protocol}//${location.host}${p}`;
 const TAB_META = {
   list1:{title:'Samples Sent ≥90%', defn:'Projects where ≥90% of the samples the client ordered have shipped — they have the goods in hand; follow up to convert.'},
   list2:{title:'Invoiced · No Order', defn:'Clients invoiced in the last ~4 months whose invoices were all sample-only (no order over $50) — never converted to a firm order.'},
@@ -188,7 +192,7 @@ async function genLetter(card, opts={}){
   subj.textContent=regen?'regenerating…':'composing…'; ta.value=''; vlabel.textContent='';
   if(rbtn){ rbtn.disabled=true; rbtn.textContent='…'; }
   try{
-    const res = await fetch('/api/letter',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({list:cur,row:r,seed,showMfr:showMfr()})});
+    const res = await fetch(API('/api/letter'),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({list:cur,row:r,seed,showMfr:showMfr()})});
     const j = await res.json();
     if(j.error) throw new Error(j.error);
     subj.textContent='Subject: '+j.subject; ta.value=j.body;
@@ -261,7 +265,7 @@ document.getElementById('exportBtn').addEventListener('click',()=>{
 io = new IntersectionObserver(es=>{ if(es.some(x=>x.isIntersecting)) renderChunk(); }, {rootMargin:'400px'});
 io.observe(document.getElementById('sentinel'));
 
-fetch('/api/lists').then(r=>r.json()).then(j=>{
+fetch(API('/api/lists')).then(r=>r.json()).then(j=>{
   DATA=j; cur = LS('lastTab','list1'); if(!LISTS.includes(cur)) cur='list1';
   document.getElementById('built').textContent = 'built '+new Date(j.generatedAt).toLocaleString();
   switchTab(cur);

← 0524e78 Skip Basic Auth for direct-loopback access so the localhost  ·  back to Dw Pitch Followup  ·  Redesign viewer: 3-column board (all lists at once) + List/G 5d7759e →