Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Socket
Sign inDemoInstall

nock

Package Overview
Dependencies
Maintainers
3
Versions
431
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nock - npm Package Compare versions

Comparing version 13.0.0-beta.1 to 13.0.0-beta.2

118

lib/common.js
'use strict'
const _ = require('lodash')
const debug = require('debug')('nock.common')
const set = require('lodash.set')
const timers = require('timers')
const url = require('url')
const timers = require('timers')
const util = require('util')

@@ -197,3 +198,3 @@ /**

function headersFieldNamesToLowerCase(headers) {
if (!_.isPlainObject(headers)) {
if (!isPlainObject(headers)) {
throw Error('Headers must be provided as an object')

@@ -247,7 +248,7 @@ }

// [].concat(...) is used instead of Array.flat until v11 is the minimum Node version
if (_.isMap(headers)) {
if (util.types.isMap(headers)) {
return [].concat(...Array.from(headers, ([k, v]) => [k.toString(), v]))
}
if (_.isPlainObject(headers)) {
if (isPlainObject(headers)) {
return [].concat(...Object.entries(headers))

@@ -364,3 +365,3 @@ }

function deleteHeadersField(headers, fieldNameToDelete) {
if (!_.isPlainObject(headers)) {
if (!isPlainObject(headers)) {
throw Error('headers must be an object')

@@ -565,3 +566,3 @@ }

const expand = input =>
Object.entries(input).reduce((acc, [k, v]) => _.set(acc, k, v), {})
Object.entries(input).reduce((acc, [k, v]) => set(acc, k, v), {})

@@ -579,3 +580,3 @@ /**

if (Array.isArray(expected) || _.isPlainObject(expected)) {
if (Array.isArray(expected) || isPlainObject(expected)) {
if (actual === undefined) {

@@ -596,2 +597,47 @@ return false

/**
* Checks if `value` is a plain object, that is, an object created by the
* `Object` constructor or one with a `[[Prototype]]` of `null`.
* https://github.com/lodash/lodash/blob/588bf3e20db0ae039a822a14a8fa238c5b298e65/isPlainObject.js
*
* @param {*} value The value to check.
* @return {boolean}
*/
function isPlainObject(value) {
const isObjectLike = typeof value === 'object' && value !== null
const tag = Object.prototype.toString.call(value)
if (!isObjectLike || tag !== '[object Object]') {
return false
}
if (Object.getPrototypeOf(value) === null) {
return true
}
let proto = value
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(value) === proto
}
/**
* Creates an object with the same keys as `object` and values generated
* by running each own enumerable string keyed property of `object` thru
* `iteratee`. (iteration order is not guaranteed)
* The iteratee is invoked with three arguments: (value, key, object).
* https://github.com/lodash/lodash/blob/588bf3e20db0ae039a822a14a8fa238c5b298e65/mapValue.js
*
* @param {Object} object The object to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Object} Returns the new mapped object.
*/
function mapValue(object, iteratee) {
object = Object(object)
const result = {}
Object.keys(object).forEach(key => {
result[key] = iteratee(object[key], key, object)
})
return result
}
const timeouts = []

@@ -623,27 +669,31 @@ const intervals = []

exports.normalizeClientRequestArgs = normalizeClientRequestArgs
exports.normalizeRequestOptions = normalizeRequestOptions
exports.normalizeOrigin = normalizeOrigin
exports.isUtf8Representable = isUtf8Representable
exports.overrideRequests = overrideRequests
exports.restoreOverriddenRequests = restoreOverriddenRequests
exports.stringifyRequest = stringifyRequest
exports.isContentEncoded = isContentEncoded
exports.contentEncoding = contentEncoding
exports.isJSONContent = isJSONContent
exports.headersFieldNamesToLowerCase = headersFieldNamesToLowerCase
exports.headersFieldsArrayToLowerCase = headersFieldsArrayToLowerCase
exports.headersArrayToObject = headersArrayToObject
exports.headersInputToRawArray = headersInputToRawArray
exports.deleteHeadersField = deleteHeadersField
exports.forEachHeader = forEachHeader
exports.percentEncode = percentEncode
exports.percentDecode = percentDecode
exports.matchStringOrRegexp = matchStringOrRegexp
exports.formatQueryValue = formatQueryValue
exports.isStream = isStream
exports.dataEqual = dataEqual
exports.setTimeout = setTimeout
exports.setInterval = setInterval
exports.setImmediate = setImmediate
exports.removeAllTimers = removeAllTimers
module.exports = {
contentEncoding,
dataEqual,
deleteHeadersField,
forEachHeader,
formatQueryValue,
headersArrayToObject,
headersFieldNamesToLowerCase,
headersFieldsArrayToLowerCase,
headersInputToRawArray,
isContentEncoded,
isJSONContent,
isPlainObject,
isStream,
isUtf8Representable,
mapValue,
matchStringOrRegexp,
normalizeClientRequestArgs,
normalizeOrigin,
normalizeRequestOptions,
overrideRequests,
percentDecode,
percentEncode,
removeAllTimers,
restoreOverriddenRequests,
setImmediate,
setInterval,
setTimeout,
stringifyRequest,
}

@@ -42,5 +42,10 @@ 'use strict'

this.response = new IncomingMessage(this.socket)
this.requestBodyBuffers = []
this.playbackStarted = false
this.requestBodyBuffers = []
// For parity with Node, it's important the socket event is emitted before we begin playback.
// This flag is set when playback is triggered if we haven't yet gotten the
// socket event to indicate that playback should start as soon as it comes in.
this.readyToStartPlaybackOnSocketEvent = false
this.attachToReq()

@@ -50,6 +55,4 @@ }

attachToReq() {
const { req, response, socket, options } = this
const { req, socket, options } = this
response.req = req
for (const [name, val] of Object.entries(options.headers)) {

@@ -76,3 +79,3 @@ req.setHeader(name.toLowerCase(), val)

propagate(['error', 'timeout'], req.socket, req)
propagate(['close', 'error', 'timeout'], req.socket, req)

@@ -96,2 +99,7 @@ req.write = (...args) => this.handleWrite(...args)

process.nextTick(() => {
if (req.aborted) {
return
}
socket.connecting = false
req.emit('socket', socket)

@@ -106,2 +114,6 @@

}
if (this.readyToStartPlaybackOnSocketEvent) {
this.maybeStartPlayback()
}
})

@@ -117,2 +129,4 @@ }

// from docs: When write function is called with empty string or buffer, it does nothing and waits for more input.
// However, actually implementation checks the state of finished and aborted before checking if the first arg is empty.
handleWrite(buffer, encoding, callback) {

@@ -122,20 +136,34 @@ debug('write', arguments)

if (!req.aborted) {
if (buffer) {
if (!Buffer.isBuffer(buffer)) {
buffer = Buffer.from(buffer, encoding)
}
this.requestBodyBuffers.push(buffer)
}
// can't use instanceof Function because some test runners
// run tests in vm.runInNewContext where Function is not same
// as that in the current context
// https://github.com/nock/nock/pull/1754#issuecomment-571531407
if (typeof callback === 'function') {
callback()
}
} else {
this.emitError(new Error('Request aborted'))
if (req.finished) {
const err = new Error('write after end')
err.code = 'ERR_STREAM_WRITE_AFTER_END'
this.emitError(err)
// It seems odd to return `true` here, not sure why you'd want to have
// the stream potentially written to more, but it's what Node does.
// https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js#L662-L665
return true
}
if (req.aborted) {
return false
}
if (!buffer || buffer.length === 0) {
return true
}
if (!Buffer.isBuffer(buffer)) {
buffer = Buffer.from(buffer, encoding)
}
this.requestBodyBuffers.push(buffer)
// can't use instanceof Function because some test runners
// run tests in vm.runInNewContext where Function is not same
// as that in the current context
// https://github.com/nock/nock/pull/1754#issuecomment-571531407
if (typeof callback === 'function') {
callback()
}
common.setImmediate(function () {

@@ -152,3 +180,3 @@ req.emit('drain')

// handle the different overloaded param signatures
// handle the different overloaded arg signatures
if (typeof chunk === 'function') {

@@ -166,9 +194,5 @@ callback = chunk

if (!req.aborted && !this.playbackStarted) {
req.write(chunk, encoding)
this.startPlayback()
}
if (req.aborted) {
this.emitError(new Error('Request aborted'))
}
req.write(chunk, encoding)
req.finished = true
this.maybeStartPlayback()

@@ -180,10 +204,3 @@ return req

debug('req.flushHeaders')
const { req } = this
if (!req.aborted && !this.playbackStarted) {
this.startPlayback()
}
if (req.aborted) {
this.emitError(new Error('Request aborted'))
}
this.maybeStartPlayback()
}

@@ -193,3 +210,3 @@

debug('req.abort')
const { req, response, socket } = this
const { req, socket } = this

@@ -199,17 +216,25 @@ if (req.aborted) {

}
req.aborted = Date.now()
if (!this.playbackStarted) {
this.startPlayback()
}
const err = new Error()
err.code = 'aborted'
response.emit('close', err)
socket.destroy()
// Historically, `aborted` was undefined or a timestamp.
// Node changed this behavior in v11 to always be a bool.
req.aborted = true
req.destroyed = true
req.emit('abort')
// the order of these next three steps is important to match order of events Node would emit.
process.nextTick(() => req.emit('abort'))
const connResetError = new Error('socket hang up')
connResetError.code = 'ECONNRESET'
this.emitError(connResetError)
if (!socket.connecting) {
if (!req.res) {
// If we don't have a response then we know that the socket
// ended prematurely and we need to emit an error on the request.
// Node doesn't actually emit this during the `abort` method.
// Instead it listens to the socket's `end` and `close` events, however,
// Nock's socket doesn't have all the complexities and events to
// replicated that directly. This is an easy way to achieve the same behavior.
const connResetError = new Error('socket hang up')
connResetError.code = 'ECONNRESET'
this.emitError(connResetError)
}
socket.destroy()
}
}

@@ -248,2 +273,17 @@

maybeStartPlayback() {
const { req, socket, playbackStarted } = this
// In order to get the events in the right order we need to delay playback
// if we get here before the `socket` event is emitted.
if (socket.connecting) {
this.readyToStartPlaybackOnSocketEvent = true
return
}
if (!req.aborted && !playbackStarted) {
this.startPlayback()
}
}
startPlayback() {

@@ -290,3 +330,2 @@ debug('ending')

// otherwise an unmocked request might emit finish twice.
req.finished = true
req.emit('finish')

@@ -293,0 +332,0 @@

@@ -5,3 +5,2 @@ 'use strict'

const stringify = require('json-stringify-safe')
const _ = require('lodash')
const querystring = require('querystring')

@@ -487,3 +486,3 @@ const { URL, URLSearchParams } = require('url')

queries = querystring.parse(queries.toString())
} else if (!_.isPlainObject(queries)) {
} else if (!common.isPlainObject(queries)) {
throw Error(`Argument Error: ${queries}`)

@@ -490,0 +489,0 @@ }

'use strict'
const _ = require('lodash')
const querystring = require('querystring')

@@ -73,6 +72,6 @@

}
if (_.isPlainObject(obj)) {
return _.mapValues(obj, v => mapValuesDeep(v, cb))
if (common.isPlainObject(obj)) {
return common.mapValue(obj, v => mapValuesDeep(v, cb))
}
return cb(obj)
}

@@ -260,6 +260,2 @@ 'use strict'

if (req.aborted) {
return
}
response.rawHeaders.push(

@@ -281,15 +277,9 @@ ...selectDefaultHeaders(

process.nextTick(() =>
respondUsingInterceptor({
responseBody,
responseBuffers,
})
)
respondUsingInterceptor({
responseBody,
responseBuffers,
})
}
function respondUsingInterceptor({ responseBody, responseBuffers }) {
if (req.aborted) {
return
}
function respond() {

@@ -300,2 +290,8 @@ if (req.aborted) {

// Even though we've had the response object for awhile at this point,
// we only attach it to the request immediately before the `response`
// event because, as in Node, it alters the error handling around aborts.
req.res = response
response.req = req
debug('emitting response')

@@ -348,5 +344,14 @@ req.emit('response', response)

start()
// If there are no delays configured on the Interceptor, calling `start` will
// take the request all the way to the 'response' event during a single microtask
// execution. This setImmediate stalls the playback to ensure the correct events
// are emitted first ('socket', 'finish') and any aborts in the in the queue
// or called during a 'finish' listener can be called.
common.setImmediate(() => {
if (!req.aborted) {
start()
}
})
}
module.exports = { playbackInterceptor }

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

this.destroyed = false
this.connecting = false
this.connecting = true

@@ -73,10 +73,26 @@ // totalDelay that has already been applied to the current

/**
* Denotes that no more I/O activity should happen on this socket.
*
* The implementation in Node if far more complex as it juggles underlying async streams.
* For the purposes of Nock, we just need it to set some flags and on the first call
* emit a 'close' and optional 'error' event. Both events propagate through the request object.
*/
destroy(err) {
if (this.destroyed) {
return this
}
this.destroyed = true
this.readable = this.writable = false
if (err) {
this.emit('error', err)
}
process.nextTick(() => {
if (err) {
this.emit('error', err)
}
this.emit('close')
})
return this
}
}

@@ -10,3 +10,3 @@ {

],
"version": "13.0.0-beta.1",
"version": "13.0.0-beta.2",
"author": "Pedro Teixeira <pedro.teixeira@gmail.com>",

@@ -28,3 +28,3 @@ "repository": {

"json-stringify-safe": "^5.0.1",
"lodash": "^4.17.13",
"lodash.set": "^4.3.2",
"propagate": "^2.0.0"

@@ -31,0 +31,0 @@ },

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc