Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agents/skills/javascript-proxy-headers/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const client = createProxyAxios({
});

const response = await client.get('https://httpbin.org/ip');
console.log(response.headers['x-proxymesh-ip']);
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

### node-fetch
Expand All @@ -73,7 +73,7 @@ const client = createProxyGot({
});

const response = await client('https://httpbin.org/ip');
console.log(response.headers['x-proxymesh-ip']);
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

### undici
Expand Down
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ Then install the HTTP client(s) you use (for example `axios`, `got`, `ky`, `wret

> **Note:** This package has no runtime dependencies by default—install only the adapters you need.

## 0.3.0 breaking changes

CONNECT response headers are **not** copied onto origin `response.headers`. Read them from the per-request `proxyHeaders` Map so concurrent requests stay isolated and hop-by-hop CONNECT headers cannot impersonate origin headers (`Set-Cookie`, `Location`, and similar).

```javascript
// ≤0.2.x (removed)
response.headers['x-proxymesh-ip']

// 0.3.x
response.proxyHeaders.get('x-proxymesh-ip')
```

`agent.lastProxyHeaders` still exists as a last-write-wins snapshot. Prefer `response.proxyHeaders` (or `getProxyHeaders()`) under concurrency.

## Quick Start

### axios
Expand All @@ -54,8 +68,7 @@ const client = createProxyAxios({

const response = await client.get('https://httpbin.org/ip');

// Proxy headers are merged into response.headers
console.log(response.headers['x-proxymesh-ip']);
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

### node-fetch
Expand Down Expand Up @@ -83,7 +96,7 @@ const client = createProxyGot({
});

const response = await client('https://httpbin.org/ip');
console.log(response.headers['x-proxymesh-ip']);
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

### undici
Expand Down Expand Up @@ -160,8 +173,7 @@ const res = await proxyNeedleGet('https://httpbin.org/ip', {
proxyHeaders: { 'X-ProxyMesh-Country': 'US' }
});

// CONNECT response headers merged onto res.headers where missing
console.log(res.headers['x-proxymesh-ip']);
console.log(res.proxyHeaders.get('x-proxymesh-ip'));
```

### typed-rest-client
Expand All @@ -177,16 +189,17 @@ const client = createProxyRestClient({
proxyHeaders: { 'X-ProxyMesh-Country': 'US' }
});

await client.get('https://httpbin.org/ip');
console.log(client.proxyAgent.lastProxyHeaders?.get('x-proxymesh-ip'));
const response = await client.get('https://httpbin.org/ip');
console.log(response.result);
console.log(response.proxyHeaders?.get('x-proxymesh-ip'));
```

### Core Agent (Advanced)

For direct control, use the core `ProxyHeadersAgent`:

```javascript
import { ProxyHeadersAgent } from 'javascript-proxy-headers';
import { ProxyHeadersAgent, getProxyHeaders } from 'javascript-proxy-headers';
import https from 'https';

const agent = new ProxyHeadersAgent('http://proxy.example.com:8080', {
Expand All @@ -197,7 +210,7 @@ const agent = new ProxyHeadersAgent('http://proxy.example.com:8080', {
});

https.get('https://httpbin.org/ip', { agent }, (res) => {
// Handle response
console.log(getProxyHeaders(res)?.get('x-proxymesh-ip'));
});
```

Expand Down
25 changes: 6 additions & 19 deletions docs/axios.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const response = await client.get('https://httpbin.org/ip');
// Access response data
console.log(response.data);

// Access proxy response headers (merged into response.headers)
console.log(response.headers['x-proxymesh-ip']);
// Access proxy CONNECT headers (not merged into origin response.headers)
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

## API Reference
Expand Down Expand Up @@ -93,29 +93,16 @@ const response = await post('https://httpbin.org/post', { key: 'value' }, {

## Accessing Proxy Headers

Proxy response headers are automatically merged into `response.headers`:
CONNECT response headers are on `response.proxyHeaders` (a `Map`). They are **not** copied onto origin `response.headers`.

```javascript
const response = await client.get('https://httpbin.org/ip');

// Proxy headers are available in response.headers
const proxyIp = response.headers['x-proxymesh-ip'];
const proxyCountry = response.headers['x-proxymesh-country'];
const proxyIp = response.proxyHeaders.get('x-proxymesh-ip');
const proxyCountry = response.proxyHeaders.get('x-proxymesh-country');
```

You can also access the underlying agent to get the last proxy headers:

```javascript
const client = await createProxyAxios({
proxy: 'http://proxy:8080',
proxyHeaders: { 'X-ProxyMesh-Country': 'US' }
});

await client.get('https://httpbin.org/ip');

// Access via the agent
console.log(client.proxyAgent.lastProxyHeaders);
```
`client.proxyAgent.lastProxyHeaders` is a last-write-wins snapshot of the most recent CONNECT. Use `response.proxyHeaders` for concurrent requests. Non-2xx responses attach the same Map on `error.response.proxyHeaders`.

## All Request Methods

Expand Down
26 changes: 20 additions & 6 deletions docs/core-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ new ProxyHeadersAgent(proxy, options)
### Example

```javascript
import { ProxyHeadersAgent } from 'javascript-proxy-headers';
import { ProxyHeadersAgent, getProxyHeaders } from 'javascript-proxy-headers';
import https from 'https';

const agent = new ProxyHeadersAgent('http://proxy.example.com:8080', {
Expand All @@ -56,7 +56,7 @@ const req = https.request({
res.on('data', chunk => body += chunk);
res.on('end', () => {
console.log(body);
console.log('Last proxy headers:', agent.lastProxyHeaders);
console.log('CONNECT headers:', getProxyHeaders(res));
});
});

Expand All @@ -72,7 +72,7 @@ req.end();
| `proxyAuth` | `string \| null` | Base64-encoded proxy auth |
| `proxyProtocol` | `string` | Proxy URL protocol (`http:` or `https:`) |
| `proxyHeaders` | `Object` | Headers to send to proxy |
| `lastProxyHeaders` | `Map \| null` | Headers from last CONNECT response |
| `lastProxyHeaders` | `Map \| null` | Most recent CONNECT headers (last-write-wins under concurrency) |

## ConnectError

Expand Down Expand Up @@ -184,6 +184,20 @@ const response = parseConnectResponse(buffer);
// }
```

### getProxyHeaders(source)

Return the CONNECT `Map` attached to a TLS socket, Node `IncomingMessage`, or HTTP client response. Use this instead of `agent.lastProxyHeaders` when requests may overlap.

```javascript
import { ProxyHeadersAgent, getProxyHeaders } from 'javascript-proxy-headers';
import https from 'https';

const agent = new ProxyHeadersAgent('http://proxy:8080');
https.get('https://example.com/', { agent }, (res) => {
console.log(getProxyHeaders(res)?.get('x-proxymesh-ip'));
});
```

## Using with Other Libraries

The core agent can be used with any library that accepts an `https.Agent`:
Expand All @@ -205,10 +219,10 @@ import { fetch, setGlobalDispatcher, Agent } from 'undici';

### With needle

For normal use, prefer the [needle adapter](needle.md) (`proxyNeedleGet` / `createProxyNeedle`), which merges CONNECT headers onto the response. To wire the agent yourself:
For normal use, prefer the [needle adapter](needle.md) (`proxyNeedleGet` / `createProxyNeedle`), which exposes CONNECT headers on `res.proxyHeaders`. To wire the agent yourself:

```javascript
import { ProxyHeadersAgent } from 'javascript-proxy-headers';
import { ProxyHeadersAgent, getProxyHeaders } from 'javascript-proxy-headers';
import needle from 'needle';

const agent = new ProxyHeadersAgent('http://proxy:8080', {
Expand All @@ -217,7 +231,7 @@ const agent = new ProxyHeadersAgent('http://proxy:8080', {

needle.get('https://httpbin.org/ip', { agent }, (err, resp) => {
console.log(resp.body);
console.log(agent.lastProxyHeaders);
console.log(getProxyHeaders(resp)?.get('x-proxymesh-ip'));
});
```

Expand Down
29 changes: 18 additions & 11 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ npm install typed-rest-client

Use [ky](ky.md) or [wretch](wretch.md) together with `node-fetch` (the adapters build on the same proxy-aware fetch as the node-fetch module).

## 0.3.0 breaking changes

CONNECT headers are no longer merged into origin `response.headers`. Use `response.proxyHeaders.get('x-proxymesh-ip')` instead. That keeps CONNECT metadata off the origin response (so `Set-Cookie` / `Location` from the proxy hop cannot impersonate the target) and keeps concurrent requests isolated.

`agent.lastProxyHeaders` is last-write-wins. Prefer per-response `proxyHeaders` or `getProxyHeaders()`.

## Quick Examples

### axios
Expand All @@ -42,7 +48,7 @@ const client = await createProxyAxios({

const response = await client.get('https://httpbin.org/ip');
console.log(response.data);
console.log(response.headers['x-proxymesh-ip']);
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

### node-fetch
Expand Down Expand Up @@ -72,7 +78,7 @@ const client = await createProxyGot({

const response = await client('https://httpbin.org/ip');
console.log(response.body);
console.log(response.headers['x-proxymesh-ip']);
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

### undici
Expand Down Expand Up @@ -105,7 +111,7 @@ const client = await createProxySuperagent({

const response = await client.get('https://httpbin.org/ip');
console.log(response.body);
console.log(response.headers['x-proxymesh-ip']);
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

### ky
Expand Down Expand Up @@ -165,7 +171,7 @@ const res = await proxyNeedleGet('https://httpbin.org/ip', {
});

console.log(res.body);
console.log(res.headers['x-proxymesh-ip']);
console.log(res.proxyHeaders.get('x-proxymesh-ip'));
```

### typed-rest-client
Expand All @@ -179,8 +185,8 @@ const client = createProxyRestClient({
proxyHeaders: { 'X-ProxyMesh-Country': 'US' },
});

await client.get('https://httpbin.org/ip');
console.log(client.proxyAgent.lastProxyHeaders?.get('x-proxymesh-ip'));
const response = await client.get('https://httpbin.org/ip');
console.log(response.proxyHeaders?.get('x-proxymesh-ip'));
```

## Understanding Proxy Headers
Expand Down Expand Up @@ -209,15 +215,16 @@ Proxy response headers from the CONNECT request are captured and made available.

| Library | Access Method |
|---------|---------------|
| axios | `response.headers['header-name']` (merged) |
| axios | `response.proxyHeaders.get('header-name')` |
| node-fetch | `response.proxyHeaders.get('header-name')` |
| got | `response.headers['header-name']` (merged) |
| got | `response.proxyHeaders.get('header-name')` |
| undici | `proxyHeaders.get('header-name')` |
| superagent | `response.headers['header-name']` (merged) |
| superagent | `response.proxyHeaders.get('header-name')` |
| ky / wretch | `response.proxyHeaders.get('header-name')` on the fetch `Response` |
| make-fetch-happen | `response.proxyHeaders.get('header-name')` |
| needle | `res.headers['header-name']` (merged where not already set) |
| typed-rest-client | `client.proxyAgent.lastProxyHeaders.get('header-name')` |
| needle | `res.proxyHeaders.get('header-name')` |
| typed-rest-client | `response.proxyHeaders.get('header-name')` (or `getProxyHeaders(httpResponse.message)`) |
| core `https.Agent` | `getProxyHeaders(incomingMessage)` or `onProxyConnect` |

## Proxy Authentication

Expand Down
11 changes: 4 additions & 7 deletions docs/got.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const response = await client('https://httpbin.org/ip');
// Access response data
console.log(response.body);

// Access proxy response headers (merged into response.headers)
console.log(response.headers['x-proxymesh-ip']);
// Access proxy CONNECT headers (not merged into origin response.headers)
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

## API Reference
Expand Down Expand Up @@ -89,15 +89,12 @@ const response = await proxyPost('https://httpbin.org/post', {

## Accessing Proxy Headers

Proxy response headers are merged into `response.headers` and also available via `response.proxyHeaders`:
CONNECT response headers are on `response.proxyHeaders` (a `Map`). They are **not** copied onto origin `response.headers`.

```javascript
const response = await client('https://httpbin.org/ip');

// Merged into response.headers
const proxyIp = response.headers['x-proxymesh-ip'];

// Also available separately
const proxyIp = response.proxyHeaders.get('x-proxymesh-ip');
const proxyHeaders = response.proxyHeaders; // Map
```

Expand Down
3 changes: 1 addition & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ const client = await createProxyAxios({

const response = await client.get('https://httpbin.org/ip');

// Proxy headers are merged into response.headers
console.log(response.headers['x-proxymesh-ip']);
console.log(response.proxyHeaders.get('x-proxymesh-ip'));
```

See the [Getting Started](getting-started.md) guide for more examples.
Expand Down
8 changes: 5 additions & 3 deletions docs/make-fetch-happen.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ console.log(response.proxyHeaders.get('x-proxymesh-ip'));

### createProxyMakeFetchHappen(options)

Builds `make-fetch-happen` with a `ProxyHeadersAgent`, then wraps the fetch so each response includes `proxyHeaders` from the last CONNECT.
Builds `make-fetch-happen` with a `ProxyHeadersAgent`, then wraps the fetch so each response includes `proxyHeaders` from **that request's** CONNECT.

**Parameters:**

Expand All @@ -44,7 +44,7 @@ Builds `make-fetch-happen` with a `ProxyHeadersAgent`, then wraps the fetch so e
**Returns:** A `fetch` function with:

- `.defaults(url, opts)` — same pattern as make-fetch-happen, still wrapped with `ProxyResponse`
- `.proxyAgent` — the `ProxyHeadersAgent` instance (for example `fetch.proxyAgent.lastProxyHeaders`)
- `.proxyAgent` — the `ProxyHeadersAgent` instance

**Example:**

Expand All @@ -67,7 +67,9 @@ console.log(res.proxyHeaders.get('x-proxymesh-ip'));

## Accessing Proxy Headers

Use `response.proxyHeaders.get('x-proxymesh-ip')` on the wrapped response, or read `fetch.proxyAgent.lastProxyHeaders` after a request.
Use `response.proxyHeaders.get('x-proxymesh-ip')` on the wrapped response. `fetch.proxyAgent.lastProxyHeaders` is last-write-wins and is not safe under concurrency.

Cached responses (`cachePath`) restore the CONNECT headers from the original network fetch for this fetch instance. They do not reuse `lastProxyHeaders` from a later request. After a process restart the in-memory map is empty, so a disk cache hit may have an empty `proxyHeaders` Map rather than another request's CONNECT metadata.

## Synchronous Factory

Expand Down
8 changes: 3 additions & 5 deletions docs/needle.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# needle

[needle](https://github.com/tomas/needle) is a lean HTTP client for Node. This package routes HTTPS through `ProxyHeadersAgent` and merges CONNECT response headers into the needle response where the same keys are not already set.
[needle](https://github.com/tomas/needle) is a lean HTTP client for Node. This package routes HTTPS through `ProxyHeadersAgent` and exposes CONNECT response headers on `res.proxyHeaders`.

## Getting Started

Expand All @@ -21,9 +21,7 @@ const res = await proxyNeedleGet('https://httpbin.org/ip', {
});

console.log(res.body);
// CONNECT headers merged into res.headers when missing
console.log(res.headers['x-proxymesh-ip']);
console.log(res.proxyAgent.lastProxyHeaders);
console.log(res.proxyHeaders.get('x-proxymesh-ip'));
```

## API Reference
Expand Down Expand Up @@ -67,7 +65,7 @@ const res = await get('https://httpbin.org/ip');

## Accessing Proxy Headers

Prefer `res.headers['x-proxymesh-ip']` after merge, or `res.proxyAgent.lastProxyHeaders` for the raw `Map` from the last CONNECT.
Use `res.proxyHeaders.get('x-proxymesh-ip')`. CONNECT headers are not merged into origin `res.headers`. `res.proxyAgent.lastProxyHeaders` is a last-write-wins snapshot of the shared agent.

## Core Agent

Expand Down
Loading
Loading