node_modules ignore

This commit is contained in:
2025-05-08 23:43:47 +02:00
parent e19d52f172
commit 4574544c9f
65041 changed files with 10593536 additions and 0 deletions

162
server/node_modules/get-it/src/createRequester.ts generated vendored Normal file
View File

@@ -0,0 +1,162 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {processOptions} from './middleware/defaultOptionsProcessor'
import {validateOptions} from './middleware/defaultOptionsValidator'
import type {
HttpContext,
HttpRequest,
HttpRequestOngoing,
Middleware,
MiddlewareChannels,
MiddlewareHooks,
MiddlewareReducer,
MiddlewareResponse,
Middlewares,
Requester,
RequestOptions,
} from './types'
import {middlewareReducer} from './util/middlewareReducer'
import {createPubSub} from './util/pubsub'
const channelNames = [
'request',
'response',
'progress',
'error',
'abort',
] satisfies (keyof MiddlewareChannels)[]
const middlehooks = [
'processOptions',
'validateOptions',
'interceptRequest',
'finalizeOptions',
'onRequest',
'onResponse',
'onError',
'onReturn',
'onHeaders',
] satisfies (keyof MiddlewareHooks)[]
/** @public */
export function createRequester(initMiddleware: Middlewares, httpRequest: HttpRequest): Requester {
const loadedMiddleware: Middlewares = []
const middleware: MiddlewareReducer = middlehooks.reduce(
(ware, name) => {
ware[name] = ware[name] || []
return ware
},
{
processOptions: [processOptions],
validateOptions: [validateOptions],
} as any,
)
function request(opts: RequestOptions | string) {
const onResponse = (reqErr: Error | null, res: MiddlewareResponse, ctx: HttpContext) => {
let error = reqErr
let response: MiddlewareResponse | null = res
// We're processing non-errors first, in case a middleware converts the
// response into an error (for instance, status >= 400 == HttpError)
if (!error) {
try {
response = applyMiddleware('onResponse', res, ctx)
} catch (err: any) {
response = null
error = err
}
}
// Apply error middleware - if middleware return the same (or a different) error,
// publish as an error event. If we *don't* return an error, assume it has been handled
error = error && applyMiddleware('onError', error, ctx)
// Figure out if we should publish on error/response channels
if (error) {
channels.error.publish(error)
} else if (response) {
channels.response.publish(response)
}
}
const channels: MiddlewareChannels = channelNames.reduce((target, name) => {
target[name] = createPubSub() as MiddlewareChannels[typeof name]
return target
}, {} as any)
// Prepare a middleware reducer that can be reused throughout the lifecycle
const applyMiddleware = middlewareReducer(middleware)
// Parse the passed options
const options = applyMiddleware('processOptions', opts as RequestOptions)
// Validate the options
applyMiddleware('validateOptions', options)
// Build a context object we can pass to child handlers
const context = {options, channels, applyMiddleware}
// We need to hold a reference to the current, ongoing request,
// in order to allow cancellation. In the case of the retry middleware,
// a new request might be triggered
let ongoingRequest: HttpRequestOngoing | undefined
const unsubscribe = channels.request.subscribe((ctx) => {
// Let request adapters (node/browser) perform the actual request
ongoingRequest = httpRequest(ctx, (err, res) => onResponse(err, res!, ctx))
})
// If we abort the request, prevent further requests from happening,
// and be sure to cancel any ongoing request (obviously)
channels.abort.subscribe(() => {
unsubscribe()
if (ongoingRequest) {
ongoingRequest.abort()
}
})
// See if any middleware wants to modify the return value - for instance
// the promise or observable middlewares
const returnValue = applyMiddleware('onReturn', channels, context)
// If return value has been modified by a middleware, we expect the middleware
// to publish on the 'request' channel. If it hasn't been modified, we want to
// trigger it right away
if (returnValue === channels) {
channels.request.publish(context)
}
return returnValue
}
request.use = function use(newMiddleware: Middleware) {
if (!newMiddleware) {
throw new Error('Tried to add middleware that resolved to falsey value')
}
if (typeof newMiddleware === 'function') {
throw new Error(
'Tried to add middleware that was a function. It probably expects you to pass options to it.',
)
}
if (newMiddleware.onReturn && middleware.onReturn.length > 0) {
throw new Error(
'Tried to add new middleware with `onReturn` handler, but another handler has already been registered for this event',
)
}
middlehooks.forEach((key) => {
if (newMiddleware[key]) {
middleware[key].push(newMiddleware[key] as any)
}
})
loadedMiddleware.push(newMiddleware)
return request
}
request.clone = () => createRequester(loadedMiddleware, httpRequest)
initMiddleware.forEach(request.use)
return request
}

17
server/node_modules/get-it/src/index.browser.ts generated vendored Normal file
View File

@@ -0,0 +1,17 @@
import {createRequester} from './createRequester'
import {httpRequester} from './request/browser-request'
import type {ExportEnv, HttpRequest, Middlewares, Requester} from './types'
export type * from './types'
/** @public */
export const getIt = (
initMiddleware: Middlewares = [],
httpRequest: HttpRequest = httpRequester,
): Requester => createRequester(initMiddleware, httpRequest)
/** @public */
export const environment = 'browser' satisfies ExportEnv
/** @public */
export {adapter} from './request/browser-request'

6
server/node_modules/get-it/src/index.react-server.ts generated vendored Normal file
View File

@@ -0,0 +1,6 @@
import type {ExportEnv} from './types'
export * from './index.browser'
/** @public */
export const environment = 'react-server' satisfies ExportEnv

17
server/node_modules/get-it/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,17 @@
import {createRequester} from './createRequester'
import {httpRequester} from './request/node-request'
import type {ExportEnv, HttpRequest, Middlewares, Requester} from './types'
export type * from './types'
/** @public */
export const getIt = (
initMiddleware: Middlewares = [],
httpRequest: HttpRequest = httpRequester,
): Requester => createRequester(initMiddleware, httpRequest)
/** @public */
export const environment: ExportEnv = 'node'
/** @public */
export {adapter} from './request/node-request'

22
server/node_modules/get-it/src/middleware.browser.ts generated vendored Normal file
View File

@@ -0,0 +1,22 @@
export * from './middleware/agent/browser-agent'
export * from './middleware/base'
export * from './middleware/debug'
export * from './middleware/defaultOptionsProcessor'
export * from './middleware/defaultOptionsValidator'
export * from './middleware/headers'
export * from './middleware/httpErrors'
export * from './middleware/injectResponse'
export * from './middleware/jsonRequest'
export * from './middleware/jsonResponse'
export * from './middleware/mtls'
export * from './middleware/observable'
export * from './middleware/progress/browser-progress'
export * from './middleware/promise'
export * from './middleware/proxy'
export * from './middleware/retry/browser-retry'
export * from './middleware/urlEncoded'
import {agent} from './middleware/agent/browser-agent'
import {buildKeepAlive} from './middleware/keepAlive'
/** @public */
export const keepAlive = buildKeepAlive(agent)

22
server/node_modules/get-it/src/middleware.ts generated vendored Normal file
View File

@@ -0,0 +1,22 @@
export * from './middleware/agent/node-agent'
export * from './middleware/base'
export * from './middleware/debug'
export * from './middleware/defaultOptionsProcessor'
export * from './middleware/defaultOptionsValidator'
export * from './middleware/headers'
export * from './middleware/httpErrors'
export * from './middleware/injectResponse'
export * from './middleware/jsonRequest'
export * from './middleware/jsonResponse'
export * from './middleware/mtls'
export * from './middleware/observable'
export * from './middleware/progress/node-progress'
export * from './middleware/promise'
export * from './middleware/proxy'
export * from './middleware/retry/node-retry'
export * from './middleware/urlEncoded'
import {agent} from './middleware/agent/node-agent'
import {buildKeepAlive} from './middleware/keepAlive'
/** @public */
export const keepAlive = buildKeepAlive(agent)

View File

@@ -0,0 +1,10 @@
/**
* This middleware only has an effect in Node.js.
* @public
*/
export function agent(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_opts?: any,
): any {
return {}
}

View File

@@ -0,0 +1,33 @@
import {Agent as HttpAgent, type AgentOptions} from 'http'
import {Agent as HttpsAgent} from 'https'
import {type Middleware} from 'get-it'
const isHttpsProto = /^https:/i
/**
* Constructs a http.Agent and uses it for all requests.
* This can be used to override settings such as `maxSockets`, `maxTotalSockets` (to limit concurrency) or change the `timeout`.
* @public
*/
export function agent(opts?: AgentOptions) {
const httpAgent = new HttpAgent(opts)
const httpsAgent = new HttpsAgent(opts)
const agents = {http: httpAgent, https: httpsAgent}
return {
finalizeOptions: (options: any) => {
if (options.agent) {
return options
}
// When maxRedirects>0 we're using the follow-redirects package and this supports the `agents` option.
if (options.maxRedirects > 0) {
return {...options, agents}
}
// ... otherwise we'll have to detect which agent to use:
const isHttps = isHttpsProto.test(options.href || options.protocol)
return {...options, agent: isHttps ? httpsAgent : httpAgent}
},
} satisfies Middleware
}

19
server/node_modules/get-it/src/middleware/base.ts generated vendored Normal file
View File

@@ -0,0 +1,19 @@
import type {Middleware} from 'get-it'
const leadingSlash = /^\//
const trailingSlash = /\/$/
/** @public */
export function base(baseUrl: string) {
const baseUri = baseUrl.replace(trailingSlash, '')
return {
processOptions: (options) => {
if (/^https?:\/\//i.test(options.url)) {
return options // Already prefixed
}
const url = [baseUri, options.url.replace(leadingSlash, '')].join('/')
return Object.assign({}, options, {url})
},
} satisfies Middleware
}

103
server/node_modules/get-it/src/middleware/debug.ts generated vendored Normal file
View File

@@ -0,0 +1,103 @@
import debugIt from 'debug'
import type {Middleware} from 'get-it'
const SENSITIVE_HEADERS = ['cookie', 'authorization']
const hasOwn = Object.prototype.hasOwnProperty
const redactKeys = (source: any, redacted: any) => {
const target: any = {}
for (const key in source) {
if (hasOwn.call(source, key)) {
target[key] = redacted.indexOf(key.toLowerCase()) > -1 ? '<redacted>' : source[key]
}
}
return target
}
/** @public */
export function debug(opts: any = {}) {
const verbose = opts.verbose
const namespace = opts.namespace || 'get-it'
const defaultLogger = debugIt(namespace)
const log = opts.log || defaultLogger
const shortCircuit = log === defaultLogger && !debugIt.enabled(namespace)
let requestId = 0
return {
processOptions: (options) => {
options.debug = log
options.requestId = options.requestId || ++requestId
return options
},
onRequest: (event) => {
// Short-circuit if not enabled, to save some CPU cycles with formatting stuff
if (shortCircuit || !event) {
return event
}
const options = event.options
log('[%s] HTTP %s %s', options.requestId, options.method, options.url)
if (verbose && options.body && typeof options.body === 'string') {
log('[%s] Request body: %s', options.requestId, options.body)
}
if (verbose && options.headers) {
const headers =
opts.redactSensitiveHeaders === false
? options.headers
: redactKeys(options.headers, SENSITIVE_HEADERS)
log('[%s] Request headers: %s', options.requestId, JSON.stringify(headers, null, 2))
}
return event
},
onResponse: (res, context) => {
// Short-circuit if not enabled, to save some CPU cycles with formatting stuff
if (shortCircuit || !res) {
return res
}
const reqId = context.options.requestId
log('[%s] Response code: %s %s', reqId, res.statusCode, res.statusMessage)
if (verbose && res.body) {
log('[%s] Response body: %s', reqId, stringifyBody(res))
}
return res
},
onError: (err, context) => {
const reqId = context.options.requestId
if (!err) {
log('[%s] Error encountered, but handled by an earlier middleware', reqId)
return err
}
log('[%s] ERROR: %s', reqId, err.message)
return err
},
} satisfies Middleware
}
function stringifyBody(res: any) {
const contentType = (res.headers['content-type'] || '').toLowerCase()
const isJson = contentType.indexOf('application/json') !== -1
return isJson ? tryFormat(res.body) : res.body
}
// Attempt pretty-formatting JSON
function tryFormat(body: any) {
try {
const parsed = typeof body === 'string' ? JSON.parse(body) : body
return JSON.stringify(parsed, null, 2)
} catch {
return body
}
}

View File

@@ -0,0 +1,107 @@
import type {MiddlewareHooks, RequestOptions} from 'get-it'
const isReactNative = typeof navigator === 'undefined' ? false : navigator.product === 'ReactNative'
const defaultOptions = {timeout: isReactNative ? 60000 : 120000} satisfies Partial<RequestOptions>
/** @public */
export const processOptions = function processOptions(opts) {
const options = {
...defaultOptions,
...(typeof opts === 'string' ? {url: opts} : opts),
} satisfies RequestOptions
// Normalize timeouts
options.timeout = normalizeTimeout(options.timeout)
// Shallow-merge (override) existing query params
if (options.query) {
const {url, searchParams} = splitUrl(options.url)
for (const [key, value] of Object.entries(options.query)) {
if (value !== undefined) {
if (Array.isArray(value)) {
for (const v of value) {
searchParams.append(key, v as string)
}
} else {
searchParams.append(key, value as string)
}
}
// Merge back params into url
const search = searchParams.toString()
if (search) {
options.url = `${url}?${search}`
}
}
}
// Implicit POST if we have not specified a method but have a body
options.method =
options.body && !options.method ? 'POST' : (options.method || 'GET').toUpperCase()
return options
} satisfies MiddlewareHooks['processOptions']
/**
* Given a string URL, extracts the query string and URL from each other, and returns them.
* Note that we cannot use the `URL` constructor because of old React Native versions which are
* majorly broken and returns incorrect results:
*
* (`new URL('http://foo/?a=b').toString()` == 'http://foo/?a=b/')
*/
function splitUrl(url: string): {url: string; searchParams: URLSearchParams} {
const qIndex = url.indexOf('?')
if (qIndex === -1) {
return {url, searchParams: new URLSearchParams()}
}
const base = url.slice(0, qIndex)
const qs = url.slice(qIndex + 1)
// React Native's URL and URLSearchParams are broken, so passing a string to URLSearchParams
// does not work, leading to an empty query string. For other environments, this should be enough
if (!isReactNative) {
return {url: base, searchParams: new URLSearchParams(qs)}
}
// Sanity-check; we do not know of any environment where this is the case,
// but if it is, we should not proceed without giving a descriptive error
if (typeof decodeURIComponent !== 'function') {
throw new Error(
'Broken `URLSearchParams` implementation, and `decodeURIComponent` is not defined',
)
}
const params = new URLSearchParams()
for (const pair of qs.split('&')) {
const [key, value] = pair.split('=')
if (key) {
params.append(decodeQueryParam(key), decodeQueryParam(value || ''))
}
}
return {url: base, searchParams: params}
}
function decodeQueryParam(value: string): string {
return decodeURIComponent(value.replace(/\+/g, ' '))
}
function normalizeTimeout(time: RequestOptions['timeout']) {
if (time === false || time === 0) {
return false
}
if (time.connect || time.socket) {
return time
}
const delay = Number(time)
if (isNaN(delay)) {
return normalizeTimeout(defaultOptions.timeout)
}
return {connect: delay, socket: delay}
}

View File

@@ -0,0 +1,10 @@
import type {MiddlewareHooks} from 'get-it'
const validUrl = /^https?:\/\//i
/** @public */
export const validateOptions = function validateOptions(options) {
if (!validUrl.test(options.url)) {
throw new Error(`"${options.url}" is not a valid URL`)
}
} satisfies MiddlewareHooks['validateOptions']

15
server/node_modules/get-it/src/middleware/headers.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
import type {Middleware} from 'get-it'
/** @public */
export function headers(_headers: any, opts: any = {}) {
return {
processOptions: (options) => {
const existing = options.headers || {}
options.headers = opts.override
? Object.assign({}, existing, _headers)
: Object.assign({}, _headers, existing)
return options
},
} satisfies Middleware
}

View File

@@ -0,0 +1,30 @@
import type {Middleware} from 'get-it'
class HttpError extends Error {
response: any
request: any
constructor(res: any, ctx: any) {
super()
const truncatedUrl = res.url.length > 400 ? `${res.url.slice(0, 399)}` : res.url
let msg = `${res.method}-request to ${truncatedUrl} resulted in `
msg += `HTTP ${res.statusCode} ${res.statusMessage}`
this.message = msg.trim()
this.response = res
this.request = ctx.options
}
}
/** @public */
export function httpErrors() {
return {
onResponse: (res, ctx) => {
const isHttpError = res.statusCode >= 400
if (!isHttpError) {
return res
}
throw new HttpError(res, ctx)
},
} satisfies Middleware
}

View File

@@ -0,0 +1,37 @@
import type {Middleware, MiddlewareHooks, MiddlewareResponse} from 'get-it'
/** @public */
export function injectResponse(
opts: {
inject: (
event: Parameters<MiddlewareHooks['interceptRequest']>[1],
prevValue: Parameters<MiddlewareHooks['interceptRequest']>[0],
) => Partial<MiddlewareResponse | undefined | void>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} = {} as any,
) {
if (typeof opts.inject !== 'function') {
throw new Error('`injectResponse` middleware requires a `inject` function')
}
const inject = function inject(prevValue, event) {
const response = opts.inject(event, prevValue)
if (!response) {
return prevValue
}
// Merge defaults so we don't have to provide the most basic of details unless we want to
const options = event.context.options
return {
body: '',
url: options.url,
method: options.method!,
headers: {},
statusCode: 200,
statusMessage: 'OK',
...response,
} satisfies MiddlewareResponse
} satisfies Middleware['interceptRequest']
return {interceptRequest: inject} satisfies Middleware
}

View File

@@ -0,0 +1,35 @@
import type {Middleware} from 'get-it'
import {isBuffer} from '../util/isBuffer'
import {isPlainObject} from '../util/isPlainObject'
const serializeTypes = ['boolean', 'string', 'number']
/** @public */
export function jsonRequest() {
return {
processOptions: (options) => {
const body = options.body
if (!body) {
return options
}
const isStream = typeof body.pipe === 'function'
const shouldSerialize =
!isStream &&
!isBuffer(body) &&
(serializeTypes.indexOf(typeof body) !== -1 || Array.isArray(body) || isPlainObject(body))
if (!shouldSerialize) {
return options
}
return Object.assign({}, options, {
body: JSON.stringify(options.body),
headers: Object.assign({}, options.headers, {
'Content-Type': 'application/json',
}),
})
},
} satisfies Middleware
}

View File

@@ -0,0 +1,30 @@
import type {Middleware} from 'get-it'
/** @public */
export function jsonResponse(opts?: any) {
return {
onResponse: (response) => {
const contentType = response.headers['content-type'] || ''
const shouldDecode = (opts && opts.force) || contentType.indexOf('application/json') !== -1
if (!response.body || !contentType || !shouldDecode) {
return response
}
return Object.assign({}, response, {body: tryParse(response.body)})
},
processOptions: (options) =>
Object.assign({}, options, {
headers: Object.assign({Accept: 'application/json'}, options.headers),
}),
} satisfies Middleware
function tryParse(body: any) {
try {
return JSON.parse(body)
} catch (err: any) {
err.message = `Failed to parsed response body as JSON: ${err.message}`
throw err
}
}
}

55
server/node_modules/get-it/src/middleware/keepAlive.ts generated vendored Normal file
View File

@@ -0,0 +1,55 @@
import type {AgentOptions} from 'http'
import type {Middleware} from 'get-it'
import {NodeRequestError} from '../request/node-request'
type KeepAliveOptions = {
ms?: number
maxFree?: number
/**
How many times to retry in case of ECONNRESET error. Default: 3
*/
maxRetries?: number
}
export function buildKeepAlive(agent: (opts: AgentOptions) => Pick<Middleware, 'finalizeOptions'>) {
return function keepAlive(config: KeepAliveOptions = {}): any {
const {maxRetries = 3, ms = 1000, maxFree = 256} = config
const {finalizeOptions} = agent({
keepAlive: true,
keepAliveMsecs: ms,
maxFreeSockets: maxFree,
})
return {
finalizeOptions,
onError: (err, context) => {
// When sending request through a keep-alive enabled agent, the underlying socket might be reused. But if server closes connection at unfortunate time, client may run into a 'ECONNRESET' error.
// We retry three times in case of ECONNRESET error.
// https://nodejs.org/docs/latest-v20.x/api/http.html#requestreusedsocket
if (
(context.options.method === 'GET' || context.options.method === 'POST') &&
err instanceof NodeRequestError &&
err.code === 'ECONNRESET' &&
err.request.reusedSocket
) {
const attemptNumber = context.options.attemptNumber || 0
if (attemptNumber < maxRetries) {
// Create a new context with an increased attempt number, so we can exit if we reach a limit
const newContext = Object.assign({}, context, {
options: Object.assign({}, context.options, {attemptNumber: attemptNumber + 1}),
})
// If this is a reused socket we retry immediately
setImmediate(() => context.channels.request.publish(newContext))
return null
}
}
return err
},
} satisfies Middleware
}
}

31
server/node_modules/get-it/src/middleware/mtls.ts generated vendored Normal file
View File

@@ -0,0 +1,31 @@
import type {Middleware} from 'get-it'
import {isBrowserOptions} from '../util/isBrowserOptions'
/** @public */
export function mtls(config: any = {}) {
if (!config.ca) {
throw new Error('Required mtls option "ca" is missing')
}
if (!config.cert) {
throw new Error('Required mtls option "cert" is missing')
}
if (!config.key) {
throw new Error('Required mtls option "key" is missing')
}
return {
finalizeOptions: (options) => {
if (isBrowserOptions(options)) {
return options
}
const mtlsOpts = {
cert: config.cert,
key: config.key,
ca: config.ca,
}
return Object.assign({}, options, mtlsOpts)
},
} satisfies Middleware
}

View File

@@ -0,0 +1,36 @@
import type {Middleware} from 'get-it'
import global from '../util/global'
/** @public */
export function observable(
opts: {
implementation?: any
} = {},
) {
const Observable =
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- @TODO consider dropping checking for a global Observable since it's not on a standards track
opts.implementation || (global as any).Observable
if (!Observable) {
throw new Error(
'`Observable` is not available in global scope, and no implementation was passed',
)
}
return {
onReturn: (channels, context) =>
new Observable((observer: any) => {
channels.error.subscribe((err) => observer.error(err))
channels.progress.subscribe((event) =>
observer.next(Object.assign({type: 'progress'}, event)),
)
channels.response.subscribe((response) => {
observer.next(Object.assign({type: 'response'}, response))
observer.complete()
})
channels.request.publish(context)
return () => channels.abort.publish()
}),
} satisfies Middleware
}

View File

@@ -0,0 +1,36 @@
import type {Middleware} from 'get-it'
/** @public */
export function progress() {
return {
onRequest: (evt) => {
if (evt.adapter !== 'xhr') {
return
}
const xhr = evt.request
const context = evt.context
if ('upload' in xhr && 'onprogress' in xhr.upload) {
xhr.upload.onprogress = handleProgress('upload')
}
if ('onprogress' in xhr) {
xhr.onprogress = handleProgress('download')
}
function handleProgress(stage: 'download' | 'upload') {
return (event: any) => {
const percent = event.lengthComputable ? (event.loaded / event.total) * 100 : -1
context.channels.progress.publish({
stage,
percent,
total: event.total,
loaded: event.loaded,
lengthComputable: event.lengthComputable,
})
}
}
},
} satisfies Middleware
}

View File

@@ -0,0 +1,47 @@
import type {Middleware} from 'get-it'
import {type Progress, progressStream} from '../../util/progress-stream'
function normalizer(stage: 'download' | 'upload') {
return (prog: Pick<Progress, 'percentage' | 'length' | 'transferred'>) => ({
stage,
percent: prog.percentage,
total: prog.length,
loaded: prog.transferred,
lengthComputable: !(prog.length === 0 && prog.percentage === 0),
})
}
/** @public */
export function progress() {
let didEmitUpload = false
const onDownload = normalizer('download')
const onUpload = normalizer('upload')
return {
onHeaders: (response, evt) => {
const stream = progressStream({time: 32})
stream.on('progress', (prog) => evt.context.channels.progress.publish(onDownload(prog)))
return response.pipe(stream)
},
onRequest: (evt) => {
if (!evt.progress) {
return
}
evt.progress.on('progress', (prog: Progress) => {
didEmitUpload = true
evt.context.channels.progress.publish(onUpload(prog))
})
},
onResponse: (res, evt) => {
if (!didEmitUpload && typeof evt.options.body !== 'undefined') {
evt.channels.progress.publish(onUpload({length: 0, transferred: 0, percentage: 100}))
}
return res
},
} satisfies Middleware
}

104
server/node_modules/get-it/src/middleware/promise.ts generated vendored Normal file
View File

@@ -0,0 +1,104 @@
import type {Middleware} from 'get-it'
/** @public */
export const promise = (
options: {onlyBody?: boolean; implementation?: PromiseConstructor} = {},
) => {
const PromiseImplementation = options.implementation || Promise
if (!PromiseImplementation) {
throw new Error('`Promise` is not available in global scope, and no implementation was passed')
}
return {
onReturn: (channels, context) =>
new PromiseImplementation((resolve, reject) => {
const cancel = context.options.cancelToken
if (cancel) {
cancel.promise.then((reason: any) => {
channels.abort.publish(reason)
reject(reason)
})
}
channels.error.subscribe(reject)
channels.response.subscribe((response) => {
resolve(options.onlyBody ? (response as any).body : response)
})
// Wait until next tick in case cancel has been performed
setTimeout(() => {
try {
channels.request.publish(context)
} catch (err) {
reject(err)
}
}, 0)
}),
} satisfies Middleware
}
/**
* The cancel token API is based on the [cancelable promises proposal](https://github.com/tc39/proposal-cancelable-promises), which is currently at Stage 1.
*
* Code shamelessly stolen/borrowed from MIT-licensed [axios](https://github.com/mzabriskie/axios). Thanks to [Nick Uraltsev](https://github.com/nickuraltsev), [Matt Zabriskie](https://github.com/mzabriskie) and the other contributors of that project!
*/
/** @public */
export class Cancel {
__CANCEL__ = true
message: string | undefined
constructor(message: string | undefined) {
this.message = message
}
toString() {
return `Cancel${this.message ? `: ${this.message}` : ''}`
}
}
/** @public */
export class CancelToken {
promise: Promise<any>
reason?: Cancel
constructor(executor: (cb: (message?: string) => void) => void) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.')
}
let resolvePromise: any = null
this.promise = new Promise((resolve) => {
resolvePromise = resolve
})
executor((message?: string) => {
if (this.reason) {
// Cancellation has already been requested
return
}
this.reason = new Cancel(message)
resolvePromise(this.reason)
})
}
static source = () => {
let cancel: (message?: string) => void
const token = new CancelToken((can) => {
cancel = can
})
return {
token: token,
cancel: cancel!,
}
}
}
const isCancel = (value: any): value is Cancel => !!(value && value?.__CANCEL__)
promise.Cancel = Cancel
promise.CancelToken = CancelToken
promise.isCancel = isCancel

12
server/node_modules/get-it/src/middleware/proxy.ts generated vendored Normal file
View File

@@ -0,0 +1,12 @@
import type {Middleware} from 'get-it'
/** @public */
export function proxy(_proxy: any) {
if (_proxy !== false && (!_proxy || !_proxy.host)) {
throw new Error('Proxy middleware takes an object of host, port and auth properties')
}
return {
processOptions: (options) => Object.assign({proxy: _proxy}, options),
} satisfies Middleware
}

View File

@@ -0,0 +1,10 @@
import type {RetryOptions} from 'get-it'
import defaultShouldRetry from '../../util/browser-shouldRetry'
import sharedRetry from './shared-retry'
/** @public */
export const retry = (opts: Partial<RetryOptions> = {}) =>
sharedRetry({shouldRetry: defaultShouldRetry, ...opts})
retry.shouldRetry = defaultShouldRetry

View File

@@ -0,0 +1,10 @@
import type {RetryOptions} from 'get-it'
import defaultShouldRetry from '../../util/node-shouldRetry'
import sharedRetry from './shared-retry'
/** @public */
export const retry = (opts: Partial<RetryOptions> = {}) =>
sharedRetry({shouldRetry: defaultShouldRetry, ...opts})
retry.shouldRetry = defaultShouldRetry

View File

@@ -0,0 +1,46 @@
import type {Middleware, RetryOptions} from 'get-it'
const isStream = (stream: any) =>
stream !== null && typeof stream === 'object' && typeof stream.pipe === 'function'
/** @public */
export default (opts: RetryOptions) => {
const maxRetries = opts.maxRetries || 5
const retryDelay = opts.retryDelay || getRetryDelay
const allowRetry = opts.shouldRetry
return {
onError: (err, context) => {
const options = context.options
const max = options.maxRetries || maxRetries
const delay = options.retryDelay || retryDelay
const shouldRetry = options.shouldRetry || allowRetry
const attemptNumber = options.attemptNumber || 0
// We can't retry if body is a stream, since it'll be drained
if (isStream(options.body)) {
return err
}
// Give up?
if (!shouldRetry(err, attemptNumber, options) || attemptNumber >= max) {
return err
}
// Create a new context with an increased attempt number, so we can exit if we reach a limit
const newContext = Object.assign({}, context, {
options: Object.assign({}, options, {attemptNumber: attemptNumber + 1}),
})
// Wait a given amount of time before doing the request again
setTimeout(() => context.channels.request.publish(newContext), delay(attemptNumber))
// Signal that we've handled the error and that it should not propagate further
return null
},
} satisfies Middleware
}
function getRetryDelay(attemptNum: number) {
return 100 * Math.pow(2, attemptNum) + Math.random() * 100
}

View File

@@ -0,0 +1,61 @@
import type {Middleware} from 'get-it'
import {isBuffer} from '../util/isBuffer'
import {isPlainObject} from '../util/isPlainObject'
function encode(data: Record<string, string | Set<number | string>>): string {
const query = new URLSearchParams()
const nest = (name: string, _value: unknown) => {
const value = _value instanceof Set ? Array.from(_value) : _value
if (Array.isArray(value)) {
if (value.length) {
for (const index in value) {
nest(`${name}[${index}]`, value[index])
}
} else {
query.append(`${name}[]`, '')
}
} else if (typeof value === 'object' && value !== null) {
for (const [key, obj] of Object.entries(value)) {
nest(`${name}[${key}]`, obj)
}
} else {
query.append(name, value as string)
}
}
for (const [key, value] of Object.entries(data)) {
nest(key, value)
}
return query.toString()
}
/** @public */
export function urlEncoded() {
return {
processOptions: (options) => {
const body = options.body
if (!body) {
return options
}
const isStream = typeof body.pipe === 'function'
const shouldSerialize = !isStream && !isBuffer(body) && isPlainObject(body)
if (!shouldSerialize) {
return options
}
return {
...options,
body: encode(options.body),
headers: {
...options.headers,
'Content-Type': 'application/x-www-form-urlencoded',
},
}
},
} satisfies Middleware
}

View File

@@ -0,0 +1,226 @@
import type {HttpRequest, MiddlewareResponse, RequestOptions} from 'get-it'
import parseHeaders from 'parse-headers'
import {FetchXhr} from './browser/fetchXhr'
/**
* Use fetch if it's available, non-browser environments such as Deno, Edge Runtime and more provide fetch as a global but doesn't provide xhr
* @public
*/
export const adapter = (
typeof XMLHttpRequest === 'function' ? ('xhr' as const) : ('fetch' as const)
) satisfies import('../types').RequestAdapter
// Fallback to fetch-based XHR polyfill for non-browser environments like Workers
const XmlHttpRequest = adapter === 'xhr' ? XMLHttpRequest : FetchXhr
export const httpRequester: HttpRequest = (context, callback) => {
const opts = context.options
const options = context.applyMiddleware('finalizeOptions', opts) as RequestOptions
const timers: any = {}
// Allow middleware to inject a response, for instance in the case of caching or mocking
const injectedResponse = context.applyMiddleware('interceptRequest', undefined, {
adapter,
context,
})
// If middleware injected a response, treat it as we normally would and return it
// Do note that the injected response has to be reduced to a cross-environment friendly response
if (injectedResponse) {
const cbTimer = setTimeout(callback, 0, null, injectedResponse)
const cancel = () => clearTimeout(cbTimer)
return {abort: cancel}
}
// We'll want to null out the request on success/failure
let xhr = new XmlHttpRequest()
if (xhr instanceof FetchXhr && typeof options.fetch === 'object') {
xhr.setInit(options.fetch, options.useAbortSignal ?? true)
}
const headers = options.headers
const delays = options.timeout
// Request state
let aborted = false
let loaded = false
let timedOut = false
// Apply event handlers
xhr.onerror = (event: ProgressEvent) => {
// If fetch is used then rethrow the original error
if (xhr instanceof FetchXhr) {
onError(
event instanceof Error
? event
: new Error(`Request error while attempting to reach is ${options.url}`, {cause: event}),
)
} else {
onError(
new Error(
`Request error while attempting to reach is ${options.url}${
event.lengthComputable ? `(${event.loaded} of ${event.total} bytes transferred)` : ''
}`,
),
)
}
}
xhr.ontimeout = (event: ProgressEvent) => {
onError(
new Error(
`Request timeout while attempting to reach ${options.url}${
event.lengthComputable ? `(${event.loaded} of ${event.total} bytes transferred)` : ''
}`,
),
)
}
xhr.onabort = () => {
stopTimers(true)
aborted = true
}
xhr.onreadystatechange = function () {
// Prevent request from timing out
resetTimers()
if (aborted || !xhr || xhr.readyState !== 4) {
return
}
// Will be handled by onError
if (xhr.status === 0) {
return
}
onLoad()
}
// @todo two last options to open() is username/password
xhr.open(
options.method!,
options.url,
true, // Always async
)
// Some options need to be applied after open
xhr.withCredentials = !!options.withCredentials
// Set headers
if (headers && xhr.setRequestHeader) {
for (const key in headers) {
// eslint-disable-next-line no-prototype-builtins
if (headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, headers[key])
}
}
}
if (options.rawBody) {
xhr.responseType = 'arraybuffer'
}
// Let middleware know we're about to do a request
context.applyMiddleware('onRequest', {options, adapter, request: xhr, context})
xhr.send(options.body || null)
// Figure out which timeouts to use (if any)
if (delays) {
timers.connect = setTimeout(() => timeoutRequest('ETIMEDOUT'), delays.connect)
}
return {abort}
function abort() {
aborted = true
if (xhr) {
xhr.abort()
}
}
function timeoutRequest(code: any) {
timedOut = true
xhr.abort()
const error: any = new Error(
code === 'ESOCKETTIMEDOUT'
? `Socket timed out on request to ${options.url}`
: `Connection timed out on request to ${options.url}`,
)
error.code = code
context.channels.error.publish(error)
}
function resetTimers() {
if (!delays) {
return
}
stopTimers()
timers.socket = setTimeout(() => timeoutRequest('ESOCKETTIMEDOUT'), delays.socket)
}
function stopTimers(force?: boolean) {
// Only clear the connect timeout if we've got a connection
if (force || aborted || (xhr && xhr.readyState >= 2 && timers.connect)) {
clearTimeout(timers.connect)
}
if (timers.socket) {
clearTimeout(timers.socket)
}
}
function onError(error: Error) {
if (loaded) {
return
}
// Clean up
stopTimers(true)
loaded = true
;(xhr as any) = null
// Annoyingly, details are extremely scarce and hidden from us.
// We only really know that it is a network error
const err = (error ||
new Error(`Network error while attempting to reach ${options.url}`)) as Error & {
isNetworkError: boolean
request?: typeof options
}
err.isNetworkError = true
err.request = options
callback(err)
}
function reduceResponse(): MiddlewareResponse {
return {
body:
xhr.response ||
(xhr.responseType === '' || xhr.responseType === 'text' ? xhr.responseText : ''),
url: options.url,
method: options.method!,
headers: parseHeaders(xhr.getAllResponseHeaders()),
statusCode: xhr.status!,
statusMessage: xhr.statusText!,
}
}
function onLoad() {
if (aborted || loaded || timedOut) {
return
}
if (xhr.status === 0) {
onError(new Error('Unknown XHR error'))
return
}
// Prevent being called twice
stopTimers()
loaded = true
callback(null, reduceResponse())
}
}

View File

@@ -0,0 +1,114 @@
/**
* Mimicks the XMLHttpRequest API with only the parts needed for get-it's XHR adapter
*/
export class FetchXhr
implements Pick<XMLHttpRequest, 'open' | 'abort' | 'getAllResponseHeaders' | 'setRequestHeader'>
{
/**
* Public interface, interop with real XMLHttpRequest
*/
onabort: (() => void) | undefined
onerror: ((error?: any) => void) | undefined
onreadystatechange: (() => void) | undefined
ontimeout: XMLHttpRequest['ontimeout'] | undefined
/**
* https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
*/
readyState: 0 | 1 | 2 | 3 | 4 = 0
response: XMLHttpRequest['response']
responseText: XMLHttpRequest['responseText'] = ''
responseType: XMLHttpRequest['responseType'] = ''
status: XMLHttpRequest['status'] | undefined
statusText: XMLHttpRequest['statusText'] | undefined
withCredentials: XMLHttpRequest['withCredentials'] | undefined
/**
* Private implementation details
*/
#method!: string
#url!: string
#resHeaders!: string
#headers: Record<string, string> = {}
#controller?: AbortController
#init: RequestInit = {}
#useAbortSignal?: boolean
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- _async is only declared for typings compatibility
open(method: string, url: string, _async?: boolean) {
this.#method = method
this.#url = url
this.#resHeaders = ''
this.readyState = 1 // Open
this.onreadystatechange?.()
this.#controller = undefined
}
abort() {
if (this.#controller) {
this.#controller.abort()
}
}
getAllResponseHeaders() {
return this.#resHeaders
}
setRequestHeader(name: string, value: string) {
this.#headers[name] = value
}
// Allow setting extra fetch init options, needed for runtimes such as Vercel Edge to set `cache` and other options in React Server Components
setInit(init: RequestInit, useAbortSignal = true) {
this.#init = init
this.#useAbortSignal = useAbortSignal
}
send(body: BodyInit) {
const textBody = this.responseType !== 'arraybuffer'
const options: RequestInit = {
...this.#init,
method: this.#method,
headers: this.#headers,
body,
}
if (typeof AbortController === 'function' && this.#useAbortSignal) {
this.#controller = new AbortController()
// The instanceof check ensures environments like Edge Runtime, Node 18 with built-in fetch
// and more don't throw if `signal` doesn't implement`EventTarget`
// Native browser AbortSignal implements EventTarget, so we can use it
if (typeof EventTarget !== 'undefined' && this.#controller.signal instanceof EventTarget) {
options.signal = this.#controller.signal
}
}
// Some environments (like CloudFlare workers) don't support credentials in
// RequestInitDict, and there doesn't seem to be any easy way to check for it,
// so for now let's just make do with a document check :/
if (typeof document !== 'undefined') {
options.credentials = this.withCredentials ? 'include' : 'omit'
}
fetch(this.#url, options)
.then((res): Promise<string | ArrayBuffer> => {
res.headers.forEach((value: any, key: any) => {
this.#resHeaders += `${key}: ${value}\r\n`
})
this.status = res.status
this.statusText = res.statusText
this.readyState = 3 // Loading
this.onreadystatechange?.()
return textBody ? res.text() : res.arrayBuffer()
})
.then((resBody) => {
if (typeof resBody === 'string') {
this.responseText = resBody
} else {
this.response = resBody
}
this.readyState = 4 // Done
this.onreadystatechange?.()
})
.catch((err: Error) => {
if (err.name === 'AbortError') {
this.onabort?.()
return
}
this.onerror?.(err)
})
}
}

339
server/node_modules/get-it/src/request/node-request.ts generated vendored Normal file
View File

@@ -0,0 +1,339 @@
import decompressResponse from 'decompress-response'
import follow, {type FollowResponse, type RedirectableRequest} from 'follow-redirects'
import type {FinalizeNodeOptionsPayload, HttpRequest, MiddlewareResponse} from 'get-it'
import http from 'http'
import https from 'https'
import qs from 'querystring'
import {Readable, type Stream} from 'stream'
import url from 'url'
import {lowerCaseHeaders} from '../util/lowerCaseHeaders'
import {progressStream} from '../util/progress-stream'
import {getProxyOptions, rewriteUriForProxy} from './node/proxy'
import {concat} from './node/simpleConcat'
import {timedOut} from './node/timedOut'
import * as tunneling from './node/tunnel'
/**
* Taken from:
* https://github.com/sindresorhus/is-stream/blob/fb8caed475b4107cee3c22be3252a904020eb2d4/index.js#L3-L6
*/
const isStream = (stream: any): stream is Stream =>
stream !== null && typeof stream === 'object' && typeof stream.pipe === 'function'
/** @public */
export const adapter: import('../types').RequestAdapter = 'node'
export class NodeRequestError extends Error {
request: http.ClientRequest
code?: string | undefined
constructor(err: NodeJS.ErrnoException, req: any) {
super(err.message)
this.request = req
this.code = err.code
}
}
// Reduce a fully fledged node-style response object to
// something that works in both browser and node environment
const reduceResponse = (
res: any,
reqUrl: string,
method: string,
body: any,
): MiddlewareResponse => ({
body,
url: reqUrl,
method: method,
headers: res.headers,
statusCode: res.statusCode,
statusMessage: res.statusMessage,
})
export const httpRequester: HttpRequest = (context, cb) => {
const {options} = context
const uri = Object.assign({}, url.parse(options.url))
if (typeof fetch === 'function' && options.fetch) {
const controller = new AbortController()
const reqOpts = context.applyMiddleware('finalizeOptions', {
...uri,
method: options.method,
headers: {
...(typeof options.fetch === 'object' && options.fetch.headers
? lowerCaseHeaders(options.fetch.headers)
: {}),
...lowerCaseHeaders(options.headers),
},
maxRedirects: options.maxRedirects,
}) as FinalizeNodeOptionsPayload
const fetchOpts = {
credentials: options.withCredentials ? 'include' : 'omit',
...(typeof options.fetch === 'object' ? options.fetch : {}),
method: reqOpts.method,
headers: reqOpts.headers,
body: options.body,
signal: controller.signal,
} satisfies RequestInit
// Allow middleware to inject a response, for instance in the case of caching or mocking
const injectedResponse = context.applyMiddleware('interceptRequest', undefined, {
adapter,
context,
})
// If middleware injected a response, treat it as we normally would and return it
// Do note that the injected response has to be reduced to a cross-environment friendly response
if (injectedResponse) {
const cbTimer = setTimeout(cb, 0, null, injectedResponse)
const cancel = () => clearTimeout(cbTimer)
return {abort: cancel}
}
const request = fetch(options.url, fetchOpts)
// Let middleware know we're about to do a request
context.applyMiddleware('onRequest', {options, adapter, request, context})
request
.then(async (res) => {
const body = options.rawBody ? res.body : await res.text()
const headers = {} as Record<string, string>
res.headers.forEach((value, key) => {
headers[key] = value
})
cb(null, {
body,
url: res.url,
method: options.method!,
headers,
statusCode: res.status,
statusMessage: res.statusText,
})
})
.catch((err) => {
if (err.name == 'AbortError') return
cb(err)
})
return {abort: () => controller.abort()}
}
const bodyType = isStream(options.body) ? 'stream' : typeof options.body
if (
bodyType !== 'undefined' &&
bodyType !== 'stream' &&
bodyType !== 'string' &&
!Buffer.isBuffer(options.body)
) {
throw new Error(`Request body must be a string, buffer or stream, got ${bodyType}`)
}
const lengthHeader: any = {}
if (options.bodySize) {
lengthHeader['content-length'] = options.bodySize
} else if (options.body && bodyType !== 'stream') {
lengthHeader['content-length'] = Buffer.byteLength(options.body)
}
// Make sure callback is not called in the event of a cancellation
let aborted = false
const callback = (err: Error | null, res?: MiddlewareResponse) => !aborted && cb(err, res)
context.channels.abort.subscribe(() => {
aborted = true
})
// Create a reduced subset of options meant for the http.request() method
let reqOpts: any = Object.assign({}, uri, {
method: options.method,
headers: Object.assign({}, lowerCaseHeaders(options.headers), lengthHeader),
maxRedirects: options.maxRedirects,
})
// Figure out proxying/tunnel options
const proxy = getProxyOptions(options)
const tunnel = proxy && tunneling.shouldEnable(options)
// Allow middleware to inject a response, for instance in the case of caching or mocking
const injectedResponse = context.applyMiddleware('interceptRequest', undefined, {
adapter,
context,
})
// If middleware injected a response, treat it as we normally would and return it
// Do note that the injected response has to be reduced to a cross-environment friendly response
if (injectedResponse) {
const cbTimer = setImmediate(callback, null, injectedResponse)
const abort = () => clearImmediate(cbTimer)
return {abort}
}
// We're using the follow-redirects module to transparently follow redirects
if (options.maxRedirects !== 0) {
reqOpts.maxRedirects = options.maxRedirects || 5
}
// Apply currect options for proxy tunneling, if enabled
if (proxy && tunnel) {
reqOpts = tunneling.applyAgent(reqOpts, proxy)
} else if (proxy && !tunnel) {
reqOpts = rewriteUriForProxy(reqOpts, uri, proxy)
}
// Handle proxy authorization if present
if (!tunnel && proxy && proxy.auth && !reqOpts.headers['proxy-authorization']) {
const [username, password] = proxy.auth.username
? [proxy.auth.username, proxy.auth.password]
: proxy.auth.split(':').map((item: any) => qs.unescape(item))
const auth = Buffer.from(`${username}:${password}`, 'utf8')
const authBase64 = auth.toString('base64')
reqOpts.headers['proxy-authorization'] = `Basic ${authBase64}`
}
// Figure out transport (http/https, forwarding/non-forwarding agent)
const transport = getRequestTransport(reqOpts, proxy, tunnel)
if (typeof options.debug === 'function' && proxy) {
options.debug(
'Proxying using %s',
reqOpts.agent ? 'tunnel agent' : `${reqOpts.host}:${reqOpts.port}`,
)
}
// See if we should try to request a compressed response (and decompress on return)
const tryCompressed = reqOpts.method !== 'HEAD'
if (tryCompressed && !reqOpts.headers['accept-encoding'] && options.compress !== false) {
reqOpts.headers['accept-encoding'] =
// Workaround Bun not supporting brotli: https://github.com/oven-sh/bun/issues/267
typeof Bun !== 'undefined' ? 'gzip, deflate' : 'br, gzip, deflate'
}
let _res: http.IncomingMessage | undefined
const finalOptions = context.applyMiddleware(
'finalizeOptions',
reqOpts,
) as FinalizeNodeOptionsPayload
const request = transport.request(finalOptions, (response) => {
const res = tryCompressed ? decompressResponse(response) : response
_res = res
const resStream = context.applyMiddleware('onHeaders', res, {
headers: response.headers,
adapter,
context,
})
// On redirects, `responseUrl` is set
const reqUrl = 'responseUrl' in response ? response.responseUrl : options.url
if (options.stream) {
callback(null, reduceResponse(res, reqUrl, reqOpts.method, resStream))
return
}
// Concatenate the response body, then parse the response with middlewares
concat(resStream, (err: any, data: any) => {
if (err) {
return callback(err)
}
const body = options.rawBody ? data : data.toString()
const reduced = reduceResponse(res, reqUrl, reqOpts.method, body)
return callback(null, reduced)
})
})
function onError(err: NodeJS.ErrnoException) {
// HACK: If we have a socket error, and response has already been assigned this means
// that a response has already been sent. According to node.js docs, this is
// will result in the response erroring with an error code of 'ECONNRESET'.
// We first destroy the response, then the request, with the same error. This way the
// error is forwarded to both the response and the request.
// See the event order outlined here https://nodejs.org/api/http.html#httprequesturl-options-callback for how node.js handles the different scenarios.
if (_res) _res.destroy(err)
request.destroy(err)
}
request.once('socket', (socket: NodeJS.Socket) => {
socket.once('error', onError)
request.once('response', (response) => {
response.once('end', () => {
socket.removeListener('error', onError)
})
})
})
request.once('error', (err: NodeJS.ErrnoException) => {
if (_res) return
// The callback has already been invoked. Any error should be sent to the response.
callback(new NodeRequestError(err, request))
})
if (options.timeout) {
timedOut(request, options.timeout)
}
// Cheating a bit here; since we're not concerned about the "bundle size" in node,
// and modifying the body stream would be sorta tricky, we're just always going
// to put a progress stream in the middle here.
const {bodyStream, progress} = getProgressStream(options)
// Let middleware know we're about to do a request
context.applyMiddleware('onRequest', {options, adapter, request, context, progress})
if (bodyStream) {
bodyStream.pipe(request)
} else {
request.end(options.body)
}
return {abort: () => request.abort()}
}
function getProgressStream(options: any) {
if (!options.body) {
return {}
}
const bodyIsStream = isStream(options.body)
const length = options.bodySize || (bodyIsStream ? null : Buffer.byteLength(options.body))
if (!length) {
return bodyIsStream ? {bodyStream: options.body} : {}
}
const progress = progressStream({time: 32, length})
const bodyStream = bodyIsStream ? options.body : Readable.from(options.body)
return {bodyStream: bodyStream.pipe(progress), progress}
}
function getRequestTransport(
reqOpts: any,
proxy: any,
tunnel: any,
): {
request: (
options: any,
callback: (response: http.IncomingMessage | (http.IncomingMessage & FollowResponse)) => void,
) => http.ClientRequest | RedirectableRequest<http.ClientRequest, http.IncomingMessage>
} {
const isHttpsRequest = reqOpts.protocol === 'https:'
const transports =
reqOpts.maxRedirects === 0
? {http: http, https: https}
: {http: follow.http, https: follow.https}
if (!proxy || tunnel) {
return isHttpsRequest ? transports.https : transports.http
}
// Assume the proxy is an HTTPS proxy if port is 443, or if there is a
// `protocol` option set that starts with https
let isHttpsProxy = proxy.port === 443
if (proxy.protocol) {
isHttpsProxy = /^https:?/.test(proxy.protocol)
}
return isHttpsProxy ? transports.https : transports.http
}

124
server/node_modules/get-it/src/request/node/proxy.ts generated vendored Normal file
View File

@@ -0,0 +1,124 @@
/**
* Code borrowed from https://github.com/request/request
* Apache License 2.0
*/
import url from 'url'
function formatHostname(hostname: string) {
// canonicalize the hostname, so that 'oogle.com' won't match 'google.com'
return hostname.replace(/^\.*/, '.').toLowerCase()
}
function parseNoProxyZone(zoneStr: string) {
const zone = zoneStr.trim().toLowerCase()
const zoneParts = zone.split(':', 2)
const zoneHost = formatHostname(zoneParts[0])
const zonePort = zoneParts[1]
const hasPort = zone.indexOf(':') > -1
return {hostname: zoneHost, port: zonePort, hasPort: hasPort}
}
function uriInNoProxy(uri: any, noProxy: any) {
const port = uri.port || (uri.protocol === 'https:' ? '443' : '80')
const hostname = formatHostname(uri.hostname)
const noProxyList = noProxy.split(',')
// iterate through the noProxyList until it finds a match.
return noProxyList.map(parseNoProxyZone).some((noProxyZone: any) => {
const isMatchedAt = hostname.indexOf(noProxyZone.hostname)
const hostnameMatched =
isMatchedAt > -1 && isMatchedAt === hostname.length - noProxyZone.hostname.length
if (noProxyZone.hasPort) {
return port === noProxyZone.port && hostnameMatched
}
return hostnameMatched
})
}
function getProxyFromUri(uri: any) {
// Decide the proper request proxy to use based on the request URI object and the
// environmental variables (NO_PROXY, HTTP_PROXY, etc.)
// respect NO_PROXY environment variables (see: http://lynx.isc.org/current/breakout/lynx_help/keystrokes/environments.html)
const noProxy = process.env['NO_PROXY'] || process.env['no_proxy'] || ''
// if the noProxy is a wildcard then return null
if (noProxy === '*') {
return null
}
// if the noProxy is not empty and the uri is found return null
if (noProxy !== '' && uriInNoProxy(uri, noProxy)) {
return null
}
// Check for HTTP or HTTPS Proxy in environment, else default to null
if (uri.protocol === 'http:') {
return process.env['HTTP_PROXY'] || process.env['http_proxy'] || null
}
if (uri.protocol === 'https:') {
return (
process.env['HTTPS_PROXY'] ||
process.env['https_proxy'] ||
process.env['HTTP_PROXY'] ||
process.env['http_proxy'] ||
null
)
}
// if none of that works, return null
// (What uri protocol are you using then?)
return null
}
function getHostFromUri(uri: any) {
let host = uri.host
// Drop :port suffix from Host header if known protocol.
if (uri.port) {
if (
(uri.port === '80' && uri.protocol === 'http:') ||
(uri.port === '443' && uri.protocol === 'https:')
) {
host = uri.hostname
}
}
return host
}
function getHostHeaderWithPort(uri: any) {
const port = uri.port || (uri.protocol === 'https:' ? '443' : '80')
return `${uri.hostname}:${port}`
}
export function rewriteUriForProxy(reqOpts: any, uri: any, proxy: any) {
const headers = reqOpts.headers || {}
const options = Object.assign({}, reqOpts, {headers})
headers.host = headers.host || getHostHeaderWithPort(uri)
options.protocol = proxy.protocol || options.protocol
options.hostname = proxy.host.replace(/:\d+/, '')
options.port = proxy.port
options.host = getHostFromUri(Object.assign({}, uri, proxy))
options.href = `${options.protocol}//${options.host}${options.path}`
options.path = url.format(uri)
return options
}
export function getProxyOptions(options: any) {
let proxy
// eslint-disable-next-line no-prototype-builtins
if (options.hasOwnProperty('proxy')) {
proxy = options.proxy
} else {
const uri = url.parse(options.url)
proxy = getProxyFromUri(uri)
}
return typeof proxy === 'string' ? url.parse(proxy) : proxy
}

View File

@@ -0,0 +1,15 @@
/*! simple-concat. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
export function concat(stream: any, cb: any) {
const chunks: any = []
stream.on('data', function (chunk: any) {
chunks.push(chunk)
})
stream.once('end', function () {
if (cb) cb(null, Buffer.concat(chunks))
cb = null
})
stream.once('error', function (err: any) {
if (cb) cb(err)
cb = null
})
}

View File

@@ -0,0 +1,62 @@
// Copied from `@sanity/timed-out`
import type {IncomingMessage} from 'node:http'
import type {Socket} from 'node:net'
export function timedOut(req: any, time: any) {
if (req.timeoutTimer) {
return req
}
const delays = isNaN(time) ? time : {socket: time, connect: time}
const hostHeader = req.getHeader('host')
const host = hostHeader ? ' to ' + hostHeader : ''
if (delays.connect !== undefined) {
req.timeoutTimer = setTimeout(function timeoutHandler() {
const e: NodeJS.ErrnoException = new Error('Connection timed out on request' + host)
e.code = 'ETIMEDOUT'
req.destroy(e)
}, delays.connect)
}
// Clear the connection timeout timer once a socket is assigned to the
// request and is connected.
req.on('socket', function assign(socket: Socket) {
// Socket may come from Agent pool and may be already connected.
if (!socket.connecting) {
connect(socket)
return
}
socket.once('connect', () => connect(socket))
})
function clear() {
if (req.timeoutTimer) {
clearTimeout(req.timeoutTimer)
req.timeoutTimer = null
}
}
function connect(socket: Socket) {
clear()
if (delays.socket !== undefined) {
const socketTimeoutHandler = () => {
const e: NodeJS.ErrnoException = new Error('Socket timed out on request' + host)
e.code = 'ESOCKETTIMEDOUT'
socket.destroy(e)
}
socket.setTimeout(delays.socket, socketTimeoutHandler)
req.once('response', (response: IncomingMessage) => {
response.once('end', () => {
socket.removeListener('timeout', socketTimeoutHandler)
})
})
}
}
return req.on('error', clear)
}

166
server/node_modules/get-it/src/request/node/tunnel.ts generated vendored Normal file
View File

@@ -0,0 +1,166 @@
/**
* Code borrowed from https://github.com/request/request
* Modified to be less request-specific, more functional
* Apache License 2.0
*/
import * as tunnel from 'tunnel-agent'
import url from 'url'
const uriParts = [
'protocol',
'slashes',
'auth',
'host',
'port',
'hostname',
'hash',
'search',
'query',
'pathname',
'path',
'href',
]
const defaultProxyHeaderWhiteList = [
'accept',
'accept-charset',
'accept-encoding',
'accept-language',
'accept-ranges',
'cache-control',
'content-encoding',
'content-language',
'content-location',
'content-md5',
'content-range',
'content-type',
'connection',
'date',
'expect',
'max-forwards',
'pragma',
'referer',
'te',
'user-agent',
'via',
]
const defaultProxyHeaderExclusiveList = ['proxy-authorization']
export function shouldEnable(options: any) {
// Tunnel HTTPS by default. Allow the user to override this setting.
// If user has specified a specific tunnel override...
if (typeof options.tunnel !== 'undefined') {
return Boolean(options.tunnel)
}
// If the destination is HTTPS, tunnel.
const uri = url.parse(options.url)
if (uri.protocol === 'https:') {
return true
}
// Otherwise, do not use tunnel.
return false
}
export function applyAgent(opts: any = {}, proxy: any) {
const options = Object.assign({}, opts)
// Setup proxy header exclusive list and whitelist
const proxyHeaderWhiteList = defaultProxyHeaderWhiteList
.concat(options.proxyHeaderWhiteList || [])
.map((header) => header.toLowerCase())
const proxyHeaderExclusiveList = defaultProxyHeaderExclusiveList
.concat(options.proxyHeaderExclusiveList || [])
.map((header) => header.toLowerCase())
// Get the headers we should send to the proxy
const proxyHeaders = getAllowedProxyHeaders(options.headers, proxyHeaderWhiteList)
proxyHeaders.host = constructProxyHost(options)
// Reduce headers to the ones not exclusive for the proxy
options.headers = Object.keys(options.headers || {}).reduce((headers, header) => {
const isAllowed = proxyHeaderExclusiveList.indexOf(header.toLowerCase()) === -1
if (isAllowed) {
headers[header] = options.headers[header]
}
return headers
}, {} as any)
const tunnelFn = getTunnelFn(options, proxy)
const tunnelOptions = constructTunnelOptions(options, proxy, proxyHeaders)
options.agent = tunnelFn(tunnelOptions)
return options
}
function getTunnelFn(options: any, proxy: any) {
const uri = getUriParts(options)
const tunnelFnName = constructTunnelFnName(uri, proxy)
return tunnel[tunnelFnName]
}
function getUriParts(options: any) {
return uriParts.reduce((uri, part) => {
uri[part] = options[part]
return uri
}, {} as any)
}
type UriProtocol = `http` | `https`
type ProxyProtocol = `Http` | `Https`
function constructTunnelFnName(uri: any, proxy: any): `${UriProtocol}Over${ProxyProtocol}` {
const uriProtocol = uri.protocol === 'https:' ? 'https' : 'http'
const proxyProtocol = proxy.protocol === 'https:' ? 'Https' : 'Http'
return `${uriProtocol}Over${proxyProtocol}`
}
function constructProxyHost(uri: any) {
const port = uri.port
const protocol = uri.protocol
let proxyHost = `${uri.hostname}:`
if (port) {
proxyHost += port
} else if (protocol === 'https:') {
proxyHost += '443'
} else {
proxyHost += '80'
}
return proxyHost
}
function getAllowedProxyHeaders(headers: any, whiteList: any): any {
return Object.keys(headers)
.filter((header) => whiteList.indexOf(header.toLowerCase()) !== -1)
.reduce((set: any, header: any) => {
set[header] = headers[header]
return set
}, {})
}
function constructTunnelOptions(options: any, proxy: any, proxyHeaders: any) {
return {
proxy: {
host: proxy.hostname,
port: +proxy.port,
proxyAuth: proxy.auth,
headers: proxyHeaders,
},
headers: options.headers,
ca: options.ca,
cert: options.cert,
key: options.key,
passphrase: options.passphrase,
pfx: options.pfx,
ciphers: options.ciphers,
rejectUnauthorized: options.rejectUnauthorized,
secureOptions: options.secureOptions,
secureProtocol: options.secureProtocol,
}
}

200
server/node_modules/get-it/src/types.ts generated vendored Normal file
View File

@@ -0,0 +1,200 @@
import type {IncomingHttpHeaders, IncomingMessage} from 'http'
import type {UrlWithStringQuery} from 'url'
import type {ProgressStream} from './util/progress-stream'
/** @public */
export interface RequestOptions {
url: string
body?: any
bodySize?: number
cancelToken?: any
compress?: boolean
headers?: any
maxRedirects?: number
maxRetries?: number
retryDelay?: (attemptNumber: number) => number
method?: string
proxy?: any
query?: any
rawBody?: boolean
shouldRetry?: any
stream?: boolean
timeout?: any
tunnel?: boolean
debug?: any
requestId?: number
attemptNumber?: number
withCredentials?: boolean
/**
* Enables using the native `fetch` API instead of the default `http` module, and allows setting its options like `cache`
*/
fetch?: boolean | Omit<RequestInit, 'method'>
/**
* Some frameworks have special behavior for `fetch` when an `AbortSignal` is used, and may want to disable it unless userland specifically opts-in.
*/
useAbortSignal?: boolean
}
/** @public */
export interface Subscriber<Event> {
(event: Event): void
}
/** @public */
export interface PubSub<Message> {
publish: (message: Message) => void
subscribe: (subscriber: Subscriber<Message>) => () => void
}
/** @public */
export interface MiddlewareChannels {
request: PubSub<HttpContext>
response: PubSub<unknown>
progress: PubSub<unknown>
error: PubSub<unknown>
abort: PubSub<void>
}
/** @public */
export interface FinalizeNodeOptionsPayload extends UrlWithStringQuery {
method: RequestOptions['method']
headers: RequestOptions['headers']
maxRedirects: RequestOptions['maxRedirects']
agent?: any
cert?: any
key?: any
ca?: any
}
/** @public */
export interface MiddlewareHooks {
processOptions: (options: RequestOptions) => RequestOptions
validateOptions: (options: RequestOptions) => void | undefined
interceptRequest: (
prevValue: MiddlewareResponse | undefined,
event: {adapter: RequestAdapter; context: HttpContext},
) => MiddlewareResponse | undefined | void
finalizeOptions: (
options: FinalizeNodeOptionsPayload | RequestOptions,
) => FinalizeNodeOptionsPayload | RequestOptions
onRequest: (evt: HookOnRequestEvent) => void
onResponse: (response: MiddlewareResponse, context: HttpContext) => MiddlewareResponse
onError: (err: Error | null, context: HttpContext) => any
onReturn: (channels: MiddlewareChannels, context: HttpContext) => any
onHeaders: (
response: IncomingMessage,
evt: {
headers: IncomingHttpHeaders
adapter: RequestAdapter
context: HttpContext
},
) => ProgressStream
}
/** @public */
export interface HookOnRequestEventBase {
options: RequestOptions
context: HttpContext
request: any
}
/** @public */
export interface HookOnRequestEventNode extends HookOnRequestEventBase {
adapter: 'node'
progress: any
}
/** @public */
export interface HookOnRequestEventBrowser extends HookOnRequestEventBase {
adapter: Omit<RequestAdapter, 'node'>
progress?: undefined
}
/** @public */
export type HookOnRequestEvent = HookOnRequestEventNode | HookOnRequestEventBrowser
/** @public */
export interface HttpContext {
options: RequestOptions
channels: MiddlewareChannels
applyMiddleware: ApplyMiddleware
}
/** @public */
export type MiddlewareReducer = {
[T in keyof MiddlewareHooks]: ((
...args: Parameters<MiddlewareHooks[T]>
) => ReturnType<MiddlewareHooks[T]>)[]
}
/** @public */
export type ApplyMiddleware = <T extends keyof MiddlewareHooks>(
hook: T,
value: MiddlewareHooks[T] extends (defaultValue: infer V, ...rest: any[]) => any ? V : never,
...args: MiddlewareHooks[T] extends (defaultValue: any, ...rest: infer P) => any ? P : never
) => ReturnType<MiddlewareHooks[T]>
/** @public */
export type DefineApplyMiddleware = (middleware: MiddlewareReducer) => ApplyMiddleware
/** @public */
export type MiddlewareHookName = keyof MiddlewareHooks
/** @public */
export type Middleware = Partial<MiddlewareHooks>
/** @public */
export type Middlewares = Middleware[]
/** @public */
export interface HttpRequestOngoing {
abort: () => void
}
/** @public */
export interface MiddlewareRequest {} // eslint-disable-line @typescript-eslint/no-empty-object-type
/** @public */
export interface MiddlewareResponse {
body: any
url: string
method: string
headers: any
statusCode: number
statusMessage: string
}
/**
* request-node in node, browser-request in browsers
* @public
*/
export type HttpRequest = (
context: HttpContext,
callback: (err: Error | null, response?: MiddlewareResponse) => void,
) => HttpRequestOngoing
/** @public */
export interface RetryOptions {
shouldRetry: (err: any, num: number, options: any) => boolean
maxRetries?: number
retryDelay?: (attemptNumber: number) => number
}
/**
* Reports the environment as either "node" or "browser", depending on what entry point was used to aid bundler debugging.
* If 'browser' is used, then the globally available `fetch` class is used. While `node` will always use either `node:https` or `node:http` depending on the protocol.
* @public
*/
export type ExportEnv = 'node' | 'react-server' | 'browser'
/**
* Reports the request adapter in use. `node` is only available if `ExportEnv` is also `node`.
* When `ExportEnv` is `browser` then the adapter can be either `xhr` or `fetch`.
* In the future `fetch` will be available in `node` as well.
* @public
*/
export type RequestAdapter = 'node' | 'xhr' | 'fetch'
/** @public */
export type Requester = {
use: (middleware: Middleware) => Requester
clone: () => Requester
(options: RequestOptions | string): any
}

View File

@@ -0,0 +1,7 @@
export default (err: any, _attempt: any, options: any) => {
if (options.method !== 'GET' && options.method !== 'HEAD') {
return false
}
return err.isNetworkError || false
}

13
server/node_modules/get-it/src/util/global.ts generated vendored Normal file
View File

@@ -0,0 +1,13 @@
let actualGlobal = {} as typeof globalThis
if (typeof globalThis !== 'undefined') {
actualGlobal = globalThis
} else if (typeof window !== 'undefined') {
actualGlobal = window
} else if (typeof global !== 'undefined') {
actualGlobal = global
} else if (typeof self !== 'undefined') {
actualGlobal = self
}
export default actualGlobal

View File

@@ -0,0 +1,5 @@
import type {RequestOptions} from 'get-it'
export function isBrowserOptions(options: unknown): options is RequestOptions {
return typeof options === 'object' && options !== null && !('protocol' in options)
}

2
server/node_modules/get-it/src/util/isBuffer.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export const isBuffer =
typeof Buffer === 'undefined' ? () => false : (obj: unknown) => Buffer.isBuffer(obj)

33
server/node_modules/get-it/src/util/isPlainObject.ts generated vendored Normal file
View File

@@ -0,0 +1,33 @@
/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/
function isObject(o: unknown): o is Record<string, unknown> {
return Object.prototype.toString.call(o) === '[object Object]'
}
export function isPlainObject(o: unknown): boolean {
if (isObject(o) === false) return false
// If has modified constructor
const ctor = o.constructor
if (ctor === undefined) return true
// If has modified prototype
const prot = ctor.prototype
if (isObject(prot) === false) return false
// If constructor does not have an Object-specific method
if (
// eslint-disable-next-line no-prototype-builtins
prot.hasOwnProperty('isPrototypeOf') === false
) {
return false
}
// Most likely a plain Object
return true
}

View File

@@ -0,0 +1,6 @@
export function lowerCaseHeaders(headers: any) {
return Object.keys(headers || {}).reduce((acc, header) => {
acc[header.toLowerCase()] = headers[header]
return acc
}, {} as any)
}

View File

@@ -0,0 +1,19 @@
import type {ApplyMiddleware, MiddlewareReducer} from 'get-it'
export const middlewareReducer = (middleware: MiddlewareReducer) =>
function applyMiddleware(hook, defaultValue, ...args) {
const bailEarly = hook === 'onError'
let value = defaultValue
for (let i = 0; i < middleware[hook].length; i++) {
const handler = middleware[hook][i]
// @ts-expect-error -- find a better way to deal with argument tuples
value = handler(value, ...args)
if (bailEarly && !value) {
break
}
}
return value
} as ApplyMiddleware

View File

@@ -0,0 +1,14 @@
import allowed from 'is-retry-allowed'
export default (err: any, _num: number, options: any) => {
if (options.method !== 'GET' && options.method !== 'HEAD') {
return false
}
// Don't allow retries if we get any http status code by default
if (err.response && err.response.statusCode) {
return false
}
return allowed(err)
}

131
server/node_modules/get-it/src/util/progress-stream.ts generated vendored Normal file
View File

@@ -0,0 +1,131 @@
/**
* Inlined, reduced variant of npm `progress-stream` (https://github.com/freeall/progress-stream),
* that fixes a bug with `content-length` header. BSD 2-Clause Simplified License,
* Copyright (c) Tobias Baunbæk <freeall@gmail.com>.
*/
import type {Transform} from 'stream'
import through from 'through2'
import {speedometer} from './speedometer'
export interface Progress {
percentage: number
transferred: number
length: number
remaining: number
eta: number
runtime: number
delta: number
speed: number
}
export interface ProgressStream extends Transform {
progress(): Progress
}
export function progressStream(options: {time: number; length?: number}): ProgressStream {
let length = options.length || 0
let transferred = 0
let nextUpdate = Date.now() + options.time
let delta = 0
const speed = speedometer(5)
const startTime = Date.now()
const update = {
percentage: 0,
transferred: transferred,
length: length,
remaining: length,
eta: 0,
runtime: 0,
speed: 0,
delta: 0,
}
const emit = function (ended: boolean) {
update.delta = delta
update.percentage = ended ? 100 : length ? (transferred / length) * 100 : 0
update.speed = speed.getSpeed(delta)
update.eta = Math.round(update.remaining / update.speed)
update.runtime = Math.floor((Date.now() - startTime) / 1000)
nextUpdate = Date.now() + options.time
delta = 0
tr.emit('progress', update)
}
const write = function (
chunk: Buffer,
_enc: string,
callback: (err: Error | null, data?: Buffer) => void,
) {
const len = chunk.length
transferred += len
delta += len
update.transferred = transferred
update.remaining = length >= transferred ? length - transferred : 0
if (Date.now() >= nextUpdate) emit(false)
callback(null, chunk)
}
const end = function (callback: (err?: Error | null) => void) {
emit(true)
speed.clear()
callback()
}
const tr = through({}, write, end) as ProgressStream
const onlength = function (newLength: number) {
length = newLength
update.length = length
update.remaining = length - update.transferred
tr.emit('length', length)
}
tr.on('pipe', function (stream) {
if (length > 0) return
// Support http module
if (
stream.readable &&
!('writable' in stream) &&
'headers' in stream &&
isRecord(stream.headers)
) {
const contentLength =
typeof stream.headers['content-length'] === 'string'
? parseInt(stream.headers['content-length'], 10)
: 0
return onlength(contentLength)
}
// Support streams with a length property
if ('length' in stream && typeof stream.length === 'number') {
return onlength(stream.length)
}
// Support request module
stream.on('response', function (res) {
if (!res || !res.headers) return
if (res.headers['content-encoding'] === 'gzip') return
if (res.headers['content-length']) {
return onlength(parseInt(res.headers['content-length']))
}
})
})
tr.progress = function () {
update.speed = speed.getSpeed(0)
update.eta = Math.round(update.remaining / update.speed)
return update
}
return tr
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}

26
server/node_modules/get-it/src/util/pubsub.ts generated vendored Normal file
View File

@@ -0,0 +1,26 @@
// Code borrowed from https://github.com/bjoerge/nano-pubsub
import type {PubSub, Subscriber} from 'get-it'
export function createPubSub<Message = void>(): PubSub<Message> {
const subscribers: {[id: string]: Subscriber<Message>} = Object.create(null)
let nextId = 0
function subscribe(subscriber: Subscriber<Message>) {
const id = nextId++
subscribers[id] = subscriber
return function unsubscribe() {
delete subscribers[id]
}
}
function publish(event: Message) {
for (const id in subscribers) {
subscribers[id](event)
}
}
return {
publish,
subscribe,
}
}

52
server/node_modules/get-it/src/util/speedometer.ts generated vendored Normal file
View File

@@ -0,0 +1,52 @@
/**
* Inlined variant of npm `speedometer` (https://github.com/mafintosh/speedometer),
* MIT-licensed, Copyright (c) 2013 Mathias Buus.
*/
let tick = 1
const maxTick = 65535
const resolution = 4
let timer: ReturnType<typeof setInterval> | null = null
const inc = function () {
tick = (tick + 1) & maxTick
}
export function speedometer(seconds: number) {
if (!timer) {
timer = setInterval(inc, (1000 / resolution) | 0)
if (timer.unref) timer.unref()
}
const size = resolution * (seconds || 5)
const buffer = [0]
let pointer = 1
let last = (tick - 1) & maxTick
return {
getSpeed: function (delta: number) {
let dist = (tick - last) & maxTick
if (dist > size) dist = size
last = tick
while (dist--) {
if (pointer === size) pointer = 0
buffer[pointer] = buffer[pointer === 0 ? size - 1 : pointer - 1]
pointer++
}
if (delta) buffer[pointer - 1] += delta
const top = buffer[pointer - 1]
const btm = buffer.length < size ? 0 : buffer[pointer === size ? 0 : pointer]
return buffer.length < resolution ? top : ((top - btm) * resolution) / buffer.length
},
clear: function () {
if (timer) {
clearInterval(timer)
timer = null
}
},
}
}