Skip to content

Commit fdb1ddc

Browse files
committed
feat: browser-session v0.1.0 — reuse your browser's logged-in session from Node
Two capabilities, decoupled: - cookies (zero deps): read + AES-128-CBC-decrypt Brave/Chrome/Edge cookies via the macOS Keychain key. Handles v10/v11 + the 32-byte SHA256(host) prefix newer Chromium prepends (strip when the decrypted head isn't printable). The Node equivalent of yt-dlp --cookies-from-browser. - session (playwright optional): open any url headless with the session injected (scoped via page url so __Host-/__Secure- resolve), return rendered text + the requests the page made. Reads login-walled / JS pages a plain fetch can't. readPage(url, {browser}) composes both. CLI: cookies <domain> [--names] | read <url> [--browser]. 8 tests, tsc strict, MIT, macOS (cookie read) for now.
0 parents  commit fdb1ddc

13 files changed

Lines changed: 2502 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
node-version: [18, 20, 22]
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version: ${{ matrix.node-version }}
20+
cache: npm
21+
- run: npm ci
22+
- run: npm run typecheck
23+
- run: npm test
24+
- run: npm run build

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
dist/
3+
*.log
4+
.DS_Store

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Jason Poindexter
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# browser-session
2+
3+
[![CI](https://github.com/jpoindexter/browser-session/actions/workflows/ci.yml/badge.svg)](https://github.com/jpoindexter/browser-session/actions/workflows/ci.yml)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5+
[![Node](https://img.shields.io/badge/node-%3E%3D18-green.svg)](https://nodejs.org)
6+
[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](https://www.typescriptlang.org/)
7+
8+
**Reuse your real logged-in browser session from Node.** Two things:
9+
10+
1. **Read + decrypt your browser cookies** (Brave / Chrome / Edge) — **zero dependencies**. The Node equivalent of yt-dlp's `--cookies-from-browser`.
11+
2. **Open any page in a headless browser with that session** — read login-walled / JS-rendered pages a plain `fetch` can't (via Playwright).
12+
13+
No extension, no manual cookie export.
14+
15+
> ⚠️ **macOS only for now** (the cookie key lives in the login Keychain — one approval prompt the first time). The headless-read part is cross-platform; only the cookie auto-read is macOS-specific so far. Use a dedicated account for scraping — automated access can get accounts flagged.
16+
17+
## Install
18+
19+
```bash
20+
npm install browser-session
21+
# for the headless-read part:
22+
npm install playwright-core && npx playwright install chromium
23+
```
24+
25+
## Use
26+
27+
### Library
28+
29+
```ts
30+
import { getCookies, openWithSession, readPage } from "browser-session";
31+
32+
// 1) just the cookies (zero-dep)
33+
const c = getCookies({ browser: "brave", domain: "x.com" });
34+
if (c.ok) console.log(c.cookie); // "auth_token=…; ct0=…; …"
35+
36+
// 2) read a logged-in page in a real browser, session auto-injected
37+
const r = await readPage("https://www.reddit.com/", { browser: "brave" });
38+
if (r.ok) console.log(r.text); // rendered text of YOUR feed
39+
// console.log(r.requests); // every URL the page requested
40+
41+
// 3) bring your own cookie (any source)
42+
await openWithSession("https://x.com/i/bookmarks", "auth_token=…; ct0=…");
43+
```
44+
45+
Every call returns `{ ok: true, … } | { ok: false, error }` — errors as values, never throws.
46+
47+
### CLI
48+
49+
```bash
50+
browser-session cookies x.com --browser brave # print the cookie header
51+
browser-session cookies x.com --names # just the cookie names
52+
browser-session read https://www.reddit.com/ --browser brave # render your logged-in page
53+
browser-session read https://example.com # anonymous read
54+
```
55+
56+
## How it works
57+
58+
- **Cookies** (`src/cookies.ts`): pulls the AES key from the macOS Keychain (`<Browser> Safe Storage`), derives it (`PBKDF2(pw, "saltysalt", 1003, 16, sha1)`), reads the Cookies SQLite (copied to dodge the browser lock via the system `sqlite3`), and AES-128-CBC-decrypts each value. Handles the `v10`/`v11` prefix **and** the 32-byte `SHA256(host)` prefix newer Chromium prepends (stripped when the decrypted head isn't printable). Zero runtime deps.
59+
- **Session** (`src/session.ts`): launches headless Chromium (Playwright), injects the cookies scoped to the page origin (so `__Host-`/`__Secure-` prefixes resolve), navigates, and returns the body text + the request URLs. `playwright-core` is an **optional** dependency — the cookie reader works without it.
60+
61+
## API
62+
63+
`getCookies({ browser?, domain, profile? })` · `decryptCookie(hex, key)` · `openWithSession(url, cookie, { settleMs? })` · `readPage(url, { browser?, profile? })` · `cookieToPlaywright(header, url)` · `domainOf(url)`.
64+
65+
## License
66+
67+
MIT

0 commit comments

Comments
 (0)