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) ----
|
// ---- Matrix chat backend (replaces the Playwright scraper) ----
|
||||||
// 1. Get credentials: prefer cached, fall back to a fresh sniff via the
|
// 1. Get credentials: prefer cached, fall back to a fresh sniff via the
|
||||||
// persistent browser session that we just verified is logged in.
|
// 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();
|
let matrixCreds: MatrixCredentials | null = await loadCachedCredentials();
|
||||||
if (!matrixCreds) {
|
if (!matrixCreds) {
|
||||||
logger.info('No cached Matrix credentials — extracting via browser session...');
|
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 {
|
} else {
|
||||||
logger.info(`Loaded cached Matrix credentials (user=${matrixCreds.userId})`);
|
logger.info(`Loaded cached Matrix credentials (user=${matrixCreds.userId})`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,15 +66,25 @@ export async function extractMatrixCredentials(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let done = false;
|
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) => {
|
const onRequest = (request: import('playwright').Request) => {
|
||||||
if (done) return;
|
if (done) return;
|
||||||
try {
|
try {
|
||||||
const url = new URL(request.url());
|
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 headers = request.headers();
|
||||||
const auth = headers['authorization'] || headers['Authorization'];
|
const auth = headers['authorization'] || headers['Authorization'];
|
||||||
if (!auth || !auth.toLowerCase().startsWith('bearer ')) return;
|
const hasAuth = !!auth && auth.toLowerCase().startsWith('bearer ');
|
||||||
const token = auth.slice('bearer '.length).trim();
|
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;
|
if (!token) return;
|
||||||
const homeserver = `${url.protocol}//${url.host}`;
|
const homeserver = `${url.protocol}//${url.host}`;
|
||||||
done = true;
|
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.
|
// Resolve the user_id by calling /whoami so the caller knows who we are.
|
||||||
const whoamiRes = await fetchWithTimeout(
|
const whoamiRes = await fetchWithTimeout(
|
||||||
|
|||||||
Reference in new issue
Block a user