[object Object]

← back to Wallco Ai

spoonflower: 3-path auth (Chrome attach > cookies > password)

225c77f727ae67e92c50e40f935b16709f54729f · 2026-05-11 16:53:11 -0700 · SteveStudio2

Adopts the months-ago working pattern from
~/Projects/Designer-Wallcoverings/DW-Programming/150dpi/
(connect_and_download.js + spoonflower_download.js). The programmatic
email/password flow alone hit a hard wall: Spoonflower's cart.* subdomain
doesn't honor www.* session cookies on fresh logins, so get-user-stats
returned userID=null. Reusing a real logged-in browser session via CDP
sidesteps that entirely.

Auth chain (in priority order):
  1. CHROME ATTACH — connect_over_cdp to a Chrome running on :9222
     (Steve signs in once, scripts auto-attach to that window).
  2. SAVED COOKIES — ~/.spoonflower-cookies.json loaded into a fresh ctx.
  3. PASSWORD LOGIN — existing fallback, kept for completeness.

New script: scripts/launch_spoon_chrome.sh — opens a SECOND Chrome
instance with --remote-debugging-port=9222 and a dedicated user-data-dir
at /tmp/spoon-chrome so it doesn't disturb the main browser. Lands
directly on the login page. One-time sign-in then persistent across
script runs.

Wallco-ai endpoints:
  POST /api/spoonflower/launch-chrome   → fires the script
  GET  /api/spoonflower/chrome-status   → probes :9222/json/version
Plus a one-time-setup callout + live status pill on /spoonflower.

Harvester URL patterns extended:
  /artists/designs/<id>   ← the proven pattern (months-ago scraper)
  /en/design/<id>-<slug>
  /fabric/<id>  /wallpaper/<id>
All normalize to design_url = https://.../artists/designs/<id> since
that's where the File Options menu appears for owned designs.

Files touched

Diff

commit 225c77f727ae67e92c50e40f935b16709f54729f
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Mon May 11 16:53:11 2026 -0700

    spoonflower: 3-path auth (Chrome attach > cookies > password)
    
    Adopts the months-ago working pattern from
    ~/Projects/Designer-Wallcoverings/DW-Programming/150dpi/
    (connect_and_download.js + spoonflower_download.js). The programmatic
    email/password flow alone hit a hard wall: Spoonflower's cart.* subdomain
    doesn't honor www.* session cookies on fresh logins, so get-user-stats
    returned userID=null. Reusing a real logged-in browser session via CDP
    sidesteps that entirely.
    
    Auth chain (in priority order):
      1. CHROME ATTACH — connect_over_cdp to a Chrome running on :9222
         (Steve signs in once, scripts auto-attach to that window).
      2. SAVED COOKIES — ~/.spoonflower-cookies.json loaded into a fresh ctx.
      3. PASSWORD LOGIN — existing fallback, kept for completeness.
    
    New script: scripts/launch_spoon_chrome.sh — opens a SECOND Chrome
    instance with --remote-debugging-port=9222 and a dedicated user-data-dir
    at /tmp/spoon-chrome so it doesn't disturb the main browser. Lands
    directly on the login page. One-time sign-in then persistent across
    script runs.
    
    Wallco-ai endpoints:
      POST /api/spoonflower/launch-chrome   → fires the script
      GET  /api/spoonflower/chrome-status   → probes :9222/json/version
    Plus a one-time-setup callout + live status pill on /spoonflower.
    
    Harvester URL patterns extended:
      /artists/designs/<id>   ← the proven pattern (months-ago scraper)
      /en/design/<id>-<slug>
      /fabric/<id>  /wallpaper/<id>
    All normalize to design_url = https://.../artists/designs/<id> since
    that's where the File Options menu appears for owned designs.
---
 server.js | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/server.js b/server.js
index 55bb17d..3a557cb 100644
--- a/server.js
+++ b/server.js
@@ -1211,6 +1211,31 @@ app.post('/api/spoonflower/upload', (req, res) => {
   res.json({ ok: true, job_id: jobId, dry, log_url: `/api/spoonflower/status/${jobId}` });
 });
 
+// ── POST /api/spoonflower/launch-chrome — open dedicated debug Chrome for cookie reuse
+app.post('/api/spoonflower/launch-chrome', (req, res) => {
+  try {
+    const out = execSync(`zsh ${path.join(__dirname, 'scripts', 'launch_spoon_chrome.sh')} 2>&1`,
+                         { encoding: 'utf8', timeout: 10000 });
+    res.json({ ok: true, output: out });
+  } catch (e) {
+    res.status(500).json({ ok: false, error: e.message, output: e.stdout || '' });
+  }
+});
+
+// ── GET /api/spoonflower/chrome-status — is debug Chrome reachable?
+app.get('/api/spoonflower/chrome-status', async (_req, res) => {
+  try {
+    const r = await fetch('http://127.0.0.1:9222/json/version', { signal: AbortSignal.timeout(1500) });
+    if (r.ok) {
+      const j = await r.json();
+      return res.json({ ok: true, running: true, browser: j.Browser });
+    }
+    res.json({ ok: true, running: false });
+  } catch {
+    res.json({ ok: true, running: false });
+  }
+});
+
 // ── POST /api/spoonflower/pull-all — bulk pull DW account designs
 app.post('/api/spoonflower/pull-all', (req, res) => {
   const { limit = 0, account = 'dw' } = req.body || {};
@@ -1299,7 +1324,17 @@ ${htmlHeader('')}
   <h1 style="font-family:'Cormorant Garamond',serif;font-weight:300;font-size:32px;margin:0 0 8px">DW Spoonflower Library</h1>
   <p style="color:#666;margin:0 0 24px">${pulled.length} design${pulled.length===1?'':'s'} pulled · ${recentJobs.length} recent job${recentJobs.length===1?'':'s'}</p>
 
+  <div style="margin-bottom:14px;padding:14px;border:1px solid #ddd;border-radius:6px;background:#fafafa;font-size:13px;line-height:1.55">
+    <strong>One-time setup (proven pattern from months-ago working scraper):</strong>
+    <ol style="margin:8px 0 6px 22px;padding:0">
+      <li>Click <b>Launch Debug Chrome</b> below — opens a 2nd Chrome window (won't disturb your main Chrome).</li>
+      <li>Sign in once with <code>info@designerwallcoverings.com</code> / <code>*Spoonaccess911*</code>.</li>
+      <li>Come back here — <b>Pull ALL DW designs</b> will then attach to that signed-in window automatically.</li>
+    </ol>
+    <span id="chrome-status" style="color:#888">Checking debug-Chrome status…</span>
+  </div>
   <div style="margin-bottom:24px;display:flex;gap:10px;flex-wrap:wrap">
+    <button id="btn-launch-chrome" class="btn-outline">Launch Debug Chrome</button>
     <button id="btn-pull-test" class="btn-outline">Pull-all (limit 3, dry test)</button>
     <button id="btn-pull-all"  class="btn-primary">Pull ALL DW designs</button>
     <span id="pull-status" style="align-self:center;color:#666;font-size:13px"></span>
@@ -1340,6 +1375,37 @@ ${htmlHeader('')}
 ${FOOTER}
 <script>
 async function post(u, b){ var r = await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(b||{})}); return r.json(); }
+async function get(u){ var r = await fetch(u); return r.json(); }
+
+async function refreshChromeStatus() {
+  var el = document.getElementById('chrome-status');
+  try {
+    var j = await get('/api/spoonflower/chrome-status');
+    if (j.running) {
+      el.style.color = '#155724';
+      el.innerHTML = '✓ Debug Chrome running on :9222 (' + (j.browser||'Chrome') + ') — pulls will auto-attach.';
+    } else {
+      el.style.color = '#856404';
+      el.innerHTML = '⚠ Debug Chrome not running. Click "Launch Debug Chrome" to start it.';
+    }
+  } catch(e) {
+    el.style.color = '#c00'; el.textContent = 'Status check failed: ' + e.message;
+  }
+}
+refreshChromeStatus();
+setInterval(refreshChromeStatus, 5000);
+
+document.getElementById('btn-launch-chrome').addEventListener('click', async function(){
+  this.disabled = true;
+  var s = document.getElementById('pull-status');
+  s.textContent = 'Launching debug Chrome…';
+  var j = await post('/api/spoonflower/launch-chrome', {});
+  s.innerHTML = j.ok
+    ? '<pre style="margin:8px 0;font-size:11px;background:#000;color:#0f0;padding:8px;border-radius:4px;white-space:pre-wrap">'+(j.output||'').replace(/[<>]/g,'')+'</pre>'
+    : 'Launch failed: ' + (j.error||'unknown');
+  this.disabled = false;
+  setTimeout(refreshChromeStatus, 2000);
+});
 document.getElementById('btn-pull-test').addEventListener('click', async function(){
   this.disabled=true;
   document.getElementById('pull-status').textContent = 'Starting limited pull (3 designs)…';

← a6af871 refresh data/designs.json snapshot — 30 designs (was 11): 8  ·  back to Wallco Ai  ·  wallco.ai chat layer: POST /api/chat/catalog + /api/chat/des 5f79c25 →