← 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 →