Comparing version 4.13.0 to 4.14.0
@@ -65,3 +65,3 @@ # Class: MockPool | ||
* **reply** `(statusCode: number, replyData: string | Buffer | object, responseOptions?: MockResponseOptions) => MockScope` - define a reply for a matching request. Default for `responseOptions` is `{}`. | ||
* **reply** `(statusCode: number, replyData: string | Buffer | object | MockInterceptor.MockResponseDataHandler, responseOptions?: MockResponseOptions) => MockScope` - define a reply for a matching request. You can define this as a callback to read incoming request data. Default for `responseOptions` is `{}`. | ||
* **replyWithError** `(error: Error) => MockScope` - define an error for a matching request to throw. | ||
@@ -117,2 +117,68 @@ * **defaultReplyHeaders** `(headers: Record<string, string>) => MockInterceptor` - define default headers to be included in subsequent replies. These are in addition to headers on a specific reply. | ||
#### Example - Mocked request using reply data callbacks | ||
```js | ||
import { MockAgent, setGlobalDispatcher, request } from 'undici' | ||
const mockAgent = new MockAgent() | ||
setGlobalDispatcher(mockAgent) | ||
const mockPool = mockAgent.get('http://localhost:3000') | ||
mockPool.intercept({ | ||
path: '/echo', | ||
method: 'GET', | ||
headers: { | ||
'User-Agent': 'undici', | ||
Host: 'example.com' | ||
} | ||
}).reply(200, ({ headers }) => ({ message: headers.get('message') })) | ||
const { statusCode, body, headers } = await request('http://localhost:3000', { | ||
headers: { | ||
message: 'hello world!' | ||
} | ||
}) | ||
console.log('response received', statusCode) // response received 200 | ||
console.log('headers', headers) // { 'content-type': 'application/json' } | ||
for await (const data of body) { | ||
console.log('data', data.toString('utf8')) // { "message":"hello world!" } | ||
} | ||
``` | ||
#### Example - Mocked request using reply options callback | ||
```js | ||
import { MockAgent, setGlobalDispatcher, request } from 'undici' | ||
const mockAgent = new MockAgent() | ||
setGlobalDispatcher(mockAgent) | ||
const mockPool = mockAgent.get('http://localhost:3000') | ||
mockPool.intercept({ | ||
path: '/echo', | ||
method: 'GET', | ||
headers: { | ||
'User-Agent': 'undici', | ||
Host: 'example.com' | ||
} | ||
}).reply(({ headers }) => ({ statusCode: 200, data: { message: headers.get('message') }}))) | ||
const { statusCode, body, headers } = await request('http://localhost:3000', { | ||
headers: { | ||
message: 'hello world!' | ||
} | ||
}) | ||
console.log('response received', statusCode) // response received 200 | ||
console.log('headers', headers) // { 'content-type': 'application/json' } | ||
for await (const data of body) { | ||
console.log('data', data.toString('utf8')) // { "message":"hello world!" } | ||
} | ||
``` | ||
#### Example - Basic Mocked requests with multiple intercepts | ||
@@ -135,3 +201,3 @@ | ||
path: '/hello', | ||
method: 'GET' | ||
method: 'GET', | ||
}).reply(200, 'hello') | ||
@@ -138,0 +204,0 @@ |
@@ -13,3 +13,3 @@ import Dispatcher = require('./types/dispatcher') | ||
import mockErrors = require('./types/mock-errors') | ||
import ProxyAgent from './types/proxy-agent' | ||
import ProxyAgent = require('./types/proxy-agent') | ||
import { request, pipeline, stream, connect, upgrade } from './types/api' | ||
@@ -16,0 +16,0 @@ |
@@ -27,5 +27,2 @@ 'use strict' | ||
const buildConnector = require('./core/connect') | ||
const llhttpWasmData = require('./llhttp/llhttp.wasm.js') | ||
const llhttpSimdWasmData = require('./llhttp/llhttp_simd.wasm.js') | ||
const { | ||
@@ -417,3 +414,3 @@ kUrl, | ||
try { | ||
mod = await WebAssembly.compile(Buffer.from(llhttpWasmData, 'base64')) | ||
mod = await WebAssembly.compile(Buffer.from(require('./llhttp/llhttp_simd.wasm.js'), 'base64')) | ||
} catch (e) { | ||
@@ -426,3 +423,3 @@ /* istanbul ignore next */ | ||
// got me to remove that check to avoid breaking Node 12. | ||
mod = await WebAssembly.compile(Buffer.from(llhttpSimdWasmData, 'base64')) | ||
mod = await WebAssembly.compile(Buffer.from(require('./llhttp/llhttp.wasm.js'), 'base64')) | ||
} | ||
@@ -429,0 +426,0 @@ |
'use strict' | ||
const net = require('net') | ||
const tls = require('tls') | ||
const assert = require('assert') | ||
const util = require('./util') | ||
const { InvalidArgumentError, ConnectTimeoutError } = require('./errors') | ||
let tls // include tls conditionally since it is not always available | ||
@@ -27,2 +27,5 @@ // TODO: session re-use does not wait for the first | ||
if (protocol === 'https:') { | ||
if (!tls) { | ||
tls = require('tls') | ||
} | ||
servername = servername || options.servername || util.getServerName(host) || null | ||
@@ -29,0 +32,0 @@ |
@@ -127,2 +127,6 @@ 'use strict' | ||
stream (...args) { | ||
if (!(this instanceof FileLike)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this[kState].blobLike.stream(...args) | ||
@@ -132,2 +136,6 @@ } | ||
arrayBuffer (...args) { | ||
if (!(this instanceof FileLike)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this[kState].blobLike.arrayBuffer(...args) | ||
@@ -137,2 +145,6 @@ } | ||
slice (...args) { | ||
if (!(this instanceof FileLike)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this[kState].blobLike.slice(...args) | ||
@@ -142,2 +154,6 @@ } | ||
text (...args) { | ||
if (!(this instanceof FileLike)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this[kState].blobLike.text(...args) | ||
@@ -147,2 +163,6 @@ } | ||
get size () { | ||
if (!(this instanceof FileLike)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this[kState].blobLike.size | ||
@@ -152,2 +172,6 @@ } | ||
get type () { | ||
if (!(this instanceof FileLike)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return this[kState].blobLike.type | ||
@@ -173,2 +197,6 @@ } | ||
get [Symbol.toStringTag] () { | ||
if (!(this instanceof FileLike)) { | ||
throw new TypeError('Illegal invocation') | ||
} | ||
return 'File' | ||
@@ -175,0 +203,0 @@ } |
@@ -143,3 +143,3 @@ /* globals AbortController */ | ||
// undici implementation note: this is set as the first item in request's urlList in makeRequest | ||
// method request’s method. | ||
// method request’s method. | ||
method: request.method, | ||
@@ -149,3 +149,3 @@ // header list A copy of request’s header list. | ||
headersList: request.headersList, | ||
// unsafe-request flag Set. | ||
// unsafe-request flag Set. | ||
unsafeRequest: request.unsafeRequest, | ||
@@ -156,14 +156,14 @@ // client This’s relevant settings object. | ||
window, | ||
// priority request’s priority. | ||
// priority request’s priority. | ||
priority: request.priority, | ||
// origin request’s origin. The propagation of the origin is only significant for navigation requests | ||
// being handled by a service worker. In this scenario a request can have an origin that is different | ||
// being handled by a service worker. In this scenario a request can have an origin that is different | ||
// from the current client. | ||
origin: request.origin, | ||
// referrer request’s referrer. | ||
referrer: request.referrer, | ||
referrer: request.referrer, | ||
// referrer policy request’s referrer policy. | ||
referrerPolicy: request.referrerPolicy, | ||
// mode request’s mode. | ||
mode: request.mode, | ||
mode: request.mode, | ||
// credentials mode request’s credentials mode. | ||
@@ -174,3 +174,3 @@ credentials: request.credentials, | ||
// redirect mode request’s redirect mode. | ||
redirect: request.redirect, | ||
redirect: request.redirect, | ||
// integrity metadata request’s integrity metadata. | ||
@@ -327,3 +327,3 @@ integrity: request.integrity, | ||
// 23. If init["integrity"] exists, then set request’s integrity metadata to it. | ||
if ('integrity' in init) { | ||
if ('integrity' in init && init.integrity != null) { | ||
request.integrity = String(init.integrity) | ||
@@ -330,0 +330,0 @@ } |
@@ -64,3 +64,3 @@ 'use strict' | ||
// then return blocked. | ||
if (/^http?s/.test(url.protocol) && badPorts.includes(url.port)) { | ||
if (/^https?:/.test(url.protocol) && badPorts.includes(url.port)) { | ||
return 'blocked' | ||
@@ -67,0 +67,0 @@ } |
@@ -12,3 +12,3 @@ 'use strict' | ||
} = require('./mock-symbols') | ||
const { InvalidArgumentError } = require('../core/errors') | ||
const { InvalidArgumentError, InvalidReturnValueError } = require('../core/errors') | ||
@@ -78,6 +78,12 @@ /** | ||
/** | ||
* Mock an undici request with a defined reply. | ||
*/ | ||
reply (statusCode, data, responseOptions = {}) { | ||
createMockScopeDispatchData(statusCode, data, responseOptions = {}) { | ||
const responseData = getResponseData(data) | ||
const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {} | ||
const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers } | ||
const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers } | ||
return { statusCode, data, headers, trailers }; | ||
} | ||
validateReplyParameters(statusCode, data, responseOptions) { | ||
if (typeof statusCode === 'undefined') { | ||
@@ -92,9 +98,49 @@ throw new InvalidArgumentError('statusCode must be defined') | ||
} | ||
} | ||
const responseData = getResponseData(data) | ||
const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {} | ||
const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers } | ||
const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers } | ||
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { statusCode, data, headers, trailers }) | ||
/** | ||
* Mock an undici request with a defined reply. | ||
*/ | ||
reply (replyData) { | ||
// Values of reply aren't available right now as they | ||
// can only be available when the reply callback is invoked. | ||
if (typeof replyData === 'function') { | ||
// We'll first wrap the provided callback in another function, | ||
// this function will properly resolve the data from the callback | ||
// when invoked. | ||
const wrappedDefaultsCallback = (opts) => { | ||
// Our reply options callback contains the parameter for statusCode, data and options. | ||
const resolvedData = replyData(opts); | ||
// Check if it is in the right format | ||
if (typeof resolvedData !== 'object') { | ||
throw new InvalidArgumentError('reply options callback must return an object') | ||
} | ||
const { statusCode, data, responseOptions = {}} = resolvedData; | ||
this.validateReplyParameters(statusCode, data, responseOptions); | ||
// Since the values can be obtained immediately we return them | ||
// from this higher order function that will be resolved later. | ||
return { | ||
...this.createMockScopeDispatchData(statusCode, data, responseOptions) | ||
} | ||
} | ||
// Add usual dispatch data, but this time set the data parameter to function that will eventually provide data. | ||
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback) | ||
return new MockScope(newMockDispatch); | ||
} | ||
// We can have either one or three parameters, if we get here, | ||
// we should have 2-3 parameters. So we spread the arguments of | ||
// this function to obtain the parameters, since replyData will always | ||
// just be the statusCode. | ||
const [statusCode, data, responseOptions = {}] = [...arguments]; | ||
this.validateReplyParameters(statusCode, data, responseOptions); | ||
// Send in-already provided data like usual | ||
const dispatchData = this.createMockScopeDispatchData(statusCode, data, responseOptions); | ||
const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData) | ||
return new MockScope(newMockDispatch) | ||
} | ||
@@ -101,0 +147,0 @@ |
@@ -93,3 +93,4 @@ 'use strict' | ||
const baseData = { times: null, persist: false, consumed: false } | ||
const newMockDispatch = { ...baseData, ...key, data: { error: null, ...data } } | ||
const replyData = typeof data === 'function' ? { callback: data } : { ...data }; | ||
const newMockDispatch = { ...baseData, ...key, data: { error: null, ...replyData } } | ||
mockDispatches.push(newMockDispatch) | ||
@@ -139,4 +140,9 @@ return newMockDispatch | ||
const key = buildKey(opts) | ||
const mockDispatch = getMockDispatch(this[kDispatches], key) | ||
let mockDispatch = getMockDispatch(this[kDispatches], key) | ||
// Here's where we resolve a callback if a callback is present for the dispatch data. | ||
if (mockDispatch.data.callback) { | ||
mockDispatch.data = { ...mockDispatch.data, ...mockDispatch.data.callback(opts) } | ||
} | ||
// Parse mockDispatch data | ||
@@ -174,3 +180,3 @@ const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch | ||
function handleReply (mockDispatches) { | ||
const responseData = getResponseData(data) | ||
const responseData = getResponseData(typeof data === 'function' ? data(opts) : data); | ||
const responseHeaders = generateKeyValues(headers) | ||
@@ -177,0 +183,0 @@ const responseTrailers = generateKeyValues(trailers) |
{ | ||
"name": "undici", | ||
"version": "4.13.0", | ||
"version": "4.14.0", | ||
"description": "An HTTP/1.1 client, written from scratch for Node.js", | ||
@@ -49,4 +49,4 @@ "homepage": "https://undici.nodejs.org", | ||
"test": "npm run test:tap && npm run test:node-fetch && npm run test:fetch && npm run test:jest && tsd", | ||
"test:node-fetch": "node scripts/verifyVersion.js 16 && mocha test/node-fetch || echo Skipping", | ||
"test:fetch": "node scripts/verifyVersion.js 16 && tap test/fetch/*.js || echo Skipping", | ||
"test:node-fetch": "node scripts/verifyVersion.js 16 || mocha test/node-fetch", | ||
"test:fetch": "node scripts/verifyVersion.js 16 || tap test/fetch/*.js", | ||
"test:jest": "jest test/jest/test", | ||
@@ -53,0 +53,0 @@ "test:tap": "tap test/*.js test/diagnostics-channel/*.js", |
@@ -167,3 +167,5 @@ # undici | ||
This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard. We plan to ship breaking changes to this feature until it is out of experimental. | ||
This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard. | ||
We plan to ship breaking changes to this feature until it is out of experimental. | ||
Help us improve the test coverage by following instructions at [nodejs/undici/#951](https://github.com/nodejs/undici/issues/951). | ||
@@ -182,2 +184,33 @@ Basic usage example: | ||
#### `request.body` | ||
A body can be of the following types: | ||
- ArrayBuffer | ||
- ArrayBufferView | ||
- AsyncIterables | ||
- Blob | ||
- Iterables | ||
- String | ||
- URLSearchParams | ||
- FormData | ||
In this implementation of fetch, ```request.body ``` now accepts ```Async Iterables```. It is not present in the [Fetch Standard.](https://fetch.spec.whatwg.org) | ||
```js | ||
import { fetch } from "undici"; | ||
const data = { | ||
async *[Symbol.asyncIterator]() { | ||
yield "hello"; | ||
yield "world"; | ||
}, | ||
}; | ||
(async () => { | ||
await fetch("https://example.com", { body: data, method: 'POST' }); | ||
})(); | ||
``` | ||
#### `response.body` | ||
@@ -304,2 +337,11 @@ | ||
### Manual Redirect | ||
Since it is not possible to manually follow an HTTP redirect on server-side, | ||
Undici returns the actual response instead of an `opaqueredirect` filtered one | ||
when invoked with a `manual` redirect. This aligns `fetch()` with the other | ||
implementations in Deno and Cloudflare Workers. | ||
Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling | ||
## Collaborators | ||
@@ -306,0 +348,0 @@ |
@@ -16,3 +16,3 @@ import { URL, UrlObject } from 'url' | ||
url: string | URL | UrlObject, | ||
options?: { dispatcher?: Dispatcher } & Omit<Dispatcher.RequestOptions, 'origin' | 'path'>, | ||
options?: { dispatcher?: Dispatcher } & Omit<Dispatcher.RequestOptions, 'origin' | 'path' | 'method'> & Partial<Pick<Dispatcher.RequestOptions, 'method'>>, | ||
): Promise<Dispatcher.ResponseData>; | ||
@@ -19,0 +19,0 @@ |
@@ -45,3 +45,3 @@ // based on https://github.com/Ethan-Arrowood/undici-fetch/blob/249269714db874351589d2d364a0645d5160ae71/index.d.ts (MIT license) | ||
export type HeadersInit = Iterable<[string, string]> | Record<string, string> | ||
export type HeadersInit = string[][] | Record<string, string> | Headers | ||
@@ -104,4 +104,20 @@ export declare class Headers implements Iterable<[string, string]> { | ||
readonly signal?: AbortSignal | ||
readonly credentials?: RequestCredentials | ||
readonly mode?: RequestMode | ||
readonly referrer?: string | ||
readonly referrerPolicy?: ReferrerPolicy | ||
readonly window?: null | ||
} | ||
export type ReferrerPolicy = | ||
| '' | ||
| 'no-referrer' | ||
| 'no-referrer-when-downgrade' | ||
| 'origin' | ||
| 'origin-when-cross-origin' | ||
| 'same-origin' | ||
| 'strict-origin' | ||
| 'strict-origin-when-cross-origin' | ||
| 'unsafe-url'; | ||
export type RequestMode = 'cors' | 'navigate' | 'no-cors' | 'same-origin' | ||
@@ -108,0 +124,0 @@ |
import { IncomingHttpHeaders } from 'http' | ||
import Dispatcher from './dispatcher'; | ||
@@ -24,3 +25,8 @@ export { | ||
/** Mock an undici request with the defined reply. */ | ||
reply<TData extends object = object>(statusCode: number, data: TData | Buffer| string , responseOptions?: MockInterceptor.MockResponseOptions): MockScope<TData>; | ||
reply<TData extends object = object>(replyOptionsCallback: MockInterceptor.MockReplyOptionsCallback<TData>): MockScope<TData>; | ||
reply<TData extends object = object>( | ||
statusCode: number, | ||
data: TData | Buffer | string | MockInterceptor.MockResponseDataHandler<TData>, | ||
responseOptions?: MockInterceptor.MockResponseOptions | ||
): MockScope<TData>; | ||
/** Mock an undici request by throwing the defined reply error. */ | ||
@@ -63,7 +69,24 @@ replyWithError<TError extends Error = Error>(error: TError): MockScope; | ||
} | ||
export interface MockResponseCallbackOptions { | ||
path: string; | ||
origin: string; | ||
method: string; | ||
body?: string; | ||
headers: Headers; | ||
maxRedirections: number; | ||
} | ||
export type MockResponseDataHandler<TData extends object = object> = ( | ||
opts: MockResponseCallbackOptions | ||
) => TData | Buffer | string; | ||
export type MockReplyOptionsCallback<TData extends object = object> = ( | ||
opts: MockResponseCallbackOptions | ||
) => { statusCode: number, data: TData | Buffer | string, responseOptions?: MockResponseOptions } | ||
} | ||
interface Interceptable { | ||
interface Interceptable extends Dispatcher { | ||
/** Intercepts any matching requests that use the same origin as this mock client. */ | ||
intercept(options: MockInterceptor.Options): MockInterceptor; | ||
} |
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
791807
11423
362