'use strict'; const net = require('net'); class TimeoutError extends Error { constructor(threshold, event) { super(`Timeout awaiting '${event}' for ${threshold}ms`); this.name = 'TimeoutError'; this.code = 'ETIMEDOUT'; this.event = event; } } const reentry = Symbol('reentry'); const noop = () => {}; module.exports = (request, delays, options) => { /* istanbul ignore next: this makes sure timed-out isn't called twice */ if (request[reentry]) { return; } request[reentry] = true; let stopNewTimeouts = false; const addTimeout = (delay, callback, ...args) => { // An error had been thrown before. Going further would result in uncaught errors. // See https://github.com/sindresorhus/got/issues/631#issuecomment-435675051 if (stopNewTimeouts) { return noop; } // Event loop order is timers, poll, immediates. // The timed event may emit during the current tick poll phase, so // defer calling the handler until the poll phase completes. let immediate; const timeout = setTimeout(() => { immediate = setImmediate(callback, delay, ...args); /* istanbul ignore next: added in node v9.7.0 */ if (immediate.unref) { immediate.unref(); } }, delay); /* istanbul ignore next: in order to support electron renderer */ if (timeout.unref) { timeout.unref(); } const cancel = () => { clearTimeout(timeout); clearImmediate(immediate); }; cancelers.push(cancel); return cancel; }; const {host, hostname} = options; const timeoutHandler = (delay, event) => { request.emit('error', new TimeoutError(delay, event)); request.once('error', () => {}); // Ignore the `socket hung up` error made by request.abort() request.abort(); }; const cancelers = []; const cancelTimeouts = () => { stopNewTimeouts = true; cancelers.forEach(cancelTimeout => cancelTimeout()); }; request.once('error', cancelTimeouts); request.once('response', response => { response.once('end', cancelTimeouts); }); if (delays.request !== undefined) { addTimeout(delays.request, timeoutHandler, 'request'); } if (delays.socket !== undefined) { const socketTimeoutHandler = () => { timeoutHandler(delays.socket, 'socket'); }; request.setTimeout(delays.socket, socketTimeoutHandler); // `request.setTimeout(0)` causes a memory leak. // We can just remove the listener and forget about the timer - it's unreffed. // See https://github.com/sindresorhus/got/issues/690 cancelers.push(() => request.removeListener('timeout', socketTimeoutHandler)); } if (delays.lookup !== undefined && !request.socketPath && !net.isIP(hostname || host)) { request.once('socket', socket => { /* istanbul ignore next: hard to test */ if (socket.connecting) { const cancelTimeout = addTimeout(delays.lookup, timeoutHandler, 'lookup'); socket.once('lookup', cancelTimeout); } }); } if (delays.connect !== undefined) { request.once('socket', socket => { /* istanbul ignore next: hard to test */ if (socket.connecting) { const timeConnect = () => addTimeout(delays.connect, timeoutHandler, 'connect'); if (request.socketPath || net.isIP(hostname || host)) { socket.once('connect', timeConnect()); } else { socket.once('lookup', error => { if (error === null) { socket.once('connect', timeConnect()); } }); } } }); } if (delays.secureConnect !== undefined && options.protocol === 'https:') { request.once('socket', socket => { /* istanbul ignore next: hard to test */ if (socket.connecting) { socket.once('connect', () => { const cancelTimeout = addTimeout(delays.secureConnect, timeoutHandler, 'secureConnect'); socket.once('secureConnect', cancelTimeout); }); } }); } if (delays.send !== undefined) { request.once('socket', socket => { const timeRequest = () => addTimeout(delays.send, timeoutHandler, 'send'); /* istanbul ignore next: hard to test */ if (socket.connecting) { socket.once('connect', () => { request.once('upload-complete', timeRequest()); }); } else { request.once('upload-complete', timeRequest()); } }); } if (delays.response !== undefined) { request.once('upload-complete', () => { const cancelTimeout = addTimeout(delays.response, timeoutHandler, 'response'); request.once('response', cancelTimeout); }); } }; module.exports.TimeoutError = TimeoutError;