From e58bccf6b5230e9c1c52e5927cd51d1dd06a4e6c Mon Sep 17 00:00:00 2001 From: jjbrunton Date: Fri, 22 May 2026 22:05:32 +0100 Subject: [PATCH] Sniff Matrix token via www.reddit.com/chat/ + cookie dump endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chat.reddit.com now redirects to www.reddit.com/chat/, and that SPA was bouncing automated logins to the /login/ wall (confirmed via the new matrix-debug.png endpoint — the captured screenshot shows the login UI, not the chat surface, despite isLoggedIn() returning true seconds earlier). Going direct to the canonical URL means the session cookie set by the automated /login form-fill is applied to the very first request instead of only after a cross-subdomain redirect, which is where it was getting dropped. Switch goto from awaited domcontentloaded on the old URL to a non-awaited domcontentloaded on the new URL so the request listener can resolve the moment the SPA fires its first authed call to matrix.redditspace.com. Adds GET /api/debug/cookies as a temporary diagnostic — returns cookies visible to www.reddit.com, chat.reddit.com, and matrix.redditspace.com (values hashed by default; ?reveal=1 to dump raw). Lets us compare what the browser holds vs. what /chat/ rejects if this doesn't fix it. Co-Authored-By: Claude Opus 4.7 --- src/services/chat/matrix/token-extractor.ts | 26 +++++++++++--- src/web/routes/debug.ts | 39 +++++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/services/chat/matrix/token-extractor.ts b/src/services/chat/matrix/token-extractor.ts index ad16bec..ac60ce4 100644 --- a/src/services/chat/matrix/token-extractor.ts +++ b/src/services/chat/matrix/token-extractor.ts @@ -102,11 +102,27 @@ export async function extractMatrixCredentials( page.on('request', onRequest); - logger.info('[matrix] loading chat.reddit.com to capture access token...'); - await page.goto('https://chat.reddit.com/', { - waitUntil: 'domcontentloaded', - timeout: 30_000, - }); + // Reddit now redirects `chat.reddit.com` → `www.reddit.com/chat/`. Going + // direct to the canonical URL avoids one redirect hop and means the + // session cookie the bot already has for www.reddit.com is applied to + // the very first request, rather than only after a cross-subdomain + // bounce that's been bouncing logged-in sessions to the login wall. + // + // We start the navigation but don't await it — the request listener + // resolves credsPromise the moment the SPA fires off its first authed + // call to matrix.redditspace.com, which may happen well before the page + // reaches `networkidle`. Awaiting goto would block past that point. + logger.info('[matrix] loading www.reddit.com/chat/ to capture access token...'); + void page + .goto('https://www.reddit.com/chat/', { + waitUntil: 'domcontentloaded', + timeout: timeoutMs, + }) + .catch((err) => { + logger.warn( + `[matrix] page.goto error (ignored, still listening): ${err instanceof Error ? err.message : String(err)}`, + ); + }); const timeout = new Promise((_, reject) => setTimeout( diff --git a/src/web/routes/debug.ts b/src/web/routes/debug.ts index 2fd9fcf..2837d46 100644 --- a/src/web/routes/debug.ts +++ b/src/web/routes/debug.ts @@ -21,6 +21,45 @@ export function createDebugRouter(ctx: WebContext): Router { res.sendFile(p); }); + // Dumps the browser context's cookies (names + metadata only — values are + // hashed) so we can tell which session/OAuth cookies are set after the + // auto-login and which the chat surface might be missing. + router.get('/debug/cookies', async (req, res) => { + const browser = ctx.services.browser; + if (!browser) { + res.status(500).json({ error: 'Browser client not initialized' }); + return; + } + const context = browser.getContext(); + if (!context) { + res.status(500).json({ error: 'Browser context not available' }); + return; + } + try { + const reveal = req.query.reveal === '1'; + const urls = ['https://www.reddit.com', 'https://chat.reddit.com', 'https://matrix.redditspace.com']; + const out: Record = {}; + for (const u of urls) { + const cookies = await context.cookies(u); + out[u] = cookies.map((c) => ({ + name: c.name, + domain: c.domain, + path: c.path, + httpOnly: c.httpOnly, + secure: c.secure, + sameSite: c.sameSite, + expires: c.expires, + valueLen: c.value.length, + value: reveal ? c.value : undefined, + })); + } + res.json(out); + } catch (error) { + ctx.logger.error('Cookie dump failed:', error); + res.status(500).json({ error: String(error) }); + } + }); + router.post('/debug/inspect-dom', async (req, res) => { if (!ctx.services.chat) { res.status(500).json({ error: 'Chat service not initialized' });