UNPKG

5.39 kBJavaScriptView Raw
1import net from 'node:net';
2import unhandler from './utils/unhandle.js';
3const reentry = Symbol('reentry');
4const noop = () => { };
5export class TimeoutError extends Error {
6 constructor(threshold, event) {
7 super(`Timeout awaiting '${event}' for ${threshold}ms`);
8 Object.defineProperty(this, "event", {
9 enumerable: true,
10 configurable: true,
11 writable: true,
12 value: event
13 });
14 Object.defineProperty(this, "code", {
15 enumerable: true,
16 configurable: true,
17 writable: true,
18 value: void 0
19 });
20 this.name = 'TimeoutError';
21 this.code = 'ETIMEDOUT';
22 }
23}
24export default function timedOut(request, delays, options) {
25 if (reentry in request) {
26 return noop;
27 }
28 request[reentry] = true;
29 const cancelers = [];
30 const { once, unhandleAll } = unhandler();
31 const addTimeout = (delay, callback, event) => {
32 const timeout = setTimeout(callback, delay, delay, event);
33 timeout.unref?.();
34 const cancel = () => {
35 clearTimeout(timeout);
36 };
37 cancelers.push(cancel);
38 return cancel;
39 };
40 const { host, hostname } = options;
41 const timeoutHandler = (delay, event) => {
42 request.destroy(new TimeoutError(delay, event));
43 };
44 const cancelTimeouts = () => {
45 for (const cancel of cancelers) {
46 cancel();
47 }
48 unhandleAll();
49 };
50 request.once('error', error => {
51 cancelTimeouts();
52 // Save original behavior
53 /* istanbul ignore next */
54 if (request.listenerCount('error') === 0) {
55 throw error;
56 }
57 });
58 if (typeof delays.request !== 'undefined') {
59 const cancelTimeout = addTimeout(delays.request, timeoutHandler, 'request');
60 once(request, 'response', (response) => {
61 once(response, 'end', cancelTimeout);
62 });
63 }
64 if (typeof delays.socket !== 'undefined') {
65 const { socket } = delays;
66 const socketTimeoutHandler = () => {
67 timeoutHandler(socket, 'socket');
68 };
69 request.setTimeout(socket, socketTimeoutHandler);
70 // `request.setTimeout(0)` causes a memory leak.
71 // We can just remove the listener and forget about the timer - it's unreffed.
72 // See https://github.com/sindresorhus/got/issues/690
73 cancelers.push(() => {
74 request.removeListener('timeout', socketTimeoutHandler);
75 });
76 }
77 const hasLookup = typeof delays.lookup !== 'undefined';
78 const hasConnect = typeof delays.connect !== 'undefined';
79 const hasSecureConnect = typeof delays.secureConnect !== 'undefined';
80 const hasSend = typeof delays.send !== 'undefined';
81 if (hasLookup || hasConnect || hasSecureConnect || hasSend) {
82 once(request, 'socket', (socket) => {
83 const { socketPath } = request;
84 /* istanbul ignore next: hard to test */
85 if (socket.connecting) {
86 const hasPath = Boolean(socketPath ?? net.isIP(hostname ?? host ?? '') !== 0);
87 if (hasLookup && !hasPath && typeof socket.address().address === 'undefined') {
88 const cancelTimeout = addTimeout(delays.lookup, timeoutHandler, 'lookup');
89 once(socket, 'lookup', cancelTimeout);
90 }
91 if (hasConnect) {
92 const timeConnect = () => addTimeout(delays.connect, timeoutHandler, 'connect');
93 if (hasPath) {
94 once(socket, 'connect', timeConnect());
95 }
96 else {
97 once(socket, 'lookup', (error) => {
98 if (error === null) {
99 once(socket, 'connect', timeConnect());
100 }
101 });
102 }
103 }
104 if (hasSecureConnect && options.protocol === 'https:') {
105 once(socket, 'connect', () => {
106 const cancelTimeout = addTimeout(delays.secureConnect, timeoutHandler, 'secureConnect');
107 once(socket, 'secureConnect', cancelTimeout);
108 });
109 }
110 }
111 if (hasSend) {
112 const timeRequest = () => addTimeout(delays.send, timeoutHandler, 'send');
113 /* istanbul ignore next: hard to test */
114 if (socket.connecting) {
115 once(socket, 'connect', () => {
116 once(request, 'upload-complete', timeRequest());
117 });
118 }
119 else {
120 once(request, 'upload-complete', timeRequest());
121 }
122 }
123 });
124 }
125 if (typeof delays.response !== 'undefined') {
126 once(request, 'upload-complete', () => {
127 const cancelTimeout = addTimeout(delays.response, timeoutHandler, 'response');
128 once(request, 'response', cancelTimeout);
129 });
130 }
131 if (typeof delays.read !== 'undefined') {
132 once(request, 'response', (response) => {
133 const cancelTimeout = addTimeout(delays.read, timeoutHandler, 'read');
134 once(response, 'end', cancelTimeout);
135 });
136 }
137 return cancelTimeouts;
138}