/** * LinkedIn cookie capture via a controlled Chromium window. * * Flow * ---- * 1. The Melaya server emits `linkedin:start-login` to this runner over * Socket.IO when the user clicks "Connect LinkedIn" in the platform UI. * 2. We launch a HEADED Chromium window pointed at * https://www.linkedin.com/login. The user signs in normally — email, * password, any 2FA challenge — exactly like signing in in their * everyday browser. Their password never touches the runner or the * Melaya server: it goes from their keyboard straight to LinkedIn's * own login form. * 3. We poll the page URL. As soon as it lands on a logged-in LinkedIn * page (`/feed/*`, `/in/*`, `/mynetwork/*`, …) we read the * `li_at` and `JSESSIONID` cookies from the browser context. * 4. The cookies are returned over Socket.IO as `linkedin:login-result`. * The Melaya server stores them envelope-encrypted in * `agents.credentials` for that user, exactly like every other * connector credential. The browser window auto-closes. * * Lazy chromium install * --------------------- * We don't ship Chromium with the runner package (saves ~140 MB on * `npx @melaya/runner` first run). The first time a user clicks * "Connect LinkedIn", we shell out to `npx playwright install chromium` * and stream the install progress back to the UI so they see something * happening. Subsequent runs reuse the cached browser. * * Why a real browser at all * ------------------------- * LinkedIn's Voyager API (the only LinkedIn surface that exposes likes, * replies, and connection requests on personal accounts) is cookie-auth, * not OAuth. The cookies can only be obtained by completing a real login * that satisfies LinkedIn's CSRF + captcha + 2FA gauntlet. A headed * Chromium is the only flow that's both "the user logs in normally" and * "no DevTools required." The browser is used ONCE per ~year (or until * the session expires) — every subsequent Voyager call is direct * aiohttp HTTPS, no browser needed. */ export interface LinkedInLoginResult { ok: true; cookies: { li_at: string; JSESSIONID: string; }; profileName?: string; } export interface LinkedInLoginError { ok: false; error: string; detail?: string; } export type LinkedInLoginOutcome = LinkedInLoginResult | LinkedInLoginError; interface ProgressEmitter { (msg: string): void; } /** Close the in-flight LinkedIn sign-in browser, if any. Returns true if a * window was actually open. Best-effort — never throws. */ export declare function cancelActiveLinkedInLogin(): Promise; export declare function runLinkedInLoginFlow(progress: ProgressEmitter): Promise; /** * Pretty-print a one-liner summary of the outcome — used by the runner * CLI logger so the operator sees something on stdout while the web UI * also reflects the result via the relay. */ export declare function describeOutcome(outcome: LinkedInLoginOutcome): string; export {};