Don't crash bootstrap on Matrix token capture failure
If chat.reddit.com sniffing fails, fall through with a placeholder credential set so the post-monitoring/processor side of the bot (and the web UI) stay up. Chat features are disabled until the next successful token refresh. Also dump diagnostics on sniff timeout: final page URL, every host the page touched (with request counts + whether any carried an Authorization header), and a full-page screenshot to data/matrix-debug.png. This makes the three failure modes (page didn't load / loaded but never hit Matrix / hit Matrix without auth) distinguishable from logs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
665ff8b1f3
commit
4a3b44bc87
2 files changed
+58
-5
No files matched your search
+19
-1
@@ -147,10 +147,28 @@ export async function bootstrap(config: Config, logger: Logger): Promise<AppCont
|
||||
// ---- Matrix chat backend (replaces the Playwright scraper) ----
|
||||
// 1. Get credentials: prefer cached, fall back to a fresh sniff via the
|
||||
// persistent browser session that we just verified is logged in.
|
||||
// If the sniff fails (e.g. egress blocked, chat.reddit.com refuses to
|
||||
// load), we *don't* crash bootstrap — the post-monitoring/processor
|
||||
// side of the bot doesn't need Matrix, and keeping the web UI up makes
|
||||
// the situation diagnosable. Chat calls will fail with 401 until a
|
||||
// subsequent token refresh succeeds.
|
||||
let matrixCreds: MatrixCredentials | null = await loadCachedCredentials();
|
||||
if (!matrixCreds) {
|
||||
logger.info('No cached Matrix credentials — extracting via browser session...');
|
||||
matrixCreds = await extractMatrixCredentials(browserClient.getContext()!, logger);
|
||||
try {
|
||||
matrixCreds = await extractMatrixCredentials(browserClient.getContext()!, logger);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
'[matrix] credential extraction failed — chat features will be disabled until the next successful refresh:',
|
||||
err,
|
||||
);
|
||||
matrixCreds = {
|
||||
homeserver: 'https://matrix.redditspace.com',
|
||||
accessToken: '',
|
||||
userId: '',
|
||||
capturedAt: Date.now(),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
logger.info(`Loaded cached Matrix credentials (user=${matrixCreds.userId})`);
|
||||
}
|
||||
|
||||
@@ -66,15 +66,25 @@ export async function extractMatrixCredentials(
|
||||
});
|
||||
|
||||
let done = false;
|
||||
// Track every host the page touches so we can tell, on timeout, whether
|
||||
// chat.reddit.com failed to load at all, loaded but never reached the
|
||||
// Matrix homeserver, or reached it but without a bearer header.
|
||||
const seenHosts = new Map<string, { count: number; sawAuth: boolean }>();
|
||||
const onRequest = (request: import('playwright').Request) => {
|
||||
if (done) return;
|
||||
try {
|
||||
const url = new URL(request.url());
|
||||
if (!MATRIX_HOST_RE.test(url.hostname)) return;
|
||||
const isMatrix = MATRIX_HOST_RE.test(url.hostname);
|
||||
const headers = request.headers();
|
||||
const auth = headers['authorization'] || headers['Authorization'];
|
||||
if (!auth || !auth.toLowerCase().startsWith('bearer ')) return;
|
||||
const token = auth.slice('bearer '.length).trim();
|
||||
const hasAuth = !!auth && auth.toLowerCase().startsWith('bearer ');
|
||||
const entry = seenHosts.get(url.hostname) ?? { count: 0, sawAuth: false };
|
||||
entry.count += 1;
|
||||
if (hasAuth) entry.sawAuth = true;
|
||||
seenHosts.set(url.hostname, entry);
|
||||
if (!isMatrix) return;
|
||||
if (!hasAuth) return;
|
||||
const token = auth!.slice('bearer '.length).trim();
|
||||
if (!token) return;
|
||||
const homeserver = `${url.protocol}//${url.host}`;
|
||||
done = true;
|
||||
@@ -105,7 +115,32 @@ export async function extractMatrixCredentials(
|
||||
),
|
||||
);
|
||||
|
||||
const captured = await Promise.race([credsPromise, timeout]);
|
||||
let captured: MatrixCredentials;
|
||||
try {
|
||||
captured = await Promise.race([credsPromise, timeout]);
|
||||
} catch (err) {
|
||||
// On timeout, surface enough info to tell the three failure modes apart:
|
||||
// (a) chat.reddit.com never loaded → page.url() still about:blank, almost no hosts
|
||||
// (b) page loaded, no Matrix host hit → reddit hosts present, no matrix.*
|
||||
// (c) Matrix hit, but no Authorization → matrix.* present with sawAuth: false
|
||||
try {
|
||||
const finalUrl = page.url();
|
||||
const hostSummary = Array.from(seenHosts.entries())
|
||||
.map(([host, info]) => `${host}=${info.count}${info.sawAuth ? '*' : ''}`)
|
||||
.join(', ');
|
||||
logger.error(
|
||||
`[matrix] timeout diagnostics: finalUrl=${finalUrl} hosts=[${hostSummary || 'none'}]`,
|
||||
);
|
||||
const shotDir = path.dirname(cachePath);
|
||||
if (!fs.existsSync(shotDir)) fs.mkdirSync(shotDir, { recursive: true });
|
||||
const shotPath = path.join(shotDir, 'matrix-debug.png');
|
||||
await page.screenshot({ path: shotPath, fullPage: true }).catch(() => undefined);
|
||||
logger.error(`[matrix] saved debug screenshot to ${shotPath}`);
|
||||
} catch (diagErr) {
|
||||
logger.warn('[matrix] failed to collect timeout diagnostics:', diagErr);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Resolve the user_id by calling /whoami so the caller knows who we are.
|
||||
const whoamiRes = await fetchWithTimeout(
|
||||
|
||||
Reference in new issue
Block a user