1 | 'use strict';
|
2 | const net = require('net');
|
3 |
|
4 | class TimeoutError extends Error {
|
5 | constructor(threshold, event) {
|
6 | super(`Timeout awaiting '${event}' for ${threshold}ms`);
|
7 | this.name = 'TimeoutError';
|
8 | this.code = 'ETIMEDOUT';
|
9 | this.event = event;
|
10 | }
|
11 | }
|
12 |
|
13 | const reentry = Symbol('reentry');
|
14 |
|
15 | function addTimeout(delay, callback, ...args) {
|
16 |
|
17 |
|
18 |
|
19 | let immediate;
|
20 | const timeout = setTimeout(() => {
|
21 | immediate = setImmediate(callback, delay, ...args);
|
22 |
|
23 | if (immediate.unref) {
|
24 | immediate.unref();
|
25 | }
|
26 | }, delay);
|
27 |
|
28 |
|
29 | if (timeout.unref) {
|
30 | timeout.unref();
|
31 | }
|
32 |
|
33 | const cancel = () => {
|
34 | clearTimeout(timeout);
|
35 | clearImmediate(immediate);
|
36 | };
|
37 |
|
38 | return cancel;
|
39 | }
|
40 |
|
41 | module.exports = (request, delays, options) => {
|
42 |
|
43 | if (request[reentry]) {
|
44 | return;
|
45 | }
|
46 |
|
47 | request[reentry] = true;
|
48 | const {host, hostname} = options;
|
49 | const timeoutHandler = (delay, event) => {
|
50 | request.emit('error', new TimeoutError(delay, event));
|
51 | request.once('error', () => {});
|
52 |
|
53 | request.abort();
|
54 | };
|
55 |
|
56 | const cancelers = [];
|
57 | const cancelTimeouts = () => {
|
58 | cancelers.forEach(cancelTimeout => cancelTimeout());
|
59 | };
|
60 |
|
61 | request.once('error', cancelTimeouts);
|
62 | request.once('response', response => {
|
63 | response.once('end', cancelTimeouts);
|
64 | });
|
65 |
|
66 | if (delays.request !== undefined) {
|
67 | const cancelTimeout = addTimeout(delays.request, timeoutHandler, 'request');
|
68 | cancelers.push(cancelTimeout);
|
69 | }
|
70 |
|
71 | if (delays.socket !== undefined) {
|
72 | request.setTimeout(delays.socket, () => {
|
73 | timeoutHandler(delays.socket, 'socket');
|
74 | });
|
75 | }
|
76 |
|
77 | if (delays.lookup !== undefined && !request.socketPath && !net.isIP(hostname || host)) {
|
78 | request.once('socket', socket => {
|
79 |
|
80 | if (socket.connecting) {
|
81 | const cancelTimeout = addTimeout(delays.lookup, timeoutHandler, 'lookup');
|
82 | cancelers.push(cancelTimeout);
|
83 | socket.once('lookup', cancelTimeout);
|
84 | }
|
85 | });
|
86 | }
|
87 |
|
88 | if (delays.connect !== undefined) {
|
89 | request.once('socket', socket => {
|
90 |
|
91 | if (socket.connecting) {
|
92 | const timeConnect = () => {
|
93 | const cancelTimeout = addTimeout(delays.connect, timeoutHandler, 'connect');
|
94 | cancelers.push(cancelTimeout);
|
95 | return cancelTimeout;
|
96 | };
|
97 |
|
98 | if (request.socketPath || net.isIP(hostname || host)) {
|
99 | socket.once('connect', timeConnect());
|
100 | } else {
|
101 | socket.once('lookup', () => {
|
102 | socket.once('connect', timeConnect());
|
103 | });
|
104 | }
|
105 | }
|
106 | });
|
107 | }
|
108 |
|
109 | if (delays.secureConnect !== undefined && options.protocol === 'https:') {
|
110 | request.once('socket', socket => {
|
111 |
|
112 | if (socket.connecting) {
|
113 | socket.once('connect', () => {
|
114 | const cancelTimeout = addTimeout(delays.secureConnect, timeoutHandler, 'secureConnect');
|
115 | cancelers.push(cancelTimeout);
|
116 | socket.once('secureConnect', cancelTimeout);
|
117 | });
|
118 | }
|
119 | });
|
120 | }
|
121 |
|
122 | if (delays.send !== undefined) {
|
123 | request.once('socket', socket => {
|
124 | const timeRequest = () => {
|
125 | const cancelTimeout = addTimeout(delays.send, timeoutHandler, 'send');
|
126 | cancelers.push(cancelTimeout);
|
127 | return cancelTimeout;
|
128 | };
|
129 |
|
130 | if (socket.connecting) {
|
131 | socket.once('connect', () => {
|
132 | request.once('upload-complete', timeRequest());
|
133 | });
|
134 | } else {
|
135 | request.once('upload-complete', timeRequest());
|
136 | }
|
137 | });
|
138 | }
|
139 |
|
140 | if (delays.response !== undefined) {
|
141 | request.once('upload-complete', () => {
|
142 | const cancelTimeout = addTimeout(delays.response, timeoutHandler, 'response');
|
143 | cancelers.push(cancelTimeout);
|
144 | request.once('response', cancelTimeout);
|
145 | });
|
146 | }
|
147 | };
|
148 |
|
149 | module.exports.TimeoutError = TimeoutError;
|