初始化
This commit is contained in:
377
node_modules/undici/lib/util/cache.js
generated
vendored
Normal file
377
node_modules/undici/lib/util/cache.js
generated
vendored
Normal file
@@ -0,0 +1,377 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
safeHTTPMethods,
|
||||
pathHasQueryOrFragment
|
||||
} = require('../core/util')
|
||||
|
||||
const { serializePathWithQuery } = require('../core/util')
|
||||
|
||||
/**
|
||||
* @param {import('../../types/dispatcher.d.ts').default.DispatchOptions} opts
|
||||
*/
|
||||
function makeCacheKey (opts) {
|
||||
if (!opts.origin) {
|
||||
throw new Error('opts.origin is undefined')
|
||||
}
|
||||
|
||||
let fullPath = opts.path || '/'
|
||||
|
||||
if (opts.query && !pathHasQueryOrFragment(opts.path)) {
|
||||
fullPath = serializePathWithQuery(fullPath, opts.query)
|
||||
}
|
||||
|
||||
return {
|
||||
origin: opts.origin.toString(),
|
||||
method: opts.method,
|
||||
path: fullPath,
|
||||
headers: opts.headers
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, string[] | string>}
|
||||
* @returns {Record<string, string[] | string>}
|
||||
*/
|
||||
function normalizeHeaders (opts) {
|
||||
let headers
|
||||
if (opts.headers == null) {
|
||||
headers = {}
|
||||
} else if (typeof opts.headers[Symbol.iterator] === 'function') {
|
||||
headers = {}
|
||||
for (const x of opts.headers) {
|
||||
if (!Array.isArray(x)) {
|
||||
throw new Error('opts.headers is not a valid header map')
|
||||
}
|
||||
const [key, val] = x
|
||||
if (typeof key !== 'string' || typeof val !== 'string') {
|
||||
throw new Error('opts.headers is not a valid header map')
|
||||
}
|
||||
headers[key.toLowerCase()] = val
|
||||
}
|
||||
} else if (typeof opts.headers === 'object') {
|
||||
headers = {}
|
||||
|
||||
for (const key of Object.keys(opts.headers)) {
|
||||
headers[key.toLowerCase()] = opts.headers[key]
|
||||
}
|
||||
} else {
|
||||
throw new Error('opts.headers is not an object')
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} key
|
||||
*/
|
||||
function assertCacheKey (key) {
|
||||
if (typeof key !== 'object') {
|
||||
throw new TypeError(`expected key to be object, got ${typeof key}`)
|
||||
}
|
||||
|
||||
for (const property of ['origin', 'method', 'path']) {
|
||||
if (typeof key[property] !== 'string') {
|
||||
throw new TypeError(`expected key.${property} to be string, got ${typeof key[property]}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (key.headers !== undefined && typeof key.headers !== 'object') {
|
||||
throw new TypeError(`expected headers to be object, got ${typeof key}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} value
|
||||
*/
|
||||
function assertCacheValue (value) {
|
||||
if (typeof value !== 'object') {
|
||||
throw new TypeError(`expected value to be object, got ${typeof value}`)
|
||||
}
|
||||
|
||||
for (const property of ['statusCode', 'cachedAt', 'staleAt', 'deleteAt']) {
|
||||
if (typeof value[property] !== 'number') {
|
||||
throw new TypeError(`expected value.${property} to be number, got ${typeof value[property]}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value.statusMessage !== 'string') {
|
||||
throw new TypeError(`expected value.statusMessage to be string, got ${typeof value.statusMessage}`)
|
||||
}
|
||||
|
||||
if (value.headers != null && typeof value.headers !== 'object') {
|
||||
throw new TypeError(`expected value.rawHeaders to be object, got ${typeof value.headers}`)
|
||||
}
|
||||
|
||||
if (value.vary !== undefined && typeof value.vary !== 'object') {
|
||||
throw new TypeError(`expected value.vary to be object, got ${typeof value.vary}`)
|
||||
}
|
||||
|
||||
if (value.etag !== undefined && typeof value.etag !== 'string') {
|
||||
throw new TypeError(`expected value.etag to be string, got ${typeof value.etag}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control
|
||||
* @see https://www.iana.org/assignments/http-cache-directives/http-cache-directives.xhtml
|
||||
|
||||
* @param {string | string[]} header
|
||||
* @returns {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives}
|
||||
*/
|
||||
function parseCacheControlHeader (header) {
|
||||
/**
|
||||
* @type {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives}
|
||||
*/
|
||||
const output = {}
|
||||
|
||||
let directives
|
||||
if (Array.isArray(header)) {
|
||||
directives = []
|
||||
|
||||
for (const directive of header) {
|
||||
directives.push(...directive.split(','))
|
||||
}
|
||||
} else {
|
||||
directives = header.split(',')
|
||||
}
|
||||
|
||||
for (let i = 0; i < directives.length; i++) {
|
||||
const directive = directives[i].toLowerCase()
|
||||
const keyValueDelimiter = directive.indexOf('=')
|
||||
|
||||
let key
|
||||
let value
|
||||
if (keyValueDelimiter !== -1) {
|
||||
key = directive.substring(0, keyValueDelimiter).trimStart()
|
||||
value = directive.substring(keyValueDelimiter + 1)
|
||||
} else {
|
||||
key = directive.trim()
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'min-fresh':
|
||||
case 'max-stale':
|
||||
case 'max-age':
|
||||
case 's-maxage':
|
||||
case 'stale-while-revalidate':
|
||||
case 'stale-if-error': {
|
||||
if (value === undefined || value[0] === ' ') {
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
value.length >= 2 &&
|
||||
value[0] === '"' &&
|
||||
value[value.length - 1] === '"'
|
||||
) {
|
||||
value = value.substring(1, value.length - 1)
|
||||
}
|
||||
|
||||
const parsedValue = parseInt(value, 10)
|
||||
// eslint-disable-next-line no-self-compare
|
||||
if (parsedValue !== parsedValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (key === 'max-age' && key in output && output[key] >= parsedValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
output[key] = parsedValue
|
||||
|
||||
break
|
||||
}
|
||||
case 'private':
|
||||
case 'no-cache': {
|
||||
if (value) {
|
||||
// The private and no-cache directives can be unqualified (aka just
|
||||
// `private` or `no-cache`) or qualified (w/ a value). When they're
|
||||
// qualified, it's a list of headers like `no-cache=header1`,
|
||||
// `no-cache="header1"`, or `no-cache="header1, header2"`
|
||||
// If we're given multiple headers, the comma messes us up since
|
||||
// we split the full header by commas. So, let's loop through the
|
||||
// remaining parts in front of us until we find one that ends in a
|
||||
// quote. We can then just splice all of the parts in between the
|
||||
// starting quote and the ending quote out of the directives array
|
||||
// and continue parsing like normal.
|
||||
// https://www.rfc-editor.org/rfc/rfc9111.html#name-no-cache-2
|
||||
if (value[0] === '"') {
|
||||
// Something like `no-cache="some-header"` OR `no-cache="some-header, another-header"`.
|
||||
|
||||
// Add the first header on and cut off the leading quote
|
||||
const headers = [value.substring(1)]
|
||||
|
||||
let foundEndingQuote = value[value.length - 1] === '"'
|
||||
if (!foundEndingQuote) {
|
||||
// Something like `no-cache="some-header, another-header"`
|
||||
// This can still be something invalid, e.g. `no-cache="some-header, ...`
|
||||
for (let j = i + 1; j < directives.length; j++) {
|
||||
const nextPart = directives[j]
|
||||
const nextPartLength = nextPart.length
|
||||
|
||||
headers.push(nextPart.trim())
|
||||
|
||||
if (nextPartLength !== 0 && nextPart[nextPartLength - 1] === '"') {
|
||||
foundEndingQuote = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundEndingQuote) {
|
||||
let lastHeader = headers[headers.length - 1]
|
||||
if (lastHeader[lastHeader.length - 1] === '"') {
|
||||
lastHeader = lastHeader.substring(0, lastHeader.length - 1)
|
||||
headers[headers.length - 1] = lastHeader
|
||||
}
|
||||
|
||||
if (key in output) {
|
||||
output[key] = output[key].concat(headers)
|
||||
} else {
|
||||
output[key] = headers
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Something like `no-cache="some-header"`
|
||||
if (key in output) {
|
||||
output[key] = output[key].concat(value)
|
||||
} else {
|
||||
output[key] = [value]
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'public':
|
||||
case 'no-store':
|
||||
case 'must-revalidate':
|
||||
case 'proxy-revalidate':
|
||||
case 'immutable':
|
||||
case 'no-transform':
|
||||
case 'must-understand':
|
||||
case 'only-if-cached':
|
||||
if (value) {
|
||||
// These are qualified (something like `public=...`) when they aren't
|
||||
// allowed to be, skip
|
||||
continue
|
||||
}
|
||||
|
||||
output[key] = true
|
||||
break
|
||||
default:
|
||||
// Ignore unknown directives as per https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.3-1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | string[]} varyHeader Vary header from the server
|
||||
* @param {Record<string, string | string[]>} headers Request headers
|
||||
* @returns {Record<string, string | string[]>}
|
||||
*/
|
||||
function parseVaryHeader (varyHeader, headers) {
|
||||
if (typeof varyHeader === 'string' && varyHeader.includes('*')) {
|
||||
return headers
|
||||
}
|
||||
|
||||
const output = /** @type {Record<string, string | string[] | null>} */ ({})
|
||||
|
||||
const varyingHeaders = typeof varyHeader === 'string'
|
||||
? varyHeader.split(',')
|
||||
: varyHeader
|
||||
|
||||
for (const header of varyingHeaders) {
|
||||
const trimmedHeader = header.trim().toLowerCase()
|
||||
|
||||
output[trimmedHeader] = headers[trimmedHeader] ?? null
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this deviates from the spec a little. Empty etags ("", W/"") are valid,
|
||||
* however, including them in cached resposnes serves little to no purpose.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9110.html#name-etag
|
||||
*
|
||||
* @param {string} etag
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isEtagUsable (etag) {
|
||||
if (etag.length <= 2) {
|
||||
// Shortest an etag can be is two chars (just ""). This is where we deviate
|
||||
// from the spec requiring a min of 3 chars however
|
||||
return false
|
||||
}
|
||||
|
||||
if (etag[0] === '"' && etag[etag.length - 1] === '"') {
|
||||
// ETag: ""asd123"" or ETag: "W/"asd123"", kinda undefined behavior in the
|
||||
// spec. Some servers will accept these while others don't.
|
||||
// ETag: "asd123"
|
||||
return !(etag[1] === '"' || etag.startsWith('"W/'))
|
||||
}
|
||||
|
||||
if (etag.startsWith('W/"') && etag[etag.length - 1] === '"') {
|
||||
// ETag: W/"", also where we deviate from the spec & require a min of 3
|
||||
// chars
|
||||
// ETag: for W/"", W/"asd123"
|
||||
return etag.length !== 4
|
||||
}
|
||||
|
||||
// Anything else
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} store
|
||||
* @returns {asserts store is import('../../types/cache-interceptor.d.ts').default.CacheStore}
|
||||
*/
|
||||
function assertCacheStore (store, name = 'CacheStore') {
|
||||
if (typeof store !== 'object' || store === null) {
|
||||
throw new TypeError(`expected type of ${name} to be a CacheStore, got ${store === null ? 'null' : typeof store}`)
|
||||
}
|
||||
|
||||
for (const fn of ['get', 'createWriteStream', 'delete']) {
|
||||
if (typeof store[fn] !== 'function') {
|
||||
throw new TypeError(`${name} needs to have a \`${fn}()\` function`)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {unknown} methods
|
||||
* @returns {asserts methods is import('../../types/cache-interceptor.d.ts').default.CacheMethods[]}
|
||||
*/
|
||||
function assertCacheMethods (methods, name = 'CacheMethods') {
|
||||
if (!Array.isArray(methods)) {
|
||||
throw new TypeError(`expected type of ${name} needs to be an array, got ${methods === null ? 'null' : typeof methods}`)
|
||||
}
|
||||
|
||||
if (methods.length === 0) {
|
||||
throw new TypeError(`${name} needs to have at least one method`)
|
||||
}
|
||||
|
||||
for (const method of methods) {
|
||||
if (!safeHTTPMethods.includes(method)) {
|
||||
throw new TypeError(`element of ${name}-array needs to be one of following values: ${safeHTTPMethods.join(', ')}, got ${method}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
makeCacheKey,
|
||||
normalizeHeaders,
|
||||
assertCacheKey,
|
||||
assertCacheValue,
|
||||
parseCacheControlHeader,
|
||||
parseVaryHeader,
|
||||
isEtagUsable,
|
||||
assertCacheMethods,
|
||||
assertCacheStore
|
||||
}
|
||||
653
node_modules/undici/lib/util/date.js
generated
vendored
Normal file
653
node_modules/undici/lib/util/date.js
generated
vendored
Normal file
@@ -0,0 +1,653 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9110.html#name-date-time-formats
|
||||
*
|
||||
* @param {string} date
|
||||
* @returns {Date | undefined}
|
||||
*/
|
||||
function parseHttpDate (date) {
|
||||
// Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate
|
||||
// Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
|
||||
// Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format
|
||||
|
||||
switch (date[3]) {
|
||||
case ',': return parseImfDate(date)
|
||||
case ' ': return parseAscTimeDate(date)
|
||||
default: return parseRfc850Date(date)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://httpwg.org/specs/rfc9110.html#preferred.date.format
|
||||
*
|
||||
* @param {string} date
|
||||
* @returns {Date | undefined}
|
||||
*/
|
||||
function parseImfDate (date) {
|
||||
if (
|
||||
date.length !== 29 ||
|
||||
date[4] !== ' ' ||
|
||||
date[7] !== ' ' ||
|
||||
date[11] !== ' ' ||
|
||||
date[16] !== ' ' ||
|
||||
date[19] !== ':' ||
|
||||
date[22] !== ':' ||
|
||||
date[25] !== ' ' ||
|
||||
date[26] !== 'G' ||
|
||||
date[27] !== 'M' ||
|
||||
date[28] !== 'T'
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let weekday = -1
|
||||
if (date[0] === 'S' && date[1] === 'u' && date[2] === 'n') { // Sunday
|
||||
weekday = 0
|
||||
} else if (date[0] === 'M' && date[1] === 'o' && date[2] === 'n') { // Monday
|
||||
weekday = 1
|
||||
} else if (date[0] === 'T' && date[1] === 'u' && date[2] === 'e') { // Tuesday
|
||||
weekday = 2
|
||||
} else if (date[0] === 'W' && date[1] === 'e' && date[2] === 'd') { // Wednesday
|
||||
weekday = 3
|
||||
} else if (date[0] === 'T' && date[1] === 'h' && date[2] === 'u') { // Thursday
|
||||
weekday = 4
|
||||
} else if (date[0] === 'F' && date[1] === 'r' && date[2] === 'i') { // Friday
|
||||
weekday = 5
|
||||
} else if (date[0] === 'S' && date[1] === 'a' && date[2] === 't') { // Saturday
|
||||
weekday = 6
|
||||
} else {
|
||||
return undefined // Not a valid day of the week
|
||||
}
|
||||
|
||||
let day = 0
|
||||
if (date[5] === '0') {
|
||||
// Single digit day, e.g. "Sun Nov 6 08:49:37 1994"
|
||||
const code = date.charCodeAt(6)
|
||||
if (code < 49 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
day = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(5)
|
||||
if (code1 < 49 || code1 > 51) {
|
||||
return undefined // Not a digit between 1 and 3
|
||||
}
|
||||
const code2 = date.charCodeAt(6)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
day = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
let monthIdx = -1
|
||||
if (
|
||||
(date[8] === 'J' && date[9] === 'a' && date[10] === 'n')
|
||||
) {
|
||||
monthIdx = 0 // Jan
|
||||
} else if (
|
||||
(date[8] === 'F' && date[9] === 'e' && date[10] === 'b')
|
||||
) {
|
||||
monthIdx = 1 // Feb
|
||||
} else if (
|
||||
(date[8] === 'M' && date[9] === 'a')
|
||||
) {
|
||||
if (date[10] === 'r') {
|
||||
monthIdx = 2 // Mar
|
||||
} else if (date[10] === 'y') {
|
||||
monthIdx = 4 // May
|
||||
} else {
|
||||
return undefined // Invalid month
|
||||
}
|
||||
} else if (
|
||||
(date[8] === 'J')
|
||||
) {
|
||||
if (date[9] === 'a' && date[10] === 'n') {
|
||||
monthIdx = 0 // Jan
|
||||
} else if (date[9] === 'u') {
|
||||
if (date[10] === 'n') {
|
||||
monthIdx = 5 // Jun
|
||||
} else if (date[10] === 'l') {
|
||||
monthIdx = 6 // Jul
|
||||
} else {
|
||||
return undefined // Invalid month
|
||||
}
|
||||
} else {
|
||||
return undefined // Invalid month
|
||||
}
|
||||
} else if (
|
||||
(date[8] === 'A')
|
||||
) {
|
||||
if (date[9] === 'p' && date[10] === 'r') {
|
||||
monthIdx = 3 // Apr
|
||||
} else if (date[9] === 'u' && date[10] === 'g') {
|
||||
monthIdx = 7 // Aug
|
||||
} else {
|
||||
return undefined // Invalid month
|
||||
}
|
||||
} else if (
|
||||
(date[8] === 'S' && date[9] === 'e' && date[10] === 'p')
|
||||
) {
|
||||
monthIdx = 8 // Sep
|
||||
} else if (
|
||||
(date[8] === 'O' && date[9] === 'c' && date[10] === 't')
|
||||
) {
|
||||
monthIdx = 9 // Oct
|
||||
} else if (
|
||||
(date[8] === 'N' && date[9] === 'o' && date[10] === 'v')
|
||||
) {
|
||||
monthIdx = 10 // Nov
|
||||
} else if (
|
||||
(date[8] === 'D' && date[9] === 'e' && date[10] === 'c')
|
||||
) {
|
||||
monthIdx = 11 // Dec
|
||||
} else {
|
||||
// Not a valid month
|
||||
return undefined
|
||||
}
|
||||
|
||||
const yearDigit1 = date.charCodeAt(12)
|
||||
if (yearDigit1 < 48 || yearDigit1 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
const yearDigit2 = date.charCodeAt(13)
|
||||
if (yearDigit2 < 48 || yearDigit2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
const yearDigit3 = date.charCodeAt(14)
|
||||
if (yearDigit3 < 48 || yearDigit3 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
const yearDigit4 = date.charCodeAt(15)
|
||||
if (yearDigit4 < 48 || yearDigit4 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
const year = (yearDigit1 - 48) * 1000 + (yearDigit2 - 48) * 100 + (yearDigit3 - 48) * 10 + (yearDigit4 - 48)
|
||||
|
||||
let hour = 0
|
||||
if (date[17] === '0') {
|
||||
const code = date.charCodeAt(18)
|
||||
if (code < 48 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
hour = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(17)
|
||||
if (code1 < 48 || code1 > 50) {
|
||||
return undefined // Not a digit between 0 and 2
|
||||
}
|
||||
const code2 = date.charCodeAt(18)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
if (code1 === 50 && code2 > 51) {
|
||||
return undefined // Hour cannot be greater than 23
|
||||
}
|
||||
hour = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
let minute = 0
|
||||
if (date[20] === '0') {
|
||||
const code = date.charCodeAt(21)
|
||||
if (code < 48 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
minute = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(20)
|
||||
if (code1 < 48 || code1 > 53) {
|
||||
return undefined // Not a digit between 0 and 5
|
||||
}
|
||||
const code2 = date.charCodeAt(21)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
minute = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
let second = 0
|
||||
if (date[23] === '0') {
|
||||
const code = date.charCodeAt(24)
|
||||
if (code < 48 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
second = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(23)
|
||||
if (code1 < 48 || code1 > 53) {
|
||||
return undefined // Not a digit between 0 and 5
|
||||
}
|
||||
const code2 = date.charCodeAt(24)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
second = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
|
||||
return result.getUTCDay() === weekday ? result : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://httpwg.org/specs/rfc9110.html#obsolete.date.formats
|
||||
*
|
||||
* @param {string} date
|
||||
* @returns {Date | undefined}
|
||||
*/
|
||||
function parseAscTimeDate (date) {
|
||||
// This is assumed to be in UTC
|
||||
|
||||
if (
|
||||
date.length !== 24 ||
|
||||
date[7] !== ' ' ||
|
||||
date[10] !== ' ' ||
|
||||
date[19] !== ' '
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let weekday = -1
|
||||
if (date[0] === 'S' && date[1] === 'u' && date[2] === 'n') { // Sunday
|
||||
weekday = 0
|
||||
} else if (date[0] === 'M' && date[1] === 'o' && date[2] === 'n') { // Monday
|
||||
weekday = 1
|
||||
} else if (date[0] === 'T' && date[1] === 'u' && date[2] === 'e') { // Tuesday
|
||||
weekday = 2
|
||||
} else if (date[0] === 'W' && date[1] === 'e' && date[2] === 'd') { // Wednesday
|
||||
weekday = 3
|
||||
} else if (date[0] === 'T' && date[1] === 'h' && date[2] === 'u') { // Thursday
|
||||
weekday = 4
|
||||
} else if (date[0] === 'F' && date[1] === 'r' && date[2] === 'i') { // Friday
|
||||
weekday = 5
|
||||
} else if (date[0] === 'S' && date[1] === 'a' && date[2] === 't') { // Saturday
|
||||
weekday = 6
|
||||
} else {
|
||||
return undefined // Not a valid day of the week
|
||||
}
|
||||
|
||||
let monthIdx = -1
|
||||
if (
|
||||
(date[4] === 'J' && date[5] === 'a' && date[6] === 'n')
|
||||
) {
|
||||
monthIdx = 0 // Jan
|
||||
} else if (
|
||||
(date[4] === 'F' && date[5] === 'e' && date[6] === 'b')
|
||||
) {
|
||||
monthIdx = 1 // Feb
|
||||
} else if (
|
||||
(date[4] === 'M' && date[5] === 'a')
|
||||
) {
|
||||
if (date[6] === 'r') {
|
||||
monthIdx = 2 // Mar
|
||||
} else if (date[6] === 'y') {
|
||||
monthIdx = 4 // May
|
||||
} else {
|
||||
return undefined // Invalid month
|
||||
}
|
||||
} else if (
|
||||
(date[4] === 'J')
|
||||
) {
|
||||
if (date[5] === 'a' && date[6] === 'n') {
|
||||
monthIdx = 0 // Jan
|
||||
} else if (date[5] === 'u') {
|
||||
if (date[6] === 'n') {
|
||||
monthIdx = 5 // Jun
|
||||
} else if (date[6] === 'l') {
|
||||
monthIdx = 6 // Jul
|
||||
} else {
|
||||
return undefined // Invalid month
|
||||
}
|
||||
} else {
|
||||
return undefined // Invalid month
|
||||
}
|
||||
} else if (
|
||||
(date[4] === 'A')
|
||||
) {
|
||||
if (date[5] === 'p' && date[6] === 'r') {
|
||||
monthIdx = 3 // Apr
|
||||
} else if (date[5] === 'u' && date[6] === 'g') {
|
||||
monthIdx = 7 // Aug
|
||||
} else {
|
||||
return undefined // Invalid month
|
||||
}
|
||||
} else if (
|
||||
(date[4] === 'S' && date[5] === 'e' && date[6] === 'p')
|
||||
) {
|
||||
monthIdx = 8 // Sep
|
||||
} else if (
|
||||
(date[4] === 'O' && date[5] === 'c' && date[6] === 't')
|
||||
) {
|
||||
monthIdx = 9 // Oct
|
||||
} else if (
|
||||
(date[4] === 'N' && date[5] === 'o' && date[6] === 'v')
|
||||
) {
|
||||
monthIdx = 10 // Nov
|
||||
} else if (
|
||||
(date[4] === 'D' && date[5] === 'e' && date[6] === 'c')
|
||||
) {
|
||||
monthIdx = 11 // Dec
|
||||
} else {
|
||||
// Not a valid month
|
||||
return undefined
|
||||
}
|
||||
|
||||
let day = 0
|
||||
if (date[8] === ' ') {
|
||||
// Single digit day, e.g. "Sun Nov 6 08:49:37 1994"
|
||||
const code = date.charCodeAt(9)
|
||||
if (code < 49 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
day = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(8)
|
||||
if (code1 < 49 || code1 > 51) {
|
||||
return undefined // Not a digit between 1 and 3
|
||||
}
|
||||
const code2 = date.charCodeAt(9)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
day = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
let hour = 0
|
||||
if (date[11] === '0') {
|
||||
const code = date.charCodeAt(12)
|
||||
if (code < 48 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
hour = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(11)
|
||||
if (code1 < 48 || code1 > 50) {
|
||||
return undefined // Not a digit between 0 and 2
|
||||
}
|
||||
const code2 = date.charCodeAt(12)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
if (code1 === 50 && code2 > 51) {
|
||||
return undefined // Hour cannot be greater than 23
|
||||
}
|
||||
hour = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
let minute = 0
|
||||
if (date[14] === '0') {
|
||||
const code = date.charCodeAt(15)
|
||||
if (code < 48 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
minute = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(14)
|
||||
if (code1 < 48 || code1 > 53) {
|
||||
return undefined // Not a digit between 0 and 5
|
||||
}
|
||||
const code2 = date.charCodeAt(15)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
minute = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
let second = 0
|
||||
if (date[17] === '0') {
|
||||
const code = date.charCodeAt(18)
|
||||
if (code < 48 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
second = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(17)
|
||||
if (code1 < 48 || code1 > 53) {
|
||||
return undefined // Not a digit between 0 and 5
|
||||
}
|
||||
const code2 = date.charCodeAt(18)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
second = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
const yearDigit1 = date.charCodeAt(20)
|
||||
if (yearDigit1 < 48 || yearDigit1 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
const yearDigit2 = date.charCodeAt(21)
|
||||
if (yearDigit2 < 48 || yearDigit2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
const yearDigit3 = date.charCodeAt(22)
|
||||
if (yearDigit3 < 48 || yearDigit3 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
const yearDigit4 = date.charCodeAt(23)
|
||||
if (yearDigit4 < 48 || yearDigit4 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
const year = (yearDigit1 - 48) * 1000 + (yearDigit2 - 48) * 100 + (yearDigit3 - 48) * 10 + (yearDigit4 - 48)
|
||||
|
||||
const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
|
||||
return result.getUTCDay() === weekday ? result : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://httpwg.org/specs/rfc9110.html#obsolete.date.formats
|
||||
*
|
||||
* @param {string} date
|
||||
* @returns {Date | undefined}
|
||||
*/
|
||||
function parseRfc850Date (date) {
|
||||
let commaIndex = -1
|
||||
|
||||
let weekday = -1
|
||||
if (date[0] === 'S') {
|
||||
if (date[1] === 'u' && date[2] === 'n' && date[3] === 'd' && date[4] === 'a' && date[5] === 'y') {
|
||||
weekday = 0 // Sunday
|
||||
commaIndex = 6
|
||||
} else if (date[1] === 'a' && date[2] === 't' && date[3] === 'u' && date[4] === 'r' && date[5] === 'd' && date[6] === 'a' && date[7] === 'y') {
|
||||
weekday = 6 // Saturday
|
||||
commaIndex = 8
|
||||
}
|
||||
} else if (date[0] === 'M' && date[1] === 'o' && date[2] === 'n' && date[3] === 'd' && date[4] === 'a' && date[5] === 'y') {
|
||||
weekday = 1 // Monday
|
||||
commaIndex = 6
|
||||
} else if (date[0] === 'T') {
|
||||
if (date[1] === 'u' && date[2] === 'e' && date[3] === 's' && date[4] === 'd' && date[5] === 'a' && date[6] === 'y') {
|
||||
weekday = 2 // Tuesday
|
||||
commaIndex = 7
|
||||
} else if (date[1] === 'h' && date[2] === 'u' && date[3] === 'r' && date[4] === 's' && date[5] === 'd' && date[6] === 'a' && date[7] === 'y') {
|
||||
weekday = 4 // Thursday
|
||||
commaIndex = 8
|
||||
}
|
||||
} else if (date[0] === 'W' && date[1] === 'e' && date[2] === 'd' && date[3] === 'n' && date[4] === 'e' && date[5] === 's' && date[6] === 'd' && date[7] === 'a' && date[8] === 'y') {
|
||||
weekday = 3 // Wednesday
|
||||
commaIndex = 9
|
||||
} else if (date[0] === 'F' && date[1] === 'r' && date[2] === 'i' && date[3] === 'd' && date[4] === 'a' && date[5] === 'y') {
|
||||
weekday = 5 // Friday
|
||||
commaIndex = 6
|
||||
} else {
|
||||
// Not a valid day name
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (
|
||||
date[commaIndex] !== ',' ||
|
||||
(date.length - commaIndex - 1) !== 23 ||
|
||||
date[commaIndex + 1] !== ' ' ||
|
||||
date[commaIndex + 4] !== '-' ||
|
||||
date[commaIndex + 8] !== '-' ||
|
||||
date[commaIndex + 11] !== ' ' ||
|
||||
date[commaIndex + 14] !== ':' ||
|
||||
date[commaIndex + 17] !== ':' ||
|
||||
date[commaIndex + 20] !== ' ' ||
|
||||
date[commaIndex + 21] !== 'G' ||
|
||||
date[commaIndex + 22] !== 'M' ||
|
||||
date[commaIndex + 23] !== 'T'
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let day = 0
|
||||
if (date[commaIndex + 2] === '0') {
|
||||
// Single digit day, e.g. "Sun Nov 6 08:49:37 1994"
|
||||
const code = date.charCodeAt(commaIndex + 3)
|
||||
if (code < 49 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
day = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(commaIndex + 2)
|
||||
if (code1 < 49 || code1 > 51) {
|
||||
return undefined // Not a digit between 1 and 3
|
||||
}
|
||||
const code2 = date.charCodeAt(commaIndex + 3)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
day = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
let monthIdx = -1
|
||||
if (
|
||||
(date[commaIndex + 5] === 'J' && date[commaIndex + 6] === 'a' && date[commaIndex + 7] === 'n')
|
||||
) {
|
||||
monthIdx = 0 // Jan
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'F' && date[commaIndex + 6] === 'e' && date[commaIndex + 7] === 'b')
|
||||
) {
|
||||
monthIdx = 1 // Feb
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'M' && date[commaIndex + 6] === 'a' && date[commaIndex + 7] === 'r')
|
||||
) {
|
||||
monthIdx = 2 // Mar
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'A' && date[commaIndex + 6] === 'p' && date[commaIndex + 7] === 'r')
|
||||
) {
|
||||
monthIdx = 3 // Apr
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'M' && date[commaIndex + 6] === 'a' && date[commaIndex + 7] === 'y')
|
||||
) {
|
||||
monthIdx = 4 // May
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'J' && date[commaIndex + 6] === 'u' && date[commaIndex + 7] === 'n')
|
||||
) {
|
||||
monthIdx = 5 // Jun
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'J' && date[commaIndex + 6] === 'u' && date[commaIndex + 7] === 'l')
|
||||
) {
|
||||
monthIdx = 6 // Jul
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'A' && date[commaIndex + 6] === 'u' && date[commaIndex + 7] === 'g')
|
||||
) {
|
||||
monthIdx = 7 // Aug
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'S' && date[commaIndex + 6] === 'e' && date[commaIndex + 7] === 'p')
|
||||
) {
|
||||
monthIdx = 8 // Sep
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'O' && date[commaIndex + 6] === 'c' && date[commaIndex + 7] === 't')
|
||||
) {
|
||||
monthIdx = 9 // Oct
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'N' && date[commaIndex + 6] === 'o' && date[commaIndex + 7] === 'v')
|
||||
) {
|
||||
monthIdx = 10 // Nov
|
||||
} else if (
|
||||
(date[commaIndex + 5] === 'D' && date[commaIndex + 6] === 'e' && date[commaIndex + 7] === 'c')
|
||||
) {
|
||||
monthIdx = 11 // Dec
|
||||
} else {
|
||||
// Not a valid month
|
||||
return undefined
|
||||
}
|
||||
|
||||
const yearDigit1 = date.charCodeAt(commaIndex + 9)
|
||||
if (yearDigit1 < 48 || yearDigit1 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
const yearDigit2 = date.charCodeAt(commaIndex + 10)
|
||||
if (yearDigit2 < 48 || yearDigit2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
|
||||
let year = (yearDigit1 - 48) * 10 + (yearDigit2 - 48) // Convert ASCII codes to number
|
||||
|
||||
// RFC 6265 states that the year is in the range 1970-2069.
|
||||
// @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1
|
||||
//
|
||||
// 3. If the year-value is greater than or equal to 70 and less than or
|
||||
// equal to 99, increment the year-value by 1900.
|
||||
// 4. If the year-value is greater than or equal to 0 and less than or
|
||||
// equal to 69, increment the year-value by 2000.
|
||||
year += year < 70 ? 2000 : 1900
|
||||
|
||||
let hour = 0
|
||||
if (date[commaIndex + 12] === '0') {
|
||||
const code = date.charCodeAt(commaIndex + 13)
|
||||
if (code < 48 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
hour = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(commaIndex + 12)
|
||||
if (code1 < 48 || code1 > 50) {
|
||||
return undefined // Not a digit between 0 and 2
|
||||
}
|
||||
const code2 = date.charCodeAt(commaIndex + 13)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
if (code1 === 50 && code2 > 51) {
|
||||
return undefined // Hour cannot be greater than 23
|
||||
}
|
||||
hour = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
let minute = 0
|
||||
if (date[commaIndex + 15] === '0') {
|
||||
const code = date.charCodeAt(commaIndex + 16)
|
||||
if (code < 48 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
minute = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(commaIndex + 15)
|
||||
if (code1 < 48 || code1 > 53) {
|
||||
return undefined // Not a digit between 0 and 5
|
||||
}
|
||||
const code2 = date.charCodeAt(commaIndex + 16)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
minute = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
let second = 0
|
||||
if (date[commaIndex + 18] === '0') {
|
||||
const code = date.charCodeAt(commaIndex + 19)
|
||||
if (code < 48 || code > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
second = code - 48 // Convert ASCII code to number
|
||||
} else {
|
||||
const code1 = date.charCodeAt(commaIndex + 18)
|
||||
if (code1 < 48 || code1 > 53) {
|
||||
return undefined // Not a digit between 0 and 5
|
||||
}
|
||||
const code2 = date.charCodeAt(commaIndex + 19)
|
||||
if (code2 < 48 || code2 > 57) {
|
||||
return undefined // Not a digit
|
||||
}
|
||||
second = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
|
||||
}
|
||||
|
||||
const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
|
||||
return result.getUTCDay() === weekday ? result : undefined
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseHttpDate
|
||||
}
|
||||
28
node_modules/undici/lib/util/promise.js
generated
vendored
Normal file
28
node_modules/undici/lib/util/promise.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @template {*} T
|
||||
* @typedef {Object} DeferredPromise
|
||||
* @property {Promise<T>} promise
|
||||
* @property {(value?: T) => void} resolve
|
||||
* @property {(reason?: any) => void} reject
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template {*} T
|
||||
* @returns {DeferredPromise<T>} An object containing a promise and its resolve/reject methods.
|
||||
*/
|
||||
function createDeferredPromise () {
|
||||
let res
|
||||
let rej
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
res = resolve
|
||||
rej = reject
|
||||
})
|
||||
|
||||
return { promise, resolve: res, reject: rej }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createDeferredPromise
|
||||
}
|
||||
32
node_modules/undici/lib/util/stats.js
generated
vendored
Normal file
32
node_modules/undici/lib/util/stats.js
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
kConnected,
|
||||
kPending,
|
||||
kRunning,
|
||||
kSize,
|
||||
kFree,
|
||||
kQueued
|
||||
} = require('../core/symbols')
|
||||
|
||||
class ClientStats {
|
||||
constructor (client) {
|
||||
this.connected = client[kConnected]
|
||||
this.pending = client[kPending]
|
||||
this.running = client[kRunning]
|
||||
this.size = client[kSize]
|
||||
}
|
||||
}
|
||||
|
||||
class PoolStats {
|
||||
constructor (pool) {
|
||||
this.connected = pool[kConnected]
|
||||
this.free = pool[kFree]
|
||||
this.pending = pool[kPending]
|
||||
this.queued = pool[kQueued]
|
||||
this.running = pool[kRunning]
|
||||
this.size = pool[kSize]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ClientStats, PoolStats }
|
||||
425
node_modules/undici/lib/util/timers.js
generated
vendored
Normal file
425
node_modules/undici/lib/util/timers.js
generated
vendored
Normal file
@@ -0,0 +1,425 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* This module offers an optimized timer implementation designed for scenarios
|
||||
* where high precision is not critical.
|
||||
*
|
||||
* The timer achieves faster performance by using a low-resolution approach,
|
||||
* with an accuracy target of within 500ms. This makes it particularly useful
|
||||
* for timers with delays of 1 second or more, where exact timing is less
|
||||
* crucial.
|
||||
*
|
||||
* It's important to note that Node.js timers are inherently imprecise, as
|
||||
* delays can occur due to the event loop being blocked by other operations.
|
||||
* Consequently, timers may trigger later than their scheduled time.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The fastNow variable contains the internal fast timer clock value.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
let fastNow = 0
|
||||
|
||||
/**
|
||||
* RESOLUTION_MS represents the target resolution time in milliseconds.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 1000
|
||||
*/
|
||||
const RESOLUTION_MS = 1e3
|
||||
|
||||
/**
|
||||
* TICK_MS defines the desired interval in milliseconds between each tick.
|
||||
* The target value is set to half the resolution time, minus 1 ms, to account
|
||||
* for potential event loop overhead.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 499
|
||||
*/
|
||||
const TICK_MS = (RESOLUTION_MS >> 1) - 1
|
||||
|
||||
/**
|
||||
* fastNowTimeout is a Node.js timer used to manage and process
|
||||
* the FastTimers stored in the `fastTimers` array.
|
||||
*
|
||||
* @type {NodeJS.Timeout}
|
||||
*/
|
||||
let fastNowTimeout
|
||||
|
||||
/**
|
||||
* The kFastTimer symbol is used to identify FastTimer instances.
|
||||
*
|
||||
* @type {Symbol}
|
||||
*/
|
||||
const kFastTimer = Symbol('kFastTimer')
|
||||
|
||||
/**
|
||||
* The fastTimers array contains all active FastTimers.
|
||||
*
|
||||
* @type {FastTimer[]}
|
||||
*/
|
||||
const fastTimers = []
|
||||
|
||||
/**
|
||||
* These constants represent the various states of a FastTimer.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The `NOT_IN_LIST` constant indicates that the FastTimer is not included
|
||||
* in the `fastTimers` array. Timers with this status will not be processed
|
||||
* during the next tick by the `onTick` function.
|
||||
*
|
||||
* A FastTimer can be re-added to the `fastTimers` array by invoking the
|
||||
* `refresh` method on the FastTimer instance.
|
||||
*
|
||||
* @type {-2}
|
||||
*/
|
||||
const NOT_IN_LIST = -2
|
||||
|
||||
/**
|
||||
* The `TO_BE_CLEARED` constant indicates that the FastTimer is scheduled
|
||||
* for removal from the `fastTimers` array. A FastTimer in this state will
|
||||
* be removed in the next tick by the `onTick` function and will no longer
|
||||
* be processed.
|
||||
*
|
||||
* This status is also set when the `clear` method is called on the FastTimer instance.
|
||||
*
|
||||
* @type {-1}
|
||||
*/
|
||||
const TO_BE_CLEARED = -1
|
||||
|
||||
/**
|
||||
* The `PENDING` constant signifies that the FastTimer is awaiting processing
|
||||
* in the next tick by the `onTick` function. Timers with this status will have
|
||||
* their `_idleStart` value set and their status updated to `ACTIVE` in the next tick.
|
||||
*
|
||||
* @type {0}
|
||||
*/
|
||||
const PENDING = 0
|
||||
|
||||
/**
|
||||
* The `ACTIVE` constant indicates that the FastTimer is active and waiting
|
||||
* for its timer to expire. During the next tick, the `onTick` function will
|
||||
* check if the timer has expired, and if so, it will execute the associated callback.
|
||||
*
|
||||
* @type {1}
|
||||
*/
|
||||
const ACTIVE = 1
|
||||
|
||||
/**
|
||||
* The onTick function processes the fastTimers array.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function onTick () {
|
||||
/**
|
||||
* Increment the fastNow value by the TICK_MS value, despite the actual time
|
||||
* that has passed since the last tick. This approach ensures independence
|
||||
* from the system clock and delays caused by a blocked event loop.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
fastNow += TICK_MS
|
||||
|
||||
/**
|
||||
* The `idx` variable is used to iterate over the `fastTimers` array.
|
||||
* Expired timers are removed by replacing them with the last element in the array.
|
||||
* Consequently, `idx` is only incremented when the current element is not removed.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
let idx = 0
|
||||
|
||||
/**
|
||||
* The len variable will contain the length of the fastTimers array
|
||||
* and will be decremented when a FastTimer should be removed from the
|
||||
* fastTimers array.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
let len = fastTimers.length
|
||||
|
||||
while (idx < len) {
|
||||
/**
|
||||
* @type {FastTimer}
|
||||
*/
|
||||
const timer = fastTimers[idx]
|
||||
|
||||
// If the timer is in the ACTIVE state and the timer has expired, it will
|
||||
// be processed in the next tick.
|
||||
if (timer._state === PENDING) {
|
||||
// Set the _idleStart value to the fastNow value minus the TICK_MS value
|
||||
// to account for the time the timer was in the PENDING state.
|
||||
timer._idleStart = fastNow - TICK_MS
|
||||
timer._state = ACTIVE
|
||||
} else if (
|
||||
timer._state === ACTIVE &&
|
||||
fastNow >= timer._idleStart + timer._idleTimeout
|
||||
) {
|
||||
timer._state = TO_BE_CLEARED
|
||||
timer._idleStart = -1
|
||||
timer._onTimeout(timer._timerArg)
|
||||
}
|
||||
|
||||
if (timer._state === TO_BE_CLEARED) {
|
||||
timer._state = NOT_IN_LIST
|
||||
|
||||
// Move the last element to the current index and decrement len if it is
|
||||
// not the only element in the array.
|
||||
if (--len !== 0) {
|
||||
fastTimers[idx] = fastTimers[len]
|
||||
}
|
||||
} else {
|
||||
++idx
|
||||
}
|
||||
}
|
||||
|
||||
// Set the length of the fastTimers array to the new length and thus
|
||||
// removing the excess FastTimers elements from the array.
|
||||
fastTimers.length = len
|
||||
|
||||
// If there are still active FastTimers in the array, refresh the Timer.
|
||||
// If there are no active FastTimers, the timer will be refreshed again
|
||||
// when a new FastTimer is instantiated.
|
||||
if (fastTimers.length !== 0) {
|
||||
refreshTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
function refreshTimeout () {
|
||||
// If the fastNowTimeout is already set and the Timer has the refresh()-
|
||||
// method available, call it to refresh the timer.
|
||||
// Some timer objects returned by setTimeout may not have a .refresh()
|
||||
// method (e.g. mocked timers in tests).
|
||||
if (fastNowTimeout?.refresh) {
|
||||
fastNowTimeout.refresh()
|
||||
// fastNowTimeout is not instantiated yet or refresh is not availabe,
|
||||
// create a new Timer.
|
||||
} else {
|
||||
clearTimeout(fastNowTimeout)
|
||||
fastNowTimeout = setTimeout(onTick, TICK_MS)
|
||||
// If the Timer has an unref method, call it to allow the process to exit,
|
||||
// if there are no other active handles. When using fake timers or mocked
|
||||
// environments (like Jest), .unref() may not be defined,
|
||||
fastNowTimeout?.unref()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `FastTimer` class is a data structure designed to store and manage
|
||||
* timer information.
|
||||
*/
|
||||
class FastTimer {
|
||||
[kFastTimer] = true
|
||||
|
||||
/**
|
||||
* The state of the timer, which can be one of the following:
|
||||
* - NOT_IN_LIST (-2)
|
||||
* - TO_BE_CLEARED (-1)
|
||||
* - PENDING (0)
|
||||
* - ACTIVE (1)
|
||||
*
|
||||
* @type {-2|-1|0|1}
|
||||
* @private
|
||||
*/
|
||||
_state = NOT_IN_LIST
|
||||
|
||||
/**
|
||||
* The number of milliseconds to wait before calling the callback.
|
||||
*
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
_idleTimeout = -1
|
||||
|
||||
/**
|
||||
* The time in milliseconds when the timer was started. This value is used to
|
||||
* calculate when the timer should expire.
|
||||
*
|
||||
* @type {number}
|
||||
* @default -1
|
||||
* @private
|
||||
*/
|
||||
_idleStart = -1
|
||||
|
||||
/**
|
||||
* The function to be executed when the timer expires.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
_onTimeout
|
||||
|
||||
/**
|
||||
* The argument to be passed to the callback when the timer expires.
|
||||
*
|
||||
* @type {*}
|
||||
* @private
|
||||
*/
|
||||
_timerArg
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Function} callback A function to be executed after the timer
|
||||
* expires.
|
||||
* @param {number} delay The time, in milliseconds that the timer should wait
|
||||
* before the specified function or code is executed.
|
||||
* @param {*} arg
|
||||
*/
|
||||
constructor (callback, delay, arg) {
|
||||
this._onTimeout = callback
|
||||
this._idleTimeout = delay
|
||||
this._timerArg = arg
|
||||
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timer's start time to the current time, and reschedules the timer
|
||||
* to call its callback at the previously specified duration adjusted to the
|
||||
* current time.
|
||||
* Using this on a timer that has already called its callback will reactivate
|
||||
* the timer.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
refresh () {
|
||||
// In the special case that the timer is not in the list of active timers,
|
||||
// add it back to the array to be processed in the next tick by the onTick
|
||||
// function.
|
||||
if (this._state === NOT_IN_LIST) {
|
||||
fastTimers.push(this)
|
||||
}
|
||||
|
||||
// If the timer is the only active timer, refresh the fastNowTimeout for
|
||||
// better resolution.
|
||||
if (!fastNowTimeout || fastTimers.length === 1) {
|
||||
refreshTimeout()
|
||||
}
|
||||
|
||||
// Setting the state to PENDING will cause the timer to be reset in the
|
||||
// next tick by the onTick function.
|
||||
this._state = PENDING
|
||||
}
|
||||
|
||||
/**
|
||||
* The `clear` method cancels the timer, preventing it from executing.
|
||||
*
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
clear () {
|
||||
// Set the state to TO_BE_CLEARED to mark the timer for removal in the next
|
||||
// tick by the onTick function.
|
||||
this._state = TO_BE_CLEARED
|
||||
|
||||
// Reset the _idleStart value to -1 to indicate that the timer is no longer
|
||||
// active.
|
||||
this._idleStart = -1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This module exports a setTimeout and clearTimeout function that can be
|
||||
* used as a drop-in replacement for the native functions.
|
||||
*/
|
||||
module.exports = {
|
||||
/**
|
||||
* The setTimeout() method sets a timer which executes a function once the
|
||||
* timer expires.
|
||||
* @param {Function} callback A function to be executed after the timer
|
||||
* expires.
|
||||
* @param {number} delay The time, in milliseconds that the timer should
|
||||
* wait before the specified function or code is executed.
|
||||
* @param {*} [arg] An optional argument to be passed to the callback function
|
||||
* when the timer expires.
|
||||
* @returns {NodeJS.Timeout|FastTimer}
|
||||
*/
|
||||
setTimeout (callback, delay, arg) {
|
||||
// If the delay is less than or equal to the RESOLUTION_MS value return a
|
||||
// native Node.js Timer instance.
|
||||
return delay <= RESOLUTION_MS
|
||||
? setTimeout(callback, delay, arg)
|
||||
: new FastTimer(callback, delay, arg)
|
||||
},
|
||||
/**
|
||||
* The clearTimeout method cancels an instantiated Timer previously created
|
||||
* by calling setTimeout.
|
||||
*
|
||||
* @param {NodeJS.Timeout|FastTimer} timeout
|
||||
*/
|
||||
clearTimeout (timeout) {
|
||||
// If the timeout is a FastTimer, call its own clear method.
|
||||
if (timeout[kFastTimer]) {
|
||||
/**
|
||||
* @type {FastTimer}
|
||||
*/
|
||||
timeout.clear()
|
||||
// Otherwise it is an instance of a native NodeJS.Timeout, so call the
|
||||
// Node.js native clearTimeout function.
|
||||
} else {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The setFastTimeout() method sets a fastTimer which executes a function once
|
||||
* the timer expires.
|
||||
* @param {Function} callback A function to be executed after the timer
|
||||
* expires.
|
||||
* @param {number} delay The time, in milliseconds that the timer should
|
||||
* wait before the specified function or code is executed.
|
||||
* @param {*} [arg] An optional argument to be passed to the callback function
|
||||
* when the timer expires.
|
||||
* @returns {FastTimer}
|
||||
*/
|
||||
setFastTimeout (callback, delay, arg) {
|
||||
return new FastTimer(callback, delay, arg)
|
||||
},
|
||||
/**
|
||||
* The clearTimeout method cancels an instantiated FastTimer previously
|
||||
* created by calling setFastTimeout.
|
||||
*
|
||||
* @param {FastTimer} timeout
|
||||
*/
|
||||
clearFastTimeout (timeout) {
|
||||
timeout.clear()
|
||||
},
|
||||
/**
|
||||
* The now method returns the value of the internal fast timer clock.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
now () {
|
||||
return fastNow
|
||||
},
|
||||
/**
|
||||
* Trigger the onTick function to process the fastTimers array.
|
||||
* Exported for testing purposes only.
|
||||
* Marking as deprecated to discourage any use outside of testing.
|
||||
* @deprecated
|
||||
* @param {number} [delay=0] The delay in milliseconds to add to the now value.
|
||||
*/
|
||||
tick (delay = 0) {
|
||||
fastNow += delay - RESOLUTION_MS + 1
|
||||
onTick()
|
||||
onTick()
|
||||
},
|
||||
/**
|
||||
* Reset FastTimers.
|
||||
* Exported for testing purposes only.
|
||||
* Marking as deprecated to discourage any use outside of testing.
|
||||
* @deprecated
|
||||
*/
|
||||
reset () {
|
||||
fastNow = 0
|
||||
fastTimers.length = 0
|
||||
clearTimeout(fastNowTimeout)
|
||||
fastNowTimeout = null
|
||||
},
|
||||
/**
|
||||
* Exporting for testing purposes only.
|
||||
* Marking as deprecated to discourage any use outside of testing.
|
||||
* @deprecated
|
||||
*/
|
||||
kFastTimer
|
||||
}
|
||||
Reference in New Issue
Block a user