Add auth health check before browser creation
- Rewrite session.mjs: check connection status via SDK before creating browser. If NEEDS_AUTH + can_reauth, auto re-auth with stored creds. If can't re-auth, send Telegram alert and skip platform. - Wire ensureAuth() into job_applier.mjs before createBrowser() - Jobs are returned to queue (not failed) when auth is down Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ process.stderr.write = (chunk, ...args) => { logStream.write(chunk); return orig
|
||||
import { getJobsByStatus, updateJobStatus, appendLog, loadConfig, isAlreadyApplied } from './lib/queue.mjs';
|
||||
import { acquireLock } from './lib/lock.mjs';
|
||||
import { createBrowser } from './lib/browser.mjs';
|
||||
import { ensureAuth } from './lib/session.mjs';
|
||||
import { FormFiller } from './lib/form_filler.mjs';
|
||||
import { applyToJob, supportedTypes } from './lib/apply/index.mjs';
|
||||
import { sendTelegram, formatApplySummary } from './lib/notify.mjs';
|
||||
@@ -135,6 +136,19 @@ async function main() {
|
||||
if (platform === 'external') {
|
||||
browser = await createBrowser(settings, null); // no profile needed
|
||||
} else {
|
||||
// Check auth status before creating browser
|
||||
const connectionIds = settings.kernel?.connection_ids || {};
|
||||
const kernelApiKey = process.env.KERNEL_API_KEY || settings.kernel_api_key;
|
||||
const authResult = await ensureAuth(platform, kernelApiKey, connectionIds);
|
||||
if (!authResult.ok) {
|
||||
console.error(` ❌ ${platform} auth failed: ${authResult.reason}`);
|
||||
await sendTelegram(settings, `⚠️ *${platform}* auth failed — ${authResult.reason}`).catch(() => {});
|
||||
// Mark all jobs for this platform as needing retry
|
||||
for (const job of platformJobs) {
|
||||
updateJobStatus(job.id, 'new', { retry_reason: 'auth_failed' });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
browser = await createBrowser(settings, platform);
|
||||
console.log(' ✅ Logged in\n');
|
||||
}
|
||||
|
||||
116
lib/session.mjs
116
lib/session.mjs
@@ -1,67 +1,83 @@
|
||||
/**
|
||||
* session.mjs — Kernel Managed Auth session refresh
|
||||
* Call refreshSession() before creating a browser to ensure the profile is fresh
|
||||
* session.mjs — Kernel Managed Auth session management
|
||||
* Checks auth status before browser creation, triggers re-auth when needed.
|
||||
*
|
||||
* Flow:
|
||||
* 1. Check connection status via SDK
|
||||
* 2. If AUTHENTICATED → good to go
|
||||
* 3. If NEEDS_AUTH + can_reauth → trigger login(), poll until done
|
||||
* 4. If NEEDS_AUTH + !can_reauth → return false (caller should alert + skip)
|
||||
*/
|
||||
import { createRequire } from 'module';
|
||||
import {
|
||||
KERNEL_SDK_PATH, SESSION_REFRESH_POLL_TIMEOUT, SESSION_REFRESH_POLL_WAIT,
|
||||
SESSION_LOGIN_VERIFY_WAIT
|
||||
} from './constants.mjs';
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
export async function refreshSession(platform, apiKey, connectionIds = {}) {
|
||||
const connectionId = connectionIds[platform];
|
||||
if (!connectionId) throw new Error(`No Kernel connection ID configured for platform: ${platform} — add it to settings.json under kernel.connection_ids`);
|
||||
|
||||
function getKernel(apiKey) {
|
||||
const Kernel = require(KERNEL_SDK_PATH);
|
||||
const kernel = new Kernel({ apiKey });
|
||||
|
||||
console.log(` 🔄 Refreshing ${platform} session...`);
|
||||
|
||||
// Trigger re-auth (uses stored credentials automatically)
|
||||
const loginResp = await kernel.auth.connections.login(connectionId);
|
||||
|
||||
if (loginResp.status === 'SUCCESS') {
|
||||
console.log(` ✅ ${platform} session refreshed`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not immediately successful, poll for up to 30s
|
||||
console.log(` ⏳ ${platform} session pending (status: ${loginResp.status}), polling...`);
|
||||
const start = Date.now();
|
||||
let pollCount = 0;
|
||||
while (Date.now() - start < SESSION_REFRESH_POLL_TIMEOUT) {
|
||||
await new Promise(r => setTimeout(r, SESSION_REFRESH_POLL_WAIT));
|
||||
pollCount++;
|
||||
const conn = await kernel.auth.connections.retrieve(connectionId);
|
||||
if (conn.status === 'SUCCESS') {
|
||||
console.log(` ✅ ${platform} session refreshed (after ${pollCount} polls)`);
|
||||
return true;
|
||||
}
|
||||
if (['FAILED', 'EXPIRED', 'CANCELED'].includes(conn.status)) {
|
||||
console.warn(` ⚠️ ${platform} session refresh failed: ${conn.status} (after ${pollCount} polls)`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(` ⚠️ ${platform} session refresh timed out`);
|
||||
return false;
|
||||
return new Kernel({ apiKey });
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify login after browser connects — if not logged in, trigger refresh and retry
|
||||
* Check auth connection status and re-auth if needed.
|
||||
* Returns { ok: true } or { ok: false, reason: string }
|
||||
*/
|
||||
export async function ensureLoggedIn(page, verifyFn, platform, apiKey, connectionIds = {}, maxAttempts = 2) {
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
const loggedIn = await verifyFn(page);
|
||||
if (loggedIn) return true;
|
||||
export async function ensureAuth(platform, apiKey, connectionIds = {}) {
|
||||
const connectionId = connectionIds[platform];
|
||||
if (!connectionId) {
|
||||
return { ok: false, reason: `no connection ID configured for ${platform}` };
|
||||
}
|
||||
|
||||
if (attempt < maxAttempts) {
|
||||
console.warn(` ⚠️ ${platform} not logged in (attempt ${attempt}), refreshing session...`);
|
||||
await refreshSession(platform, apiKey, connectionIds);
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.waitForTimeout(SESSION_LOGIN_VERIFY_WAIT);
|
||||
const kernel = getKernel(apiKey);
|
||||
|
||||
// Check current status
|
||||
let conn;
|
||||
try {
|
||||
conn = await kernel.auth.connections.retrieve(connectionId);
|
||||
} catch (e) {
|
||||
return { ok: false, reason: `connection ${connectionId} not found: ${e.message}` };
|
||||
}
|
||||
|
||||
if (conn.status === 'AUTHENTICATED') {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
// NEEDS_AUTH — can we auto re-auth?
|
||||
if (!conn.can_reauth) {
|
||||
return { ok: false, reason: `${platform} needs manual re-login (can_reauth=false). Go to Kernel dashboard or run: kernel auth connections login ${connectionId}` };
|
||||
}
|
||||
|
||||
// Trigger re-auth with stored credentials
|
||||
console.log(` 🔄 ${platform} session expired — re-authenticating...`);
|
||||
try {
|
||||
await kernel.auth.connections.login(connectionId);
|
||||
} catch (e) {
|
||||
return { ok: false, reason: `re-auth login() failed: ${e.message}` };
|
||||
}
|
||||
|
||||
// Poll until complete
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < SESSION_REFRESH_POLL_TIMEOUT) {
|
||||
await new Promise(r => setTimeout(r, SESSION_REFRESH_POLL_WAIT));
|
||||
try {
|
||||
conn = await kernel.auth.connections.retrieve(connectionId);
|
||||
} catch (e) {
|
||||
return { ok: false, reason: `polling failed: ${e.message}` };
|
||||
}
|
||||
|
||||
if (conn.status === 'AUTHENTICATED') {
|
||||
console.log(` ✅ ${platform} re-authenticated`);
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
if (conn.flow_status === 'FAILED') {
|
||||
return { ok: false, reason: `re-auth failed: ${conn.error_message || conn.error_code || 'unknown'}` };
|
||||
}
|
||||
if (conn.flow_status === 'EXPIRED' || conn.flow_status === 'CANCELED') {
|
||||
return { ok: false, reason: `re-auth ${conn.flow_status.toLowerCase()}` };
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return { ok: false, reason: `re-auth timed out after ${SESSION_REFRESH_POLL_TIMEOUT / 1000}s` };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user