import vercelFetch from '@vercel/fetch' import { FetcherError } from '@commerce/utils/errors' import jwt from 'jsonwebtoken' import { OrdercloudConfig } from '../index' // Get an instance to vercel fetch const fetch = vercelFetch() // Get token util async function getToken(baseUrl: string) { // If not, get a new one and store it const authResponse = await fetch(`${baseUrl}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json', }, body: `client_id=${process.env.NEXT_PUBLIC_ORDERCLOUD_CLIENT_ID}&grant_type=client_credentials`, }) // If something failed getting the auth response if (!authResponse.ok) { // Get the body of it const error = await authResponse.json() // And return an error throw new FetcherError({ errors: [{ message: error.error_description.Code }], status: error.error_description.HttpStatus, }) } // Return the token return authResponse.json().then((response) => response.access_token) } export async function fetchData( opts: { path: string method: string baseUrl: string fetchOptions?: Record body?: Record }, retries = 0 ): Promise { // Destructure opts const { path, body, fetchOptions, baseUrl, method = 'GET' } = opts // Decode token const decoded = jwt.decode(global.token as string) as jwt.JwtPayload | null // If token is not present or its expired, get a new one and store it if ( !global.token || (typeof decoded?.exp === 'number' && decoded?.exp * 1000 < +new Date()) ) { // Get a new one const token = await getToken(baseUrl) // Store it global.token = token } // Do the request with the correct headers const dataResponse = await fetch(`${baseUrl}/v1${path}`, { ...fetchOptions, method, headers: { ...fetchOptions?.headers, 'Content-Type': 'application/json', accept: 'application/json, text/plain, */*', authorization: `Bearer ${global.token}`, }, body: body ? JSON.stringify(body) : undefined, }) // If something failed getting the data response if (!dataResponse.ok) { // Log error console.log(await dataResponse.textConverted()) // If token is expired if (dataResponse.status === 401) { // Get a new one const token = await getToken(baseUrl) // Store it global.token = token } // And if retries left if (retries < 2) { // Refetch return fetchData(opts, retries + 1) } // Get the body of it const error = await dataResponse.json() // And return an error throw new FetcherError({ errors: [{ message: error.error_description.Code }], status: error.error_description.HttpStatus, }) } try { // Return data response as json return (await dataResponse.json()) as Promise } catch (error) { // If response is empty return it as text return null as unknown as Promise } } const serverFetcher: ( getConfig: () => OrdercloudConfig ) => ( method: string, path: string, body?: Record, fetchOptions?: Record ) => Promise = (getConfig) => async ( method: string, path: string, body?: Record, fetchOptions?: Record ) => { // Get provider config const { commerceUrl } = getConfig() // Return the data and specify the expected type return fetchData({ fetchOptions, method, baseUrl: commerceUrl, path, body, }) } export default serverFetcher