Skip to content

Commit b8dabf9

Browse files
committed
5.0.0
1 parent a44ca1a commit b8dabf9

File tree

6 files changed

+88
-99
lines changed

6 files changed

+88
-99
lines changed

.travis.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: node_js
22
node_js:
3-
- '13'
4-
- '12'
5-
- '10'
3+
- '16'
4+
- '14'
65
after_success: npm run coveralls

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ Yarn:
2323
> - Sending a chunk greater than [`highWaterMark`](https://nodejs.org/api/stream.html#stream_new_stream_writable_options) will result in invalid `upload` and `response` timings. You can avoid this by splitting the payload into smaller chunks.
2424
2525
```js
26-
const https = require('https');
27-
const timer = require('@szmarczak/http-timer');
26+
import https from 'https';
27+
import timer from '@szmarczak/http-timer';
2828

2929
const request = https.get('https://httpbin.org/anything');
3030
timer(request);

package.json

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
{
22
"name": "@szmarczak/http-timer",
3-
"version": "4.0.6",
3+
"version": "5.0.0",
44
"description": "Timings for HTTP requests",
5-
"main": "dist/source",
5+
"type": "module",
6+
"exports": "./dist/source/index.js",
67
"engines": {
7-
"node": ">=10"
8+
"node": ">=14.16"
89
},
910
"scripts": {
10-
"test": "xo && tsc --noEmit && nyc ava",
11+
"test": "xo && ava",
1112
"build": "del-cli dist && tsc",
1213
"prepare": "npm run build",
13-
"coveralls": "nyc report --reporter=text-lcov | coveralls"
14+
"coveralls": "exit 0 && nyc report --reporter=text-lcov | coveralls"
1415
},
1516
"files": [
1617
"dist/source"
1718
],
1819
"keywords": [
1920
"http",
2021
"https",
22+
"http2",
2123
"timer",
22-
"timings"
24+
"timings",
25+
"performance",
26+
"measure"
2327
],
2428
"repository": {
2529
"type": "git",
@@ -32,20 +36,21 @@
3236
},
3337
"homepage": "https://github.com/szmarczak/http-timer#readme",
3438
"dependencies": {
35-
"defer-to-connect": "^2.0.0"
39+
"defer-to-connect": "^2.0.1"
3640
},
3741
"devDependencies": {
3842
"@ava/typescript": "^2.0.0",
3943
"@sindresorhus/tsconfig": "^1.0.2",
40-
"@types/node": "^16.3.1",
44+
"@types/node": "^16.7.0",
4145
"ava": "^3.15.0",
4246
"coveralls": "^3.1.1",
43-
"del-cli": "^3.0.1",
44-
"http2-wrapper": "^2.0.7",
47+
"del-cli": "^4.0.1",
48+
"http2-wrapper": "^2.1.4",
4549
"nyc": "^15.1.0",
4650
"p-event": "^4.2.0",
51+
"ts-node": "^10.2.1",
4752
"typescript": "^4.3.5",
48-
"xo": "^0.39.1"
53+
"xo": "^0.44.0"
4954
},
5055
"types": "dist/source",
5156
"nyc": {
@@ -58,15 +63,26 @@
5863
},
5964
"xo": {
6065
"rules": {
61-
"@typescript-eslint/no-non-null-assertion": "off"
66+
"@typescript-eslint/no-non-null-assertion": "off",
67+
"@typescript-eslint/no-unsafe-assignment": "off",
68+
"@typescript-eslint/no-unsafe-member-access": "off",
69+
"@typescript-eslint/no-unsafe-call": "off"
6270
}
6371
},
6472
"ava": {
65-
"typescript": {
66-
"compile": false,
67-
"rewritePaths": {
68-
"tests/": "dist/tests/"
69-
}
70-
}
73+
"files": [
74+
"tests/*"
75+
],
76+
"timeout": "1m",
77+
"nonSemVerExperiments": {
78+
"nextGenConfig": true,
79+
"configurableModuleFormat": true
80+
},
81+
"extensions": {
82+
"ts": "module"
83+
},
84+
"nodeArguments": [
85+
"--loader=ts-node/esm"
86+
]
7187
}
7288
}

source/index.ts

Lines changed: 22 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import {EventEmitter} from 'events';
2-
import {Socket} from 'net';
3-
import {ClientRequest, IncomingMessage} from 'http';
1+
import {errorMonitor} from 'node:events';
2+
import {types} from 'node:util';
3+
import type {EventEmitter} from 'node:events';
4+
import type {Socket} from 'node:net';
5+
import type {ClientRequest, IncomingMessage} from 'node:http';
46
import deferToConnect from 'defer-to-connect';
5-
import {types} from 'util';
67

78
export interface Timings {
89
start: number;
@@ -35,8 +36,6 @@ export interface IncomingMessageWithTimings extends IncomingMessage {
3536
timings?: Timings;
3637
}
3738

38-
const nodejsMajorVersion = Number(process.versions.node.split('.')[0]);
39-
4039
const timer = (request: ClientRequestWithTimings): Timings => {
4140
if (request.timings) {
4241
return request.timings;
@@ -61,38 +60,24 @@ const timer = (request: ClientRequestWithTimings): Timings => {
6160
request: undefined,
6261
firstByte: undefined,
6362
download: undefined,
64-
total: undefined
65-
}
63+
total: undefined,
64+
},
6665
};
6766

6867
request.timings = timings;
6968

7069
const handleError = (origin: EventEmitter): void => {
71-
const emit = origin.emit.bind(origin);
72-
origin.emit = (event, ...args: unknown[]) => {
73-
// Catches the `error` event
74-
if (event === 'error') {
75-
timings.error = Date.now();
76-
timings.phases.total = timings.error - timings.start;
77-
78-
origin.emit = emit;
79-
}
80-
81-
// Saves the original behavior
82-
return emit(event, ...args);
83-
};
70+
origin.once(errorMonitor, () => {
71+
timings.error = Date.now();
72+
timings.phases.total = timings.error - timings.start;
73+
});
8474
};
8575

8676
handleError(request);
8777

8878
const onAbort = (): void => {
8979
timings.abort = Date.now();
90-
91-
// Let the `end` response event be responsible for setting the total phase,
92-
// unless the Node.js major version is >= 13.
93-
if (!timings.response || nodejsMajorVersion >= 13) {
94-
timings.phases.total = Date.now() - timings.start;
95-
}
80+
timings.phases.total = timings.abort - timings.start;
9681
};
9782

9883
request.prependOnceListener('abort', onAbort);
@@ -123,14 +108,11 @@ const timer = (request: ClientRequestWithTimings): Timings => {
123108
}
124109

125110
timings.phases.tcp = timings.connect - timings.lookup;
126-
127-
// This callback is called before flushing any data,
128-
// so we don't need to set `timings.phases.request` here.
129111
},
130112
secureConnect: () => {
131113
timings.secureConnect = Date.now();
132114
timings.phases.tls = timings.secureConnect - timings.connect!;
133-
}
115+
},
134116
});
135117
};
136118

@@ -145,16 +127,7 @@ const timer = (request: ClientRequestWithTimings): Timings => {
145127
timings.phases.request = timings.upload - (timings.secureConnect ?? timings.connect!);
146128
};
147129

148-
const writableFinished = (): boolean => {
149-
if (typeof request.writableFinished === 'boolean') {
150-
return request.writableFinished;
151-
}
152-
153-
// Node.js doesn't have `request.writableFinished` property
154-
return request.finished && (request as any).outputSize === 0 && (!request.socket || request.socket.writableLength === 0);
155-
};
156-
157-
if (writableFinished()) {
130+
if (request.writableFinished) {
158131
onUpload();
159132
} else {
160133
request.prependOnceListener('finish', onUpload);
@@ -169,6 +142,14 @@ const timer = (request: ClientRequestWithTimings): Timings => {
169142
handleError(response);
170143

171144
response.prependOnceListener('end', () => {
145+
request.off('abort', onAbort);
146+
response.off('aborted', onAbort);
147+
148+
if (timings.phases.total) {
149+
// Aborted or errored
150+
return;
151+
}
152+
172153
timings.end = Date.now();
173154
timings.phases.download = timings.end - timings.response!;
174155
timings.phases.total = timings.end - timings.start;
@@ -181,7 +162,3 @@ const timer = (request: ClientRequestWithTimings): Timings => {
181162
};
182163

183164
export default timer;
184-
185-
// For CommonJS default export support
186-
module.exports = timer;
187-
module.exports.default = timer;

tests/test.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import {EventEmitter} from 'events';
2-
import http = require('http');
3-
import {ClientRequest, IncomingMessage} from 'http';
4-
import https = require('https');
5-
import {AddressInfo} from 'net';
6-
import util = require('util');
1+
import process from 'node:process';
2+
import {URL} from 'node:url';
3+
import {EventEmitter} from 'node:events';
4+
import http, {ClientRequest, IncomingMessage} from 'node:http';
5+
import https from 'node:https';
6+
import {AddressInfo} from 'node:net';
7+
import util from 'node:util';
78
import pEvent from 'p-event';
89
import test from 'ava';
9-
import timer, {Timings, ClientRequestWithTimings, IncomingMessageWithTimings} from '../source';
10-
import http2 = require('http2-wrapper');
10+
import http2 from 'http2-wrapper';
11+
import timer, {Timings, ClientRequestWithTimings, IncomingMessageWithTimings} from '../source/index.js';
1112

1213
let server: http.Server & {
1314
url?: string;
@@ -71,7 +72,7 @@ test('by default everything is set to undefined', t => {
7172
request: undefined,
7273
firstByte: undefined,
7374
download: undefined,
74-
total: undefined
75+
total: undefined,
7576
});
7677
});
7778

@@ -157,7 +158,7 @@ test('no memory leak (`lookup` event)', async t => {
157158

158159
test('sets `total` on request error', async t => {
159160
const request = http.get(server.url!, {
160-
timeout: 1
161+
timeout: 1,
161162
});
162163
request.on('timeout', () => {
163164
request.abort();
@@ -356,6 +357,18 @@ test('sets `abort` on response.destroy()', async t => {
356357
t.is(typeof timings.abort, 'number');
357358
});
358359

360+
test.cb('works on already writableFinished request', t => {
361+
const request = http.get(server.url!);
362+
request.end(() => {
363+
const timings = timer(request);
364+
365+
t.is(typeof timings.upload, 'number');
366+
t.is(typeof timings.phases.request, 'number');
367+
368+
t.end();
369+
});
370+
});
371+
359372
const version = process.versions.node.split('.').map(v => Number(v)) as [number, number, number];
360373
if (version[0] >= 16) {
361374
test('skips proxy-wrapped sockets', async t => {

tsconfig.json

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
11
{
2+
"extends": "@sindresorhus/tsconfig",
23
"compilerOptions": {
3-
// https://github.com/sindresorhus/tsconfig/blob/3f5f189a9b895e0d9da72078b574df7b917f5ec8/tsconfig.json
44
"outDir": "dist",
5-
"target": "es2018", // Node.js 10
6-
"module": "commonjs",
7-
"moduleResolution": "node",
8-
"resolveJsonModule": true,
9-
"jsx": "react",
10-
"declaration": true,
11-
"pretty": true,
12-
"newLine": "lf",
13-
"stripInternal": true,
14-
"strict": true,
15-
"noImplicitReturns": true,
16-
"noUnusedLocals": true,
17-
"noUnusedParameters": true,
18-
"noFallthroughCasesInSwitch": true,
19-
"noUncheckedIndexedAccess": true,
20-
"noPropertyAccessFromIndexSignature": true,
21-
"noEmitOnError": true,
22-
"useDefineForClassFields": true,
23-
"forceConsistentCasingInFileNames": true,
24-
"skipLibCheck": true
5+
"target": "es2020", // Node.js 14
6+
"lib": [
7+
"es2020"
8+
]
259
},
2610
"include": [
2711
"source",

0 commit comments

Comments
 (0)