diff --git a/README.md b/README.md index 0b7b3ef..e9ff0fd 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,9 @@ Emitted when a video request is made, including after any redirects, retries, an Emitted when a video response has been found and has started downloading, including after any successful reconnects. +#### Forwarded events + +Any events emitted from the [request](https://nodejs.org/api/http.html#http_class_http_clientrequest) or [response](https://nodejs.org/api/http.html#http_class_http_serverresponse) objects will be forwarded to the miniget stream. # Install diff --git a/package-lock.json b/package-lock.json index ef717e5..a33b4f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -206,39 +206,75 @@ "type-detect": "4.0.8" } }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } + }, + "@sinonjs/samsam": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.2.0.tgz", + "integrity": "sha512-CaIcyX5cDsjcW/ab7HposFWzV1kC++4HNsfnEdFJa7cP1QIuILAKV+BgfeqRXhcnSAc76r/Rh/O5C+300BwUIw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, - "@types/lolex": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/lolex/-/lolex-5.1.0.tgz", - "integrity": "sha512-hCQ2dOEQUw1LwofdIpMMGGqENd5p5ANzvcTe1nXTjcQL84r7tcLXFJlBgi0Ggz0f7BLmE2epf0C5Q07iq2gV0g==", - "dev": true - }, "@types/mocha": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.1.tgz", "integrity": "sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q==", "dev": true }, - "@types/nock": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz", - "integrity": "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw==", - "dev": true, - "requires": { - "nock": "*" - } - }, "@types/node": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.0.tgz", "integrity": "sha512-zwrxviZS08kRX40nqBrmERElF2vpw4IUTd5khkhBTfFH8AOaeoLVx48EC4+ZzS2/Iga7NevncqnsUSYjM4OWYA==", "dev": true }, + "@types/sinon": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.8.tgz", + "integrity": "sha512-IVnI820FZFMGI+u1R+2VdRaD/82YIQTdqLYC9DLPszZuynAJDtCvCtCs3bmyL66s7FqRM3+LPX7DhHnVTaagDw==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", + "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", + "dev": true + }, "aggregate-error": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", @@ -977,6 +1013,12 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1162,6 +1204,12 @@ "minimist": "^1.2.0" } }, + "just-extend": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", + "dev": true + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -1184,6 +1232,12 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -1193,15 +1247,6 @@ "chalk": "^2.0.1" } }, - "lolex": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-6.0.0.tgz", - "integrity": "sha512-ad9IBHbfVJ3bPAotDxnCgJgKcNK5/mrRAfbJzXhY5+PEmuBWP7wyHQlA6L8TfSfPlqlDjY4K7IG6mbzsrIBx1A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, "longjohn": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/longjohn/-/longjohn-0.2.12.tgz", @@ -1399,6 +1444,19 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, + "nise": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", + "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, "nock": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/nock/-/nock-12.0.0.tgz", @@ -1623,6 +1681,15 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, "picomatch": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", @@ -1767,6 +1834,38 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "sinon": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.0.tgz", + "integrity": "sha512-eSNXz1XMcGEMHw08NJXSyTHIu6qTCOiN8x9ODACmZpNQpr0aXTBXBnI4xTzQzR+TEpOmLiKowGf9flCuKIzsbw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.2.0", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", diff --git a/package.json b/package.json index 9f2a430..d069bde 100644 --- a/package.json +++ b/package.json @@ -26,14 +26,14 @@ }, "dependencies": {}, "devDependencies": { - "@types/lolex": "^5.1.0", "@types/mocha": "^7.0.0", "@types/node": "^13.1.0", - "lolex": "^6.0.0", + "@types/sinon": "^9.0.8", "longjohn": "^0.2.12", "mocha": "^7.0.1", "nock": "^12.0.0", "nyc": "^15.0.0", + "sinon": "^9.2.0", "stream-equal": "^1.1.1", "ts-node": "^8.10.1", "typescript": "^3.9.3" diff --git a/src/index.ts b/src/index.ts index aa7405b..55f4e4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { RequestOptions, IncomingMessage, ClientRequest } from 'http'; +import { EventEmitter } from 'events'; import http from 'http'; import https from 'https'; import { parse as urlParse } from 'url'; @@ -13,6 +14,10 @@ const httpLibs: { const redirectStatusCodes = new Set([301, 302, 303, 307, 308]); const retryStatusCodes = new Set([429, 503]); +// `request`, `response`, `abort` left out, miniget will emit these. +const requestEvents = ['connect', 'continue', 'information', 'socket', 'timeout', 'upgrade']; +const responseEvents = ['aborted', 'close']; + namespace Miniget { export interface Options extends RequestOptions { maxRedirects?: number; @@ -119,6 +124,12 @@ function Miniget(url: string, options: Miniget.Options = {}): Miniget.Stream { } }; + const forwardEvents = (ee: EventEmitter, events: string[]) => { + for (let event of events) { + ee.on(event, stream.emit.bind(stream, event)); + } + }; + const doDownload = (): void => { if (aborted) { return; } let parsed: RequestOptions, httpLib; @@ -185,6 +196,7 @@ function Miniget(url: string, options: Miniget.Options = {}): Miniget.Stream { } return; } + let decodedStream = res as unknown as Transform; const cleanup = (): void => { res.removeListener('data', ondata); @@ -224,8 +236,10 @@ function Miniget(url: string, options: Miniget.Options = {}): Miniget.Stream { activeDecodedStream = decodedStream; stream.emit('response', res); res.on('error', onerror); + forwardEvents(res, responseEvents); }); activeRequest.on('error', onRequestError); + forwardEvents(activeRequest, requestEvents); stream.emit('request', activeRequest); }; diff --git a/test/request-test.ts b/test/request-test.ts index c1cb54d..bc734c5 100644 --- a/test/request-test.ts +++ b/test/request-test.ts @@ -2,20 +2,22 @@ import fs from 'fs'; import path from 'path'; import zlib from 'zlib'; import assert from 'assert'; -import nock from 'nock'; -import lolex from 'lolex'; -import streamEqual from 'stream-equal'; -import miniget from '../dist'; import { Transform } from 'stream'; import { IncomingMessage, ClientRequest } from 'http'; + +import miniget from '../dist'; + +import nock from 'nock'; +import sinon from 'sinon'; +import streamEqual from 'stream-equal'; import 'longjohn'; nock.disableNetConnect(); describe('Make a request', () => { afterEach(() => { nock.cleanAll(); }); - let clock: lolex.InstalledClock; - beforeEach(() => clock = lolex.install()); + let clock: sinon.SinonFakeTimers; + beforeEach(() => clock = sinon.useFakeTimers()); afterEach(() => clock.uninstall()); describe('with `.text()`', () => { @@ -759,13 +761,28 @@ describe('Make a request', () => { const scope = nock('http://hello.net') .head('/world') .reply(200, '', { 'content-length': '10' }); - const req = miniget('http://hello.net/world', { method: 'HEAD' }); - req.on('error', done); - req.on('response', res => { + const stream = miniget('http://hello.net/world', { method: 'HEAD' }); + stream.on('error', done); + stream.on('response', res => { scope.done(); assert.equal(res.headers['content-length'], '10'); done(); }); }); }); + + it('Events from request and response are forwarded to miniget stream', (done) => { + const scope = nock('https://randomhost.com') + .get('/randompath') + .reply(200, 'hi'); + const stream = miniget('https://randomhost.com/randompath'); + const socketSpy = sinon.spy(); + stream.on('socket', socketSpy); + stream.on('end', () => { + scope.done(); + assert.equal(socketSpy.callCount, 1); + done(); + }); + stream.resume(); + }); });