Fix #4: remove global axios-retry side-effect; add dedicated downloadClient
- wxccRoutes.js no longer mutates the default axios instance, which was causing every bare axios call in the codebase (S3 downloads, Assets diagnostics) to inherit retries as a side-effect of route file load order. - jiraService.js now defines a private downloadClient (own timeout, own retry policy) used by attachFileToJira and fetchAndConvertTranscript for fetching pre-signed S3 URLs. - Assets AQL/GET remain bare axios calls; they're one-shot diagnostics and should not auto-retry. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
1070967870
commit
c4a0a6934e
2 changed files with 26 additions and 20 deletions
|
|
@ -1,22 +1,9 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import axios from 'axios';
|
|
||||||
import axiosRetry from 'axios-retry';
|
|
||||||
import { logger, webexLogger } from '../utilities/logger.js';
|
import { logger, webexLogger } from '../utilities/logger.js';
|
||||||
import * as jiraService from '../services/jiraService.js';
|
import * as jiraService from '../services/jiraService.js';
|
||||||
import grokService from '../services/grokService.js';
|
import grokService from '../services/grokService.js';
|
||||||
import config from '../config/index.js';
|
import config from '../config/index.js';
|
||||||
|
|
||||||
// Configure retry for transient failures
|
|
||||||
axiosRetry(axios, {
|
|
||||||
retries: 3,
|
|
||||||
retryDelay: (retryCount) => Math.pow(2, retryCount) * 1000,
|
|
||||||
retryCondition: (error) => {
|
|
||||||
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
|
|
||||||
error.response?.status === 429 ||
|
|
||||||
error.response?.status >= 500;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// ========================
|
// ========================
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,11 @@ const createJiraClient = () => {
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply retry policy to this instance (keep in sync with the global axios-retry
|
// Retry policy scoped to this client only. We used to also mutate the
|
||||||
// configured in wxccRoutes.js for the S3 downloads and any other raw calls).
|
// global axios instance from wxccRoutes.js, which caused every bare axios
|
||||||
// Using exponential backoff to match the current policy in routes.
|
// call (S3 downloads, Assets AQL, etc.) to inherit these retries as a
|
||||||
|
// side-effect. That's been removed — anything that needs retries now uses
|
||||||
|
// an explicit instance (jiraClient here, downloadClient below).
|
||||||
axiosRetry(client, {
|
axiosRetry(client, {
|
||||||
retries: 3,
|
retries: 3,
|
||||||
retryDelay: (retryCount) => Math.pow(2, retryCount) * 1000,
|
retryDelay: (retryCount) => Math.pow(2, retryCount) * 1000,
|
||||||
|
|
@ -48,6 +50,24 @@ const createJiraClient = () => {
|
||||||
|
|
||||||
export const jiraClient = createJiraClient();
|
export const jiraClient = createJiraClient();
|
||||||
|
|
||||||
|
// Dedicated instance for fetching pre-signed S3 URLs (audio + transcript
|
||||||
|
// files that Webex CC hands us). Kept separate from jiraClient because:
|
||||||
|
// 1. Different base URL (no baseURL — we always pass the full pre-signed URL).
|
||||||
|
// 2. No Authorization header (the S3 URL is already signed).
|
||||||
|
// 3. We want retries here — S3 pre-signed downloads are the flakiest thing
|
||||||
|
// in the pipeline (transient 5xx, TLS resets, TCP timeouts).
|
||||||
|
// Kept private (not exported); callers in this module use it directly.
|
||||||
|
const downloadClient = axios.create({ timeout: 20000 });
|
||||||
|
axiosRetry(downloadClient, {
|
||||||
|
retries: 3,
|
||||||
|
retryDelay: (retryCount) => Math.pow(2, retryCount) * 1000,
|
||||||
|
retryCondition: (error) => {
|
||||||
|
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
|
||||||
|
error.response?.status === 429 ||
|
||||||
|
error.response?.status >= 500;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ========================
|
// ========================
|
||||||
// Existing Functions (updated to use jiraClient where possible)
|
// Existing Functions (updated to use jiraClient where possible)
|
||||||
// ========================
|
// ========================
|
||||||
|
|
@ -256,9 +276,8 @@ async function attachBufferToJira(jiraKey, fileBuffer, fileName) {
|
||||||
export async function attachFileToJira(jiraKey, fileUrl, fileName) {
|
export async function attachFileToJira(jiraKey, fileUrl, fileName) {
|
||||||
let fileBuffer;
|
let fileBuffer;
|
||||||
try {
|
try {
|
||||||
const dl = await axios.get(fileUrl, {
|
const dl = await downloadClient.get(fileUrl, {
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer'
|
||||||
timeout: 20000
|
|
||||||
});
|
});
|
||||||
fileBuffer = Buffer.from(dl.data);
|
fileBuffer = Buffer.from(dl.data);
|
||||||
} catch (dlErr) {
|
} catch (dlErr) {
|
||||||
|
|
@ -308,7 +327,7 @@ function formatTranscriptToHumanReadable(data) {
|
||||||
|
|
||||||
async function fetchAndConvertTranscript(url) {
|
async function fetchAndConvertTranscript(url) {
|
||||||
try {
|
try {
|
||||||
const resp = await axios.get(url, { timeout: 10000 });
|
const resp = await downloadClient.get(url, { timeout: 10000 });
|
||||||
return formatTranscriptToHumanReadable(resp.data);
|
return formatTranscriptToHumanReadable(resp.data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn(`Failed to fetch/convert transcript: ${e.message}`);
|
logger.warn(`Failed to fetch/convert transcript: ${e.message}`);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue