Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@whatwg-node/fetch

Package Overview
Dependencies
Maintainers
1
Versions
475
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@whatwg-node/fetch - npm Package Compare versions

Comparing version 0.5.4-alpha-20221228115139-6b81aa1 to 0.5.4-alpha-20221230002923-0c64426

dist/getFormDataMethod.js

17

CHANGELOG.md
# @whatwg-node/fetch
## 0.5.4-alpha-20221228115139-6b81aa1
## 0.5.4-alpha-20221230002923-0c64426
### Patch Changes
- [#154](https://github.com/ardatan/whatwg-node/pull/154) [`320f888`](https://github.com/ardatan/whatwg-node/commit/320f88850f3ef46c36619cce86494c7e833592cd) Thanks [@ardatan](https://github.com/ardatan)! - dependencies updates:
- [#237](https://github.com/ardatan/whatwg-node/pull/237) [`b4f4d6a`](https://github.com/ardatan/whatwg-node/commit/b4f4d6a07770bc1e9470b9e7fe7c239e75dd803b) Thanks [@enisdenjo](https://github.com/enisdenjo)! - http2 support when using Node ponyfill
- Added dependency [`@whatwg-node/node-fetch@0.0.0` ↗︎](https://www.npmjs.com/package/@whatwg-node/node-fetch/v/0.0.0) (to `dependencies`)
- Removed dependency [`abort-controller@^3.0.0` ↗︎](https://www.npmjs.com/package/abort-controller/v/3.0.0) (from `dependencies`)
- Removed dependency [`form-data-encoder@^1.7.1` ↗︎](https://www.npmjs.com/package/form-data-encoder/v/1.7.1) (from `dependencies`)
- Removed dependency [`formdata-node@^4.3.1` ↗︎](https://www.npmjs.com/package/formdata-node/v/4.3.1) (from `dependencies`)
- Removed dependency [`node-fetch@^2.6.7` ↗︎](https://www.npmjs.com/package/node-fetch/v/2.6.7) (from `dependencies`)
- Removed dependency [`undici@^5.12.0` ↗︎](https://www.npmjs.com/package/undici/v/5.12.0) (from `dependencies`)
- Removed dependency [`web-streams-polyfill@^3.2.0` ↗︎](https://www.npmjs.com/package/web-streams-polyfill/v/3.2.0) (from `dependencies`)
- [#154](https://github.com/ardatan/whatwg-node/pull/154) [`39c11c2`](https://github.com/ardatan/whatwg-node/commit/39c11c265ca3169d64a2ac4f239a6b90ce49e804) Thanks [@ardatan](https://github.com/ardatan)! - New Fetch API implementation for Node
- Updated dependencies [[`39c11c2`](https://github.com/ardatan/whatwg-node/commit/39c11c265ca3169d64a2ac4f239a6b90ce49e804)]:
- @whatwg-node/node-fetch@0.0.1-alpha-20221228115139-6b81aa1
## 0.5.3

@@ -23,0 +10,0 @@

@@ -0,1 +1,5 @@

const http2 = require('http2')
const handleFileRequest = require("./handle-file-request");
const readableStreamToReadable = require("./readableStreamToReadable");
module.exports = function createNodePonyfill(opts = {}) {

@@ -8,45 +12,70 @@

const newNodeFetch = require('@whatwg-node/node-fetch');
const ponyfills = {};
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;
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;
}
if (opts.formDataLimits) {
ponyfills.Body = class Body extends newNodeFetch.Body {
constructor(body, userOpts) {
super(body, {
formDataLimits: opts.formDataLimits,
...userOpts,
});
}
ponyfills.AbortController = globalThis.AbortController;
ponyfills.ReadableStream = globalThis.ReadableStream;
ponyfills.WritableStream = globalThis.WritableStream;
ponyfills.TransformStream = globalThis.TransformStream;
ponyfills.Blob = globalThis.Blob;
ponyfills.crypto = globalThis.crypto;
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.Request = class Request extends newNodeFetch.Request {
constructor(input, userOpts) {
super(input, {
formDataLimits: opts.formDataLimits,
...userOpts,
});
}
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.Response = class Response extends newNodeFetch.Response {
constructor(body, userOpts) {
super(body, {
formDataLimits: opts.formDataLimits,
...userOpts,
});
}
ponyfills.TextDecoder = function TextDecoder(encoding = 'utf-8') {
return {
decode(buf) {
return Buffer.from(buf).toString(encoding);
}

@@ -66,3 +95,253 @@ }

// 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();
}
let method = (options || {}).method;
const headers = {};
if ('headers' in (options || {})) {
let isHttp2 = false;
for (const [key, value] of Object.entries(options.headers)) {
if (key.startsWith(':')) {
// omit http2 headers
isHttp2 = true;
} else {
headers[key] = value
}
}
if (isHttp2) {
// translate http2 if applicable
method = options.headers[http2.constants.HTTP2_HEADER_METHOD];
const scheme = options.headers[http2.constants.HTTP2_HEADER_SCHEME];
const authority = options.headers[http2.constants.HTTP2_HEADER_AUTHORITY];
const path = options.headers[http2.constants.HTTP2_HEADER_PATH];
headers.host = authority;
requestOrUrl = `${scheme}://${authority}${path}`
}
}
const fixedOptions = {
...options,
method,
headers,
};
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) {
ponyfills.Response.redirect = function (url, status = 302) {
return new ponyfills.Response(null, {
status,
headers: {
Location: url,
},
});
};
}
if (!ponyfills.Response.json) {
ponyfills.Response.json = function (data, init = {}) {
return new ponyfills.Response(JSON.stringify(data), {
...init,
headers: {
"Content-Type": "application/json",
...init.headers,
},
});
};
}
if (!ponyfills.Response.error) {
ponyfills.Response.error = function () {
return new ponyfills.Response(null, {
status: 500,
});
};
}
return ponyfills;
}
{
"name": "@whatwg-node/fetch",
"version": "0.5.4-alpha-20221228115139-6b81aa1",
"version": "0.5.4-alpha-20221230002923-0c64426",
"description": "Cross Platform Smart Fetch Ponyfill",

@@ -22,4 +22,9 @@ "author": "Arda TANRIKULU <ardatanrikulu@gmail.com>",

"@peculiar/webcrypto": "^1.4.0",
"@whatwg-node/node-fetch": "0.0.1-alpha-20221228115139-6b81aa1",
"busboy": "^1.6.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"
},

@@ -26,0 +31,0 @@ "publishConfig": {

@@ -1,58 +0,36 @@

import * as fetchAPI from '@whatwg-node/fetch';
import { createTestContainer } from '../../server/test/create-test-container';
describe('getFormDataMethod', () => {
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,
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!');
});
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,
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();
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!');
});
it('should handle file limits', async () => {
const limitedFetchAPI = fetchAPI.createFetch({
formDataLimits: {
fileSize: 1,
},
});
const formData = new limitedFetchAPI.FormData();
const greetingsFile = new limitedFetchAPI.File(['Hello world!'], 'greetings.txt', { type: 'text/plain' });
formData.append('greetings', greetingsFile);
const proxyRequest = new limitedFetchAPI.Request('http://localhost:8080', {
method: 'POST',
body: formData,
});
const formDataInText = await proxyRequest.text();
const contentType = proxyRequest.headers.get('content-type')!;
const requestWillParse = new limitedFetchAPI.Request('http://localhost:8080', {
method: 'POST',
body: formDataInText,
headers: {
'content-type': contentType,
},
});
await expect(() => requestWillParse.formData()).rejects.toThrowError('File size limit exceeded: 1 bytes');
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc