const DEBUG = process.env.IDEMPIERE_DEBUG === '1' || process.env.IDEMPIERE_DEBUG === 'true' // Hard ceiling for a single iDempiere call. nginx already cuts the upstream at 60 s // (504), this only guards the paths that bypass nginx or hang before headers. // Deliberately generous: reports / big lists legitimately take a while. export const IDEMPIERE_FETCH_TIMEOUT_MS = Number(process.env.IDEMPIERE_FETCH_TIMEOUT_MS) > 0 ? Number(process.env.IDEMPIERE_FETCH_TIMEOUT_MS) : 90_000 /** * Remember the outcome of the last iDempiere call on this request so * refreshTokenHelper can tell an auth failure (refresh + retry is right) from a * backend failure (502/504/timeout — retrying only piles more load on a stalled * iDempiere; the 2026-09-10 incident was exactly that retry storm). */ export function noteIdempiereError(event: any, error: any, url: string) { if (!event?.context) return const status = Number(error?.response?.status ?? error?.status ?? error?.statusCode ?? 0) || 0 event.context.lastIdempiereError = { status, url, at: Date.now(), name: error?.name } } export function clearIdempiereError(event: any) { if (event?.context?.lastIdempiereError) event.context.lastIdempiereError = null } export default async function fetchHelper(event: any, url: string, method: string = 'GET', token: string, body: any) { const config = useRuntimeConfig() const logshipToken = getCookie(event, 'logship_it') let options: any = {} if(token || logshipToken) { options = { ...options, headers: { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer '+(token || logshipToken) } } } if(body) { options = { ...options, body: body } } const fullUrl = config.api.url+'/'+url if (DEBUG) { console.log('╔════════════════════════════════════════════════════════') console.log('║ iDEMPIERE API REQUEST') console.log('╠════════════════════════════════════════════════════════') console.log('║ Timestamp:', new Date().toISOString()) console.log('║ Method:', method) console.log('║ URL:', fullUrl) console.log('║ Headers:', JSON.stringify({ ...options.headers, Authorization: options.headers?.Authorization ? 'Bearer [REDACTED]' : undefined }, null, 2)) if(body) { console.log('║ Body:', JSON.stringify(body, null, 2)) } console.log('╚════════════════════════════════════════════════════════') } const startTime = Date.now() try { const response = await $fetch(fullUrl, { method: method, // ofetch retries GET once on 408/425/429/500/502/503/504 by default — never // do that against iDempiere: the routes already have their own refresh+retry // and a stalled backend must not be hit twice per call. retry: 0, timeout: IDEMPIERE_FETCH_TIMEOUT_MS, ...options }) clearIdempiereError(event) if (DEBUG) { const duration = Date.now() - startTime console.log('╔════════════════════════════════════════════════════════') console.log('║ iDEMPIERE API RESPONSE') console.log('╠════════════════════════════════════════════════════════') console.log('║ Timestamp:', new Date().toISOString()) console.log('║ Method:', method) console.log('║ URL:', fullUrl) console.log('║ Duration:', duration, 'ms') console.log('║ Status: SUCCESS') console.log('║ Response:', JSON.stringify(response, null, 2)) console.log('╚════════════════════════════════════════════════════════') } return response } catch (error: any) { noteIdempiereError(event, error, url) if (DEBUG) { const duration = Date.now() - startTime console.log('╔════════════════════════════════════════════════════════') console.log('║ iDEMPIERE API ERROR') console.log('╠════════════════════════════════════════════════════════') console.log('║ Timestamp:', new Date().toISOString()) console.log('║ Method:', method) console.log('║ URL:', fullUrl) console.log('║ Duration:', duration, 'ms') console.log('║ Status: ERROR') console.log('║ Error:', JSON.stringify({name: error?.name, message: error?.message, statusCode: error?.statusCode, data: error?.data}, null, 2)) console.log('╚════════════════════════════════════════════════════════') } throw error } }