mirror of
https://github.com/ThatGuySam/doesitarm.git
synced 2026-05-18 06:44:46 -07:00
Finish axios migration via shared native HTTP helper
Replace all in-scope axios callsites with a new helpers/http.js wrapper over native fetch, including JSON/text GET, JSON POST, HEAD checks, and transient 5xx retry behavior; update all browser, build, script, and proxy API clients to use it; add focused unit tests; and remove axios from package dependencies. Constraint: Preserve API/build and deployment behavior while lowering transport surface area. Rejected: inline fetch replacements at each callsite | rejected to avoid inconsistent error/retry semantics. Confidence: high Scope-risk: moderate Directive: Keep helper in place as the transport boundary and update tests when changing request semantics. Tested: pnpm run -s typecheck, pnpm -s run test-prebuild, pnpm -s run test, pnpm -s run test:browser, pnpm -s run netlify-build, smoke GETs on /apple-silicon-app-test and /apple-silicon-app-test/?version=2 Not-tested: branch/netlify deployment health in CI pipeline after merge
This commit is contained in:
parent
d39a2a1d6c
commit
d45b587434
25 changed files with 824 additions and 267 deletions
|
|
@ -12,9 +12,8 @@
|
|||
// GET /api/tiles/public/static/3/4/2.json?turn=37038&games=wot
|
||||
// DoesItAPI.tiles.public.static(3)(4)(`${2}.json`).get({ turn: 37, games: 'wot' })
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
import { getApiUrl } from '~/helpers/url.js'
|
||||
import { requestJson } from '~/helpers/http.js'
|
||||
|
||||
// Use msw
|
||||
import '~/test/msw/use.js'
|
||||
|
|
@ -22,8 +21,7 @@ import '~/test/msw/use.js'
|
|||
// const defaultFetchMethod = (...args) => console.log(...args) // mock
|
||||
|
||||
const defaultFetchMethod = async function (...args) {
|
||||
return axios(...args)
|
||||
.then( response => response.data )
|
||||
return requestJson(...args)
|
||||
.catch( error => {
|
||||
if ( error?.response?.status !== 404 ) {
|
||||
console.error( error )
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import axios from 'axios'
|
||||
import { parse } from 'fast-xml-parser'
|
||||
|
||||
import {
|
||||
|
|
@ -8,6 +7,10 @@ import {
|
|||
sitemapIndexFileName,
|
||||
} from '~/helpers/constants.js'
|
||||
import { isValidHttpUrl } from '~/helpers/check-types.js'
|
||||
import {
|
||||
getText,
|
||||
headOk
|
||||
} from '~/helpers/http.js'
|
||||
|
||||
const sitemapFilesToTry = [
|
||||
sitemapIndexFileName,
|
||||
|
|
@ -106,12 +109,7 @@ export async function fetchAllUrlsFromSitemaps ( urlString ) {
|
|||
// console.log( 'sitemapUrl', sitemapUrl.href )
|
||||
|
||||
// Just do a quich HEAD request to see if the file exists with getting the whole body
|
||||
const exists = await axios.head( sitemapUrl.href )
|
||||
.catch( () => false )
|
||||
.then( response => {
|
||||
// console.log( 'response', response.status )
|
||||
return response.status < 300
|
||||
} )
|
||||
const exists = await headOk( sitemapUrl.href )
|
||||
|
||||
// console.log( 'exists', exists )
|
||||
|
||||
|
|
@ -123,8 +121,7 @@ export async function fetchAllUrlsFromSitemaps ( urlString ) {
|
|||
getMethod: async sitemapPath => {
|
||||
const sitemapUrl = new URL( sitemapPath, urlString )
|
||||
|
||||
const sitemapXml = await axios.get( sitemapUrl.href )
|
||||
.then( response => response.data )
|
||||
const sitemapXml = await getText( sitemapUrl.href )
|
||||
|
||||
return sitemapXml
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import fs from 'fs-extra'
|
||||
import axios from 'axios'
|
||||
import 'dotenv/config.js'
|
||||
|
||||
import {
|
||||
|
|
@ -8,6 +7,7 @@ import {
|
|||
// storkExecutablePath,
|
||||
storkTomlPath,
|
||||
} from '~/helpers/stork/config.js'
|
||||
import { getText } from '~/helpers/http.js'
|
||||
|
||||
export async function downloadStorkToml () {
|
||||
// Check if the toml file exists
|
||||
|
|
@ -20,12 +20,9 @@ export async function downloadStorkToml () {
|
|||
|
||||
apiUrl.pathname = storkTomlPath.replace('static/', '')
|
||||
|
||||
const response = await axios({
|
||||
method: "get",
|
||||
url: apiUrl.toString(),
|
||||
})
|
||||
const storkToml = await getText( apiUrl.toString() )
|
||||
|
||||
await fs.writeFile( storkTomlPath, response.data, { encoding: null })
|
||||
await fs.writeFile( storkTomlPath, storkToml, { encoding: null })
|
||||
|
||||
// Get toml file stats
|
||||
const stats = await fs.stat( storkTomlPath )
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import fs from 'fs-extra'
|
||||
import { google } from 'googleapis'
|
||||
import axios from 'axios'
|
||||
|
||||
import { playlists, benchmarksPlaylistId } from './playlists.js'
|
||||
import { getJson } from '~/helpers/http.js'
|
||||
|
||||
|
||||
export const youtubeVideoPath = './static/api/youtube-videos.json'
|
||||
|
|
@ -167,8 +167,7 @@ export async function saveYouTubeVideos () {
|
|||
// const youtubeVideos = await getYouTubeVideos()
|
||||
|
||||
// Locked previously sucessful YouTube API data for now
|
||||
const youtubeVideos = await axios( process.env.VIDEO_SOURCE )
|
||||
.then( response => response.data )
|
||||
const youtubeVideos = await getJson( process.env.VIDEO_SOURCE )
|
||||
|
||||
|
||||
// Save to JSON
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import plist from 'plist'
|
||||
import axios from 'axios'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import * as zip from '@zip.js/zip.js'
|
||||
|
||||
import { isString } from './check-types.js'
|
||||
import { postJson } from './http.js'
|
||||
import parseMacho from './macho/index.js'
|
||||
|
||||
// Vite Web Workers - https://vitejs.dev/guide/features.html#web-workers
|
||||
|
|
@ -331,14 +331,13 @@ export default class AppFilesScanner {
|
|||
|
||||
// console.log( 'this.testResultStore', this.testResultStore )
|
||||
|
||||
const { supportedVersionNumber } = await axios.post( this.testResultStore , {
|
||||
const responseData = await postJson( this.testResultStore, {
|
||||
filename,
|
||||
appVersion,
|
||||
result,
|
||||
machoMeta: JSON.stringify( machoMeta ),
|
||||
infoPlist: JSON.stringify( infoPlist )
|
||||
})
|
||||
.then( response => response.data )
|
||||
} )
|
||||
.catch(function (error) {
|
||||
console.error(error)
|
||||
|
||||
|
|
@ -348,7 +347,7 @@ export default class AppFilesScanner {
|
|||
})
|
||||
|
||||
return {
|
||||
supportedVersionNumber
|
||||
supportedVersionNumber: responseData?.supportedVersionNumber ?? null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import fs from 'fs-extra'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import axios from 'axios'
|
||||
|
||||
import statuses, { getStatusName } from './statuses.js'
|
||||
import appStoreGenres from './app-store/genres.js'
|
||||
|
|
@ -14,6 +13,7 @@ import { byTimeThenNull } from './sort-list.js'
|
|||
import {
|
||||
cliOptions
|
||||
} from '~/helpers/cli-options.js'
|
||||
import { getJson } from './http.js'
|
||||
|
||||
const md = new MarkdownIt()
|
||||
|
||||
|
|
@ -277,9 +277,9 @@ const lookForLastUpdated = function (app, commits) {
|
|||
async function fetchBundleGenres () {
|
||||
const genresJsonUrl = `${process.env.VFUNCTIONS_URL}/app-store/listings-sheet?fields=bundleId,genreIds`
|
||||
|
||||
return await axios.get( genresJsonUrl )
|
||||
.then( response => {
|
||||
return new Map( response.data.apps )
|
||||
return await getJson( genresJsonUrl )
|
||||
.then( data => {
|
||||
return new Map( data.apps )
|
||||
})
|
||||
.catch(function (error) {
|
||||
// handle error
|
||||
|
|
@ -315,9 +315,9 @@ export default async function () {
|
|||
// console.log('readmeContent', readmeContent)
|
||||
|
||||
// Fetch Commits
|
||||
const response = await axios.get(process.env.COMMITS_SOURCE)
|
||||
const response = await getJson( process.env.COMMITS_SOURCE )
|
||||
// Extract commit from response data
|
||||
const commits = response.data.data.viewer.repository.defaultBranchRef.target.history.edges
|
||||
const commits = response.data.viewer.repository.defaultBranchRef.target.history.edges
|
||||
// console.log('commits', commits)
|
||||
|
||||
// Save commits to file just in case
|
||||
|
|
@ -328,13 +328,11 @@ export default async function () {
|
|||
const scanListMap = new Map()
|
||||
|
||||
// Store app scans
|
||||
await axios
|
||||
.get(process.env.SCANS_SOURCE)
|
||||
|
||||
await getJson( process.env.SCANS_SOURCE )
|
||||
.then( async response => {
|
||||
const appBundles = []
|
||||
|
||||
for (const appScan of response.data.appList) {
|
||||
for (const appScan of response.appList) {
|
||||
|
||||
// Add app to bundle list
|
||||
appBundles.push([
|
||||
|
|
@ -349,11 +347,7 @@ export default async function () {
|
|||
|
||||
await fs.writeJson('./static/app-bundles.json', appBundles)
|
||||
|
||||
return response
|
||||
})
|
||||
.then(function (response) {
|
||||
|
||||
response.data.appList.forEach( appScan => {
|
||||
response.appList.forEach( appScan => {
|
||||
|
||||
const appName = appScan.aliases[0]
|
||||
|
||||
|
|
@ -424,8 +418,6 @@ export default async function () {
|
|||
relatedLinks
|
||||
})
|
||||
})
|
||||
|
||||
return
|
||||
})
|
||||
.catch(function (error) {
|
||||
// handle error
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import axios from 'axios'
|
||||
|
||||
import { makeSlug } from './slug.js'
|
||||
import { getJson } from './http.js'
|
||||
|
||||
export function getDeviceEndpoint ( slug ) {
|
||||
return `/device/${ slug }`
|
||||
|
|
@ -12,10 +11,7 @@ export default async function () {
|
|||
|
||||
const devicesJsonUrl = `${process.env.VFUNCTIONS_URL}/api/devices`
|
||||
|
||||
const rawDeviceList = await axios.get(devicesJsonUrl)
|
||||
.then( response => {
|
||||
return response.data
|
||||
})
|
||||
const rawDeviceList = await getJson( devicesJsonUrl )
|
||||
.catch(function (error) {
|
||||
// handle error
|
||||
console.warn('Error fetching device list', error)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import axios from 'axios'
|
||||
|
||||
// import { statuses } from './build-app-list'
|
||||
import { getAppEndpoint } from './app-derived'
|
||||
import { makeSlug } from './slug.js'
|
||||
import { getJson } from './http.js'
|
||||
|
||||
|
||||
// console.log('process.env.GAMES_SOURCE', process.env.GAMES_SOURCE)
|
||||
|
|
@ -68,11 +67,10 @@ function parseStatus(game) {
|
|||
export default async function () {
|
||||
|
||||
// Fetch Sheet data
|
||||
const gamesSheet = await axios
|
||||
.get(process.env.GAMES_SOURCE)
|
||||
const gamesSheet = await getJson( process.env.GAMES_SOURCE )
|
||||
.then(function (response) {
|
||||
// handle success
|
||||
return response.data.records
|
||||
return response.records
|
||||
})
|
||||
.catch(function (error) {
|
||||
// handle error
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// import { promises as fs } from 'fs'
|
||||
// import MarkdownIt from 'markdown-it'
|
||||
// import slugify from 'slugify'
|
||||
import axios from 'axios'
|
||||
|
||||
// import statuses from './statuses'
|
||||
// import parseDate from './parse-github-date'
|
||||
|
|
@ -11,6 +10,7 @@ const marked = require('marked')
|
|||
const HTMLParser = require(`node-html-parser`)
|
||||
|
||||
import { getAppEndpoint } from './app-derived'
|
||||
import { getJson } from './http.js'
|
||||
|
||||
|
||||
const statusesTranslations = {
|
||||
|
|
@ -117,13 +117,13 @@ class MakeHomebrewList {
|
|||
allFormulaeResponse
|
||||
] = await Promise.all([
|
||||
// Fetch Gihub Issue List
|
||||
axios.get(process.env.HOMEBREW_SOURCE),
|
||||
getJson( process.env.HOMEBREW_SOURCE ),
|
||||
// Fetch Official Homebrew Formulae List
|
||||
axios.get('https://formulae.brew.sh/api/formula.json')
|
||||
getJson( 'https://formulae.brew.sh/api/formula.json' )
|
||||
])
|
||||
|
||||
// Extract commit from response data
|
||||
const issueMarkdown = issueResponse.data.data.repository.issue.body
|
||||
const issueMarkdown = issueResponse.data.repository.issue.body
|
||||
|
||||
// Parse markdown
|
||||
const issueHTML = marked(issueMarkdown)
|
||||
|
|
@ -132,10 +132,10 @@ class MakeHomebrewList {
|
|||
const dom = HTMLParser.parse(issueHTML)
|
||||
|
||||
// Store the original array
|
||||
this.allFormulaeArray = allFormulaeResponse.data
|
||||
this.allFormulaeArray = allFormulaeResponse
|
||||
|
||||
// Extract list from allFormulaeResponse and map into an object for easy access
|
||||
this.allFormulae = Object.fromEntries(allFormulaeResponse.data.map(formula => {
|
||||
this.allFormulae = Object.fromEntries(allFormulaeResponse.map(formula => {
|
||||
return [
|
||||
formula.full_name,
|
||||
formula
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ export default async function ( applist ) {
|
|||
// const videosJsonUrl = process.env.VIDEO_SOURCE || `${process.env.VFUNCTIONS_URL}/videos.json`
|
||||
|
||||
// Fetch Commits
|
||||
// const response = await axios.get( videosJsonUrl )
|
||||
// const response = await fetch( videosJsonUrl )
|
||||
// Extract commit from response data
|
||||
const fetchedVideos = await fs.readJson( youtubeVideoPath )//response.data
|
||||
|
||||
|
|
|
|||
244
helpers/http.js
Normal file
244
helpers/http.js
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
function sleep ( delayMs ) {
|
||||
return new Promise( resolve => setTimeout( resolve, delayMs ) )
|
||||
}
|
||||
|
||||
function normalizeUrl ( url ) {
|
||||
if ( url instanceof URL ) {
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
return String( url )
|
||||
}
|
||||
|
||||
function toRequestConfig ( input, options = {} ) {
|
||||
if ( typeof input === 'string' || input instanceof URL ) {
|
||||
return {
|
||||
...options,
|
||||
url: normalizeUrl( input )
|
||||
}
|
||||
}
|
||||
|
||||
if ( input && typeof input === 'object' && 'url' in input ) {
|
||||
return {
|
||||
...input,
|
||||
...options,
|
||||
url: normalizeUrl( input.url )
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error( 'Expected a request URL or config object with a url field.' )
|
||||
}
|
||||
|
||||
function createHeaders ( inputHeaders = {} ) {
|
||||
return new Headers( inputHeaders )
|
||||
}
|
||||
|
||||
function hasResponseStatus ( error ) {
|
||||
return typeof error?.response?.status === 'number'
|
||||
}
|
||||
|
||||
export function shouldRetryError ( error ) {
|
||||
return hasResponseStatus( error ) && error.response.status >= 500
|
||||
}
|
||||
|
||||
export class HttpError extends Error {
|
||||
constructor ( message, {
|
||||
cause,
|
||||
data = null,
|
||||
method,
|
||||
status,
|
||||
statusText,
|
||||
url
|
||||
} ) {
|
||||
super( message )
|
||||
|
||||
this.name = 'HttpError'
|
||||
this.cause = cause
|
||||
this.method = method
|
||||
this.status = status
|
||||
this.url = url
|
||||
this.response = {
|
||||
data,
|
||||
status,
|
||||
statusText,
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function parseResponseBody ( response, responseType ) {
|
||||
if ( responseType === 'none' ) {
|
||||
return null
|
||||
}
|
||||
|
||||
if ( responseType === 'text' ) {
|
||||
return await response.text()
|
||||
}
|
||||
|
||||
const text = await response.text()
|
||||
|
||||
if ( text.length === 0 ) {
|
||||
return null
|
||||
}
|
||||
|
||||
return JSON.parse( text )
|
||||
}
|
||||
|
||||
function buildRequestInit ( {
|
||||
body,
|
||||
cache,
|
||||
credentials,
|
||||
headers,
|
||||
method,
|
||||
mode,
|
||||
redirect,
|
||||
signal
|
||||
} ) {
|
||||
return {
|
||||
body,
|
||||
cache,
|
||||
credentials,
|
||||
headers,
|
||||
method,
|
||||
mode,
|
||||
redirect,
|
||||
signal
|
||||
}
|
||||
}
|
||||
|
||||
export async function request ( input, options = {} ) {
|
||||
const config = toRequestConfig( input, options )
|
||||
const {
|
||||
attempts = 1,
|
||||
cache,
|
||||
credentials,
|
||||
data,
|
||||
delayMs = 1000,
|
||||
headers: inputHeaders,
|
||||
method: inputMethod = 'GET',
|
||||
mode,
|
||||
redirect,
|
||||
responseType = 'json',
|
||||
signal,
|
||||
url
|
||||
} = config
|
||||
const method = inputMethod.toUpperCase()
|
||||
const headers = createHeaders( inputHeaders )
|
||||
|
||||
let body
|
||||
if ( data !== undefined ) {
|
||||
body = JSON.stringify( data )
|
||||
|
||||
if ( !headers.has( 'Accept' ) ) {
|
||||
headers.set( 'Accept', 'application/json' )
|
||||
}
|
||||
|
||||
if ( !headers.has( 'Content-Type' ) ) {
|
||||
headers.set( 'Content-Type', 'application/json' )
|
||||
}
|
||||
}
|
||||
|
||||
let lastError
|
||||
|
||||
for ( let attempt = 1; attempt <= attempts; attempt += 1 ) {
|
||||
try {
|
||||
const response = await fetch( url, buildRequestInit({
|
||||
body,
|
||||
cache,
|
||||
credentials,
|
||||
headers,
|
||||
method,
|
||||
mode,
|
||||
redirect,
|
||||
signal
|
||||
}) )
|
||||
const responseData = await parseResponseBody( response, responseType )
|
||||
|
||||
if ( !response.ok ) {
|
||||
throw new HttpError(
|
||||
`${ method } ${ url } failed with status ${ response.status }`,
|
||||
{
|
||||
data: responseData,
|
||||
method,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
url
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
data: responseData,
|
||||
response
|
||||
}
|
||||
} catch ( error ) {
|
||||
lastError = error
|
||||
|
||||
if ( attempt >= attempts || !shouldRetryError( error ) ) {
|
||||
throw error
|
||||
}
|
||||
|
||||
await sleep( delayMs )
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError
|
||||
}
|
||||
|
||||
export async function getJson ( url, options = {} ) {
|
||||
const { data } = await request( url, {
|
||||
...options,
|
||||
method: 'GET',
|
||||
responseType: 'json'
|
||||
} )
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getText ( url, options = {} ) {
|
||||
const { data } = await request( url, {
|
||||
...options,
|
||||
method: 'GET',
|
||||
responseType: 'text'
|
||||
} )
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export async function postJson ( url, data, options = {} ) {
|
||||
const { data: responseData } = await request( url, {
|
||||
...options,
|
||||
data,
|
||||
method: 'POST',
|
||||
responseType: 'json'
|
||||
} )
|
||||
|
||||
return responseData
|
||||
}
|
||||
|
||||
export async function requestJson ( input, options = {} ) {
|
||||
const { data } = await request( input, {
|
||||
...options,
|
||||
responseType: 'json'
|
||||
} )
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export async function headOk ( url, options = {} ) {
|
||||
try {
|
||||
await request( url, {
|
||||
...options,
|
||||
method: 'HEAD',
|
||||
responseType: 'none'
|
||||
} )
|
||||
|
||||
return true
|
||||
} catch ( error ) {
|
||||
if ( error instanceof Error ) {
|
||||
return false
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,13 @@
|
|||
import fs from 'fs-extra'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
getJson,
|
||||
shouldRetryError
|
||||
} from '~/helpers/http.js'
|
||||
|
||||
import {
|
||||
sitemapEndpointsPath
|
||||
} from '~/helpers/pagefind/config.js'
|
||||
|
||||
function shouldRetryError ( error: unknown ) {
|
||||
const status = ( error as { response?: { status?: number } } )?.response?.status
|
||||
|
||||
return typeof status === 'number' && status >= 500
|
||||
}
|
||||
|
||||
async function fetchJsonWithRetries (
|
||||
url: string,
|
||||
{
|
||||
|
|
@ -21,25 +18,10 @@ async function fetchJsonWithRetries (
|
|||
delayMs?: number
|
||||
} = {}
|
||||
) {
|
||||
let lastError: unknown
|
||||
|
||||
for ( let attempt = 1; attempt <= attempts; attempt += 1 ) {
|
||||
try {
|
||||
const response = await axios.get( url )
|
||||
|
||||
return response.data
|
||||
} catch ( error ) {
|
||||
lastError = error
|
||||
|
||||
if ( attempt >= attempts || !shouldRetryError( error ) ) {
|
||||
throw error
|
||||
}
|
||||
|
||||
await new Promise( resolve => setTimeout( resolve, delayMs ) )
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError
|
||||
return await getJson( url, {
|
||||
attempts,
|
||||
delayMs
|
||||
} )
|
||||
}
|
||||
|
||||
export async function loadSitemapEndpoints () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue