Socket
Book a DemoSign in
Socket

undici

Package Overview
Dependencies
Maintainers
3
Versions
272
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

undici - npm Package Compare versions

Comparing version
7.24.5
to
7.24.6
+8
-6
docs/docs/api/DiagnosticsChannel.md

@@ -185,4 +185,4 @@ # Diagnostics Channel Support

// Handshake response details
console.log(handshakeResponse.status) // 101 for successful WebSocket upgrade
console.log(handshakeResponse.statusText) // 'Switching Protocols'
console.log(handshakeResponse.status) // 101 for HTTP/1.1, 200 for HTTP/2 extended CONNECT
console.log(handshakeResponse.statusText) // 'Switching Protocols' for HTTP/1.1, commonly 'OK' for HTTP/2 in Node.js
console.log(handshakeResponse.headers) // Object containing response headers

@@ -194,11 +194,13 @@ })

The `handshakeResponse` object contains the HTTP response that upgraded the connection to WebSocket:
The `handshakeResponse` object contains the HTTP response that established the WebSocket connection:
- `status` (number): The HTTP status code (101 for successful WebSocket upgrade)
- `statusText` (string): The HTTP status message ('Switching Protocols' for successful upgrade)
- `status` (number): The HTTP status code (`101` for HTTP/1.1 upgrade, `200` for HTTP/2 extended CONNECT)
- `statusText` (string): The HTTP status message (`'Switching Protocols'` for HTTP/1.1, commonly `'OK'` for HTTP/2 in Node.js)
- `headers` (object): The HTTP response headers from the server, including:
- `sec-websocket-accept` and other WebSocket-related headers
- `upgrade: 'websocket'`
- `connection: 'upgrade'`
- `sec-websocket-accept` and other WebSocket-related headers
The `upgrade` and `connection` headers are only present for HTTP/1.1 handshakes.
This information is particularly useful for debugging and monitoring WebSocket connections, as it provides access to the initial HTTP handshake response that established the WebSocket connection.

@@ -205,0 +207,0 @@

@@ -13,2 +13,10 @@ # Fetch

When you use `FormData` as a request body, keep `fetch` and `FormData` from the
same implementation. Use the built-in global `FormData` with the built-in
global `fetch()`, and use `undici`'s `FormData` with `undici.fetch()`.
If you want the installed `undici` package to provide the globals, call
[`install()`](/docs/api/GlobalInstallation.md) so `fetch`, `Headers`,
`Response`, `Request`, and `FormData` are installed together as a matching set.
## Response

@@ -15,0 +23,0 @@

@@ -46,2 +46,50 @@ # Global Installation

## Using `FormData` with `fetch`
If you send a `FormData` body, use matching implementations for `fetch` and
`FormData`.
These two patterns are safe:
```js
// Built-in globals from Node.js
const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```
```js
// Globals installed from the undici package
import { install } from 'undici'
install()
const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```
After `install()`, `fetch`, `Headers`, `Response`, `Request`, and `FormData`
all come from the installed `undici` package, so they work as a matching set.
If you do not want to install globals, import both from `undici` instead:
```js
import { fetch, FormData } from 'undici'
const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```
Avoid mixing a global `FormData` with `undici.fetch()`, or `undici.FormData`
with the built-in global `fetch()`. Keeping them paired avoids surprising
multipart behavior across Node.js and undici versions.
## Use Cases

@@ -48,0 +96,0 @@

@@ -22,2 +22,89 @@ # Undici Module vs. Node.js Built-in Fetch

## Keep `fetch` and `FormData` from the same implementation
When you send a `FormData` body, keep `fetch` and `FormData` together from the
same implementation.
Use one of these patterns:
### Built-in globals
```js
const body = new FormData()
body.set('name', 'some')
body.set('someOtherProperty', '8000')
await fetch('https://example.com', {
method: 'POST',
body
})
```
### `undici` module imports
```js
import { fetch, FormData } from 'undici'
const body = new FormData()
body.set('name', 'some')
body.set('someOtherProperty', '8000')
await fetch('https://example.com', {
method: 'POST',
body
})
```
### `undici.install()` globals
If you want the installed `undici` package to provide the globals, call
[`install()`](/docs/api/GlobalInstallation.md):
```js
import { install } from 'undici'
install()
const body = new FormData()
body.set('name', 'some')
body.set('someOtherProperty', '8000')
await fetch('https://example.com', {
method: 'POST',
body
})
```
`install()` replaces the global `fetch`, `Headers`, `Response`, `Request`, and
`FormData` implementations with undici's versions, and also installs undici's
`WebSocket`, `CloseEvent`, `ErrorEvent`, `MessageEvent`, and `EventSource`
globals.
Avoid mixing implementations in the same request, for example:
```js
import { fetch } from 'undici'
const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```
```js
import { FormData } from 'undici'
const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```
Those combinations may behave differently across Node.js and undici versions.
Using matching pairs keeps multipart handling predictable.
## When you do NOT need to install undici

@@ -123,8 +210,8 @@

Installing undici from npm does not replace the built-in globals. If you want
your installed version to override the global `fetch`, use
[`setGlobalDispatcher`](/docs/api/GlobalInstallation.md) or import `fetch`
your installed version to replace the global `fetch` and related classes, use
[`install()`](/docs/api/GlobalInstallation.md). Otherwise, import `fetch`
directly from `'undici'`:
```js
import { fetch } from 'undici'; // uses your installed version, not the built-in
import { fetch } from 'undici' // uses your installed version, not the built-in
```

@@ -131,0 +218,0 @@

@@ -180,6 +180,8 @@ 'use strict'

evt => {
const {
address: { address, port }
} = evt
debugLog('connection opened %s%s', address, port ? `:${port}` : '')
if (evt.address != null) {
const { address, port } = evt.address
debugLog('connection opened %s%s', address, port ? `:${port}` : '')
} else {
debugLog('connection opened')
}
})

@@ -186,0 +188,0 @@

@@ -415,9 +415,17 @@ 'use strict'

} else if (headerName === 'connection') {
const value = typeof val === 'string' ? val.toLowerCase() : null
if (value !== 'close' && value !== 'keep-alive') {
// Per RFC 7230 Section 6.1, Connection header can contain
// a comma-separated list of connection option tokens (header names)
const value = typeof val === 'string' ? val : null
if (value === null) {
throw new InvalidArgumentError('invalid connection header')
}
if (value === 'close') {
request.reset = true
for (const token of value.toLowerCase().split(',')) {
const trimmed = token.trim()
if (!isValidHTTPToken(trimmed)) {
throw new InvalidArgumentError('invalid connection header')
}
if (trimmed === 'close') {
request.reset = true
}
}

@@ -424,0 +432,0 @@ } else if (headerName === 'expect') {

@@ -443,15 +443,35 @@ 'use strict'

if (val) {
if (typeof val === 'string') {
val = [val]
obj[key] = val
if (val !== undefined) {
if (!Object.hasOwn(obj, key)) {
const headersValue = typeof headers[i + 1] === 'string'
? headers[i + 1]
: Array.isArray(headers[i + 1])
? headers[i + 1].map(x => x.toString('latin1'))
: headers[i + 1].toString('latin1')
if (key === '__proto__') {
Object.defineProperty(obj, key, {
value: headersValue,
enumerable: true,
configurable: true,
writable: true
})
} else {
obj[key] = headersValue
}
} else {
if (typeof val === 'string') {
val = [val]
obj[key] = val
}
val.push(headers[i + 1].toString('latin1'))
}
val.push(headers[i + 1].toString('latin1'))
} else {
const headersValue = headers[i + 1]
if (typeof headersValue === 'string') {
obj[key] = headersValue
} else {
obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('latin1')) : headersValue.toString('latin1')
}
const headersValue = typeof headers[i + 1] === 'string'
? headers[i + 1]
: Array.isArray(headers[i + 1])
? headers[i + 1].map(x => x.toString('latin1'))
: headers[i + 1].toString('latin1')
obj[key] = headersValue
}

@@ -458,0 +478,0 @@ }

@@ -455,61 +455,66 @@ 'use strict'

client[kConnector]({
host,
hostname,
protocol,
port,
servername: client[kServerName],
localAddress: client[kLocalAddress]
}, (err, socket) => {
if (err) {
handleConnectError(client, err, { host, hostname, protocol, port })
client[kResume]()
return
}
try {
client[kConnector]({
host,
hostname,
protocol,
port,
servername: client[kServerName],
localAddress: client[kLocalAddress]
}, (err, socket) => {
if (err) {
handleConnectError(client, err, { host, hostname, protocol, port })
client[kResume]()
return
}
if (client.destroyed) {
util.destroy(socket.on('error', noop), new ClientDestroyedError())
client[kResume]()
return
}
if (client.destroyed) {
util.destroy(socket.on('error', noop), new ClientDestroyedError())
client[kResume]()
return
}
assert(socket)
assert(socket)
try {
client[kHTTPContext] = socket.alpnProtocol === 'h2'
? connectH2(client, socket)
: connectH1(client, socket)
} catch (err) {
socket.destroy().on('error', noop)
handleConnectError(client, err, { host, hostname, protocol, port })
client[kResume]()
return
}
try {
client[kHTTPContext] = socket.alpnProtocol === 'h2'
? connectH2(client, socket)
: connectH1(client, socket)
} catch (err) {
socket.destroy().on('error', noop)
handleConnectError(client, err, { host, hostname, protocol, port })
client[kResume]()
return
}
client[kConnecting] = false
client[kConnecting] = false
socket[kCounter] = 0
socket[kMaxRequests] = client[kMaxRequests]
socket[kClient] = client
socket[kError] = null
socket[kCounter] = 0
socket[kMaxRequests] = client[kMaxRequests]
socket[kClient] = client
socket[kError] = null
if (channels.connected.hasSubscribers) {
channels.connected.publish({
connectParams: {
host,
hostname,
protocol,
port,
version: client[kHTTPContext]?.version,
servername: client[kServerName],
localAddress: client[kLocalAddress]
},
connector: client[kConnector],
socket
})
}
if (channels.connected.hasSubscribers) {
channels.connected.publish({
connectParams: {
host,
hostname,
protocol,
port,
version: client[kHTTPContext]?.version,
servername: client[kServerName],
localAddress: client[kLocalAddress]
},
connector: client[kConnector],
socket
})
}
client.emit('connect', client[kUrl], [client])
client.emit('connect', client[kUrl], [client])
client[kResume]()
})
} catch (err) {
handleConnectError(client, err, { host, hostname, protocol, port })
client[kResume]()
})
}
}

@@ -516,0 +521,0 @@

@@ -138,3 +138,3 @@ 'use strict'

const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
if (!canCacheResponse(this.#cacheType, statusCode, resHeaders, cacheControlDirectives)) {
if (!canCacheResponse(this.#cacheType, statusCode, resHeaders, cacheControlDirectives, this.#cacheKey.headers)) {
return downstreamOnHeaders()

@@ -344,4 +344,5 @@ }

* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} [reqHeaders]
*/
function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives) {
function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives, reqHeaders) {
// Status code must be final and understood.

@@ -377,7 +378,15 @@ if (statusCode < 200 || NOT_UNDERSTOOD_STATUS_CODES.includes(statusCode)) {

// https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
if (resHeaders.authorization) {
if (!cacheControlDirectives.public || typeof resHeaders.authorization !== 'string') {
if (reqHeaders?.authorization) {
if (
!cacheControlDirectives.public &&
!cacheControlDirectives['s-maxage'] &&
!cacheControlDirectives['must-revalidate']
) {
return false
}
if (typeof reqHeaders.authorization !== 'string') {
return false
}
if (

@@ -384,0 +393,0 @@ Array.isArray(cacheControlDirectives['no-cache']) &&

@@ -30,3 +30,4 @@ 'use strict'

kMockAgentAcceptsNonStandardSearchParameters: Symbol('mock agent accepts non standard search parameters'),
kMockCallHistoryAddLog: Symbol('mock call history add log')
kMockCallHistoryAddLog: Symbol('mock call history add log'),
kTotalDispatchCount: Symbol('total dispatch count')
}

@@ -9,3 +9,4 @@ 'use strict'

kOrigin,
kGetNetConnect
kGetNetConnect,
kTotalDispatchCount
} = require('./mock-symbols')

@@ -210,2 +211,4 @@ const { serializePathWithQuery } = require('../core/util')

mockDispatches.push(newMockDispatch)
// Track total number of intercepts ever registered for better error messages
mockDispatches[kTotalDispatchCount] = (mockDispatches[kTotalDispatchCount] || 0) + 1
return newMockDispatch

@@ -406,4 +409,7 @@ }

const netConnect = agent[kGetNetConnect]()
const totalInterceptsCount = this[kDispatches][kTotalDispatchCount] || this[kDispatches].length
const pendingInterceptsCount = this[kDispatches].filter(({ consumed }) => !consumed).length
const interceptsMessage = `, ${pendingInterceptsCount} interceptor(s) remaining out of ${totalInterceptsCount} defined`
if (netConnect === false) {
throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`)
throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)${interceptsMessage}`)
}

@@ -413,3 +419,3 @@ if (checkNetConnect(netConnect, origin)) {

} else {
throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)`)
throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)${interceptsMessage}`)
}

@@ -416,0 +422,0 @@ } else {

@@ -28,2 +28,14 @@ 'use strict'

function getSocketAddress (socket) {
if (typeof socket?.address === 'function') {
return socket.address()
}
if (typeof socket?.session?.socket?.address === 'function') {
return socket.session.socket.address()
}
return null
}
/**

@@ -495,3 +507,3 @@ * @typedef {object} Handler

channels.open.publish({
address: response.socket.address(),
address: getSocketAddress(response.socket),
protocol: this.#protocol,

@@ -498,0 +510,0 @@ extensions: this.#extensions,

{
"name": "undici",
"version": "7.24.5",
"version": "7.24.6",
"description": "An HTTP/1.1 client, written from scratch for Node.js",

@@ -132,3 +132,3 @@ "homepage": "https://undici.nodejs.org",

"tsd": "^0.33.0",
"typescript": "^5.6.2",
"typescript": "^6.0.2",
"ws": "^8.11.0"

@@ -135,0 +135,0 @@ },

@@ -157,2 +157,53 @@ # undici

### Keep `fetch` and `FormData` together
When you send a `FormData` body, keep `fetch` and `FormData` from the same
implementation.
Use one of these patterns:
```js
// Built-in globals
const body = new FormData()
body.set('name', 'some')
await fetch('https://example.com', {
method: 'POST',
body
})
```
```js
// undici module imports
import { fetch, FormData } from 'undici'
const body = new FormData()
body.set('name', 'some')
await fetch('https://example.com', {
method: 'POST',
body
})
```
If you want the installed `undici` package to provide the globals, call
`install()` first:
```js
import { install } from 'undici'
install()
const body = new FormData()
body.set('name', 'some')
await fetch('https://example.com', {
method: 'POST',
body
})
```
`install()` replaces the global `fetch`, `Headers`, `Response`, `Request`, and
`FormData` implementations with undici's versions, so they all match.
Avoid mixing a global `FormData` with `undici.fetch()`, or `undici.FormData`
with the built-in global `fetch()`.
### Version Compatibility

@@ -267,2 +318,7 @@

When you call `install()`, these globals come from the same undici
implementation. For example, global `fetch` and global `FormData` will both be
undici's versions, which is the recommended setup if you want to use undici
through globals.
This is useful for:

@@ -269,0 +325,0 @@ - Polyfilling environments that don't have fetch