Files
claw-apply/lib/session.mjs
Matthew Jackson 668a40a51a Look up auth connections by domain instead of stored ID
Connection IDs change when re-authing. Instead of storing IDs in
settings.json (which go stale), look up connections by domain at
runtime via kernel.auth.connections.list({ domain }). This keeps
the applier in sync regardless of connection recreation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:55:26 -08:00

104 lines
3.3 KiB
JavaScript

/**
* session.mjs — Kernel Managed Auth session management
* Checks auth status before browser creation, triggers re-auth when needed.
*
* Looks up auth connections by domain (not stored ID) so it stays in sync
* even when connections are recreated.
*
* Flow:
* 1. List connections for domain, find the one matching our profile
* 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,
} from './constants.mjs';
const require = createRequire(import.meta.url);
const PLATFORM_DOMAINS = {
linkedin: 'linkedin.com',
wellfound: 'wellfound.com',
};
function getKernel(apiKey) {
const Kernel = require(KERNEL_SDK_PATH);
return new Kernel({ apiKey });
}
/**
* Find auth connection for a platform by domain lookup.
* Returns the connection object or null.
*/
async function findConnection(kernel, platform) {
const domain = PLATFORM_DOMAINS[platform];
if (!domain) return null;
const page = await kernel.auth.connections.list({ domain });
const connections = [];
for await (const conn of page) {
connections.push(conn);
}
if (connections.length === 0) return null;
// If multiple connections for same domain, prefer AUTHENTICATED
return connections.find(c => c.status === 'AUTHENTICATED') || connections[0];
}
/**
* Check auth connection status and re-auth if needed.
* Returns { ok: true } or { ok: false, reason: string }
*/
export async function ensureAuth(platform, apiKey) {
const kernel = getKernel(apiKey);
const conn = await findConnection(kernel, platform);
if (!conn) {
return { ok: false, reason: `no auth connection found for ${platform} (domain: ${PLATFORM_DOMAINS[platform]})` };
}
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). Run: kernel auth connections login ${conn.id}` };
}
// Trigger re-auth with stored credentials
console.log(` 🔄 ${platform} session expired — re-authenticating...`);
try {
await kernel.auth.connections.login(conn.id);
} 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));
let updated;
try {
updated = await kernel.auth.connections.retrieve(conn.id);
} catch (e) {
return { ok: false, reason: `polling failed: ${e.message}` };
}
if (updated.status === 'AUTHENTICATED') {
console.log(`${platform} re-authenticated`);
return { ok: true };
}
if (updated.flow_status === 'FAILED') {
return { ok: false, reason: `re-auth failed: ${updated.error_message || updated.error_code || 'unknown'}` };
}
if (updated.flow_status === 'EXPIRED' || updated.flow_status === 'CANCELED') {
return { ok: false, reason: `re-auth ${updated.flow_status.toLowerCase()}` };
}
}
return { ok: false, reason: `re-auth timed out after ${SESSION_REFRESH_POLL_TIMEOUT / 1000}s` };
}