Skip to content

Commit

Permalink
drop node support for < v18.17.0 (#3125)
Browse files Browse the repository at this point in the history
* drop node support for < v18.17.0

* fixup

* fixup
  • Loading branch information
KhafraDev authored Apr 16, 2024
1 parent 6cbfd7d commit f90c161
Show file tree
Hide file tree
Showing 14 changed files with 27 additions and 448 deletions.
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
Loading

0 comments on commit f90c161

Please sign in to comment.