Explorar el Código

refactor: troca fetch por axios com retry na integração alfred/ixc

leonardo hace 1 mes
padre
commit
df1affb405

+ 2 - 0
package.json

@@ -16,6 +16,8 @@
   },
   "dependencies": {
     "@qdrant/js-client-rest": "^1.11.0",
+    "axios": "^1.19.0",
+    "axios-retry": "^4.5.0",
     "baileys": "^6.7.23",
     "bcryptjs": "^2.4.3",
     "cors": "^2.8.5",

+ 20 - 22
src/functions/atualizaTokenIxcIntegradorERetorna.js

@@ -1,4 +1,5 @@
 import { Integracao, INTEGRACAO_TIPO_IXC } from "../models/Integracao.model.js";
+import ixcHttpClient from "../services/ixcHttpClient.js";
 
 function parseOutros(outros) {
   if (!outros) return {};
@@ -22,41 +23,38 @@ export async function atualizaTokenIxcIntegradorERetorna() {
 
   if (outros.token) {
     try {
-      const statusRes = await fetch(`${baseUrl}usuario/status`, {
-        method: "POST",
-        headers: { Authorization: `Bearer ${outros.token}` },
-        signal: AbortSignal.timeout(10_000)
-      });
-      if (statusRes.ok) {
-        const statusJson = await statusRes.json().catch(() => null);
-        if (statusJson?.ok) return integracao;
-      }
+      const statusRes = await ixcHttpClient.post(
+        `${baseUrl}usuario/status`,
+        {},
+        { headers: { Authorization: `Bearer ${outros.token}` }, timeout: 10_000 }
+      );
+      if (statusRes.data?.ok) return integracao;
     } catch {
-  
+
     }
   }
 
-  const loginRes = await fetch(`${baseUrl}usuario/login`, {
-    method: "POST",
-    headers: { "Content-Type": "application/json" },
-    body: JSON.stringify({ Login: outros.Login, Senha: outros.Senha }),
-    signal: AbortSignal.timeout(10_000)
-  });
-
-  if (!loginRes.ok) {
-    const err = new Error(`ixc_integrador_login_falhou:${loginRes.status}`);
+  let loginRes;
+  try {
+    loginRes = await ixcHttpClient.post(
+      `${baseUrl}usuario/login`,
+      { Login: outros.Login, Senha: outros.Senha },
+      { timeout: 10_000 }
+    );
+  } catch (error) {
+    const err = new Error(`ixc_integrador_login_falhou:${error.response?.status ?? "sem_resposta"}`);
     err.statusCode = 502;
     throw err;
   }
 
-  const loginJson = await loginRes.json().catch(() => null);
-  if (!loginJson?.token) {
+  const loginToken = loginRes.data?.token;
+  if (!loginToken) {
     const err = new Error("ixc_integrador_login_sem_token");
     err.statusCode = 502;
     throw err;
   }
 
   return integracao.$query().patchAndFetch({
-    Outros: JSON.stringify({ Login: outros.Login, Senha: outros.Senha, token: loginJson.token })
+    Outros: JSON.stringify({ Login: outros.Login, Senha: outros.Senha, token: loginToken })
   });
 }

+ 18 - 0
src/services/ixcHttpClient.js

@@ -0,0 +1,18 @@
+import axios from "axios";
+import axiosRetry from "axios-retry";
+
+// O IXC/Alfred (sistema externo que hospeda clientes/contratos/OS) e conhecido por
+// ser lento/instavel. Timeout e retry padrao aqui evitam requisicoes penduradas
+// indefinidamente e uma falha momentanea virar erro direto pro usuario final.
+const ixcHttpClient = axios.create({ timeout: 15_000 });
+
+axiosRetry(ixcHttpClient, {
+  retries: 2,
+  retryDelay: axiosRetry.exponentialDelay,
+  // So reenvia quando nao houve resposta nenhuma (timeout/conexao caiu) - se o Alfred
+  // respondeu algo (mesmo erro 5xx), nao arriscamos reenviar uma operacao que pode
+  // ja ter sido processada do lado de la.
+  retryCondition: (error) => !error.response && (error.code === "ECONNABORTED" || axiosRetry.isNetworkError(error))
+});
+
+export default ixcHttpClient;

+ 17 - 15
src/services/ixcIntegradorService.js

@@ -1,4 +1,5 @@
 import { atualizaTokenIxcIntegradorERetorna } from "../functions/atualizaTokenIxcIntegradorERetorna.js";
+import ixcHttpClient from "./ixcHttpClient.js";
 
 function parseOutros(outros) {
   if (!outros) return {};
@@ -15,29 +16,30 @@ async function fetchJson(path, { method = "GET", body } = {}) {
   const { token } = parseOutros(integracao.Outros);
   const baseUrl = baseUrlComBarra(integracao.Url);
 
-  const res = await fetch(`${baseUrl}${path}`, {
-    method,
-    headers: {
-      Authorization: `Bearer ${token}`,
-      ...(body ? { "Content-Type": "application/json" } : {})
-    },
-    body: body ? JSON.stringify(body) : undefined,
-    signal: AbortSignal.timeout(15_000)
-  });
-
-  if (!res.ok) {
-    const err = new Error(`ixc_integrador_fetch_error:${res.status}`);
+  let res;
+  try {
+    res = await ixcHttpClient.request({
+      url: `${baseUrl}${path}`,
+      method,
+      headers: { Authorization: `Bearer ${token}` },
+      data: body
+    });
+  } catch (error) {
+    const err = new Error(`ixc_integrador_fetch_error:${error.response?.status ?? "sem_resposta"}`);
     err.statusCode = 502;
     throw err;
   }
 
-  try {
-    return await res.json();
-  } catch {
+  // axios tenta fazer JSON.parse internamente e, se falhar (ex: HTML de erro),
+  // devolve o corpo cru como string em vez de lancar - detectamos isso aqui
+  // pra manter o mesmo contrato de erro que o res.json() do fetch tinha.
+  if (typeof res.data === "string") {
     const err = new Error("ixc_integrador_invalid_json");
     err.statusCode = 502;
     throw err;
   }
+
+  return res.data;
 }
 
 export async function buscarClientes(busca) {