← Commit history

adom-stock: work inline in a wiki README (adom/wiki#229)

Colby Knox ·87b26acf83 ·7d ago ·parent 2dcef96
A bare ::adom-stock{} inline defaults to the host page's own stock route (the frame route fills src in server side), and requests carry the reader's session (credentials same-origin; the opaque frame still sends none and keeps using the minted cap).
1 file changed +11−3
widgets/stock-widget.js+11−3
@@ -1,6 +1,7 @@ /* <adom-stock>: live distributor stock and price for a component page (adom/wiki#186). MIT.  * Data: an inline <script type="application/json"> child first, else fetch(src).- * src is the page's stock route: /api/pages/OWNER/SLUG/stock */+ * src is the page's stock route: /api/pages/OWNER/SLUG/stock. Inline in a wiki README a bare ::adom-stock{}+ * defaults to the host page's own route (the sandboxed frame route fills it in server side). */ (() => {   const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));   const CSS = `@font-face{font-family:'Satoshi';src:url('https://adom.inc/fonts/satoshi/satoshi-400-normal.woff2') format('woff2');font-weight:400;font-display:swap}@@ -27,7 +28,7 @@ a.pill:hover{border-color:var(--accent)}.pill .v{color:var(--muted);font-weight:       if (!this.querySelector('script[type="application/json"]') && document.readyState === 'loading') await new Promise(r => document.addEventListener('DOMContentLoaded', r, { once: true }));       const inline = this.querySelector('script[type="application/json"]');       if (inline) { try { data = JSON.parse(inline.textContent); } catch (e) { err = 'bad inline data'; } }-      const src = this.getAttribute('src');+      const src = this.getAttribute('src') || this.ownRoute();       if (!data && src) ({ data, err } = await this.load(src, false));       // Nothing cached yet and the reader is signed in: they asked, so spend the lookup now.       if (!err && data && src && data.refresh === 'declined' && data.signed_in !== false && !Object.values(data.vendors || {}).some(Boolean)) ({ data, err } = await this.load(src, true));@@ -37,12 +38,19 @@ a.pill:hover{border-color:var(--accent)}.pill .v{color:var(--muted);font-weight:         e.preventDefault(); a.textContent = 'checking'; const r = await this.load(src, true); this.render(r.data, r.err);       });     }+    // Inline in a wiki README (not the sandboxed frame at /OWNER/SLUG/widget/NAME): the host page's own stock route.+    ownRoute() {+      if (/^\/[^/]+\/[^/]+\/widget\/[^/]+\/?$/.test(location.pathname)) return null;+      const m = location.pathname.match(/^\/([^/]+)\/([^/]+)/);+      return m ? `/api/pages/${m[1]}/${m[2]}/stock` : null;+    }     // fresh=false reads the wiki's cache only; fresh=true lets the wiki spend a vendor lookup (counted against the daily budget).     async load(src, fresh) {       // The frame is opaque-origin (no session); the src carries the user cap the wiki minted for a signed-in reader.+      // Inline, the reader's own session goes with the request instead.       let cap = ''; try { cap = new URL(src, 'https://wiki.adom.inc').searchParams.get('cap') || ''; } catch (e) {}       const headers = fresh ? { 'X-Adom-Widget': 'stock', ...(cap ? { 'X-Adom-Cap': cap } : {}) } : {};-      try { const r = await fetch(src, { credentials: 'omit', headers }); const data = await r.json(); return { data, err: r.ok ? null : (data && data.error ? data.error : `stock route answered ${r.status}`) }; }+      try { const r = await fetch(src, { credentials: 'same-origin', headers }); const data = await r.json(); return { data, err: r.ok ? null : (data && data.error ? data.error : `stock route answered ${r.status}`) }; }       catch (e) { return { data: null, err: 'could not reach the stock route (' + (e && e.message ? String(e.message).slice(0, 60) : 'blocked') + ')' }; }     }     render(d, err) {