From fa3068e2e11da454a39e364f903270b487f13ec5 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 01/16] consolidate exports in error.js --- src/http/error.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/http/error.js b/src/http/error.js index d839809..273cd4a 100644 --- a/src/http/error.js +++ b/src/http/error.js @@ -6,7 +6,6 @@ class TimeoutError extends Error { this.name = 'TimeoutError' } } -exports.TimeoutError = TimeoutError class AbortError extends Error { constructor (message = 'The operation was aborted.') { @@ -14,7 +13,6 @@ class AbortError extends Error { this.name = 'AbortError' } } -exports.AbortError = AbortError class HTTPError extends Error { /** @@ -26,4 +24,9 @@ class HTTPError extends Error { this.response = response } } -exports.HTTPError = HTTPError + +module.exports = { + AbortError, + HTTPError, + TimeoutError +} From 758f4b93e7f1a8cff5da6a4d0342215dba4621c1 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 02/16] consolidate exports in fetch.rn.js --- src/fetch.rn.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/fetch.rn.js b/src/fetch.rn.js index 54688ed..a2925d7 100644 --- a/src/fetch.rn.js +++ b/src/fetch.rn.js @@ -1,18 +1,20 @@ // @ts-nocheck 'use strict' // @ts-ignore -const { Headers, Request, Response, fetch } = require('react-native-fetch-api') +const { Headers: rnHeaders, Request: rnRequest, Response: rnResponse, fetch: rnFetch } = require('react-native-fetch-api') /** @type {import('electron-fetch').default} */ -const rnFetch = fetch +const fetch = rnFetch /** @type {import('electron-fetch').Headers} */ -const rnHeaders = Headers +const Headers = rnHeaders /** @type {import('electron-fetch').Request} */ -const rnRequest = Request +const Request = rnRequest /** @type {import('electron-fetch').Response} */ -const rnResponse = Response -module.exports = rnFetch -module.exports.Headers = rnHeaders -module.exports.Request = rnRequest -module.exports.Response = rnResponse -module.exports.default = rnFetch +const Response = rnResponse + +module.exports = { + Headers, + Request, + Response, + default: fetch +} From 5a5a157399d0c0ef91946cc0dcbd6541213e217f Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 03/16] give exports a name in fetch.browser.js --- src/fetch.browser.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fetch.browser.js b/src/fetch.browser.js index 57b8c79..fc34113 100644 --- a/src/fetch.browser.js +++ b/src/fetch.browser.js @@ -7,4 +7,6 @@ */ // use window.fetch if it is available, fall back to node-fetch if not -module.exports = require('native-fetch') +const nativeFetch = require('native-fetch') + +module.exports = nativeFetch From 6a01b9022c1798feea98829453bfb2a31f8224fe Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 04/16] move require calls to top level in fetch.js --- src/fetch.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/fetch.js b/src/fetch.js index a941d92..8001503 100644 --- a/src/fetch.js +++ b/src/fetch.js @@ -7,12 +7,14 @@ */ const { isElectronMain } = require('./env') +const nativeFetch = require('native-fetch') +const electronFetch = require('electron-fetch') // use window.fetch if it is available, fall back to node-fetch if not -let impl = 'native-fetch' +let impl = nativeFetch if (isElectronMain) { - impl = 'electron-fetch' + impl = electronFetch } -module.exports = require(impl) +module.exports = impl From 843a41e600a0524f9a440a45800bb901cdac0408 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 05/16] top level require() in http/fetch.js --- src/http/fetch.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/http/fetch.js b/src/http/fetch.js index 1a27129..4cc9c38 100644 --- a/src/http/fetch.js +++ b/src/http/fetch.js @@ -8,15 +8,16 @@ * @property {globalThis.Headers} fetchImpl.Headers */ -let implName = './fetch.node' +const fetchNode = require('./fetch.node') +const fetchBrowser = require('./fetch.browser') + +let fetch = fetchNode; if (typeof XMLHttpRequest === 'function') { // Electron has `XMLHttpRequest` and should get the browser implementation // instead of node. - implName = './fetch.browser' + fetch = fetchBrowser } /** @type {fetchImpl} */ -const fetch = require(implName) - module.exports = fetch From 78abcf15be035129d018f0540d345c8ab42468ad Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 06/16] simplify require() statements --- src/files/glob-source.js | 2 +- src/http.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/files/glob-source.js b/src/files/glob-source.js index 49c114c..e556f41 100644 --- a/src/files/glob-source.js +++ b/src/files/glob-source.js @@ -1,7 +1,7 @@ 'use strict' -const fsp = require('fs').promises const fs = require('fs') +const { promises: fsp } = fs const glob = require('it-glob') const Path = require('path') const errCode = require('err-code') diff --git a/src/http.js b/src/http.js index 59bc296..4d95514 100644 --- a/src/http.js +++ b/src/http.js @@ -3,7 +3,8 @@ const { fetch, Request, Headers } = require('./http/fetch') const { TimeoutError, HTTPError } = require('./http/error') -const merge = require('merge-options').bind({ ignoreUndefined: true }) +const mergeOptions = require('merge-options') +const merge = mergeOptions.bind({ ignoreUndefined: true }) const { URL, URLSearchParams } = require('iso-url') const anySignal = require('any-signal') const browserReableStreamToIt = require('browser-readablestream-to-it') From 996d37835c48841a9b246c38a75ab6008a1d3a52 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 07/16] require() full file paths --- rn-test.require.js | 6 +++--- src/fetch.js | 2 +- src/files/url-source.js | 2 +- src/http.js | 6 +++--- src/http/fetch.browser.js | 4 ++-- src/http/fetch.js | 4 ++-- src/http/fetch.node.js | 2 +- src/http/fetch.rn.js | 4 ++-- test/env.spec.js | 2 +- test/files/glob-source.spec.js | 4 ++-- test/files/url-source.spec.js | 2 +- test/http.spec.js | 4 ++-- test/supports.spec.js | 4 ++-- 13 files changed, 23 insertions(+), 23 deletions(-) diff --git a/rn-test.require.js b/rn-test.require.js index 9a37889..b784149 100644 --- a/rn-test.require.js +++ b/rn-test.require.js @@ -1,8 +1,8 @@ 'use strict' -const { polyfill: polyfillReadableStream } = require('react-native-polyfill-globals/src/readable-stream') -const { polyfill: polyfillURL } = require('react-native-polyfill-globals/src/url') -const { polyfill: polyfillEncoding } = require('react-native-polyfill-globals/src/encoding') +const { polyfill: polyfillReadableStream } = require('react-native-polyfill-globals/src/readable-stream.js') +const { polyfill: polyfillURL } = require('react-native-polyfill-globals/src/url.js') +const { polyfill: polyfillEncoding } = require('react-native-polyfill-globals/src/encoding.js') polyfillURL() polyfillReadableStream() diff --git a/src/fetch.js b/src/fetch.js index 8001503..79fc5f2 100644 --- a/src/fetch.js +++ b/src/fetch.js @@ -6,7 +6,7 @@ * @typedef {globalThis.Response} Response */ -const { isElectronMain } = require('./env') +const { isElectronMain } = require('./env.js') const nativeFetch = require('native-fetch') const electronFetch = require('electron-fetch') diff --git a/src/files/url-source.js b/src/files/url-source.js index 87fe284..854129b 100644 --- a/src/files/url-source.js +++ b/src/files/url-source.js @@ -1,6 +1,6 @@ 'use strict' -const HTTP = require('../http') +const HTTP = require('../http.js') /** * diff --git a/src/http.js b/src/http.js index 4d95514..c07838f 100644 --- a/src/http.js +++ b/src/http.js @@ -1,14 +1,14 @@ /* eslint-disable no-undef */ 'use strict' -const { fetch, Request, Headers } = require('./http/fetch') -const { TimeoutError, HTTPError } = require('./http/error') +const { fetch, Request, Headers } = require('./http/fetch.js') +const { TimeoutError, HTTPError } = require('./http/error.js') const mergeOptions = require('merge-options') const merge = mergeOptions.bind({ ignoreUndefined: true }) const { URL, URLSearchParams } = require('iso-url') const anySignal = require('any-signal') const browserReableStreamToIt = require('browser-readablestream-to-it') -const { isBrowser, isWebWorker } = require('./env') +const { isBrowser, isWebWorker } = require('./env.js') const all = require('it-all') /** diff --git a/src/http/fetch.browser.js b/src/http/fetch.browser.js index 61b78cc..2893edd 100644 --- a/src/http/fetch.browser.js +++ b/src/http/fetch.browser.js @@ -1,8 +1,8 @@ 'use strict' -const { TimeoutError, AbortError } = require('./error') +const { TimeoutError, AbortError } = require('./error.js') // @ts-expect-error -const { Response, Request, Headers, default: fetch } = require('../fetch') +const { Response, Request, Headers, default: fetch } = require('../fetch.js') /** * @typedef {import('../types').FetchOptions} FetchOptions diff --git a/src/http/fetch.js b/src/http/fetch.js index 4cc9c38..5e8a916 100644 --- a/src/http/fetch.js +++ b/src/http/fetch.js @@ -8,8 +8,8 @@ * @property {globalThis.Headers} fetchImpl.Headers */ -const fetchNode = require('./fetch.node') -const fetchBrowser = require('./fetch.browser') +const fetchNode = require('./fetch.node.js') +const fetchBrowser = require('./fetch.browser.js') let fetch = fetchNode; diff --git a/src/http/fetch.node.js b/src/http/fetch.node.js index ef5fcb3..cbe8afb 100644 --- a/src/http/fetch.node.js +++ b/src/http/fetch.node.js @@ -1,6 +1,6 @@ 'use strict' // @ts-expect-error Request, Response and Headers are global types but concrete in implementations -const { Request, Response, Headers, default: defaultFetch, fetch: fetchFetch } = require('../fetch') +const { Request, Response, Headers, default: defaultFetch, fetch: fetchFetch } = require('../fetch.js') // @ts-ignore const toStream = require('it-to-stream') const { Buffer } = require('buffer') diff --git a/src/http/fetch.rn.js b/src/http/fetch.rn.js index da7735c..4ec9191 100644 --- a/src/http/fetch.rn.js +++ b/src/http/fetch.rn.js @@ -1,8 +1,8 @@ // @ts-nocheck 'use strict' -const { TimeoutError, AbortError } = require('./error') -const { Response, Request, Headers, default: fetch } = require('../fetch') +const { TimeoutError, AbortError } = require('./error.js') +const { Response, Request, Headers, default: fetch } = require('../fetch.js') /** * @typedef {import('../types').FetchOptions} FetchOptions diff --git a/test/env.spec.js b/test/env.spec.js index 13d5352..bc01975 100644 --- a/test/env.spec.js +++ b/test/env.spec.js @@ -2,7 +2,7 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const env = require('../src/env') +const env = require('../src/env.js') describe('env', function () { it('isElectron should have the correct value in each env', function () { diff --git a/test/files/glob-source.spec.js b/test/files/glob-source.spec.js index fea6867..d5a06ce 100644 --- a/test/files/glob-source.spec.js +++ b/test/files/glob-source.spec.js @@ -2,12 +2,12 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const globSource = require('../../src/files/glob-source') +const globSource = require('../../src/files/glob-source.js') const all = require('it-all') const path = require('path') const { isNode -} = require('../../src/env') +} = require('../../src/env.js') const fs = require('fs') function fixtureDir () { diff --git a/test/files/url-source.spec.js b/test/files/url-source.spec.js index 79a3635..ab30478 100644 --- a/test/files/url-source.spec.js +++ b/test/files/url-source.spec.js @@ -4,7 +4,7 @@ const { expect } = require('aegir/utils/chai') const all = require('it-all') -const urlSource = require('../../src/files/url-source') +const urlSource = require('../../src/files/url-source.js') const { Buffer } = require('buffer') describe('url-source', function () { diff --git a/test/http.spec.js b/test/http.spec.js index 9978ea6..c4a1303 100644 --- a/test/http.spec.js +++ b/test/http.spec.js @@ -2,13 +2,13 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const HTTP = require('../src/http') +const HTTP = require('../src/http.js') // @ts-ignore const toStream = require('it-to-stream') const delay = require('delay') const drain = require('it-drain') const all = require('it-all') -const { isBrowser, isWebWorker, isReactNative } = require('../src/env') +const { isBrowser, isWebWorker, isReactNative } = require('../src/env.js') const { Buffer } = require('buffer') const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string') const { toString: uint8ArrayToString } = require('uint8arrays/to-string') diff --git a/test/supports.spec.js b/test/supports.spec.js index b0070a8..2ccaf87 100644 --- a/test/supports.spec.js +++ b/test/supports.spec.js @@ -2,8 +2,8 @@ /* eslint-env mocha */ const { expect } = require('aegir/utils/chai') -const supports = require('../src/supports') -const env = require('../src/env') +const supports = require('../src/supports.js') +const env = require('../src/env.js') describe('supports', function () { it('supportsFileReader should return false in node', function () { From 2cc507abe744d813b0a16f95156799e2c7233548 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 08/16] full filepath for typedef import(types.d.ts) --- src/files/glob-source.js | 4 ++-- src/files/url-source.js | 4 ++-- src/http.js | 4 ++-- src/http/fetch.browser.js | 4 ++-- src/http/fetch.node.js | 4 ++-- src/http/fetch.rn.js | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/files/glob-source.js b/src/files/glob-source.js index e556f41..06fd6f5 100644 --- a/src/files/glob-source.js +++ b/src/files/glob-source.js @@ -11,8 +11,8 @@ const errCode = require('err-code') * * @param {string} cwd - The directory to start matching the pattern in * @param {string} pattern - Glob pattern to match - * @param {import('../types').GlobSourceOptions} [options] - Optional options - * @returns {AsyncGenerator} File objects that match glob + * @param {import('../types.d.ts').GlobSourceOptions} [options] - Optional options + * @returns {AsyncGenerator} File objects that match glob */ module.exports = async function * globSource (cwd, pattern, options) { options = options || {} diff --git a/src/files/url-source.js b/src/files/url-source.js index 854129b..1e643b0 100644 --- a/src/files/url-source.js +++ b/src/files/url-source.js @@ -5,7 +5,7 @@ const HTTP = require('../http.js') /** * * @param {string} url - * @param {import("../types").HTTPOptions} [options] + * @param {import('../types.d.ts').HTTPOptions} [options] * @returns {{ path: string; content?: AsyncIterable }} */ const urlSource = (url, options) => { @@ -18,7 +18,7 @@ const urlSource = (url, options) => { /** * * @param {string} url - * @param {import("../types").HTTPOptions} [options] + * @param {import('../types.d.ts').HTTPOptions} [options] * @returns {AsyncIterable} */ async function * readURLContent (url, options) { diff --git a/src/http.js b/src/http.js index c07838f..c40d789 100644 --- a/src/http.js +++ b/src/http.js @@ -13,8 +13,8 @@ const all = require('it-all') /** * @typedef {import('stream').Readable} NodeReadableStream - * @typedef {import('./types').HTTPOptions} HTTPOptions - * @typedef {import('./types').ExtendedResponse} ExtendedResponse + * @typedef {import('./types.d.ts').HTTPOptions} HTTPOptions + * @typedef {import('./types.d.ts').ExtendedResponse} ExtendedResponse */ /** diff --git a/src/http/fetch.browser.js b/src/http/fetch.browser.js index 2893edd..17d4d1b 100644 --- a/src/http/fetch.browser.js +++ b/src/http/fetch.browser.js @@ -5,8 +5,8 @@ const { TimeoutError, AbortError } = require('./error.js') const { Response, Request, Headers, default: fetch } = require('../fetch.js') /** - * @typedef {import('../types').FetchOptions} FetchOptions - * @typedef {import('../types').ProgressFn} ProgressFn + * @typedef {import('../types.d.ts').FetchOptions} FetchOptions + * @typedef {import('../types.d.ts').ProgressFn} ProgressFn */ /** diff --git a/src/http/fetch.node.js b/src/http/fetch.node.js index cbe8afb..a1124d7 100644 --- a/src/http/fetch.node.js +++ b/src/http/fetch.node.js @@ -7,8 +7,8 @@ const { Buffer } = require('buffer') /** * @typedef {import('stream').Readable} NodeReadableStream * - * @typedef {import('../types').FetchOptions} FetchOptions - * @typedef {import('../types').ProgressFn} ProgressFn + * @typedef {import('../types.d.ts').FetchOptions} FetchOptions + * @typedef {import('../types.d.ts').ProgressFn} ProgressFn */ // undici and node-fetch have different exports diff --git a/src/http/fetch.rn.js b/src/http/fetch.rn.js index 4ec9191..6eeb002 100644 --- a/src/http/fetch.rn.js +++ b/src/http/fetch.rn.js @@ -5,8 +5,8 @@ const { TimeoutError, AbortError } = require('./error.js') const { Response, Request, Headers, default: fetch } = require('../fetch.js') /** - * @typedef {import('../types').FetchOptions} FetchOptions - * @typedef {import('../types').ProgressFn} ProgressFn + * @typedef {import('../types.d.ts').FetchOptions} FetchOptions + * @typedef {import('../types.d.ts').ProgressFn} ProgressFn */ /** From af6447f0aad65b9f20d4c19d52bdbcf87cfe174e Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 09/16] don't require CJS when checking for IS_NODE --- src/env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/env.js b/src/env.js index c89a1b0..07c128c 100644 --- a/src/env.js +++ b/src/env.js @@ -7,7 +7,7 @@ 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 +const IS_NODE = typeof process !== 'undefined' && typeof process.release !== 'undefined' && process.release.name === 'node' && !IS_ELECTRON // @ts-ignore - we either ignore worker scope or dom scope 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' From 596df49481390e33dbae906d6772b5404075323e Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 10/16] fix isNodeReadableStream() type guard 'readable' is not an own property. and there is no 'writable' --- src/http.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/http.js b/src/http.js index c40d789..0152324 100644 --- a/src/http.js +++ b/src/http.js @@ -336,8 +336,9 @@ const isWebReadableStream = (value) => { * @returns {value is NodeReadableStream} */ const isNodeReadableStream = (value) => - Object.prototype.hasOwnProperty.call(value, 'readable') && - Object.prototype.hasOwnProperty.call(value, 'writable') + 'readable' in value && + 'destroy' in value && + Symbol.asyncIterator in value HTTP.HTTPError = HTTPError HTTP.TimeoutError = TimeoutError From a1df1296ff1c797ea5a21d97dc8b7e2a6dc64b3e Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:00:00 +0000 Subject: [PATCH 11/16] check for XHR in fetch code --- src/http/fetch.browser.js | 12 ++++++++---- src/http/fetch.rn.js | 8 ++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/http/fetch.browser.js b/src/http/fetch.browser.js index 17d4d1b..d4cc0d0 100644 --- a/src/http/fetch.browser.js +++ b/src/http/fetch.browser.js @@ -2,7 +2,7 @@ const { TimeoutError, AbortError } = require('./error.js') // @ts-expect-error -const { Response, Request, Headers, default: fetch } = require('../fetch.js') +const { Response, Request, Headers, default: fetchWithStreaming } = require('../fetch.js') /** * @typedef {import('../types.d.ts').FetchOptions} FetchOptions @@ -95,8 +95,6 @@ const fetchWithProgress = (url, options = {}) => { }) } -const fetchWithStreaming = fetch - /** * @param {string | Request} url * @param {FetchOptions} options @@ -106,6 +104,12 @@ const fetchWith = (url, options = {}) => ? fetchWithProgress(url, options) : fetchWithStreaming(url, options) +let fetch = fetchWithStreaming + +if (typeof XMLHttpRequest === 'function') { + fetch = fetchWith +} + /** * Parse Headers from a XMLHttpRequest * @@ -137,7 +141,7 @@ class ResponseWithURL extends Response { } module.exports = { - fetch: fetchWith, + fetch, Request, Headers } diff --git a/src/http/fetch.rn.js b/src/http/fetch.rn.js index 6eeb002..1eea5b1 100644 --- a/src/http/fetch.rn.js +++ b/src/http/fetch.rn.js @@ -2,7 +2,7 @@ 'use strict' const { TimeoutError, AbortError } = require('./error.js') -const { Response, Request, Headers, default: fetch } = require('../fetch.js') +const { Response, Request, Headers, default: fetchWithStreaming } = require('../fetch.js') /** * @typedef {import('../types.d.ts').FetchOptions} FetchOptions @@ -88,8 +88,6 @@ const fetchWithProgress = (url, options = {}) => { }) } -const fetchWithStreaming = fetch - /** * @param {string | Request} url * @param {FetchOptions} options @@ -99,6 +97,8 @@ const fetchWith = (url, options = {}) => ? fetchWithProgress(url, options) : fetchWithStreaming(url, options) +const fetch = fetchWith + /** * Parse Headers from a XMLHttpRequest * @@ -130,7 +130,7 @@ class ResponseWithURL extends Response { } module.exports = { - fetch: fetchWith, + fetch, Request, Headers, ResponseWithURL From 5febfc39e5342772a8cc5b272854a807c7b2b541 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:00:00 +0000 Subject: [PATCH 12/16] delete obsolete node-fetch polyfill node.js has native fetch for a while now. it just doesn't have native XMLHttpRequest. --- package.json | 4 - src/fetch.browser.js | 12 --- src/fetch.js | 11 +-- src/http/fetch.browser.js | 147 ------------------------------------- src/http/fetch.js | 150 ++++++++++++++++++++++++++++++++++---- src/http/fetch.node.js | 100 ------------------------- 6 files changed, 138 insertions(+), 286 deletions(-) delete mode 100644 src/fetch.browser.js delete mode 100644 src/http/fetch.browser.js delete mode 100644 src/http/fetch.node.js diff --git a/package.json b/package.json index a72c73e..422f042 100644 --- a/package.json +++ b/package.json @@ -146,13 +146,11 @@ "any-signal": "^3.0.0", "browser-readablestream-to-it": "^1.0.0", "buffer": "^6.0.1", - "electron-fetch": "^1.7.2", "err-code": "^3.0.1", "is-electron": "^2.2.0", "iso-url": "^1.1.5", "it-all": "^1.0.4", "it-glob": "^1.0.1", - "it-to-stream": "^1.0.0", "merge-options": "^3.0.4", "nanoid": "^3.1.20", "native-fetch": "^3.0.0", @@ -174,10 +172,8 @@ "util": "^0.12.3" }, "browser": { - "./src/http/fetch.js": "./src/http/fetch.browser.js", "./src/temp-dir.js": "./src/temp-dir.browser.js", "./src/path-join.js": "./src/path-join.browser.js", - "./src/fetch.js": "./src/fetch.browser.js", "./src/files/glob-source.js": false, "./test/files/glob-source.spec.js": false, "electron-fetch": false, diff --git a/src/fetch.browser.js b/src/fetch.browser.js deleted file mode 100644 index fc34113..0000000 --- a/src/fetch.browser.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict' - -/** - * @typedef {globalThis.Headers} Headers - * @typedef {globalThis.Request} Request - * @typedef {globalThis.Response} Response - */ - -// use window.fetch if it is available, fall back to node-fetch if not -const nativeFetch = require('native-fetch') - -module.exports = nativeFetch diff --git a/src/fetch.js b/src/fetch.js index 79fc5f2..6029023 100644 --- a/src/fetch.js +++ b/src/fetch.js @@ -6,15 +6,6 @@ * @typedef {globalThis.Response} Response */ -const { isElectronMain } = require('./env.js') const nativeFetch = require('native-fetch') -const electronFetch = require('electron-fetch') -// use window.fetch if it is available, fall back to node-fetch if not -let impl = nativeFetch - -if (isElectronMain) { - impl = electronFetch -} - -module.exports = impl +module.exports = nativeFetch diff --git a/src/http/fetch.browser.js b/src/http/fetch.browser.js deleted file mode 100644 index d4cc0d0..0000000 --- a/src/http/fetch.browser.js +++ /dev/null @@ -1,147 +0,0 @@ -'use strict' - -const { TimeoutError, AbortError } = require('./error.js') -// @ts-expect-error -const { Response, Request, Headers, default: fetchWithStreaming } = require('../fetch.js') - -/** - * @typedef {import('../types.d.ts').FetchOptions} FetchOptions - * @typedef {import('../types.d.ts').ProgressFn} ProgressFn - */ - -/** - * Fetch with progress - * - * @param {string | Request} url - * @param {FetchOptions} [options] - * @returns {Promise} - */ -const fetchWithProgress = (url, options = {}) => { - const request = new XMLHttpRequest() - request.open(options.method || 'GET', url.toString(), true) - - const { timeout, headers } = options - - if (timeout && timeout > 0 && timeout < Infinity) { - request.timeout = timeout - } - - if (options.overrideMimeType != null) { - request.overrideMimeType(options.overrideMimeType) - } - - if (headers) { - for (const [name, value] of new Headers(headers)) { - request.setRequestHeader(name, value) - } - } - - if (options.signal) { - options.signal.onabort = () => request.abort() - } - - if (options.onUploadProgress) { - request.upload.onprogress = options.onUploadProgress - } - - // Note: Need to use `arraybuffer` here instead of `blob` because `Blob` - // instances coming from JSDOM are not compatible with `Response` from - // node-fetch (which is the setup we get when testing with jest because - // it uses JSDOM which does not provide a global fetch - // https://github.com/jsdom/jsdom/issues/1724) - request.responseType = 'arraybuffer' - - return new Promise((resolve, reject) => { - /** - * @param {Event} event - */ - const handleEvent = (event) => { - switch (event.type) { - case 'error': { - resolve(Response.error()) - break - } - case 'load': { - resolve( - new ResponseWithURL(request.responseURL, request.response, { - status: request.status, - statusText: request.statusText, - headers: parseHeaders(request.getAllResponseHeaders()) - }) - ) - break - } - case 'timeout': { - reject(new TimeoutError()) - break - } - case 'abort': { - reject(new AbortError()) - break - } - default: { - break - } - } - } - request.onerror = handleEvent - request.onload = handleEvent - request.ontimeout = handleEvent - request.onabort = handleEvent - - // @ts-expect-error options.body can be a node readable stream, which isn't compatible with XHR, but this - // file is a browser override so you won't get a node readable stream so ignore the error - request.send(options.body) - }) -} - -/** - * @param {string | Request} url - * @param {FetchOptions} options - */ -const fetchWith = (url, options = {}) => - (options.onUploadProgress != null) - ? fetchWithProgress(url, options) - : fetchWithStreaming(url, options) - -let fetch = fetchWithStreaming - -if (typeof XMLHttpRequest === 'function') { - fetch = fetchWith -} - -/** - * Parse Headers from a XMLHttpRequest - * - * @param {string} input - * @returns {Headers} - */ -const parseHeaders = (input) => { - const headers = new Headers() - for (const line of input.trim().split(/[\r\n]+/)) { - const index = line.indexOf(': ') - if (index > 0) { - headers.set(line.slice(0, index), line.slice(index + 1)) - } - } - - return headers -} - -class ResponseWithURL extends Response { - /** - * @param {string} url - * @param {BodyInit} body - * @param {ResponseInit} options - */ - constructor (url, body, options) { - super(body, options) - Object.defineProperty(this, 'url', { value: url }) - } -} - -module.exports = { - fetch, - Request, - Headers -} diff --git a/src/http/fetch.js b/src/http/fetch.js index 5e8a916..d4cc0d0 100644 --- a/src/http/fetch.js +++ b/src/http/fetch.js @@ -1,23 +1,147 @@ 'use strict' +const { TimeoutError, AbortError } = require('./error.js') +// @ts-expect-error +const { Response, Request, Headers, default: fetchWithStreaming } = require('../fetch.js') + +/** + * @typedef {import('../types.d.ts').FetchOptions} FetchOptions + * @typedef {import('../types.d.ts').ProgressFn} ProgressFn + */ + /** - * @typedef {object} fetchImpl - * @property {globalThis.fetch} fetchImpl.fetch - * @property {globalThis.Request} fetchImpl.Request - * @property {globalThis.Response} fetchImpl.Response - * @property {globalThis.Headers} fetchImpl.Headers + * Fetch with progress + * + * @param {string | Request} url + * @param {FetchOptions} [options] + * @returns {Promise} */ +const fetchWithProgress = (url, options = {}) => { + const request = new XMLHttpRequest() + request.open(options.method || 'GET', url.toString(), true) + + const { timeout, headers } = options + + if (timeout && timeout > 0 && timeout < Infinity) { + request.timeout = timeout + } + + if (options.overrideMimeType != null) { + request.overrideMimeType(options.overrideMimeType) + } + + if (headers) { + for (const [name, value] of new Headers(headers)) { + request.setRequestHeader(name, value) + } + } + + if (options.signal) { + options.signal.onabort = () => request.abort() + } + + if (options.onUploadProgress) { + request.upload.onprogress = options.onUploadProgress + } + + // Note: Need to use `arraybuffer` here instead of `blob` because `Blob` + // instances coming from JSDOM are not compatible with `Response` from + // node-fetch (which is the setup we get when testing with jest because + // it uses JSDOM which does not provide a global fetch + // https://github.com/jsdom/jsdom/issues/1724) + request.responseType = 'arraybuffer' + + return new Promise((resolve, reject) => { + /** + * @param {Event} event + */ + const handleEvent = (event) => { + switch (event.type) { + case 'error': { + resolve(Response.error()) + break + } + case 'load': { + resolve( + new ResponseWithURL(request.responseURL, request.response, { + status: request.status, + statusText: request.statusText, + headers: parseHeaders(request.getAllResponseHeaders()) + }) + ) + break + } + case 'timeout': { + reject(new TimeoutError()) + break + } + case 'abort': { + reject(new AbortError()) + break + } + default: { + break + } + } + } + request.onerror = handleEvent + request.onload = handleEvent + request.ontimeout = handleEvent + request.onabort = handleEvent -const fetchNode = require('./fetch.node.js') -const fetchBrowser = require('./fetch.browser.js') + // @ts-expect-error options.body can be a node readable stream, which isn't compatible with XHR, but this + // file is a browser override so you won't get a node readable stream so ignore the error + request.send(options.body) + }) +} + +/** + * @param {string | Request} url + * @param {FetchOptions} options + */ +const fetchWith = (url, options = {}) => + (options.onUploadProgress != null) + ? fetchWithProgress(url, options) + : fetchWithStreaming(url, options) -let fetch = fetchNode; +let fetch = fetchWithStreaming if (typeof XMLHttpRequest === 'function') { - // Electron has `XMLHttpRequest` and should get the browser implementation - // instead of node. - fetch = fetchBrowser + fetch = fetchWith +} + +/** + * Parse Headers from a XMLHttpRequest + * + * @param {string} input + * @returns {Headers} + */ +const parseHeaders = (input) => { + const headers = new Headers() + for (const line of input.trim().split(/[\r\n]+/)) { + const index = line.indexOf(': ') + if (index > 0) { + headers.set(line.slice(0, index), line.slice(index + 1)) + } + } + + return headers } -/** @type {fetchImpl} */ -module.exports = fetch +class ResponseWithURL extends Response { + /** + * @param {string} url + * @param {BodyInit} body + * @param {ResponseInit} options + */ + constructor (url, body, options) { + super(body, options) + Object.defineProperty(this, 'url', { value: url }) + } +} + +module.exports = { + fetch, + Request, + Headers +} diff --git a/src/http/fetch.node.js b/src/http/fetch.node.js deleted file mode 100644 index a1124d7..0000000 --- a/src/http/fetch.node.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict' -// @ts-expect-error Request, Response and Headers are global types but concrete in implementations -const { Request, Response, Headers, default: defaultFetch, fetch: fetchFetch } = require('../fetch.js') -// @ts-ignore -const toStream = require('it-to-stream') -const { Buffer } = require('buffer') -/** - * @typedef {import('stream').Readable} NodeReadableStream - * - * @typedef {import('../types.d.ts').FetchOptions} FetchOptions - * @typedef {import('../types.d.ts').ProgressFn} ProgressFn - */ - -// undici and node-fetch have different exports -const nativeFetch = defaultFetch ?? fetchFetch - -/** - * @param {string|Request} url - * @param {FetchOptions} [options] - * @returns {Promise} - */ -const fetch = (url, options = {}) => - // @ts-ignore - nativeFetch(url, withUploadProgress(options)) - -/** - * Takes fetch options and wraps request body to track upload progress if - * `onUploadProgress` is supplied. Otherwise returns options as is. - * - * @param {FetchOptions} options - * @returns {FetchOptions} - */ -const withUploadProgress = (options) => { - const { onUploadProgress, body } = options - if (onUploadProgress && body) { - // This works around the fact that electron-fetch serializes `Uint8Array`s - // and `ArrayBuffer`s to strings. - const content = normalizeBody(body) - - // @ts-expect-error this is node-fetch - const rsp = new Response(content) - // @ts-expect-error this is node-fetch - const source = iterateBodyWithProgress(/** @type {NodeReadableStream} */(rsp.body), onUploadProgress) - return { - ...options, - body: toStream.readable(source) - } - } else { - return options - } -} - -/** - * @param {BodyInit | NodeReadableStream} input - */ -const normalizeBody = (input) => { - if (input instanceof ArrayBuffer) { - return Buffer.from(input) - } else if (ArrayBuffer.isView(input)) { - return Buffer.from(input.buffer, input.byteOffset, input.byteLength) - } else if (typeof input === 'string') { - return Buffer.from(input) - } - return input -} - -/** - * Takes body from native-fetch response as body and `onUploadProgress` handler - * and returns async iterable that emits body chunks and emits - * `onUploadProgress`. - * - * @param {NodeReadableStream | null} body - * @param {ProgressFn} onUploadProgress - * @returns {AsyncIterable} - */ -const iterateBodyWithProgress = async function * (body, onUploadProgress) { - if (body == null) { - onUploadProgress({ total: 0, loaded: 0, lengthComputable: true }) - } else if (Buffer.isBuffer(body)) { - const total = body.byteLength - const lengthComputable = true - yield body - onUploadProgress({ total, loaded: total, lengthComputable }) - } else { - const total = 0 - const lengthComputable = false - let loaded = 0 - for await (const chunk of body) { - loaded += chunk.byteLength - yield chunk - onUploadProgress({ total, loaded, lengthComputable }) - } - } -} - -module.exports = { - fetch, - Request, - Headers -} From fdffa4d97f06608183a677ea2c2cc746472f91dd Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:00:00 +0000 Subject: [PATCH 13/16] fully delete node-fetch polyfill --- package.json | 2 -- src/fetch.js | 9 ++++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 422f042..a8ba75a 100644 --- a/package.json +++ b/package.json @@ -153,8 +153,6 @@ "it-glob": "^1.0.1", "merge-options": "^3.0.4", "nanoid": "^3.1.20", - "native-fetch": "^3.0.0", - "node-fetch": "^2.6.8", "react-native-fetch-api": "^3.0.0", "stream-to-it": "^0.2.2" }, diff --git a/src/fetch.js b/src/fetch.js index 6029023..580b9c1 100644 --- a/src/fetch.js +++ b/src/fetch.js @@ -6,6 +6,9 @@ * @typedef {globalThis.Response} Response */ -const nativeFetch = require('native-fetch') - -module.exports = nativeFetch +module.exports = { + Headers: globalThis.Headers, + Request: globalThis.Request, + Response: globalThis.Response, + default: globalThis.fetch +} From aaee2ba47ed350693a1797e7eb8289b6c610d296 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:00:00 +0000 Subject: [PATCH 14/16] delete dead code, workaround for node-fetch --- src/http.js | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/src/http.js b/src/http.js index 0152324..dcbfcb1 100644 --- a/src/http.js +++ b/src/http.js @@ -12,7 +12,6 @@ const { isBrowser, isWebWorker } = require('./env.js') const all = require('it-all') /** - * @typedef {import('stream').Readable} NodeReadableStream * @typedef {import('./types.d.ts').HTTPOptions} HTTPOptions * @typedef {import('./types.d.ts').ExtendedResponse} ExtendedResponse */ @@ -255,7 +254,7 @@ const ndjson = async function * (source) { * Stream to AsyncIterable * * @template TChunk - * @param {ReadableStream | NodeReadableStream | null} source + * @param {ReadableStream | AsyncIterable | null} source * @returns {AsyncIterable} */ const fromStream = (source) => { @@ -263,25 +262,6 @@ const fromStream = (source) => { return source } - // Workaround for https://github.com/node-fetch/node-fetch/issues/766 - if (isNodeReadableStream(source)) { - const iter = source[Symbol.asyncIterator]() - return { - [Symbol.asyncIterator] () { - return { - next: iter.next.bind(iter), - return (value) { - source.destroy() - if (typeof iter.return === 'function') { - return iter.return() - } - return Promise.resolve({ done: true, value }) - } - } - } - } - } - if (isWebReadableStream(source)) { const reader = source.getReader() return (async function * () { @@ -331,15 +311,6 @@ const isWebReadableStream = (value) => { return value && typeof /** @type {any} */(value).getReader === 'function' } -/** - * @param {any} value - * @returns {value is NodeReadableStream} - */ -const isNodeReadableStream = (value) => - 'readable' in value && - 'destroy' in value && - Symbol.asyncIterator in value - HTTP.HTTPError = HTTPError HTTP.TimeoutError = TimeoutError HTTP.streamToAsyncIterator = fromStream From 9df6dda359599ab6ab63a91531886c5bc98a481b Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:00:00 +0000 Subject: [PATCH 15/16] delete dead code, redundant check for ReadableStream a ReadableStream is an AsyncIterable now, so this block is never run. TypeScript realizes this, giving source the 'never' type. --- src/http.js | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/http.js b/src/http.js index dcbfcb1..fbbc72e 100644 --- a/src/http.js +++ b/src/http.js @@ -262,26 +262,6 @@ const fromStream = (source) => { return source } - if (isWebReadableStream(source)) { - const reader = source.getReader() - return (async function * () { - try { - while (true) { - // Read from the stream - const { done, value } = await reader.read() - // Exit if we're done - if (done) return - // Else yield the chunk - if (value) { - yield value - } - } - } finally { - reader.releaseLock() - } - })() - } - throw new TypeError('Body can\'t be converted to AsyncIterable') } @@ -299,18 +279,6 @@ const isAsyncIterable = (value) => { typeof /** @type {any} */(value)[Symbol.asyncIterator] === 'function' } -/** - * Check for web readable stream - * - * @template {unknown} TChunk - * @template {any} Other - * @param {Other|ReadableStream} value - * @returns {value is ReadableStream} - */ -const isWebReadableStream = (value) => { - return value && typeof /** @type {any} */(value).getReader === 'function' -} - HTTP.HTTPError = HTTPError HTTP.TimeoutError = TimeoutError HTTP.streamToAsyncIterator = fromStream From 1c96e37d7ad5c9b5209c2933a4950fab881f930a Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:00:00 +0000 Subject: [PATCH 16/16] rename rn-test.config.cjs The old version of lilconfig in use by rn-test only supports .js and .cjs extensions, not .mjs, in its getDefaultSearchPlaces() function. Only v3.1.0 and newer has .mjs. https://github.com/antonk52/lilconfig/commit/4d104d6d76f58068a5bffb9f923d50fbea57c61d --- rn-test.config.js => rn-test.config.cjs | 2 +- rn-test.require.js => rn-test.require.cjs | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename rn-test.config.js => rn-test.config.cjs (83%) rename rn-test.require.js => rn-test.require.cjs (100%) diff --git a/rn-test.config.js b/rn-test.config.cjs similarity index 83% rename from rn-test.config.js rename to rn-test.config.cjs index 6e94e80..d37e5b4 100644 --- a/rn-test.config.js +++ b/rn-test.config.cjs @@ -1,7 +1,7 @@ 'use strict' module.exports = { - require: require.resolve('./rn-test.require.js'), + require: require.resolve('./rn-test.require.cjs'), runner: 'mocha', modules: [ 'react-native-url-polyfill', diff --git a/rn-test.require.js b/rn-test.require.cjs similarity index 100% rename from rn-test.require.js rename to rn-test.require.cjs