diff --git a/src/env.js b/src/env.js index fbd8383..20b3efc 100644 --- a/src/env.js +++ b/src/env.js @@ -2,11 +2,13 @@ const isElectron = require('is-electron') const IS_ENV_WITH_DOM = typeof window === 'object' && typeof document === 'object' && document.nodeType === 9 +// @ts-ignore const IS_ELECTRON = isElectron() const IS_BROWSER = IS_ENV_WITH_DOM && !IS_ELECTRON const IS_ELECTRON_MAIN = IS_ELECTRON && !IS_ENV_WITH_DOM const IS_ELECTRON_RENDERER = IS_ELECTRON && IS_ENV_WITH_DOM const IS_NODE = typeof require === 'function' && typeof process !== 'undefined' && typeof process.release !== 'undefined' && process.release.name === 'node' && !IS_ELECTRON +// @ts-ignore // eslint-disable-next-line no-undef const IS_WEBWORKER = typeof importScripts === 'function' && typeof self !== 'undefined' && typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope const IS_TEST = typeof process !== 'undefined' && typeof process.env !== 'undefined' && process.env.NODE_ENV === 'test' diff --git a/src/files/normalise-input.js b/src/files/normalise-input.js index 32c9c11..3456f7c 100644 --- a/src/files/normalise-input.js +++ b/src/files/normalise-input.js @@ -275,8 +275,10 @@ async function * readBlob (blob, options) { const getNextChunk = () => new Promise((resolve, reject) => { reader.onloadend = e => { - const data = e.target.result - resolve(data.byteLength === 0 ? null : data) + if (e.target) { + const data = /** @type {ArrayBuffer} */(e.target.result) + resolve(data.byteLength === 0 ? null : data) + } } reader.onerror = reject diff --git a/src/globalthis.js b/src/globalthis.js index 6e56a77..91a9845 100644 --- a/src/globalthis.js +++ b/src/globalthis.js @@ -1,3 +1,4 @@ +// @ts-nocheck /* eslint-disable no-undef */ /* eslint-disable no-extend-native */ /* eslint-disable strict */ diff --git a/src/http.js b/src/http.js index 2ee9a20..3576108 100644 --- a/src/http.js +++ b/src/http.js @@ -3,13 +3,14 @@ const fetch = require('node-fetch') const merge = require('merge-options').bind({ ignoreUndefined: true }) +const anySignal = require('any-signal') const { URL, URLSearchParams } = require('iso-url') const TextDecoder = require('./text-encoder') -const AbortController = require('abort-controller') -const anySignal = require('any-signal') +const { AbortController } = require('abort-controller') const Request = fetch.Request -const Headers = fetch.Headers + +/** @typedef {import("./types").HttpResponse} HttpResponse */ class TimeoutError extends Error { constructor () { @@ -73,20 +74,20 @@ const defaults = { } /** - * @typedef {Object} APIOptions - creates a new type named 'SpecialType' - * @prop {any} [body] - Request body - * @prop {Object} [json] - JSON shortcut - * @prop {string} [method] - GET, POST, PUT, DELETE, etc. - * @prop {string} [base] - The base URL to use in case url is a relative URL - * @prop {Headers|Record} [headers] - Request header. - * @prop {number} [timeout] - Amount of time until request should timeout in ms. - * @prop {AbortSignal} [signal] - Signal to abort the request. - * @prop {URLSearchParams|Object} [searchParams] - URL search param. - * @prop {string} [credentials] - * @prop {boolean} [throwHttpErrors] - * @prop {function(URLSearchParams): URLSearchParams } [transformSearchParams] - * @prop {function(any): any} [transform] - When iterating the response body, transform each chunk with this function. - * @prop {function(Response): Promise} [handleError] - Handle errors + * @typedef {object} APIOptions - creates a new type named 'SpecialType' + * @property {any} [body] - Request body + * @property {object} [json] - JSON shortcut + * @property {string} [method] - GET, POST, PUT, DELETE, etc. + * @property {string} [base] - The base URL to use in case url is a relative URL + * @property {Headers|Record} [headers] - Request header. + * @property {number} [timeout] - Amount of time until request should timeout in ms. + * @property {AbortSignal} [signal] - Signal to abort the request. + * @property {URLSearchParams|object} [searchParams] - URL search param. + * @property {string} [credentials] + * @property {boolean} [throwHttpErrors] + * @property {function(URLSearchParams): URLSearchParams } [transformSearchParams] + * @property {function(any): any} [transform] - When iterating the response body, transform each chunk with this function. + * @property {function(Response): Promise} [handleError] - Handle errors */ class HTTP { @@ -104,12 +105,12 @@ class HTTP { * * @param {string | URL | Request} resource * @param {APIOptions} options - * @returns {Promise} + * @returns {Promise} */ async fetch (resource, options = {}) { /** @type {APIOptions} */ const opts = merge(this.opts, options) - opts.headers = new Headers(opts.headers) + // opts.headers = new Headers(opts.headers) // validate resource type if (typeof resource !== 'string' && !(resource instanceof URL || resource instanceof Request)) { @@ -137,8 +138,9 @@ class HTTP { } if (opts.json !== undefined) { - opts.body = JSON.stringify(opts.json) - opts.headers.set('content-type', 'application/json') + opts.body = JSON.stringify(opts.json); + + /** @type {Headers} */(opts.headers).set('content-type', 'application/json') } const abortController = new AbortController() @@ -183,7 +185,7 @@ class HTTP { /** * @param {string | URL | Request} resource * @param {APIOptions} options - * @returns {Promise} + * @returns {Promise} */ post (resource, options = {}) { return this.fetch(resource, { @@ -195,7 +197,7 @@ class HTTP { /** * @param {string | URL | Request} resource * @param {APIOptions} options - * @returns {Promise} + * @returns {Promise} */ get (resource, options = {}) { return this.fetch(resource, { @@ -207,7 +209,7 @@ class HTTP { /** * @param {string | URL | Request} resource * @param {APIOptions} options - * @returns {Promise} + * @returns {Promise} */ put (resource, options = {}) { return this.fetch(resource, { @@ -219,7 +221,7 @@ class HTTP { /** * @param {string | URL | Request} resource * @param {APIOptions} options - * @returns {Promise} + * @returns {Promise} */ delete (resource, options = {}) { return this.fetch(resource, { @@ -231,7 +233,7 @@ class HTTP { /** * @param {string | URL | Request} resource * @param {APIOptions} options - * @returns {Promise} + * @returns {Promise} */ options (resource, options = {}) { return this.fetch(resource, { @@ -245,7 +247,7 @@ class HTTP { * Parses NDJSON chunks from an iterator * * @param {AsyncGenerator} source - * @returns {AsyncGenerator} + * @returns {AsyncGenerator} */ const ndjson = async function * (source) { const decoder = new TextDecoder() @@ -320,39 +322,40 @@ const isAsyncIterator = (obj) => { HTTP.HTTPError = HTTPError HTTP.TimeoutError = TimeoutError HTTP.streamToAsyncIterator = streamToAsyncIterator +HTTP.ndjson = (s) => ndjson(streamToAsyncIterator(s)) /** * @param {string | URL | Request} resource - * @param {APIOptions} options - * @returns {Promise} + * @param {APIOptions} [options] + * @returns {Promise} */ HTTP.post = (resource, options) => new HTTP(options).post(resource, options) /** * @param {string | URL | Request} resource - * @param {APIOptions} options - * @returns {Promise} + * @param {APIOptions} [options] + * @returns {Promise} */ HTTP.get = (resource, options) => new HTTP(options).get(resource, options) /** * @param {string | URL | Request} resource - * @param {APIOptions} options - * @returns {Promise} + * @param {APIOptions} [options] + * @returns {Promise} */ HTTP.put = (resource, options) => new HTTP(options).put(resource, options) /** * @param {string | URL | Request} resource - * @param {APIOptions} options - * @returns {Promise} + * @param {APIOptions} [options] + * @returns {Promise} */ HTTP.delete = (resource, options) => new HTTP(options).delete(resource, options) /** * @param {string | URL | Request} resource - * @param {APIOptions} options - * @returns {Promise} + * @param {APIOptions} [options] + * @returns {Promise} */ HTTP.options = (resource, options) => new HTTP(options).options(resource, options) diff --git a/src/index.js b/src/index.js index e69de29..7d8dfdd 100644 --- a/src/index.js +++ b/src/index.js @@ -0,0 +1,5 @@ +'use strict' + +module.exports = { + HTTP: require('./http') +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..72f422e --- /dev/null +++ b/src/types.ts @@ -0,0 +1,5 @@ + +export interface HttpResponse extends Response { + iterator: () => AsyncIterator; + ndjson: AsyncGeneratorFunction +} \ No newline at end of file diff --git a/test/http.spec.js b/test/http.spec.js index b79ea62..e477399 100644 --- a/test/http.spec.js +++ b/test/http.spec.js @@ -5,33 +5,35 @@ const { expect } = require('aegir/utils/chai') const HTTP = require('../src/http') const toStream = require('it-to-stream') const delay = require('delay') -const AbortController = require('abort-controller') +const { AbortController } = require('abort-controller') const drain = require('it-drain') const all = require('it-all') const { isBrowser, isWebWorker } = require('../src/env') const { Buffer } = require('buffer') +const ECHO_SERVER = /** @type {string|null} */(process.env.ECHO_SERVER) + describe('http', function () { it('makes a GET request', async function () { - const req = await HTTP.get(`${process.env.ECHO_SERVER}/echo/query?test=one`) + const req = await HTTP.get(`${ECHO_SERVER}/echo/query?test=one`) const rsp = await req.json() expect(rsp).to.be.deep.eq({ test: 'one' }) }) it('makes a GET request with redirect', async function () { - const req = await HTTP.get(`${process.env.ECHO_SERVER}/redirect?to=${encodeURI(`${process.env.ECHO_SERVER}/echo/query?test=one`)}`) + const req = await HTTP.get(`${ECHO_SERVER}/redirect?to=${encodeURI(`${ECHO_SERVER}/echo/query?test=one`)}`) const rsp = await req.json() expect(rsp).to.be.deep.eq({ test: 'one' }) }) it('makes a GET request with a really short timeout', function () { - return expect(HTTP.get(`${process.env.ECHO_SERVER}/redirect?to=${encodeURI(`${process.env.ECHO_SERVER}/echo/query?test=one`)}`, { + return expect(HTTP.get(`${ECHO_SERVER}/redirect?to=${encodeURI(`${ECHO_SERVER}/echo/query?test=one`)}`, { timeout: 1 })).to.eventually.be.rejectedWith().instanceOf(HTTP.TimeoutError) }) it('respects headers', async function () { - const req = await HTTP.post(`${process.env.ECHO_SERVER}/echo/headers`, { + const req = await HTTP.post(`${ECHO_SERVER}/echo/headers`, { headers: { foo: 'bar' } @@ -46,13 +48,13 @@ describe('http', function () { bar: 'baz' } }) - const req = await http.post(`${process.env.ECHO_SERVER}/echo/headers`) + const req = await http.post(`${ECHO_SERVER}/echo/headers`) const rsp = await req.json() expect(rsp).to.have.property('bar', 'baz') }) it('makes a JSON request', async () => { - const req = await HTTP.post(`${process.env.ECHO_SERVER}/echo`, { + const req = await HTTP.post(`${ECHO_SERVER}/echo`, { json: { test: 2 } @@ -63,7 +65,7 @@ describe('http', function () { }) it('makes a DELETE request', async () => { - const req = await HTTP.delete(`${process.env.ECHO_SERVER}/echo`, { + const req = await HTTP.delete(`${ECHO_SERVER}/echo`, { json: { test: 2 } @@ -76,7 +78,7 @@ describe('http', function () { it('allow async aborting', async function () { const controller = new AbortController() - const res = HTTP.get(process.env.ECHO_SERVER, { + const res = HTTP.get(ECHO_SERVER, { signal: controller.signal }) controller.abort() @@ -85,7 +87,7 @@ describe('http', function () { }) it('parses the response as ndjson', async function () { - const res = await HTTP.post(`${process.env.ECHO_SERVER}/echo`, { + const res = await HTTP.post(`${ECHO_SERVER}/echo`, { body: '{}\n{}' }) @@ -96,7 +98,7 @@ describe('http', function () { it('parses the response as an async iterable', async function () { const res = await HTTP.post('echo', { - base: process.env.ECHO_SERVER, + base: ECHO_SERVER, body: 'hello world' }) @@ -120,7 +122,7 @@ describe('http', function () { throw err }()) - const res = await HTTP.post(process.env.ECHO_SERVER, { + const res = await HTTP.post(ECHO_SERVER, { body: toStream.readable(body) }) @@ -143,7 +145,7 @@ describe('http', function () { throw err }()) - const res = await HTTP.post(process.env.ECHO_SERVER, { + const res = await HTTP.post(ECHO_SERVER, { body: toStream.readable(body), signal: controller.signal })