Comparing version 5.22.0 to 5.22.1
@@ -10,15 +10,21 @@ # Errors | ||
| Error | Error Codes | Description | | ||
| ------------------------------------ | ------------------------------------- | -------------------------------------------------- | | ||
| `InvalidArgumentError` | `UND_ERR_INVALID_ARG` | passed an invalid argument. | | ||
| `InvalidReturnValueError` | `UND_ERR_INVALID_RETURN_VALUE` | returned an invalid value. | | ||
| `RequestAbortedError` | `UND_ERR_ABORTED` | the request has been aborted by the user | | ||
| `ClientDestroyedError` | `UND_ERR_DESTROYED` | trying to use a destroyed client. | | ||
| `ClientClosedError` | `UND_ERR_CLOSED` | trying to use a closed client. | | ||
| `SocketError` | `UND_ERR_SOCKET` | there is an error with the socket. | | ||
| `NotSupportedError` | `UND_ERR_NOT_SUPPORTED` | encountered unsupported functionality. | | ||
| `RequestContentLengthMismatchError` | `UND_ERR_REQ_CONTENT_LENGTH_MISMATCH` | request body does not match content-length header | | ||
| `ResponseContentLengthMismatchError` | `UND_ERR_RES_CONTENT_LENGTH_MISMATCH` | response body does not match content-length header | | ||
| `InformationalError` | `UND_ERR_INFO` | expected error with reason | | ||
| `ResponseExceededMaxSizeError` | `UND_ERR_RES_EXCEEDED_MAX_SIZE` | response body exceed the max size allowed | | ||
| Error | Error Codes | Description | | ||
| ------------------------------------ | ------------------------------------- | ------------------------------------------------------------------------- | | ||
| `UndiciError` | `UND_ERR` | all errors below are extended from `UndiciError`. | | ||
| `ConnectTimeoutError` | `UND_ERR_CONNECT_TIMEOUT` | socket is destroyed due to connect timeout. | | ||
| `HeadersTimeoutError` | `UND_ERR_HEADERS_TIMEOUT` | socket is destroyed due to headers timeout. | | ||
| `HeadersOverflowError` | `UND_ERR_HEADERS_OVERFLOW` | socket is destroyed due to headers' max size being exceeded. | | ||
| `BodyTimeoutError` | `UND_ERR_BODY_TIMEOUT` | socket is destroyed due to body timeout. | | ||
| `ResponseStatusCodeError` | `UND_ERR_RESPONSE_STATUS_CODE` | an error is thrown when `throwOnError` is `true` for status codes >= 400. | | ||
| `InvalidArgumentError` | `UND_ERR_INVALID_ARG` | passed an invalid argument. | | ||
| `InvalidReturnValueError` | `UND_ERR_INVALID_RETURN_VALUE` | returned an invalid value. | | ||
| `RequestAbortedError` | `UND_ERR_ABORTED` | the request has been aborted by the user | | ||
| `ClientDestroyedError` | `UND_ERR_DESTROYED` | trying to use a destroyed client. | | ||
| `ClientClosedError` | `UND_ERR_CLOSED` | trying to use a closed client. | | ||
| `SocketError` | `UND_ERR_SOCKET` | there is an error with the socket. | | ||
| `NotSupportedError` | `UND_ERR_NOT_SUPPORTED` | encountered unsupported functionality. | | ||
| `RequestContentLengthMismatchError` | `UND_ERR_REQ_CONTENT_LENGTH_MISMATCH` | request body does not match content-length header | | ||
| `ResponseContentLengthMismatchError` | `UND_ERR_RES_CONTENT_LENGTH_MISMATCH` | response body does not match content-length header | | ||
| `InformationalError` | `UND_ERR_INFO` | expected error with reason | | ||
| `ResponseExceededMaxSizeError` | `UND_ERR_RES_EXCEEDED_MAX_SIZE` | response body exceed the max size allowed | | ||
@@ -25,0 +31,0 @@ ### `SocketError` |
@@ -11,2 +11,4 @@ # Fetch | ||
In Node versions v18.13.0 and above and v19.2.0 and above, undici will default to using Node's [File](https://nodejs.org/api/buffer.html#class-file) class. In versions where it's not available, it will default to the undici one. | ||
## FormData | ||
@@ -13,0 +15,0 @@ |
# Class: WebSocket | ||
> ⚠️ Warning: the WebSocket API is experimental and has known bugs. | ||
> ⚠️ Warning: the WebSocket API is experimental. | ||
Extends: [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) | ||
The WebSocket object provides a way to manage a WebSocket connection to a server, allowing bidirectional communication. The API follows the [WebSocket spec](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket). | ||
The WebSocket object provides a way to manage a WebSocket connection to a server, allowing bidirectional communication. The API follows the [WebSocket spec](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) and [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455). | ||
@@ -14,4 +14,27 @@ ## `new WebSocket(url[, protocol])` | ||
* **url** `URL | string` - The url's protocol *must* be `ws` or `wss`. | ||
* **protocol** `string | string[]` (optional) - Subprotocol(s) to request the server use. | ||
* **protocol** `string | string[] | WebSocketInit` (optional) - Subprotocol(s) to request the server use, or a [`Dispatcher`](./Dispatcher.md). | ||
### Example: | ||
This example will not work in browsers or other platforms that don't allow passing an object. | ||
```mjs | ||
import { WebSocket, ProxyAgent } from 'undici' | ||
const proxyAgent = new ProxyAgent('my.proxy.server') | ||
const ws = new WebSocket('wss://echo.websocket.events', { | ||
dispatcher: proxyAgent, | ||
protocols: ['echo', 'chat'] | ||
}) | ||
``` | ||
If you do not need a custom Dispatcher, it's recommended to use the following pattern: | ||
```mjs | ||
import { WebSocket } from 'undici' | ||
const ws = new WebSocket('wss://echo.websocket.events', ['echo', 'chat']) | ||
``` | ||
## Read More | ||
@@ -18,0 +41,0 @@ |
@@ -27,2 +27,3 @@ import Dispatcher from'./types/dispatcher' | ||
export * from './types/content-type' | ||
export * from './types/cache' | ||
export { Interceptable } from './types/mock-interceptor' | ||
@@ -56,2 +57,3 @@ | ||
var fetch: typeof import('./types/fetch').fetch; | ||
var caches: typeof import('./types/cache').caches; | ||
} |
@@ -124,2 +124,9 @@ 'use strict' | ||
module.exports.getGlobalOrigin = getGlobalOrigin | ||
const { CacheStorage } = require('./lib/cache/cachestorage') | ||
const { kConstruct } = require('./lib/cache/symbols') | ||
// Cache & CacheStorage are tightly coupled with fetch. Even if it may run | ||
// in an older version of Node, it doesn't have any use without fetch. | ||
module.exports.caches = new CacheStorage(kConstruct) | ||
} | ||
@@ -126,0 +133,0 @@ |
@@ -572,3 +572,6 @@ // @ts-check | ||
const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) | ||
message = Buffer.from(llhttp.memory.buffer, ptr, len).toString() | ||
message = | ||
'Response does not match the HTTP/1.1 protocol (' + | ||
Buffer.from(llhttp.memory.buffer, ptr, len).toString() + | ||
')' | ||
} | ||
@@ -1498,5 +1501,7 @@ throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset)) | ||
const onData = function (chunk) { | ||
if (finished) { | ||
return | ||
} | ||
try { | ||
assert(!finished) | ||
if (!writer.write(chunk) && this.pause) { | ||
@@ -1510,3 +1515,5 @@ this.pause() | ||
const onDrain = function () { | ||
assert(!finished) | ||
if (finished) { | ||
return | ||
} | ||
@@ -1513,0 +1520,0 @@ if (body.resume) { |
@@ -126,2 +126,3 @@ 'use strict' | ||
length = 0 | ||
let hasUnknownSizeValue = false | ||
@@ -142,3 +143,7 @@ for (const [name, value] of object) { | ||
blobParts.push(chunk, value, rn) | ||
length += chunk.byteLength + value.size + rn.byteLength | ||
if (typeof value.size === 'number') { | ||
length += chunk.byteLength + value.size + rn.byteLength | ||
} else { | ||
hasUnknownSizeValue = true | ||
} | ||
} | ||
@@ -150,2 +155,5 @@ } | ||
length += chunk.byteLength | ||
if (hasUnknownSizeValue) { | ||
length = null | ||
} | ||
@@ -152,0 +160,0 @@ // Set source to object. |
const assert = require('assert') | ||
const { atob } = require('buffer') | ||
const { isValidHTTPToken, isomorphicDecode } = require('./util') | ||
const { isomorphicDecode } = require('./util') | ||
const encoder = new TextEncoder() | ||
// Regex | ||
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-z0-9]+$/ | ||
/** | ||
* @see https://mimesniff.spec.whatwg.org/#http-token-code-point | ||
*/ | ||
const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-Za-z0-9]+$/ | ||
const HTTP_WHITESPACE_REGEX = /(\u000A|\u000D|\u0009|\u0020)/ // eslint-disable-line | ||
// https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point | ||
const HTTP_QUOTED_STRING_TOKENS = /^(\u0009|\x{0020}-\x{007E}|\x{0080}-\x{00FF})+$/ // eslint-disable-line | ||
/** | ||
* @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point | ||
*/ | ||
const HTTP_QUOTED_STRING_TOKENS = /[\u0009|\u0020-\u007E|\u0080-\u00FF]/ // eslint-disable-line | ||
@@ -41,4 +45,2 @@ // https://fetch.spec.whatwg.org/#data-url-processor | ||
// from mimeType. | ||
// Note: This will only remove U+0020 SPACE code | ||
// points, if any. | ||
// Undici implementation note: we need to store the | ||
@@ -49,3 +51,3 @@ // length because if the mimetype has spaces removed, | ||
const mimeTypeLength = mimeType.length | ||
mimeType = mimeType.replace(/^(\u0020)+|(\u0020)+$/g, '') | ||
mimeType = removeASCIIWhitespace(mimeType, true, true) | ||
@@ -238,3 +240,3 @@ // 7. If position is past the end of input, then | ||
// from input. | ||
input = input.trim() | ||
input = removeHTTPWhitespace(input, true, true) | ||
@@ -280,3 +282,3 @@ // 2. Let position be a position variable for input, | ||
// 8. Remove any trailing HTTP whitespace from subtype. | ||
subtype = subtype.trimEnd() | ||
subtype = removeHTTPWhitespace(subtype, false, true) | ||
@@ -289,2 +291,5 @@ // 9. If subtype is the empty string or does not solely | ||
const typeLowercase = type.toLowerCase() | ||
const subtypeLowercase = subtype.toLowerCase() | ||
// 10. Let mimeType be a new MIME type record whose type | ||
@@ -295,8 +300,8 @@ // is type, in ASCII lowercase, and subtype is subtype, | ||
const mimeType = { | ||
type: type.toLowerCase(), | ||
subtype: subtype.toLowerCase(), | ||
type: typeLowercase, | ||
subtype: subtypeLowercase, | ||
/** @type {Map<string, string>} */ | ||
parameters: new Map(), | ||
// https://mimesniff.spec.whatwg.org/#mime-type-essence | ||
essence: `${type}/${subtype}` | ||
essence: `${typeLowercase}/${subtypeLowercase}` | ||
} | ||
@@ -379,4 +384,3 @@ | ||
// 2. Remove any trailing HTTP whitespace from parameterValue. | ||
// Note: it says "trailing" whitespace; leading is fine. | ||
parameterValue = parameterValue.trimEnd() | ||
parameterValue = removeHTTPWhitespace(parameterValue, false, true) | ||
@@ -398,3 +402,3 @@ // 3. If parameterValue is the empty string, then continue. | ||
HTTP_TOKEN_CODEPOINTS.test(parameterName) && | ||
!HTTP_QUOTED_STRING_TOKENS.test(parameterValue) && | ||
(parameterValue.length === 0 || HTTP_QUOTED_STRING_TOKENS.test(parameterValue)) && | ||
!mimeType.parameters.has(parameterName) | ||
@@ -533,7 +537,7 @@ ) { | ||
assert(mimeType !== 'failure') | ||
const { type, subtype, parameters } = mimeType | ||
const { parameters, essence } = mimeType | ||
// 1. Let serialization be the concatenation of mimeType’s | ||
// type, U+002F (/), and mimeType’s subtype. | ||
let serialization = `${type}/${subtype}` | ||
let serialization = essence | ||
@@ -553,3 +557,3 @@ // 2. For each name → value of mimeType’s parameters: | ||
// points or value is the empty string, then: | ||
if (!isValidHTTPToken(value)) { | ||
if (!HTTP_TOKEN_CODEPOINTS.test(value)) { | ||
// 1. Precede each occurence of U+0022 (") or | ||
@@ -574,2 +578,55 @@ // U+005C (\) in value with U+005C (\). | ||
/** | ||
* @see https://fetch.spec.whatwg.org/#http-whitespace | ||
* @param {string} char | ||
*/ | ||
function isHTTPWhiteSpace (char) { | ||
return char === '\r' || char === '\n' || char === '\t' || char === ' ' | ||
} | ||
/** | ||
* @see https://fetch.spec.whatwg.org/#http-whitespace | ||
* @param {string} str | ||
*/ | ||
function removeHTTPWhitespace (str, leading = true, trailing = true) { | ||
let lead = 0 | ||
let trail = str.length - 1 | ||
if (leading) { | ||
for (; lead < str.length && isHTTPWhiteSpace(str[lead]); lead++); | ||
} | ||
if (trailing) { | ||
for (; trail > 0 && isHTTPWhiteSpace(str[trail]); trail--); | ||
} | ||
return str.slice(lead, trail + 1) | ||
} | ||
/** | ||
* @see https://infra.spec.whatwg.org/#ascii-whitespace | ||
* @param {string} char | ||
*/ | ||
function isASCIIWhitespace (char) { | ||
return char === '\r' || char === '\n' || char === '\t' || char === '\f' || char === ' ' | ||
} | ||
/** | ||
* @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace | ||
*/ | ||
function removeASCIIWhitespace (str, leading = true, trailing = true) { | ||
let lead = 0 | ||
let trail = str.length - 1 | ||
if (leading) { | ||
for (; lead < str.length && isASCIIWhitespace(str[lead]); lead++); | ||
} | ||
if (trailing) { | ||
for (; trail > 0 && isASCIIWhitespace(str[trail]); trail--); | ||
} | ||
return str.slice(lead, trail + 1) | ||
} | ||
module.exports = { | ||
@@ -576,0 +633,0 @@ dataURLProcessor, |
@@ -470,3 +470,3 @@ 'use strict' | ||
if ('headers' in init && init.headers != null) { | ||
fill(response[kState].headersList, init.headers) | ||
fill(response[kHeaders], init.headers) | ||
} | ||
@@ -573,3 +573,4 @@ | ||
filterResponse, | ||
Response | ||
Response, | ||
cloneResponse | ||
} |
@@ -1031,3 +1031,4 @@ 'use strict' | ||
urlHasHttpsScheme, | ||
urlIsHttpHttpsScheme | ||
urlIsHttpHttpsScheme, | ||
readAllBytes | ||
} |
@@ -54,2 +54,9 @@ 'use strict' | ||
webidl.illegalConstructor = function () { | ||
throw webidl.errors.exception({ | ||
header: 'TypeError', | ||
message: 'Illegal constructor' | ||
}) | ||
} | ||
// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values | ||
@@ -56,0 +63,0 @@ webidl.util.Type = function (V) { |
@@ -16,3 +16,5 @@ 'use strict' | ||
const { fetching } = require('../fetch/index') | ||
const { Headers } = require('../fetch/headers') | ||
const { getGlobalDispatcher } = require('../global') | ||
const { kHeadersList } = require('../core/symbols') | ||
@@ -30,4 +32,5 @@ const channels = {} | ||
* @param {(response: any) => void} onEstablish | ||
* @param {Partial<import('../../types/websocket').WebSocketInit>} options | ||
*/ | ||
function establishWebSocketConnection (url, protocols, ws, onEstablish) { | ||
function establishWebSocketConnection (url, protocols, ws, onEstablish, options) { | ||
// 1. Let requestURL be a copy of url, with its scheme set to "http", if url’s | ||
@@ -53,2 +56,9 @@ // scheme is "ws", and to "https" otherwise. | ||
// Note: undici extension, allow setting custom headers. | ||
if (options.headers) { | ||
const headersList = new Headers(options.headers)[kHeadersList] | ||
request.headersList = headersList | ||
} | ||
// 3. Append (`Upgrade`, `websocket`) to request’s header list. | ||
@@ -94,3 +104,3 @@ // 4. Append (`Connection`, `Upgrade`) to request’s header list. | ||
useParallelQueue: true, | ||
dispatcher: getGlobalDispatcher(), | ||
dispatcher: options.dispatcher ?? getGlobalDispatcher(), | ||
processResponse (response) { | ||
@@ -97,0 +107,0 @@ // 1. If response is a network error or its status is not 101, |
@@ -46,3 +46,3 @@ 'use strict' | ||
if (payloadLength === 126) { | ||
new DataView(buffer.buffer).setUint16(2, bodyLength) | ||
buffer.writeUInt16BE(bodyLength, 2) | ||
} else if (payloadLength === 127) { | ||
@@ -49,0 +49,0 @@ // Clear extended payload length |
@@ -21,2 +21,3 @@ 'use strict' | ||
const { kEnumerableProperty, isBlobLike } = require('../core/util') | ||
const { getGlobalDispatcher } = require('../global') | ||
const { types } = require('util') | ||
@@ -55,4 +56,6 @@ | ||
const options = webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'](protocols) | ||
url = webidl.converters.USVString(url) | ||
protocols = webidl.converters['DOMString or sequence<DOMString>'](protocols) | ||
protocols = options.protocols | ||
@@ -115,3 +118,4 @@ // 1. Let urlRecord be the result of applying the URL parser to url. | ||
this, | ||
(response) => this.#onConnectionEstablished(response) | ||
(response) => this.#onConnectionEstablished(response), | ||
options | ||
) | ||
@@ -583,2 +587,32 @@ | ||
// This implements the propsal made in https://github.com/whatwg/websockets/issues/42 | ||
webidl.converters.WebSocketInit = webidl.dictionaryConverter([ | ||
{ | ||
key: 'protocols', | ||
converter: webidl.converters['DOMString or sequence<DOMString>'], | ||
get defaultValue () { | ||
return [] | ||
} | ||
}, | ||
{ | ||
key: 'dispatcher', | ||
converter: (V) => V, | ||
get defaultValue () { | ||
return getGlobalDispatcher() | ||
} | ||
}, | ||
{ | ||
key: 'headers', | ||
converter: webidl.nullableConverter(webidl.converters.HeadersInit) | ||
} | ||
]) | ||
webidl.converters['DOMString or sequence<DOMString> or WebSocketInit'] = function (V) { | ||
if (webidl.util.Type(V) === 'Object' && !(Symbol.iterator in V)) { | ||
return webidl.converters.WebSocketInit(V) | ||
} | ||
return { protocols: webidl.converters['DOMString or sequence<DOMString>'](V) } | ||
} | ||
webidl.converters.WebSocketSendData = function (V) { | ||
@@ -585,0 +619,0 @@ if (webidl.util.Type(V) === 'Object') { |
{ | ||
"name": "undici", | ||
"version": "5.22.0", | ||
"version": "5.22.1", | ||
"description": "An HTTP/1.1 client, written from scratch for Node.js", | ||
@@ -45,3 +45,3 @@ "homepage": "https://undici.nodejs.org", | ||
"build:node": "npx esbuild@0.14.38 index-fetch.js --bundle --platform=node --outfile=undici-fetch.js", | ||
"prebuild:wasm": "docker build -t llhttp_wasm_builder -f build/Dockerfile .", | ||
"prebuild:wasm": "node build/wasm.js --prebuild", | ||
"build:wasm": "node build/wasm.js --docker", | ||
@@ -52,3 +52,3 @@ "lint": "standard | snazzy", | ||
"test:cookies": "node scripts/verifyVersion 16 || tap test/cookie/*.js", | ||
"test:node-fetch": "node scripts/verifyVersion.js 16 || mocha test/node-fetch", | ||
"test:node-fetch": "node scripts/verifyVersion.js 16 || mocha --exit test/node-fetch", | ||
"test:fetch": "node scripts/verifyVersion.js 16 || (npm run build:node && tap --expose-gc test/fetch/*.js && tap test/webidl/*.js)", | ||
@@ -66,3 +66,3 @@ "test:jest": "node scripts/verifyVersion.js 14 || jest", | ||
"prebench:run": "node benchmarks/wait.js", | ||
"bench:run": "CONNECTIONS=1 node --experimental-wasm-simd benchmarks/benchmark.js; CONNECTIONS=50 node --experimental-wasm-simd benchmarks/benchmark.js", | ||
"bench:run": "CONNECTIONS=1 node benchmarks/benchmark.js; CONNECTIONS=50 node benchmarks/benchmark.js", | ||
"serve:website": "docsify serve .", | ||
@@ -69,0 +69,0 @@ "prepare": "husky install", |
@@ -7,3 +7,6 @@ import { IncomingHttpHeaders } from "./header"; | ||
declare namespace Errors { | ||
export class UndiciError extends Error { } | ||
export class UndiciError extends Error { | ||
name: string; | ||
code: string; | ||
} | ||
@@ -35,2 +38,8 @@ /** Connect timeout error. */ | ||
export class ResponseStatusCodeError extends UndiciError { | ||
constructor ( | ||
message?: string, | ||
statusCode?: number, | ||
headers?: IncomingHttpHeaders | string[] | null, | ||
body?: null | Record<string, any> | string | ||
); | ||
name: 'ResponseStatusCodeError'; | ||
@@ -37,0 +46,0 @@ code: 'UND_ERR_RESPONSE_STATUS_CODE'; |
@@ -173,2 +173,4 @@ // These types are not exported, and are only used internally | ||
illegalConstructor (): never | ||
/** | ||
@@ -175,0 +177,0 @@ * @see https://webidl.spec.whatwg.org/#es-to-record |
@@ -13,2 +13,4 @@ /// <reference types="node" /> | ||
} from './patch' | ||
import Dispatcher from './dispatcher' | ||
import { HeadersInit } from './fetch' | ||
@@ -71,3 +73,3 @@ export type BinaryType = 'blob' | 'arraybuffer' | ||
prototype: WebSocket | ||
new (url: string | URL, protocols?: string | string[]): WebSocket | ||
new (url: string | URL, protocols?: string | string[] | WebSocketInit): WebSocket | ||
readonly CLOSED: number | ||
@@ -126,1 +128,7 @@ readonly CLOSING: number | ||
} | ||
interface WebSocketInit { | ||
protocols?: string | string[], | ||
dispatcher?: Dispatcher, | ||
headers?: HeadersInit | ||
} |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1122723
148
19360