Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drop node support for < v18.17.0 #3125

Merged
merged 3 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 0 additions & 6 deletions docs/docs/api/Fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ Undici exposes a fetch() method starts the process of fetching a resource from t

Documentation and examples can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/fetch).

## File

This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/File)

In Node versions v18.13.0 and above and v19.2.0 and above, undici will default to using Node's [File](https://nodejs.org/api/buffer.html#class-file) class. In versions where it's not available, it will default to the undici one.

## FormData

This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/FormData).
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ module.exports.Headers = require('./lib/web/fetch/headers').Headers
module.exports.Response = require('./lib/web/fetch/response').Response
module.exports.Request = require('./lib/web/fetch/request').Request
module.exports.FormData = require('./lib/web/fetch/formdata').FormData
module.exports.File = require('./lib/web/fetch/file').File
module.exports.File = globalThis.File ?? require('node:buffer').File
module.exports.FileReader = require('./lib/web/fileapi/filereader').FileReader

const { setGlobalOrigin, getGlobalOrigin } = require('./lib/web/fetch/global')
Expand Down
1 change: 0 additions & 1 deletion lib/core/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,5 @@ module.exports = {
isHttpOrHttpsPrefixed,
nodeMajor,
nodeMinor,
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13),
safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE']
}
2 changes: 1 addition & 1 deletion lib/dispatcher/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ class Client extends DispatcherBase {
allowH2,
socketPath,
timeout: connectTimeout,
...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
...(autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
...connect
})
}
Expand Down
2 changes: 1 addition & 1 deletion lib/dispatcher/pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class Pool extends PoolBase {
allowH2,
socketPath,
timeout: connectTimeout,
...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
...(autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
...connect
})
}
Expand Down
221 changes: 5 additions & 216 deletions lib/web/fetch/file.js
Original file line number Diff line number Diff line change
@@ -1,100 +1,10 @@
'use strict'

const { EOL } = require('node:os')
const { Blob, File: NativeFile } = require('node:buffer')
const { types } = require('node:util')
const { Blob, File } = require('node:buffer')
const { kState } = require('./symbols')
const { isBlobLike } = require('./util')
const { webidl } = require('./webidl')
const { parseMIMEType, serializeAMimeType } = require('./data-url')
const { kEnumerableProperty } = require('../../core/util')

const encoder = new TextEncoder()

class File extends Blob {
constructor (fileBits, fileName, options = {}) {
// The File constructor is invoked with two or three parameters, depending
// on whether the optional dictionary parameter is used. When the File()
// constructor is invoked, user agents must run the following steps:
webidl.argumentLengthCheck(arguments, 2, { header: 'File constructor' })

fileBits = webidl.converters['sequence<BlobPart>'](fileBits)
fileName = webidl.converters.USVString(fileName)
options = webidl.converters.FilePropertyBag(options)

// 1. Let bytes be the result of processing blob parts given fileBits and
// options.
// Note: Blob handles this for us

// 2. Let n be the fileName argument to the constructor.
const n = fileName

// 3. Process FilePropertyBag dictionary argument by running the following
// substeps:

// 1. If the type member is provided and is not the empty string, let t
// be set to the type dictionary member. If t contains any characters
// outside the range U+0020 to U+007E, then set t to the empty string
// and return from these substeps.
// 2. Convert every character in t to ASCII lowercase.
let t = options.type
let d

// eslint-disable-next-line no-labels
substep: {
if (t) {
t = parseMIMEType(t)

if (t === 'failure') {
t = ''
// eslint-disable-next-line no-labels
break substep
}

t = serializeAMimeType(t).toLowerCase()
}

// 3. If the lastModified member is provided, let d be set to the
// lastModified dictionary member. If it is not provided, set d to the
// current date and time represented as the number of milliseconds since
// the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]).
d = options.lastModified
}

// 4. Return a new File object F such that:
// F refers to the bytes byte sequence.
// F.size is set to the number of total bytes in bytes.
// F.name is set to n.
// F.type is set to t.
// F.lastModified is set to d.

super(processBlobParts(fileBits, options), { type: t })
this[kState] = {
name: n,
lastModified: d,
type: t
}
}

get name () {
webidl.brandCheck(this, File)

return this[kState].name
}

get lastModified () {
webidl.brandCheck(this, File)

return this[kState].lastModified
}

get type () {
webidl.brandCheck(this, File)

return this[kState].type
}
}

// TODO(@KhafraDev): remove
class FileLike {
constructor (blobLike, fileName, options = {}) {
// TODO: argument idl type check
Expand Down Expand Up @@ -196,136 +106,15 @@ class FileLike {
}
}

Object.defineProperties(File.prototype, {
[Symbol.toStringTag]: {
value: 'File',
configurable: true
},
name: kEnumerableProperty,
lastModified: kEnumerableProperty
})

webidl.converters.Blob = webidl.interfaceConverter(Blob)

webidl.converters.BlobPart = function (V, opts) {
if (webidl.util.Type(V) === 'Object') {
if (isBlobLike(V)) {
return webidl.converters.Blob(V, { strict: false })
}

if (ArrayBuffer.isView(V) || types.isAnyArrayBuffer(V)) {
return webidl.converters.BufferSource(V, opts)
}
}

return webidl.converters.USVString(V, opts)
}

webidl.converters['sequence<BlobPart>'] = webidl.sequenceConverter(
webidl.converters.BlobPart
)

// https://www.w3.org/TR/FileAPI/#dfn-FilePropertyBag
webidl.converters.FilePropertyBag = webidl.dictionaryConverter([
{
key: 'lastModified',
converter: webidl.converters['long long'],
get defaultValue () {
return Date.now()
}
},
{
key: 'type',
converter: webidl.converters.DOMString,
defaultValue: ''
},
{
key: 'endings',
converter: (value) => {
value = webidl.converters.DOMString(value)
value = value.toLowerCase()

if (value !== 'native') {
value = 'transparent'
}

return value
},
defaultValue: 'transparent'
}
])

/**
* @see https://www.w3.org/TR/FileAPI/#process-blob-parts
* @param {(NodeJS.TypedArray|Blob|string)[]} parts
* @param {{ type: string, endings: string }} options
*/
function processBlobParts (parts, options) {
// 1. Let bytes be an empty sequence of bytes.
/** @type {NodeJS.TypedArray[]} */
const bytes = []

// 2. For each element in parts:
for (const element of parts) {
// 1. If element is a USVString, run the following substeps:
if (typeof element === 'string') {
// 1. Let s be element.
let s = element

// 2. If the endings member of options is "native", set s
// to the result of converting line endings to native
// of element.
if (options.endings === 'native') {
s = convertLineEndingsNative(s)
}

// 3. Append the result of UTF-8 encoding s to bytes.
bytes.push(encoder.encode(s))
} else if (ArrayBuffer.isView(element) || types.isArrayBuffer(element)) {
// 2. If element is a BufferSource, get a copy of the
// bytes held by the buffer source, and append those
// bytes to bytes.
if (element.buffer) {
bytes.push(
new Uint8Array(element.buffer, element.byteOffset, element.byteLength)
)
} else { // ArrayBuffer
bytes.push(new Uint8Array(element))
}
} else if (isBlobLike(element)) {
// 3. If element is a Blob, append the bytes it represents
// to bytes.
bytes.push(element)
}
}

// 3. Return bytes.
return bytes
}

/**
* @see https://www.w3.org/TR/FileAPI/#convert-line-endings-to-native
* @param {string} s
*/
function convertLineEndingsNative (s) {
// 1. Let native line ending be be the code point U+000A LF.
// 2. If the underlying platform’s conventions are to
// represent newlines as a carriage return and line feed
// sequence, set native line ending to the code point
// U+000D CR followed by the code point U+000A LF.
// NOTE: We are using the native line ending for the current
// platform, provided by node's os module.

return s.replace(/\r?\n/g, EOL)
}

// If this function is moved to ./util.js, some tools (such as
// rollup) will warn about circular dependencies. See:
// https://github.com/nodejs/undici/issues/1629
function isFileLike (object) {
return (
(NativeFile && object instanceof NativeFile) ||
object instanceof File || (
(object instanceof File) ||
(
object &&
(typeof object.stream === 'function' ||
typeof object.arrayBuffer === 'function') &&
Expand All @@ -334,4 +123,4 @@ function isFileLike (object) {
)
}

module.exports = { File, FileLike, isFileLike }
module.exports = { FileLike, isFileLike }
4 changes: 2 additions & 2 deletions lib/web/fetch/formdata-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
const { isUSVString, bufferToLowerCasedHeaderName } = require('../../core/util')
const { utf8DecodeBytes } = require('./util')
const { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require('./data-url')
const { isFileLike, File: UndiciFile } = require('./file')
const { isFileLike } = require('./file')
const { makeEntry } = require('./formdata')
const assert = require('node:assert')
const { File: NodeFile } = require('node:buffer')

const File = globalThis.File ?? NodeFile ?? UndiciFile
const File = globalThis.File ?? NodeFile

const formDataNameBuffer = Buffer.from('form-data; name="')
const filenameBuffer = Buffer.from('; filename')
Expand Down
6 changes: 3 additions & 3 deletions lib/web/fetch/formdata.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
const { isBlobLike, iteratorMixin } = require('./util')
const { kState } = require('./symbols')
const { kEnumerableProperty } = require('../../core/util')
const { File: UndiciFile, FileLike, isFileLike } = require('./file')
const { FileLike, isFileLike } = require('./file')
const { webidl } = require('./webidl')
const { File: NativeFile } = require('node:buffer')
const nodeUtil = require('node:util')

/** @type {globalThis['File']} */
const File = NativeFile ?? UndiciFile
const File = globalThis.File ?? NativeFile

// https://xhr.spec.whatwg.org/#formdata
class FormData {
Expand Down Expand Up @@ -231,7 +231,7 @@ function makeEntry (name, value, filename) {
lastModified: value.lastModified
}

value = (NativeFile && value instanceof NativeFile) || value instanceof UndiciFile
value = value instanceof NativeFile
? new File([value], filename, options)
: new FileLike(value, filename, options)
}
Expand Down
6 changes: 2 additions & 4 deletions lib/web/fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const {
} = require('./constants')
const EE = require('node:events')
const { Readable, pipeline, finished } = require('node:stream')
const { addAbortListener, isErrored, isReadable, nodeMajor, nodeMinor, bufferToLowerCasedHeaderName } = require('../../core/util')
const { addAbortListener, isErrored, isReadable, bufferToLowerCasedHeaderName } = require('../../core/util')
const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require('./data-url')
const { getGlobalDispatcher } = require('../../global')
const { webidl } = require('./webidl')
Expand Down Expand Up @@ -310,9 +310,7 @@ function finalizeAndReportTiming (response, initiatorType = 'other') {
}

// https://w3c.github.io/resource-timing/#dfn-mark-resource-timing
const markResourceTiming = (nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 2))
? performance.markResourceTiming
: () => {}
const markResourceTiming = performance.markResourceTiming

// https://fetch.spec.whatwg.org/#abort-fetch
function abortFetch (p, request, responseObject, error) {
Expand Down
19 changes: 6 additions & 13 deletions lib/web/websocket/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,19 +211,12 @@ const fatalDecoder = hasIntl ? new TextDecoder('utf-8', { fatal: true }) : undef
*/
const utf8Decode = hasIntl
? fatalDecoder.decode.bind(fatalDecoder)
: !isUtf8
? function () { // TODO: remove once node 18 or < node v18.14.0 is dropped
process.emitWarning('ICU is not supported and no fallback exists. Please upgrade to at least Node v18.14.0.', {
code: 'UNDICI-WS-NO-ICU'
})
throw new TypeError('Invalid utf-8 received.')
}
: function (buffer) {
if (isUtf8(buffer)) {
return buffer.toString('utf-8')
}
throw new TypeError('Invalid utf-8 received.')
}
: function (buffer) {
if (isUtf8(buffer)) {
return buffer.toString('utf-8')
}
throw new TypeError('Invalid utf-8 received.')
}

module.exports = {
isConnecting,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
"ws": "^8.11.0"
},
"engines": {
"node": ">=18.0"
"node": ">=18.17"
},
"standard": {
"env": [
Expand Down