playwright-attach-chrome 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
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.
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # playwright-attach-chrome
2
+
3
+ > Drive a **real Chrome window** with Playwright. Capture, filter, and export every network request the page makes — JSON / NDJSON / HAR. MIT.
4
+
5
+ [![npm](https://img.shields.io/npm/v/playwright-attach-chrome.svg)](https://www.npmjs.com/package/playwright-attach-chrome)
6
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ [العربية ↓](#عربي)
9
+
10
+ ---
11
+
12
+ ## Why
13
+
14
+ Playwright normally launches its own ephemeral Chromium. That means **no cookies, no logins, no extensions** from your real browser. If you want to reverse-engineer an internal API of a SaaS you're already logged into, that's a hard wall.
15
+
16
+ This package launches Chrome with a **persistent user-data-dir** and **`--remote-debugging-port`**, then connects Playwright to it via CDP. You log in once, Chrome remembers you forever (the cookies live in the data dir), and Playwright sees every request the page makes.
17
+
18
+ Works on macOS, Linux, and Windows. Auto-detects Chrome / Chromium / Edge / Brave.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install -g playwright-attach-chrome
24
+ # or run without install:
25
+ npx playwright-attach-chrome capture --url https://example.com
26
+ ```
27
+
28
+ ## Quick start — CLI
29
+
30
+ ```bash
31
+ # Launch Chrome at a URL, capture every request, save to HAR:
32
+ pac capture \
33
+ --url https://business.gathern.co \
34
+ --filter api.gathern.co \
35
+ --har gathern.har \
36
+ --user-data-dir ~/.gathern-profile
37
+ ```
38
+
39
+ You'll see Chrome open. **Log in once.** Click around. Every matching request is printed live:
40
+
41
+ ```
42
+ → GET https://api.gathern.co/v1/business/chalet/list?access-token=...
43
+ ← 200 https://api.gathern.co/v1/business/chalet/list?access-token=...
44
+ → POST https://api.gathern.co/v1/business/reservation/filter
45
+ ← 200 https://api.gathern.co/v1/business/reservation/filter
46
+ ```
47
+
48
+ Press Ctrl-C — `gathern.har` is written. Open it in Chrome DevTools (Network tab → drag-and-drop) or Postman / Insomnia.
49
+
50
+ Reuse the same `--user-data-dir` next time and you skip the login.
51
+
52
+ ### CLI flags
53
+
54
+ | Flag | Description |
55
+ | --- | --- |
56
+ | `--url <url>` | URL to open in the first tab |
57
+ | `--filter <pattern>` | Substring (e.g. `api.gathern.co`) or `/regex/` (e.g. `/\\/api\\/v\\d/`) |
58
+ | `--har <path>` | Write a HAR 1.2 file on exit |
59
+ | `--ndjson <path>` | Stream every event as NDJSON (one JSON per line) |
60
+ | `--user-data-dir <path>` | Persistent profile dir. Reuse across runs to keep the login. |
61
+ | `--port <number>` | CDP port (default 9222) |
62
+ | `--chrome-path <path>` | Manual Chrome binary path |
63
+ | `--no-bodies` | Skip request/response bodies (saves memory) |
64
+ | `--max-body-bytes <n>` | Truncate bodies larger than n bytes (default 1 MB) |
65
+ | `--quiet` | Don't print the live log |
66
+
67
+ ### Attach to an already-running Chrome
68
+
69
+ If you've already launched Chrome with `--remote-debugging-port=9222` yourself:
70
+
71
+ ```bash
72
+ pac attach --port 9222 --filter api.gathern.co --har out.har
73
+ ```
74
+
75
+ Useful when you want to debug a session that's already in progress without restarting Chrome.
76
+
77
+ ## Quick start — library
78
+
79
+ ```ts
80
+ import { launchChrome, attach, captureContext, writeHar } from 'playwright-attach-chrome';
81
+
82
+ const chrome = await launchChrome({
83
+ startUrl: 'https://business.gathern.co',
84
+ userDataDir: '/Users/me/.gathern-profile', // persistent: keep login
85
+ });
86
+
87
+ const { browser, defaultContext } = await attach({ cdpUrl: chrome.cdpUrl });
88
+
89
+ const capture = captureContext(defaultContext, {
90
+ filter: 'api.gathern.co',
91
+ onEvent: (e) => console.log(e.direction, e.method, e.url, e.status),
92
+ });
93
+
94
+ // ...let the user navigate, then:
95
+ process.on('SIGINT', async () => {
96
+ await capture.stop();
97
+ await writeHar(capture.events, 'gathern.har');
98
+ await browser.close();
99
+ process.exit(0);
100
+ });
101
+
102
+ await new Promise(() => {}); // park
103
+ ```
104
+
105
+ ## The trick (for anyone curious)
106
+
107
+ Chrome only allows DevTools Protocol attach on **a profile that was launched with the flag**. Your normal Chrome — the one with your real Gmail, Drive, banking tabs — is **not attachable**, on purpose: Chrome blocks it so a hostile script can't silently steal your cookies.
108
+
109
+ The workaround is a **parallel Chrome profile**:
110
+
111
+ ```bash
112
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
113
+ --remote-debugging-port=9222 \
114
+ --user-data-dir=$HOME/.my-capture-profile
115
+ ```
116
+
117
+ That spawns a separate Chrome window with its own cookies, extensions, and bookmarks. You log in *there* once. Persisted. Playwright connects to that one.
118
+
119
+ This package wraps that ceremony so you can stop typing the flag soup.
120
+
121
+ ## Caveats
122
+
123
+ - **Can't attach to your real Chrome.** That's a hard CDP rule, not a bug here.
124
+ - **First run = log in once.** After that, reuse the same `--user-data-dir` and you're in.
125
+ - **Bodies are stored in memory.** Long sessions on heavy sites can grow. Use `--max-body-bytes` or `--no-bodies`.
126
+ - **Cleanup is your call.** The CLI leaves the Chrome window open when you Ctrl-C, so you can keep using it. Quit Chrome manually when done.
127
+
128
+ ## License
129
+
130
+ MIT.
131
+
132
+ ---
133
+
134
+ <a id="عربي"></a>
135
+
136
+ # playwright-attach-chrome — العربية
137
+
138
+ > شغّل Playwright على نافذة Chrome حقيقية. التقط طلبات الشبكة وفلترها واحفظها بصيغة JSON / NDJSON / HAR.
139
+
140
+ ## ليش
141
+
142
+ Playwright عادةً يفتح متصفح Chromium مؤقت بدون كوكيز ولا حسابك المسجّل. لو تبي تتجسس على API داخلي لخدمة أنت أصلًا داخل عليها (Gathern مثلًا)، هذا يقطع الطريق.
143
+
144
+ الأداة هذي تشغّل Chrome **بملف تعريف ثابت** على المنفذ ‎9222‎ (CDP)، ثم تربط Playwright عليه. تسجّل دخولك مرة وحدة، وChrome يحفظك للأبد (الكوكيز محفوظة في مجلد البروفايل)، وPlaywright يشوف كل طلب يطلع من الصفحة.
145
+
146
+ تدعم macOS و Linux و Windows. تكتشف Chrome / Chromium / Edge / Brave تلقائيًا.
147
+
148
+ ## التثبيت
149
+
150
+ ```bash
151
+ npm install -g playwright-attach-chrome
152
+ # أو بدون تثبيت:
153
+ npx playwright-attach-chrome capture --url https://example.com
154
+ ```
155
+
156
+ ## استعمال سريع — أوامر طرفية
157
+
158
+ ```bash
159
+ pac capture \
160
+ --url https://business.gathern.co \
161
+ --filter api.gathern.co \
162
+ --har gathern.har \
163
+ --user-data-dir ~/.gathern-profile
164
+ ```
165
+
166
+ يفتح Chrome. **سجّل دخولك مرة وحدة.** تجوّل في الموقع. كل طلب يطابق الفلتر يظهر في الطرفية مباشرة.
167
+
168
+ اضغط Ctrl-C — يُكتب ملف `gathern.har`. افتحه في Chrome DevTools (سحب وإفلات على لسان Network) أو في Postman / Insomnia.
169
+
170
+ استخدم نفس `--user-data-dir` في المرة الجاية تتفادى تسجيل الدخول.
171
+
172
+ ## الحيلة (لو تحب تفهم)
173
+
174
+ Chrome يرفض الاتصال عبر DevTools Protocol على **بروفايلك الأصلي** — البروفايل اللي فيه جيميل والبنك وكل شي — أمنيًا، حتى لا يقدر سكربت خبيث يسرق كوكيزك بصمت.
175
+
176
+ الحل: **بروفايل Chrome موازي**:
177
+
178
+ ```bash
179
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
180
+ --remote-debugging-port=9222 \
181
+ --user-data-dir=$HOME/.my-capture-profile
182
+ ```
183
+
184
+ نافذة Chrome ثانية، كوكيزها وحساباتها مستقلة. تسجّل دخولك فيها. وPlaywright يربط عليها.
185
+
186
+ الأداة هذي تختصر عليك كتابة هذا كله.
187
+
188
+ ## ملاحظات مهمة
189
+
190
+ - **ما تقدر تربط على Chrome العادي** — قاعدة من Chrome نفسه، مو من الأداة.
191
+ - **سجّل دخولك أول مرة فقط** — أعد استخدام نفس `--user-data-dir` لاحقًا.
192
+ - **الأجسام (bodies) تتخزن في الذاكرة** — للجلسات الطويلة استخدم `--max-body-bytes` أو `--no-bodies`.
193
+ - **التنظيف عليك** — لما تضغط Ctrl-C، نافذة Chrome تبقى مفتوحة لتكمل عملك. اقفلها يدويًا.
194
+
195
+ ## الترخيص
196
+
197
+ MIT.
@@ -0,0 +1,23 @@
1
+ import { type Browser, type BrowserContext } from 'playwright-core';
2
+ export interface AttachOptions {
3
+ /** CDP endpoint, e.g. `http://localhost:9222`. */
4
+ cdpUrl: string;
5
+ /** Slow-mo, ms between actions. Useful for demos. */
6
+ slowMo?: number;
7
+ }
8
+ export interface AttachedSession {
9
+ browser: Browser;
10
+ /** All contexts (one per Chrome profile). Usually length 1. */
11
+ contexts: BrowserContext[];
12
+ /** Default context — the existing profile's context. */
13
+ defaultContext: BrowserContext;
14
+ }
15
+ /**
16
+ * Connect to a running Chrome instance via CDP. The Chrome must have been
17
+ * started with `--remote-debugging-port=<port>` and `--user-data-dir=<dir>`.
18
+ *
19
+ * Returns immediately; the caller is responsible for closing the browser
20
+ * (or leaving Chrome running and just disconnecting).
21
+ */
22
+ export declare function attach(opts: AttachOptions): Promise<AttachedSession>;
23
+ //# sourceMappingURL=attach.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../src/attach.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE9E,MAAM,WAAW,aAAa;IAC1B,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,wDAAwD;IACxD,cAAc,EAAE,cAAc,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAe1E"}
package/dist/attach.js ADDED
@@ -0,0 +1,20 @@
1
+ import { chromium } from 'playwright-core';
2
+ /**
3
+ * Connect to a running Chrome instance via CDP. The Chrome must have been
4
+ * started with `--remote-debugging-port=<port>` and `--user-data-dir=<dir>`.
5
+ *
6
+ * Returns immediately; the caller is responsible for closing the browser
7
+ * (or leaving Chrome running and just disconnecting).
8
+ */
9
+ export async function attach(opts) {
10
+ const browser = await chromium.connectOverCDP(opts.cdpUrl, {
11
+ slowMo: opts.slowMo,
12
+ });
13
+ const contexts = browser.contexts();
14
+ const defaultContext = contexts[0];
15
+ if (!defaultContext) {
16
+ throw new Error(`Connected to ${opts.cdpUrl} but no browser context was returned. Is Chrome actually running?`);
17
+ }
18
+ return { browser, contexts, defaultContext };
19
+ }
20
+ //# sourceMappingURL=attach.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attach.js","sourceRoot":"","sources":["../src/attach.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAqC,MAAM,iBAAiB,CAAC;AAiB9E;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAmB;IAC5C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE;QACvD,MAAM,EAAE,IAAI,CAAC,MAAM;KACtB,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IACpC,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,CAAC,cAAc,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACX,gBAAgB,IAAI,CAAC,MAAM,mEAAmE,CACjG,CAAC;IACN,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AACjD,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { BrowserContext } from 'playwright-core';
2
+ export interface CapturedEvent {
3
+ ts: string;
4
+ direction: 'request' | 'response';
5
+ method: string;
6
+ url: string;
7
+ status?: number;
8
+ requestHeaders?: Record<string, string>;
9
+ responseHeaders?: Record<string, string>;
10
+ requestBody?: string | null;
11
+ responseBody?: string | null;
12
+ resourceType?: string;
13
+ }
14
+ export interface CaptureOptions {
15
+ /** Substring or RegExp the URL must match. */
16
+ filter?: string | RegExp;
17
+ /** Include request headers. Default true. */
18
+ includeRequestHeaders?: boolean;
19
+ /** Include response headers. Default true. */
20
+ includeResponseHeaders?: boolean;
21
+ /** Include request body. Default true. */
22
+ includeRequestBody?: boolean;
23
+ /** Include response body. Default true. */
24
+ includeResponseBody?: boolean;
25
+ /** Max response body size in bytes (UTF-8). Larger bodies are truncated. */
26
+ maxBodyBytes?: number;
27
+ /** If set, append each event as NDJSON to this file path. */
28
+ outputNdjson?: string;
29
+ /** Called for each captured event. */
30
+ onEvent?: (event: CapturedEvent) => void;
31
+ }
32
+ export interface CaptureHandle {
33
+ /** Stop listening and close the output file (if any). */
34
+ stop: () => Promise<void>;
35
+ /** In-memory list of all captured events. */
36
+ events: CapturedEvent[];
37
+ }
38
+ /**
39
+ * Subscribe to every request/response in a Playwright context, optionally
40
+ * filtering by URL pattern. Pairs requests with their responses but emits
41
+ * each as a separate event so streams stay flush-friendly.
42
+ */
43
+ export declare function captureContext(context: BrowserContext, opts?: CaptureOptions): CaptureHandle;
44
+ //# sourceMappingURL=capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,iBAAiB,CAAC;AAE/E,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC3B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,6CAA6C;IAC7C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,8CAA8C;IAC9C,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,0CAA0C;IAC1C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,2CAA2C;IAC3C,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC5C;AAUD,MAAM,WAAW,aAAa;IAC1B,yDAAyD;IACzD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,6CAA6C;IAC7C,MAAM,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC1B,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,cAAmB,GAC1B,aAAa,CA4Ff"}
@@ -0,0 +1,120 @@
1
+ import { createWriteStream } from 'node:fs';
2
+ const DEFAULTS = {
3
+ includeRequestHeaders: true,
4
+ includeResponseHeaders: true,
5
+ includeRequestBody: true,
6
+ includeResponseBody: true,
7
+ maxBodyBytes: 1_000_000,
8
+ };
9
+ /**
10
+ * Subscribe to every request/response in a Playwright context, optionally
11
+ * filtering by URL pattern. Pairs requests with their responses but emits
12
+ * each as a separate event so streams stay flush-friendly.
13
+ */
14
+ export function captureContext(context, opts = {}) {
15
+ const settings = { ...DEFAULTS, ...opts };
16
+ const events = [];
17
+ let stream = null;
18
+ if (opts.outputNdjson) {
19
+ stream = createWriteStream(opts.outputNdjson, { flags: 'a' });
20
+ }
21
+ const matches = (url) => {
22
+ if (!opts.filter)
23
+ return true;
24
+ return typeof opts.filter === 'string'
25
+ ? url.includes(opts.filter)
26
+ : opts.filter.test(url);
27
+ };
28
+ const emit = (event) => {
29
+ events.push(event);
30
+ opts.onEvent?.(event);
31
+ if (stream) {
32
+ stream.write(JSON.stringify(event) + '\n');
33
+ }
34
+ };
35
+ const onRequest = async (req) => {
36
+ if (!matches(req.url()))
37
+ return;
38
+ let requestBody = null;
39
+ if (settings.includeRequestBody) {
40
+ const post = req.postData();
41
+ requestBody = post === null ? null : truncate(post, settings.maxBodyBytes);
42
+ }
43
+ emit({
44
+ ts: new Date().toISOString(),
45
+ direction: 'request',
46
+ method: req.method(),
47
+ url: req.url(),
48
+ requestHeaders: settings.includeRequestHeaders ? await safeHeaders(req) : undefined,
49
+ requestBody,
50
+ resourceType: req.resourceType(),
51
+ });
52
+ };
53
+ const onResponse = async (res) => {
54
+ if (!matches(res.url()))
55
+ return;
56
+ let responseBody = null;
57
+ if (settings.includeResponseBody) {
58
+ try {
59
+ const buf = await res.body();
60
+ responseBody = truncate(buf.toString('utf8'), settings.maxBodyBytes);
61
+ }
62
+ catch {
63
+ responseBody = null;
64
+ }
65
+ }
66
+ emit({
67
+ ts: new Date().toISOString(),
68
+ direction: 'response',
69
+ method: res.request().method(),
70
+ url: res.url(),
71
+ status: res.status(),
72
+ responseHeaders: settings.includeResponseHeaders ? await safeResponseHeaders(res) : undefined,
73
+ responseBody,
74
+ resourceType: res.request().resourceType(),
75
+ });
76
+ };
77
+ const onPage = (page) => {
78
+ page.on('request', (req) => {
79
+ void onRequest(req);
80
+ });
81
+ page.on('response', (res) => {
82
+ void onResponse(res);
83
+ });
84
+ };
85
+ for (const page of context.pages()) {
86
+ onPage(page);
87
+ }
88
+ context.on('page', onPage);
89
+ return {
90
+ events,
91
+ stop: async () => {
92
+ context.off('page', onPage);
93
+ if (stream) {
94
+ await new Promise((resolve) => stream.end(resolve));
95
+ }
96
+ },
97
+ };
98
+ }
99
+ async function safeHeaders(req) {
100
+ try {
101
+ return await req.allHeaders();
102
+ }
103
+ catch {
104
+ return req.headers();
105
+ }
106
+ }
107
+ async function safeResponseHeaders(res) {
108
+ try {
109
+ return await res.allHeaders();
110
+ }
111
+ catch {
112
+ return res.headers();
113
+ }
114
+ }
115
+ function truncate(body, max) {
116
+ if (body.length <= max)
117
+ return body;
118
+ return body.slice(0, max) + `…[truncated ${body.length - max} bytes]`;
119
+ }
120
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ /**
3
+ * Locate a Chrome / Chromium-family binary on disk. Returns the first match
4
+ * across the candidate list for the current platform, or throws.
5
+ */
6
+ export declare function findChromeBinary(): string;
7
+ export interface LaunchChromeOptions {
8
+ /** Path to Chrome binary. Auto-detected if omitted. */
9
+ chromePath?: string;
10
+ /** CDP debugging port. Default 9222. */
11
+ port?: number;
12
+ /** User data dir. A temp dir is created if omitted (fresh profile each run). */
13
+ userDataDir?: string;
14
+ /** URL to open in the first tab. */
15
+ startUrl?: string;
16
+ /** Extra flags to pass to Chrome. */
17
+ extraArgs?: string[];
18
+ }
19
+ export interface LaunchedChrome {
20
+ process: ChildProcess;
21
+ port: number;
22
+ userDataDir: string;
23
+ cdpUrl: string;
24
+ }
25
+ /**
26
+ * Launch Chrome with remote debugging enabled and a dedicated profile dir.
27
+ * Returns once the CDP endpoint responds, so the caller can immediately
28
+ * connect via `chromium.connectOverCDP()`.
29
+ */
30
+ export declare function launchChrome(opts?: LaunchChromeOptions): Promise<LaunchedChrome>;
31
+ //# sourceMappingURL=chrome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome.d.ts","sourceRoot":"","sources":["../src/chrome.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAiC9D;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAkBzC;AAED,MAAM,WAAW,mBAAmB;IAChC,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,cAAc,CAAC,CA2B1F"}
package/dist/chrome.js ADDED
@@ -0,0 +1,91 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, mkdtempSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { setTimeout as delay } from 'node:timers/promises';
6
+ const MAC_CANDIDATES = [
7
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
8
+ '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
9
+ '/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev',
10
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
11
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
12
+ '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
13
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
14
+ ];
15
+ const LINUX_CANDIDATES = [
16
+ '/usr/bin/google-chrome',
17
+ '/usr/bin/google-chrome-stable',
18
+ '/usr/bin/chromium',
19
+ '/usr/bin/chromium-browser',
20
+ '/usr/bin/microsoft-edge',
21
+ '/usr/bin/brave-browser',
22
+ '/snap/bin/chromium',
23
+ ];
24
+ const WIN_CANDIDATES = [
25
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
26
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
27
+ 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
28
+ 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
29
+ ];
30
+ /**
31
+ * Locate a Chrome / Chromium-family binary on disk. Returns the first match
32
+ * across the candidate list for the current platform, or throws.
33
+ */
34
+ export function findChromeBinary() {
35
+ const candidates = process.platform === 'darwin'
36
+ ? MAC_CANDIDATES
37
+ : process.platform === 'win32'
38
+ ? WIN_CANDIDATES
39
+ : LINUX_CANDIDATES;
40
+ for (const candidate of candidates) {
41
+ if (existsSync(candidate)) {
42
+ return candidate;
43
+ }
44
+ }
45
+ throw new Error(`Could not find Chrome on ${process.platform}. Looked in:\n ${candidates.join('\n ')}\n` +
46
+ `Pass --chrome-path to specify the binary explicitly.`);
47
+ }
48
+ /**
49
+ * Launch Chrome with remote debugging enabled and a dedicated profile dir.
50
+ * Returns once the CDP endpoint responds, so the caller can immediately
51
+ * connect via `chromium.connectOverCDP()`.
52
+ */
53
+ export async function launchChrome(opts = {}) {
54
+ const binary = opts.chromePath ?? findChromeBinary();
55
+ const port = opts.port ?? 9222;
56
+ const userDataDir = opts.userDataDir ?? mkdtempSync(join(tmpdir(), 'pac-chrome-'));
57
+ const args = [
58
+ `--remote-debugging-port=${port}`,
59
+ `--user-data-dir=${userDataDir}`,
60
+ '--no-first-run',
61
+ '--no-default-browser-check',
62
+ ...(opts.extraArgs ?? []),
63
+ ];
64
+ if (opts.startUrl) {
65
+ args.push(opts.startUrl);
66
+ }
67
+ const child = spawn(binary, args, { stdio: 'ignore', detached: false });
68
+ child.on('error', (err) => {
69
+ console.error('[chrome] failed to start:', err.message);
70
+ });
71
+ const cdpUrl = `http://localhost:${port}`;
72
+ await waitForCdp(cdpUrl);
73
+ return { process: child, port, userDataDir, cdpUrl };
74
+ }
75
+ async function waitForCdp(cdpUrl, timeoutMs = 15000) {
76
+ const start = Date.now();
77
+ while (Date.now() - start < timeoutMs) {
78
+ try {
79
+ const res = await fetch(`${cdpUrl}/json/version`);
80
+ if (res.ok) {
81
+ return;
82
+ }
83
+ }
84
+ catch {
85
+ // Chrome not up yet, keep polling.
86
+ }
87
+ await delay(150);
88
+ }
89
+ throw new Error(`Chrome CDP did not respond at ${cdpUrl} within ${timeoutMs}ms.`);
90
+ }
91
+ //# sourceMappingURL=chrome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome.js","sourceRoot":"","sources":["../src/chrome.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { attach } from './attach.js';
4
+ import { captureContext } from './capture.js';
5
+ import { launchChrome } from './chrome.js';
6
+ import { writeHar } from './har.js';
7
+ const program = new Command();
8
+ program
9
+ .name('playwright-attach-chrome')
10
+ .description('Launch (or attach to) a Chrome window, then capture & export the network traffic of whatever you do inside it.')
11
+ .version('0.1.0');
12
+ program
13
+ .command('capture')
14
+ .description('Launch Chrome, attach Playwright, capture network as you browse.')
15
+ .option('-u, --url <url>', 'URL to open in the first tab')
16
+ .option('-f, --filter <pattern>', 'Only capture URLs containing this substring (or matching this /regex/)')
17
+ .option('-o, --har <path>', 'Write a HAR file on exit')
18
+ .option('-n, --ndjson <path>', 'Stream events as NDJSON to this file')
19
+ .option('-p, --port <number>', 'CDP port (default 9222)', '9222')
20
+ .option('--chrome-path <path>', 'Path to Chrome binary (auto-detected if omitted)')
21
+ .option('--user-data-dir <path>', 'Chrome profile directory. Reuse the same path across runs to keep your session/cookies.')
22
+ .option('--no-bodies', 'Skip request/response bodies (saves memory on large pages)')
23
+ .option('--max-body-bytes <number>', 'Truncate bodies larger than this many bytes', '1000000')
24
+ .option('--quiet', 'Suppress the live log')
25
+ .action(async (opts) => {
26
+ const filter = parseFilter(opts.filter);
27
+ const chrome = await launchChrome({
28
+ chromePath: opts.chromePath,
29
+ port: Number(opts.port),
30
+ userDataDir: opts.userDataDir,
31
+ startUrl: opts.url,
32
+ });
33
+ log(`Chrome started.`);
34
+ log(` CDP: ${chrome.cdpUrl}`);
35
+ log(` Profile dir: ${chrome.userDataDir}`);
36
+ if (opts.userDataDir) {
37
+ log(` (Profile is persistent — reuse --user-data-dir on next run to keep your login.)`);
38
+ }
39
+ else {
40
+ log(` (Profile is a temp dir — session will be lost when Chrome closes.)`);
41
+ }
42
+ const { browser, defaultContext } = await attach({ cdpUrl: chrome.cdpUrl });
43
+ log(`Playwright attached. Capturing…`);
44
+ if (filter) {
45
+ log(` Filter: ${opts.filter}`);
46
+ }
47
+ if (opts.ndjson) {
48
+ log(` Streaming → ${opts.ndjson}`);
49
+ }
50
+ if (opts.har) {
51
+ log(` HAR (on exit) → ${opts.har}`);
52
+ }
53
+ log(`\n Press Ctrl-C to stop and flush.\n`);
54
+ const capture = captureContext(defaultContext, {
55
+ filter,
56
+ includeRequestBody: opts.bodies !== false,
57
+ includeResponseBody: opts.bodies !== false,
58
+ maxBodyBytes: Number(opts.maxBodyBytes),
59
+ outputNdjson: opts.ndjson,
60
+ onEvent: opts.quiet ? undefined : printEvent,
61
+ });
62
+ const cleanup = async () => {
63
+ log('\nStopping capture…');
64
+ await capture.stop();
65
+ if (opts.har) {
66
+ await writeHar(capture.events, opts.har);
67
+ log(`HAR written: ${opts.har} (${capture.events.length} events)`);
68
+ }
69
+ await browser.close().catch(() => { });
70
+ // Leave the Chrome process running — the user owns it. They can quit it manually.
71
+ process.exit(0);
72
+ };
73
+ process.on('SIGINT', cleanup);
74
+ process.on('SIGTERM', cleanup);
75
+ // Park forever; capture happens through event listeners.
76
+ await new Promise(() => { });
77
+ });
78
+ program
79
+ .command('attach')
80
+ .description('Attach to an already-running Chrome (launched separately with --remote-debugging-port).')
81
+ .requiredOption('-p, --port <number>', 'CDP port the existing Chrome is listening on')
82
+ .option('-f, --filter <pattern>', 'URL substring or /regex/')
83
+ .option('-o, --har <path>', 'Write a HAR file on exit')
84
+ .option('-n, --ndjson <path>', 'Stream events as NDJSON to this file')
85
+ .option('--no-bodies', 'Skip request/response bodies')
86
+ .option('--max-body-bytes <number>', 'Truncate bodies larger than this many bytes', '1000000')
87
+ .option('--quiet', 'Suppress the live log')
88
+ .action(async (opts) => {
89
+ const filter = parseFilter(opts.filter);
90
+ const cdpUrl = `http://localhost:${opts.port}`;
91
+ log(`Attaching to ${cdpUrl}…`);
92
+ const { browser, defaultContext } = await attach({ cdpUrl });
93
+ log(`Attached. Capturing…`);
94
+ if (filter)
95
+ log(` Filter: ${opts.filter}`);
96
+ if (opts.ndjson)
97
+ log(` Streaming → ${opts.ndjson}`);
98
+ if (opts.har)
99
+ log(` HAR (on exit) → ${opts.har}`);
100
+ log(`\n Press Ctrl-C to stop and flush.\n`);
101
+ const capture = captureContext(defaultContext, {
102
+ filter,
103
+ includeRequestBody: opts.bodies !== false,
104
+ includeResponseBody: opts.bodies !== false,
105
+ maxBodyBytes: Number(opts.maxBodyBytes),
106
+ outputNdjson: opts.ndjson,
107
+ onEvent: opts.quiet ? undefined : printEvent,
108
+ });
109
+ const cleanup = async () => {
110
+ log('\nStopping capture…');
111
+ await capture.stop();
112
+ if (opts.har) {
113
+ await writeHar(capture.events, opts.har);
114
+ log(`HAR written: ${opts.har} (${capture.events.length} events)`);
115
+ }
116
+ await browser.close().catch(() => { });
117
+ process.exit(0);
118
+ };
119
+ process.on('SIGINT', cleanup);
120
+ process.on('SIGTERM', cleanup);
121
+ await new Promise(() => { });
122
+ });
123
+ program.parseAsync(process.argv).catch((err) => {
124
+ console.error(err.message);
125
+ process.exit(1);
126
+ });
127
+ function parseFilter(input) {
128
+ if (!input)
129
+ return undefined;
130
+ const re = input.match(/^\/(.*)\/([gimsuy]*)$/);
131
+ if (re && re[1] !== undefined) {
132
+ return new RegExp(re[1], re[2]);
133
+ }
134
+ return input;
135
+ }
136
+ function printEvent(event) {
137
+ if (event.direction === 'request') {
138
+ log(`→ ${event.method.padEnd(6)} ${event.url}`);
139
+ }
140
+ else {
141
+ const status = event.status ?? 0;
142
+ log(`← ${String(status).padEnd(6)} ${event.url}`);
143
+ }
144
+ }
145
+ function log(line) {
146
+ process.stdout.write(line + '\n');
147
+ }
148
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/har.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { CapturedEvent } from './capture.js';
2
+ /**
3
+ * Pair captured request/response events by URL+method and emit a HAR 1.2
4
+ * document. Open the resulting .har in Chrome DevTools (Network → Import)
5
+ * or postman / insomnia.
6
+ */
7
+ export declare function writeHar(events: CapturedEvent[], outputPath: string): Promise<void>;
8
+ //# sourceMappingURL=har.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"har.d.ts","sourceRoot":"","sources":["../src/har.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAkDlD;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BzF"}
package/dist/har.js ADDED
@@ -0,0 +1,91 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ /**
3
+ * Pair captured request/response events by URL+method and emit a HAR 1.2
4
+ * document. Open the resulting .har in Chrome DevTools (Network → Import)
5
+ * or postman / insomnia.
6
+ */
7
+ export async function writeHar(events, outputPath) {
8
+ const entries = [];
9
+ const pending = new Map();
10
+ for (const event of events) {
11
+ const key = `${event.method} ${event.url}`;
12
+ if (event.direction === 'request') {
13
+ pending.set(key, event);
14
+ continue;
15
+ }
16
+ const req = pending.get(key);
17
+ if (!req) {
18
+ // response without a paired request (rare, e.g. captured mid-flight)
19
+ continue;
20
+ }
21
+ pending.delete(key);
22
+ entries.push(buildEntry(req, event));
23
+ }
24
+ const har = {
25
+ log: {
26
+ version: '1.2',
27
+ creator: { name: 'playwright-attach-chrome', version: '0.1.0' },
28
+ entries,
29
+ },
30
+ };
31
+ await writeFile(outputPath, JSON.stringify(har, null, 2), 'utf8');
32
+ }
33
+ function buildEntry(req, res) {
34
+ const url = new URL(req.url);
35
+ const query = Array.from(url.searchParams.entries()).map(([name, value]) => ({
36
+ name,
37
+ value,
38
+ }));
39
+ const reqHeaders = headersToArray(req.requestHeaders);
40
+ const resHeaders = headersToArray(res.responseHeaders);
41
+ const contentType = res.responseHeaders?.['content-type'] ??
42
+ res.responseHeaders?.['Content-Type'] ??
43
+ 'application/octet-stream';
44
+ const postContentType = req.requestHeaders?.['content-type'] ??
45
+ req.requestHeaders?.['Content-Type'] ??
46
+ 'application/x-www-form-urlencoded';
47
+ return {
48
+ startedDateTime: req.ts,
49
+ time: Math.max(0, new Date(res.ts).getTime() - new Date(req.ts).getTime()),
50
+ request: {
51
+ method: req.method,
52
+ url: req.url,
53
+ httpVersion: 'HTTP/1.1',
54
+ headers: reqHeaders,
55
+ queryString: query,
56
+ cookies: [],
57
+ headersSize: -1,
58
+ bodySize: req.requestBody?.length ?? 0,
59
+ ...(req.requestBody ? { postData: { mimeType: postContentType, text: req.requestBody } } : {}),
60
+ },
61
+ response: {
62
+ status: res.status ?? 0,
63
+ statusText: '',
64
+ httpVersion: 'HTTP/1.1',
65
+ headers: resHeaders,
66
+ cookies: [],
67
+ content: {
68
+ size: res.responseBody?.length ?? 0,
69
+ mimeType: contentType,
70
+ ...(res.responseBody !== null && res.responseBody !== undefined
71
+ ? { text: res.responseBody }
72
+ : {}),
73
+ },
74
+ redirectURL: '',
75
+ headersSize: -1,
76
+ bodySize: res.responseBody?.length ?? 0,
77
+ },
78
+ cache: {},
79
+ timings: {
80
+ send: 0,
81
+ wait: Math.max(0, new Date(res.ts).getTime() - new Date(req.ts).getTime()),
82
+ receive: 0,
83
+ },
84
+ };
85
+ }
86
+ function headersToArray(headers) {
87
+ if (!headers)
88
+ return [];
89
+ return Object.entries(headers).map(([name, value]) => ({ name, value }));
90
+ }
91
+ //# sourceMappingURL=har.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"har.js","sourceRoot":"","sources":["../src/har.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { findChromeBinary, launchChrome } from './chrome.js';
2
+ export type { LaunchChromeOptions, LaunchedChrome } from './chrome.js';
3
+ export { attach } from './attach.js';
4
+ export type { AttachOptions, AttachedSession } from './attach.js';
5
+ export { captureContext } from './capture.js';
6
+ export type { CaptureOptions, CaptureHandle, CapturedEvent } from './capture.js';
7
+ export { writeHar } from './har.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEjF,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { findChromeBinary, launchChrome } from './chrome.js';
2
+ export { attach } from './attach.js';
3
+ export { captureContext } from './capture.js';
4
+ export { writeHar } from './har.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG7D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "playwright-attach-chrome",
3
+ "version": "0.1.0",
4
+ "description": "Attach Playwright to a real Chrome session — capture & filter network traffic from a logged-in window. Export HAR.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "playwright-attach-chrome": "./dist/cli.js",
10
+ "pac": "./dist/cli.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "dev": "tsc --watch",
20
+ "prepublishOnly": "npm run build",
21
+ "start": "node dist/cli.js"
22
+ },
23
+ "keywords": [
24
+ "playwright",
25
+ "chrome",
26
+ "cdp",
27
+ "network-capture",
28
+ "har",
29
+ "devtools",
30
+ "reverse-engineering",
31
+ "api-inspection"
32
+ ],
33
+ "author": "",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "commander": "^12.1.0",
37
+ "playwright-core": "^1.50.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.10.0",
41
+ "typescript": "^5.7.0"
42
+ },
43
+ "engines": {
44
+ "node": ">=18"
45
+ }
46
+ }