@whatwg-node/fetch
Advanced tools
Comparing version 0.5.3 to 0.5.4-alpha-20221225155445-dfcb269
# @whatwg-node/fetch | ||
## 0.5.4-alpha-20221225155445-dfcb269 | ||
### Patch Changes | ||
- [#154](https://github.com/ardatan/whatwg-node/pull/154) [`8324adc`](https://github.com/ardatan/whatwg-node/commit/8324adcc1383eb16fd6d0a6417842e0b8d9b82ba) Thanks [@ardatan](https://github.com/ardatan)! - New Fetch API implementation for Node | ||
## 0.5.3 | ||
@@ -4,0 +10,0 @@ |
@@ -11,74 +11,25 @@ const handleFileRequest = require("./handle-file-request"); | ||
const newNodeFetch = require('@whatwg-node/node-fetch'); | ||
const ponyfills = {}; | ||
if (!opts.useNodeFetch) { | ||
ponyfills.fetch = globalThis.fetch; | ||
ponyfills.Headers = globalThis.Headers; | ||
ponyfills.Request = globalThis.Request; | ||
ponyfills.Response = globalThis.Response; | ||
ponyfills.FormData = globalThis.FormData; | ||
ponyfills.File = globalThis.File; | ||
} | ||
ponyfills.AbortController = newNodeFetch.AbortController; | ||
ponyfills.AbortError = newNodeFetch.AbortError; | ||
ponyfills.AbortSignal = newNodeFetch.AbortSignal; | ||
ponyfills.Blob = newNodeFetch.Blob; | ||
ponyfills.Body = newNodeFetch.Body; | ||
ponyfills.fetch = newNodeFetch.fetch; | ||
ponyfills.File = newNodeFetch.File; | ||
ponyfills.FormData = newNodeFetch.FormData; | ||
ponyfills.Headers = newNodeFetch.Headers; | ||
ponyfills.ReadableStream = newNodeFetch.ReadableStream; | ||
ponyfills.Request = newNodeFetch.Request; | ||
ponyfills.Response = newNodeFetch.Response; | ||
ponyfills.TextEncoder = newNodeFetch.TextEncoder; | ||
ponyfills.TextDecoder = newNodeFetch.TextDecoder; | ||
ponyfills.btoa = newNodeFetch.btoa; | ||
ponyfills.AbortController = globalThis.AbortController; | ||
ponyfills.ReadableStream = globalThis.ReadableStream; | ||
ponyfills.WritableStream = globalThis.WritableStream; | ||
ponyfills.TransformStream = globalThis.TransformStream; | ||
ponyfills.Blob = globalThis.Blob; | ||
ponyfills.crypto = globalThis.crypto; | ||
const getFormDataMethod = require("./getFormDataMethod"); | ||
ponyfills.Body.prototype.formData = getFormDataMethod(ponyfills.File, opts.formDataLimits) | ||
if (!ponyfills.AbortController) { | ||
const abortControllerModule = require("abort-controller"); | ||
ponyfills.AbortController = | ||
abortControllerModule.default || abortControllerModule; | ||
} | ||
if (!ponyfills.Blob) { | ||
const bufferModule = require('buffer') | ||
ponyfills.Blob = bufferModule.Blob; | ||
} | ||
if (!ponyfills.Blob) { | ||
const formDataModule = require("formdata-node"); | ||
ponyfills.Blob = formDataModule.Blob | ||
} | ||
if (!ponyfills.ReadableStream) { | ||
try { | ||
const streamsWeb = require("stream/web"); | ||
ponyfills.ReadableStream = streamsWeb.ReadableStream; | ||
ponyfills.WritableStream = streamsWeb.WritableStream; | ||
ponyfills.TransformStream = streamsWeb.TransformStream; | ||
} catch (e) { | ||
const streamsWeb = require("web-streams-polyfill/ponyfill"); | ||
ponyfills.ReadableStream = streamsWeb.ReadableStream; | ||
ponyfills.WritableStream = streamsWeb.WritableStream; | ||
ponyfills.TransformStream = streamsWeb.TransformStream; | ||
} | ||
} | ||
ponyfills.btoa = globalThis.btoa | ||
if (!ponyfills.btoa) { | ||
ponyfills.btoa = function btoa(data) { | ||
return Buffer.from(data, 'binary').toString('base64'); | ||
}; | ||
} | ||
ponyfills.TextEncoder = function TextEncoder(encoding = 'utf-8') { | ||
return { | ||
encode(str) { | ||
return Buffer.from(str, encoding); | ||
} | ||
} | ||
} | ||
ponyfills.TextDecoder = function TextDecoder(encoding = 'utf-8') { | ||
return { | ||
decode(buf) { | ||
return Buffer.from(buf).toString(encoding); | ||
} | ||
} | ||
} | ||
if (!ponyfills.crypto) { | ||
@@ -94,200 +45,3 @@ const cryptoModule = require("crypto"); | ||
// If any of classes of Fetch API is missing, we need to ponyfill them. | ||
if (!ponyfills.fetch || | ||
!ponyfills.Request || | ||
!ponyfills.Headers || | ||
!ponyfills.Response || | ||
!ponyfills.FormData || | ||
!ponyfills.File || | ||
opts.useNodeFetch) { | ||
const [ | ||
nodeMajorStr, | ||
nodeMinorStr | ||
] = process.versions.node.split('.'); | ||
const nodeMajor = parseInt(nodeMajorStr); | ||
const nodeMinor = parseInt(nodeMinorStr); | ||
const getFormDataMethod = require('./getFormDataMethod'); | ||
if (!opts.useNodeFetch && (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 5))) { | ||
const undici = require("undici"); | ||
if (!ponyfills.Headers) { | ||
ponyfills.Headers = undici.Headers; | ||
} | ||
const streams = require("stream"); | ||
const OriginalRequest = ponyfills.Request || undici.Request; | ||
class Request extends OriginalRequest { | ||
constructor(requestOrUrl, options) { | ||
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) { | ||
if (options != null && typeof options === "object" && !options.duplex) { | ||
options.duplex = 'half'; | ||
} | ||
super(requestOrUrl, options); | ||
const contentType = this.headers.get("content-type"); | ||
if (contentType && contentType.startsWith("multipart/form-data")) { | ||
this.headers.set("content-type", contentType.split(', ')[0]); | ||
} | ||
} else { | ||
super(requestOrUrl); | ||
} | ||
this.formData = getFormDataMethod(undici.File, opts.formDataLimits); | ||
} | ||
} | ||
ponyfills.Request = Request; | ||
const originalFetch = ponyfills.fetch || undici.fetch; | ||
const fetch = function (requestOrUrl, options) { | ||
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) { | ||
if (options != null && typeof options === "object" && !options.duplex) { | ||
options.duplex = 'half'; | ||
} | ||
// We cannot use our ctor because it leaks on Node 18's global fetch | ||
return originalFetch(requestOrUrl, options); | ||
} | ||
if (requestOrUrl.url.startsWith('file:')) { | ||
return handleFileRequest(requestOrUrl.url, ponyfills.Response); | ||
} | ||
return originalFetch(requestOrUrl); | ||
}; | ||
ponyfills.fetch = fetch; | ||
if (!ponyfills.Response) { | ||
ponyfills.Response = undici.Response; | ||
} | ||
if (!ponyfills.FormData) { | ||
ponyfills.FormData = undici.FormData; | ||
} | ||
if (!ponyfills.File) { | ||
ponyfills.File = undici.File | ||
} | ||
} else { | ||
const nodeFetch = require("node-fetch"); | ||
const realFetch = ponyfills.fetch || nodeFetch.default || nodeFetch; | ||
if (!ponyfills.Headers) { | ||
ponyfills.Headers = nodeFetch.Headers; | ||
// Sveltekit | ||
if (globalThis.Headers && nodeMajor < 18) { | ||
Object.defineProperty(globalThis.Headers, Symbol.hasInstance, { | ||
value(obj) { | ||
return obj && obj.get && obj.set && obj.delete && obj.has && obj.append; | ||
}, | ||
configurable: true, | ||
}) | ||
} | ||
} | ||
const formDataEncoderModule = require("form-data-encoder"); | ||
const streams = require("stream"); | ||
const formDataModule = require("formdata-node"); | ||
if (!ponyfills.FormData) { | ||
ponyfills.FormData = formDataModule.FormData | ||
} | ||
if (!ponyfills.File) { | ||
ponyfills.File = formDataModule.File | ||
} | ||
const OriginalRequest = ponyfills.Request || nodeFetch.Request; | ||
class Request extends OriginalRequest { | ||
constructor(requestOrUrl, options) { | ||
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) { | ||
// Support schemaless URIs on the server for parity with the browser. | ||
// Ex: //github.com/ -> https://github.com/ | ||
if (/^\/\//.test(requestOrUrl.toString())) { | ||
requestOrUrl = "https:" + requestOrUrl.toString(); | ||
} | ||
const fixedOptions = { | ||
...options | ||
}; | ||
fixedOptions.headers = new ponyfills.Headers(fixedOptions.headers || {}); | ||
fixedOptions.headers.set('Connection', 'keep-alive'); | ||
if (fixedOptions.body != null) { | ||
if (fixedOptions.body[Symbol.toStringTag] === 'FormData') { | ||
const encoder = new formDataEncoderModule.FormDataEncoder(fixedOptions.body) | ||
for (const headerKey in encoder.headers) { | ||
fixedOptions.headers.set(headerKey, encoder.headers[headerKey]) | ||
} | ||
fixedOptions.body = streams.Readable.from(encoder); | ||
} else if (fixedOptions.body[Symbol.toStringTag] === 'ReadableStream') { | ||
fixedOptions.body = readableStreamToReadable(fixedOptions.body); | ||
} | ||
} | ||
super(requestOrUrl, fixedOptions); | ||
} else { | ||
super(requestOrUrl); | ||
} | ||
this.formData = getFormDataMethod(formDataModule.File, opts.formDataLimits); | ||
} | ||
} | ||
ponyfills.Request = Request; | ||
const fetch = function (requestOrUrl, options) { | ||
if (typeof requestOrUrl === "string" || requestOrUrl instanceof URL) { | ||
return fetch(new Request(requestOrUrl, options)); | ||
} | ||
if (requestOrUrl.url.startsWith('file:')) { | ||
return handleFileRequest(requestOrUrl.url, ponyfills.Response); | ||
} | ||
const abortCtrl = new ponyfills.AbortController(); | ||
return realFetch(requestOrUrl, { | ||
...options, | ||
signal: abortCtrl.signal | ||
}).then(res => { | ||
return new Proxy(res, { | ||
get(target, prop, receiver) { | ||
if (prop === 'body') { | ||
return new Proxy(res.body, { | ||
get(target, prop, receiver) { | ||
if (prop === Symbol.asyncIterator) { | ||
return () => { | ||
const originalAsyncIterator = target[Symbol.asyncIterator](); | ||
return { | ||
next() { | ||
return originalAsyncIterator.next(); | ||
}, | ||
return() { | ||
abortCtrl.abort(); | ||
return originalAsyncIterator.return(); | ||
}, | ||
throw(error) { | ||
abortCtrl.abort(error); | ||
return originalAsyncIterator.throw(error); | ||
} | ||
} | ||
} | ||
} | ||
return Reflect.get(target, prop, receiver); | ||
} | ||
}) | ||
} | ||
return Reflect.get(target, prop, receiver); | ||
} | ||
}) | ||
}); | ||
}; | ||
ponyfills.fetch = fetch; | ||
const OriginalResponse = ponyfills.Response || nodeFetch.Response; | ||
ponyfills.Response = function Response(body, init) { | ||
if (body != null && body[Symbol.toStringTag] === 'ReadableStream') { | ||
const actualBody = readableStreamToReadable(body); | ||
// Polyfill ReadableStream is not working well with node-fetch's Response | ||
return new OriginalResponse(actualBody, init); | ||
} | ||
return new OriginalResponse(body, init); | ||
}; | ||
} | ||
} | ||
if (!ponyfills.Response.redirect) { | ||
@@ -294,0 +48,0 @@ ponyfills.Response.redirect = function (url, status = 302) { |
const busboy = require('busboy'); | ||
const { resolve } = require('path'); | ||
const streams = require("stream"); | ||
@@ -4,0 +3,0 @@ |
{ | ||
"name": "@whatwg-node/fetch", | ||
"version": "0.5.3", | ||
"version": "0.5.4-alpha-20221225155445-dfcb269", | ||
"description": "Cross Platform Smart Fetch Ponyfill", | ||
@@ -22,9 +22,3 @@ "author": "Arda TANRIKULU <ardatanrikulu@gmail.com>", | ||
"@peculiar/webcrypto": "^1.4.0", | ||
"abort-controller": "^3.0.0", | ||
"busboy": "^1.6.0", | ||
"form-data-encoder": "^1.7.1", | ||
"formdata-node": "^4.3.1", | ||
"node-fetch": "^2.6.7", | ||
"undici": "^5.12.0", | ||
"web-streams-polyfill": "^3.2.0" | ||
"busboy": "^1.6.0" | ||
}, | ||
@@ -31,0 +25,0 @@ "publishConfig": { |
@@ -1,36 +0,34 @@ | ||
import { createTestContainer } from '../../server/test/create-test-container'; | ||
import * as fetchAPI from '@whatwg-node/fetch' | ||
describe('getFormDataMethod', () => { | ||
createTestContainer(fetchAPI => { | ||
it('should parse fields correctly', async () => { | ||
const formData = new fetchAPI.FormData(); | ||
formData.append('greetings', 'Hello world!'); | ||
formData.append('bye', 'Goodbye world!'); | ||
const request = new fetchAPI.Request('http://localhost:8080', { | ||
method: 'POST', | ||
body: formData, | ||
}); | ||
const formdata = await request.formData(); | ||
expect(formdata.get('greetings')).toBe('Hello world!'); | ||
expect(formdata.get('bye')).toBe('Goodbye world!'); | ||
it('should parse fields correctly', async () => { | ||
const formData = new fetchAPI.FormData(); | ||
formData.append('greetings', 'Hello world!'); | ||
formData.append('bye', 'Goodbye world!'); | ||
const request = new fetchAPI.Request('http://localhost:8080', { | ||
method: 'POST', | ||
body: formData, | ||
}); | ||
it('should parse and receive text files correctly', async () => { | ||
const formData = new fetchAPI.FormData(); | ||
const greetingsFile = new fetchAPI.File(['Hello world!'], 'greetings.txt', { type: 'text/plain' }); | ||
const byeFile = new fetchAPI.File(['Goodbye world!'], 'bye.txt', { type: 'text/plain' }); | ||
formData.append('greetings', greetingsFile); | ||
formData.append('bye', byeFile); | ||
const request = new fetchAPI.Request('http://localhost:8080', { | ||
method: 'POST', | ||
body: formData, | ||
}); | ||
const formdata = await request.formData(); | ||
const receivedGreetingsFile = formdata.get('greetings') as File; | ||
const receivedGreetingsText = await receivedGreetingsFile.text(); | ||
expect(receivedGreetingsText).toBe('Hello world!'); | ||
const receivedByeFile = formdata.get('bye') as File; | ||
const receivedByeText = await receivedByeFile.text(); | ||
expect(receivedByeText).toBe('Goodbye world!'); | ||
const formdata = await request.formData(); | ||
expect(formdata.get('greetings')).toBe('Hello world!'); | ||
expect(formdata.get('bye')).toBe('Goodbye world!'); | ||
}); | ||
it('should parse and receive text files correctly', async () => { | ||
const formData = new fetchAPI.FormData(); | ||
const greetingsFile = new fetchAPI.File(['Hello world!'], 'greetings.txt', { type: 'text/plain' }); | ||
const byeFile = new fetchAPI.File(['Goodbye world!'], 'bye.txt', { type: 'text/plain' }); | ||
formData.append('greetings', greetingsFile); | ||
formData.append('bye', byeFile); | ||
const request = new fetchAPI.Request('http://localhost:8080', { | ||
method: 'POST', | ||
body: formData, | ||
}); | ||
const formdata = await request.formData(); | ||
const receivedGreetingsFile = formdata.get('greetings') as File; | ||
const receivedGreetingsText = await receivedGreetingsFile.text(); | ||
expect(receivedGreetingsText).toBe('Hello world!'); | ||
const receivedByeFile = formdata.get('bye') as File; | ||
const receivedByeText = await receivedByeFile.text(); | ||
expect(receivedByeText).toBe('Goodbye world!'); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
2
0
18277
327
- Removedabort-controller@^3.0.0
- Removedform-data-encoder@^1.7.1
- Removedformdata-node@^4.3.1
- Removednode-fetch@^2.6.7
- Removedundici@^5.12.0
- Removedweb-streams-polyfill@^3.2.0
- Removed@fastify/busboy@2.1.1(transitive)
- Removedabort-controller@3.0.0(transitive)
- Removedevent-target-shim@5.0.1(transitive)
- Removedform-data-encoder@1.9.0(transitive)
- Removedformdata-node@4.4.1(transitive)
- Removednode-domexception@1.0.0(transitive)
- Removednode-fetch@2.7.0(transitive)
- Removedtr46@0.0.3(transitive)
- Removedundici@5.28.4(transitive)
- Removedweb-streams-polyfill@3.3.34.0.0-beta.3(transitive)
- Removedwebidl-conversions@3.0.1(transitive)
- Removedwhatwg-url@5.0.0(transitive)