2026-05-21 17:16:56 -07:00
|
|
|
/**
|
|
|
|
|
* End-to-end check: does a stored Tryst cookie constitute a live login?
|
|
|
|
|
*
|
2026-06-03 23:06:59 -07:00
|
|
|
* pnpm --filter @cocottetech/bookings-tryst-adapter e2e:cookie-login
|
2026-05-21 17:16:56 -07:00
|
|
|
*
|
|
|
|
|
* The cookie is a secret and is read from OUTSIDE the repo so the auto-commit
|
|
|
|
|
* service never sees it. Provide it one of two ways:
|
|
|
|
|
*
|
|
|
|
|
* TRYST_COOKIE="_tryst_session=...; other=..." pnpm ... e2e:cookie-login
|
|
|
|
|
* # or drop it in a file and point at it / use the default path:
|
|
|
|
|
* TRYST_COOKIE_FILE=/abs/path/to/cookie.txt pnpm ... e2e:cookie-login
|
|
|
|
|
*
|
|
|
|
|
* Default file path: ~/.cache/cocotte/tryst-cookie.txt
|
|
|
|
|
*
|
|
|
|
|
* Optional env:
|
|
|
|
|
* HEADFUL=1 run the browser headful (watch it)
|
|
|
|
|
* TRYST_USER_AGENT=... override the browser UA (match the cookie's device)
|
|
|
|
|
*
|
|
|
|
|
* This script does NOT touch platform.db — it verifies the login only. Wiring
|
|
|
|
|
* the cookie through the credentials vault is a separate, prod-DB-gated step.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
|
import { homedir } from 'node:os';
|
|
|
|
|
import { join } from 'node:path';
|
|
|
|
|
|
|
|
|
|
import { parseCookieBlob, cookieNames } from '../src/adapter/cookie-blob.js';
|
|
|
|
|
import { verifyTrystSession } from '../src/adapter/tryst-session.js';
|
|
|
|
|
|
|
|
|
|
const DEFAULT_COOKIE_FILE = join(homedir(), '.cache', 'cocotte', 'tryst-cookie.txt');
|
|
|
|
|
|
|
|
|
|
/** Line to stdout. CLI tooling — stdout is the deliverable, not app logging. */
|
|
|
|
|
function out(line = ''): void {
|
|
|
|
|
process.stdout.write(`${line}\n`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Line to stderr. */
|
|
|
|
|
function err(line = ''): void {
|
|
|
|
|
process.stderr.write(`${line}\n`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loadCookieBlob(): string {
|
|
|
|
|
const inline = process.env['TRYST_COOKIE'];
|
|
|
|
|
if (inline && inline.trim().length > 0) {
|
|
|
|
|
return inline;
|
|
|
|
|
}
|
|
|
|
|
const file = process.env['TRYST_COOKIE_FILE'] ?? DEFAULT_COOKIE_FILE;
|
|
|
|
|
try {
|
|
|
|
|
return readFileSync(file, 'utf8');
|
|
|
|
|
} catch {
|
|
|
|
|
printNoCookieHelp(file);
|
|
|
|
|
process.exit(2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function printNoCookieHelp(triedFile: string): void {
|
|
|
|
|
for (const line of [
|
|
|
|
|
'',
|
|
|
|
|
'No Tryst cookie found.',
|
|
|
|
|
' · TRYST_COOKIE env var: not set',
|
|
|
|
|
` · cookie file: not readable at ${triedFile}`,
|
|
|
|
|
'',
|
|
|
|
|
'How to get the cookie (the session cookie is HttpOnly — use DevTools, not document.cookie):',
|
|
|
|
|
' 1. Sign in to https://app.tryst.link in your browser.',
|
|
|
|
|
' 2. Open DevTools → Network tab → click any request to app.tryst.link.',
|
|
|
|
|
' 3. Under Request Headers, copy the entire "Cookie:" value.',
|
|
|
|
|
' 4. Pass it via TRYST_COOKIE="..." or save it to the file path above.',
|
|
|
|
|
'',
|
|
|
|
|
]) {
|
|
|
|
|
err(line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function main(): Promise<void> {
|
|
|
|
|
const blob = loadCookieBlob();
|
|
|
|
|
|
|
|
|
|
let cookies;
|
|
|
|
|
try {
|
|
|
|
|
cookies = parseCookieBlob(blob);
|
|
|
|
|
} catch (parseErr) {
|
|
|
|
|
err(`Could not parse the cookie blob: ${(parseErr as Error).message}`);
|
|
|
|
|
process.exit(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out(`Parsed ${cookies.length} cookie(s): ${cookieNames(cookies).join(', ')}`);
|
|
|
|
|
out('Launching stealth Chromium and navigating to app.tryst.link …');
|
|
|
|
|
out();
|
|
|
|
|
|
|
|
|
|
const screenshotPath = join('/tmp', `tryst-cookie-login-${Date.now()}.png`);
|
|
|
|
|
const userAgent = process.env['TRYST_USER_AGENT'];
|
|
|
|
|
const res = await verifyTrystSession(cookies, {
|
|
|
|
|
headless: process.env['HEADFUL'] !== '1',
|
|
|
|
|
...(userAgent ? { userAgent } : {}),
|
|
|
|
|
screenshotPath,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
out(res.loggedIn ? '✓ LOGGED IN' : '✗ NOT LOGGED IN');
|
|
|
|
|
out(` final URL : ${res.finalUrl || '(none)'}`);
|
|
|
|
|
out(` page title: ${res.pageTitle || '(none)'}`);
|
|
|
|
|
if (res.handle) out(` account : ${res.handle}`);
|
|
|
|
|
out(` reason : ${res.reason}`);
|
|
|
|
|
if (res.error) out(` error : ${res.error}`);
|
|
|
|
|
if (res.screenshotPath) out(` screenshot: ${res.screenshotPath}`);
|
|
|
|
|
|
|
|
|
|
process.exit(res.loggedIn ? 0 : 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void main();
|