Files
claw-apply/lib/session.mjs
Matthew Jackson 80d2323a37 Dynamic profile lookup from auth connection
Profile name is returned by ensureAuth() from the auth connection
(looked up by domain). No more storing profile names in settings.json
for the applier flow. createBrowser() still supports legacy platform
keys as fallback for searcher/setup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:02:19 -08:00

104 lines
3.4 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, profileName: conn.profile_name };
}
// 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, profileName: updated.profile_name };
}
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` };
}