@ecomplus/client
Version:
JS client for E-Com Plus REST APIs
176 lines (164 loc) • 5.12 kB
JavaScript
import env from './env'
import {
API_STORE,
API_PASSPORT,
API_MODULES,
API_STOREFRONT,
API_GRAPHS,
API_PLATFORM
} from './constants'
import axios from './axios'
// set delay between requests by API
const delays = {}
delays[API_STORE] = delays[API_MODULES] = delays[API_PLATFORM] = 250
delays[API_STOREFRONT] = 400
delays[API_GRAPHS] = 570
delays[API_PASSPORT] = 1070
// count current scheduled requests by API
const scheduledRequests = {}
let concurrentRequests = 0
// store APIs on idle after 503 response
const waitingApis = []
// check for debug option
const checkEnvVar = prop => (env[prop] === true || env[prop] === 'true')
const debug = checkEnvVar('ECOMCLIENT_DEBUG')
const request = (config, api, delay = 170, scheduleTime) => {
if (debug) {
console.log(`[ecomClient]: (${scheduleTime} ~ ${Date.now()}) send ${config.method} ${config.url}`)
}
if (checkEnvVar('ECOMCLIENT_NOTIMEOUT') && config.timeout) {
// reset request timeout
config.timeout = 0
}
const next = () => {
return new Promise((resolve, reject) => {
concurrentRequests++
axios.request(config)
.then(resolve)
.catch(err => {
if (debug) {
err.message = `[ecomClient]: ${err.message}`
console.error(err)
}
// handle 503 errors here
const { response } = err
if (response && response.status === 503) {
// service unavailable, probably blocked by proxy
if (api) {
// add API to idle
waitingApis.push(api)
}
// retry with new promise
// wait and resend request
return setTimeout(() => {
if (api) {
// unset API idle
const index = waitingApis.indexOf(api)
if (index > -1) {
waitingApis.splice(index, 1)
}
}
// new axios request without error handler
axios.request(config).then(resolve).catch(reject)
}, delay >= 170 ? delay : 170)
}
reject(err)
})
.finally(() => {
concurrentRequests--
})
})
}
const { ecomClientAxiosMidd } = globalThis
if (typeof ecomClientAxiosMidd === 'function') {
return new Promise((resolve, reject) => {
ecomClientAxiosMidd(config)
.then((res) => {
if (res) {
resolve(res)
return
}
next().then(resolve).catch(reject)
})
.catch((err) => {
if (debug) {
err.message = `[ecomClient midd]: ${err.message}`
console.error(err)
}
next().then(resolve).catch(reject)
})
})
}
return next()
}
export default axiosConfig => {
const { url, method, baseURL, timeout } = axiosConfig
if (url.indexOf('.json') === -1) {
// all APIs endpoints have JSON extension
axiosConfig.url = url.replace(/^([^?]+)(\?.*)?$/, '$1.json$2')
}
let uri = axios.getUri(axiosConfig)
if (!uri.startsWith('http')) {
// complete absolute URI
if (baseURL.slice(-1) === '/' && uri.charAt(0) === '/') {
// prevent duplicated bars
uri = uri.substr(1)
}
uri = baseURL + uri
}
if (debug) {
console.log(`[ecomClient]: ${((method && method.toUpperCase()) || 'GET')} '${uri}'`)
}
let api, delay
for (api in delays) {
if (delays[api] !== undefined && uri.indexOf(api) === 0) {
// API matched
// delayed request
delay = delays[api]
break
}
}
if (!delay) {
// minimum 50ms delay
delay = 50
}
// scheduled request
// set request queue position based on current API scheduled requests
// there's no delay when queue is 0 (first request)
const queue = scheduledRequests[api] || 0
scheduledRequests[api] = queue + 1
// returns promise resolved with request after timeout
return new Promise((resolve, reject) => {
let retries = 0
const schedule = () => {
// calculate final delay with API queue and concurrent requests multipliers
const requestDelay = delay * queue + concurrentRequests * 2.5
let scheduleTime
if (debug) {
scheduleTime = Date.now()
console.log(`[ecomClient]: (${scheduleTime}) request delay ${requestDelay}ms`)
}
setTimeout(() => {
if (waitingApis.indexOf(api) <= -1) {
// send request and reset scheduled requests count
scheduledRequests[api]--
request(axiosConfig, api, delay, scheduleTime)
.then(resolve)
.catch(err => {
// retry server errors for requests without timeout
if (!timeout && retries < 2 && err.response && err.response.status >= 500) {
setTimeout(schedule, Math.max(delay, 600))
return retries++
}
reject(err)
})
} else {
// API on idle due to 503 response
// schedule request again
schedule()
}
}, requestDelay)
}
schedule()
})
}