diff --git a/package.json b/package.json index d4e7a8d..735dde0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "queuenorth-website", "private": true, - "version": "0.5.3", + "version": "0.5.4", "type": "module", "scripts": { "dev": "concurrently \"vite\" \"node server/index.js\"", diff --git a/server/index.js b/server/index.js index 30d2ae9..a4efc63 100644 --- a/server/index.js +++ b/server/index.js @@ -216,6 +216,9 @@ const ZOHO_REDIRECT_URI = process.env.ZOHO_REDIRECT_URI || '' let zohoAccessToken = null let zohoTokenExpiry = 0 +// 10 second timeout for all Zoho API calls +const ZOHO_TIMEOUT_MS = 10000 + async function getZohoAccessToken() { // Return cached token if still valid (with 60s buffer) if (zohoAccessToken && Date.now() < zohoTokenExpiry - 60000) { @@ -232,7 +235,36 @@ async function getZohoAccessToken() { redirect_uri: ZOHO_REDIRECT_URI, }) - const response = await fetch(`${url}?${params.toString()}`, { method: 'POST' }) + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), ZOHO_TIMEOUT_MS) + + let response + try { + response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params.toString(), + signal: controller.signal, + }) + } catch (err) { + if (err.name === 'AbortError') { + log.warn('[Zoho] Token fetch timed out after', ZOHO_TIMEOUT_MS, 'ms') + } else { + log.error('[Zoho] Token fetch error:', err.message) + } + clearTimeout(timeoutId) + return null + } finally { + clearTimeout(timeoutId) + } + + // Issue #3: Check response.ok before parsing JSON + if (!response.ok) { + const text = await response.text() + log.error(`[Zoho] Token fetch failed (${response.status}):`, text) + return null + } + const data = await response.json() if (data.access_token) { @@ -266,7 +298,8 @@ async function forwardToZoho(leadData) { return } - const url = `${ZOHO_API_DOMAIN}/crm/v8/Leads` + // Issue #8: Prevent double-slash in URL path + const url = `${ZOHO_API_DOMAIN.replace(/\/$/, '')}/crm/v8/Leads` const payload = { data: [ { @@ -281,9 +314,8 @@ async function forwardToZoho(leadData) { ], } - // Issue #5: Add timeout using AbortController const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout + const timeoutId = setTimeout(() => controller.abort(), ZOHO_TIMEOUT_MS) try { const response = await fetch(url, { @@ -307,7 +339,7 @@ async function forwardToZoho(leadData) { log.info('[Zoho] Lead forwarded successfully:', result.data?.[0]?.details?.id || 'no id returned') } catch (fetchErr) { if (fetchErr.name === 'AbortError') { - log.warn('[Zoho] Lead forwarding timed out after 10 seconds') + log.warn('[Zoho] Lead forwarding timed out after', ZOHO_TIMEOUT_MS, 'ms') } else { log.error('[Zoho] Forwarding error:', fetchErr.message) }