← back to Pattern Vault
capture-session: auto-detect login (poll for logged-in marker, no keypress) — works via ! prefix, 5-min timeout
bd366e3a0660b60c343aba901b2a70037af4ec1a · 2026-07-09 19:01:30 -0700 · Steve
Files touched
M wpb-uploaders/capture-session.js
Diff
commit bd366e3a0660b60c343aba901b2a70037af4ec1a
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 9 19:01:30 2026 -0700
capture-session: auto-detect login (poll for logged-in marker, no keypress) — works via ! prefix, 5-min timeout
---
wpb-uploaders/capture-session.js | 53 +++++++++++++++++++++++++++++-----------
1 file changed, 39 insertions(+), 14 deletions(-)
diff --git a/wpb-uploaders/capture-session.js b/wpb-uploaders/capture-session.js
index 3565c67..4c1d8f9 100644
--- a/wpb-uploaders/capture-session.js
+++ b/wpb-uploaders/capture-session.js
@@ -1,16 +1,16 @@
#!/usr/bin/env node
/*
* Capture a logged-in session (Playwright storageState) for any WPB uploader platform.
- * Opens a HEADED browser to the platform's login/home page; YOU log in manually (SSO, 2FA,
- * CAPTCHA — all fine, it's a real window); then press Enter in this terminal to save the session.
+ * Opens a HEADED browser to the platform's login page; YOU log in manually (SSO, 2FA, CAPTCHA
+ * all fine). It AUTO-DETECTS when you're logged in and saves the session — no keypress needed,
+ * so it works even when launched via the `!` prefix. Times out after 5 minutes.
*
- * node capture-session.js <platform> # etsy* | creativemarket | redbubble | society6 | patternbank
+ * node capture-session.js <creativemarket|redbubble|society6|patternbank>
*
- * (*Etsy uses OAuth, not a browser session — for Etsy, paste an ETSY_OAUTH_TOKEN instead.)
+ * (Etsy uses OAuth, not a browser session — paste an ETSY_OAUTH_TOKEN instead.)
* Writes sessions/<platform>-state.json. Nothing is uploaded — this only captures the login.
- * MUST be run by Steve in an interactive terminal (it waits for your Enter).
*/
-const path = require('path'), fs = require('fs'), readline = require('readline');
+const path = require('path'), fs = require('fs');
const { chromium } = require('/Users/macstudio3/.npm-global/lib/node_modules/playwright');
const platform = process.argv[2];
@@ -24,23 +24,48 @@ const cfg = require(path.join(__dirname, 'platforms', `${platform}.js`));
const OUT = cfg.sessionFile;
const LOGIN = { creativemarket: 'https://creativemarket.com/login', redbubble: 'https://www.redbubble.com/auth/login',
society6: 'https://society6.com/login', patternbank: 'https://patternbank.com/login' }[platform];
+const sleep = ms => new Promise(r => setTimeout(r, ms));
-const ask = q => new Promise(r => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question(q, a => { rl.close(); r(a); }); });
+// logged-in heuristic: off the login/auth URL AND a logged-in marker is present.
+async function isLoggedIn(pg) {
+ const u = pg.url();
+ if (/\/login|\/signin|\/auth\/login|\/sign_in/i.test(u)) return false;
+ const marker = await pg.$('a[href*="logout" i], a[href*="sign_out" i], button:has-text("Log out"), button:has-text("Sign out"), a[href*="/account" i], [aria-label*="account" i], img[alt*="avatar" i], [class*="avatar" i]').catch(() => null);
+ return !!marker;
+}
(async () => {
fs.mkdirSync(path.dirname(OUT), { recursive: true });
console.log(`\n🔐 Capturing ${cfg.name} session → ${OUT}`);
- console.log(` A browser window will open at ${LOGIN}`);
- console.log(` Log in fully (SSO / 2FA / CAPTCHA all OK), reach your dashboard, THEN come back here.\n`);
+ console.log(` A browser window is opening at ${LOGIN}`);
+ console.log(` Log in fully (SSO / 2FA / CAPTCHA all OK) and reach your dashboard.`);
+ console.log(` I'll auto-detect the login and save it — no keypress needed.\n`);
+
const b = await chromium.launch({ headless: false, args: ['--start-maximized'] });
const ctx = await b.newContext({ viewport: null });
const pg = await ctx.newPage();
await pg.goto(LOGIN, { waitUntil: 'domcontentloaded', timeout: 60000 }).catch(() => {});
- await ask(' ▶ When you are fully logged in, press ENTER here to save the session… ');
- await ctx.storageState({ path: OUT });
+ const deadline = Date.now() + 5 * 60 * 1000; // 5 min
+ let stable = 0, saved = false;
+ while (Date.now() < deadline) {
+ await sleep(3000);
+ let ok = false;
+ try { ok = await isLoggedIn(pg); } catch { /* page navigating */ }
+ if (ok) { stable++; } else { stable = 0; }
+ if (stable >= 2) { // logged-in confirmed across two checks
+ await ctx.storageState({ path: OUT });
+ saved = true;
+ break;
+ }
+ if (Math.round((deadline - Date.now()) / 1000) % 30 === 0) console.log(` …waiting for login (${Math.round((deadline - Date.now()) / 1000)}s left)`);
+ }
await b.close();
- const ok = fs.existsSync(OUT) && fs.statSync(OUT).size > 200;
- console.log(ok ? `\n✅ Saved ${cfg.name} session (${fs.statSync(OUT).size} bytes) → ${OUT}` : `\n⚠️ Session file looks empty — did the login complete?`);
- console.log(` Next: verify with node run.js ${platform} <manifest> (still fail-loud + <=4/day).`);
+
+ if (saved && fs.existsSync(OUT) && fs.statSync(OUT).size > 200) {
+ console.log(`\n✅ Saved ${cfg.name} session (${fs.statSync(OUT).size} bytes) → ${OUT}`);
+ console.log(` Tell Claude "captured ${platform}" and it will calibrate the uploader (still fail-loud + <=4/day).`);
+ } else {
+ console.log(`\n⚠️ No login detected within 5 min — session NOT saved. Re-run and complete the login.`);
+ }
})();
← b68bdf0 gitignore: uploader sessions + ARMED runtime flags
·
back to Pattern Vault
·
capture-session: robust login detect (no-password-field + do 6800cc2 →