1 | import net from 'node:net';
|
2 | import unhandler from './utils/unhandle.js';
|
3 | const reentry = Symbol('reentry');
|
4 | const noop = () => { };
|
5 | export 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 | }
|
24 | export 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 |
|
53 |
|
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 |
|
71 |
|
72 |
|
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 |
|
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 |
|
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 | }
|