wikibase-edit
Version:
Edit Wikibase from NodeJS
83 lines (74 loc) • 2.84 kB
text/typescript
import fetch from 'cross-fetch'
import { debug, debugMode } from '../debug.js'
import type { AbsoluteUrl } from '../types/common.js'
import type { Agent as HttpAgent } from 'node:http'
import type { Agent as HttpsAgent } from 'node:https'
export type HttpHeaderKey = 'content-type' | 'cookie' | 'user-agent'
export type HttpHeaders = Partial<Record<HttpHeaderKey, string>>
export type HttpMethodLowerCased = 'get' | 'post' | 'put' | 'delete'
export type HttpMethod = HttpMethodLowerCased | Uppercase<HttpMethodLowerCased>
export interface CustomFetchOptions {
method?: HttpMethod
headers?: HttpHeaders
timeout?: number
agent?: HttpAgent | HttpsAgent
body?: string
}
export type HttpRequestAgent = HttpAgent | HttpsAgent
export async function customFetch (url: AbsoluteUrl, { timeout, ...options }: CustomFetchOptions = {}) {
options.headers ??= {}
options.headers['accept-encoding'] = 'gzip,deflate'
if (debugMode) {
const { method = 'get', headers, body } = options
debug('request', method.toUpperCase(), url, {
headers: obfuscateHeaders(headers),
body: obfuscateBody({ url, body }),
})
}
try {
return await fetchWithTimeout(url, options, timeout)
} catch (err) {
if (err.type === 'aborted') {
const rephrasedErr = new Error('request timeout')
rephrasedErr.cause = err
throw rephrasedErr
} else {
throw err
}
}
}
// Based on https://stackoverflow.com/questions/46946380/fetch-api-request-timeout#57888548
function fetchWithTimeout (url: AbsoluteUrl, options: CustomFetchOptions, timeoutMs = 120_000) {
const controller = new AbortController()
const promise = fetch(url, {
keepalive: true,
signal: controller.signal,
credentials: 'include',
mode: 'cors',
...options,
})
const timeout = setTimeout(() => controller.abort(), timeoutMs)
return promise.finally(() => clearTimeout(timeout))
}
function obfuscateHeaders (headers: HttpHeaders) {
const obfuscatedHeadersEntries = Object.entries(headers).map(([ name, value ]) => [ name.toLowerCase(), value ])
const obfuscatedHeaders = Object.fromEntries(obfuscatedHeadersEntries)
if (obfuscatedHeaders.authorization) {
obfuscatedHeaders.authorization = obfuscatedHeaders.authorization.replace(/"[^"]+"/g, '"***"')
}
if (obfuscatedHeaders.cookie) {
obfuscatedHeaders.cookie = obfuscateParams(obfuscatedHeaders.cookie)
}
return obfuscatedHeaders
}
function obfuscateBody ({ url, body = '' }: { url: string, body?: string }) {
const { searchParams } = new URL(url)
if (searchParams.get('action') === 'login') {
return obfuscateParams(body)
} else {
return body.replace(/token=[^=\s;&]+([=\s;&]?)/g, 'token=***$1')
}
}
function obfuscateParams (urlEncodedStr: string) {
return urlEncodedStr.replace(/=[^=\s;&]+([=\s;&]?)/g, '=***$1')
}