@allanfsouza/aether-sdk 2.4.5 → 2.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http-client.d.ts +1 -0
- package/dist/http-client.js +91 -4
- package/package.json +2 -2
- package/src/http-client.ts +117 -5
package/dist/http-client.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { PlataformaClient } from "./index.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Cria uma instância do Axios pré-configurada.
|
|
5
5
|
* - Injeta automaticamente headers de Auth e Projeto.
|
|
6
|
+
* - Renova token automaticamente quando expira (401).
|
|
6
7
|
* - Converte erros em AetherError.
|
|
7
8
|
*/
|
|
8
9
|
export declare function createHttpClient(client: PlataformaClient): AxiosInstance;
|
package/dist/http-client.js
CHANGED
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
// src/http-client.ts
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import { handleAxiosError } from "./errors.js";
|
|
4
|
+
// Flag para evitar múltiplos refreshes simultâneos
|
|
5
|
+
let isRefreshing = false;
|
|
6
|
+
// Fila de requisições aguardando o refresh
|
|
7
|
+
let failedQueue = [];
|
|
8
|
+
/**
|
|
9
|
+
* Processa a fila de requisições após o refresh.
|
|
10
|
+
*/
|
|
11
|
+
const processQueue = (error, token = null) => {
|
|
12
|
+
failedQueue.forEach((prom) => {
|
|
13
|
+
if (error) {
|
|
14
|
+
prom.reject(error);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
prom.resolve(token);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
failedQueue = [];
|
|
21
|
+
};
|
|
4
22
|
/**
|
|
5
23
|
* Cria uma instância do Axios pré-configurada.
|
|
6
24
|
* - Injeta automaticamente headers de Auth e Projeto.
|
|
25
|
+
* - Renova token automaticamente quando expira (401).
|
|
7
26
|
* - Converte erros em AetherError.
|
|
8
27
|
*/
|
|
9
28
|
export function createHttpClient(client) {
|
|
10
29
|
const http = axios.create({
|
|
11
|
-
// Adiciona o /v1 automaticamente em todas as chamadas do SDK
|
|
12
30
|
baseURL: `${client.apiUrl}/v1`,
|
|
13
31
|
headers: {
|
|
14
32
|
"Content-Type": "application/json",
|
|
15
33
|
},
|
|
16
34
|
});
|
|
17
|
-
//
|
|
35
|
+
// ===========================================================================
|
|
36
|
+
// INTERCEPTOR DE REQUEST
|
|
37
|
+
// Injeta token e projectId em todas as requisições
|
|
38
|
+
// ===========================================================================
|
|
18
39
|
http.interceptors.request.use((config) => {
|
|
19
40
|
const token = client.getToken();
|
|
20
41
|
if (token) {
|
|
@@ -25,7 +46,73 @@ export function createHttpClient(client) {
|
|
|
25
46
|
}
|
|
26
47
|
return config;
|
|
27
48
|
});
|
|
28
|
-
//
|
|
29
|
-
|
|
49
|
+
// ===========================================================================
|
|
50
|
+
// INTERCEPTOR DE RESPONSE
|
|
51
|
+
// Detecta 401 e tenta refresh automático do token
|
|
52
|
+
// ===========================================================================
|
|
53
|
+
http.interceptors.response.use((response) => response, async (error) => {
|
|
54
|
+
const originalRequest = error.config;
|
|
55
|
+
// Verifica se é erro 401 (não autorizado) e não é retry
|
|
56
|
+
const isUnauthorized = error.response?.status === 401;
|
|
57
|
+
const isRetry = originalRequest?._retry;
|
|
58
|
+
const isAuthRoute = originalRequest?.url?.includes("/auth/");
|
|
59
|
+
// Não tenta refresh se:
|
|
60
|
+
// - Não é 401
|
|
61
|
+
// - Já é um retry
|
|
62
|
+
// - É uma rota de auth (login, register, refresh)
|
|
63
|
+
// - Não tem refresh token disponível
|
|
64
|
+
if (!isUnauthorized || isRetry || isAuthRoute) {
|
|
65
|
+
return handleAxiosError(error);
|
|
66
|
+
}
|
|
67
|
+
const refreshToken = client.getRefreshToken();
|
|
68
|
+
if (!refreshToken) {
|
|
69
|
+
// Sem refresh token, limpa sessão e propaga erro
|
|
70
|
+
client.clearSession();
|
|
71
|
+
return handleAxiosError(error);
|
|
72
|
+
}
|
|
73
|
+
// Se já está fazendo refresh, aguarda na fila
|
|
74
|
+
if (isRefreshing) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
failedQueue.push({ resolve, reject });
|
|
77
|
+
})
|
|
78
|
+
.then((token) => {
|
|
79
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
80
|
+
return http(originalRequest);
|
|
81
|
+
})
|
|
82
|
+
.catch((err) => handleAxiosError(err));
|
|
83
|
+
}
|
|
84
|
+
// Marca como retry e inicia refresh
|
|
85
|
+
originalRequest._retry = true;
|
|
86
|
+
isRefreshing = true;
|
|
87
|
+
try {
|
|
88
|
+
// Chama endpoint de refresh diretamente (sem interceptor)
|
|
89
|
+
const { data } = await axios.post(`${client.apiUrl}/v1/auth/refresh`, { refreshToken }, { headers: { "Content-Type": "application/json" } });
|
|
90
|
+
const newAccessToken = data.accessToken;
|
|
91
|
+
const newRefreshToken = data.refreshToken;
|
|
92
|
+
// Atualiza tokens no client (persiste no localStorage)
|
|
93
|
+
client.setToken(newAccessToken);
|
|
94
|
+
if (newRefreshToken) {
|
|
95
|
+
client.setRefreshToken(newRefreshToken);
|
|
96
|
+
}
|
|
97
|
+
// Processa fila de requisições pendentes
|
|
98
|
+
processQueue(null, newAccessToken);
|
|
99
|
+
// Refaz a requisição original com novo token
|
|
100
|
+
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
|
|
101
|
+
return http(originalRequest);
|
|
102
|
+
}
|
|
103
|
+
catch (refreshError) {
|
|
104
|
+
// Refresh falhou - sessão expirada
|
|
105
|
+
processQueue(refreshError, null);
|
|
106
|
+
client.clearSession();
|
|
107
|
+
// Emite evento de sessão expirada (se houver listener)
|
|
108
|
+
if (typeof window !== "undefined") {
|
|
109
|
+
window.dispatchEvent(new CustomEvent("aether:session-expired"));
|
|
110
|
+
}
|
|
111
|
+
return handleAxiosError(refreshError);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
isRefreshing = false;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
30
117
|
return http;
|
|
31
118
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allanfsouza/aether-sdk",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.6",
|
|
4
4
|
"description": "SDK do Cliente para a Plataforma Aether",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -32,4 +32,4 @@
|
|
|
32
32
|
"@types/ws": "^8.5.10",
|
|
33
33
|
"typescript": "^5.3.0"
|
|
34
34
|
}
|
|
35
|
-
}
|
|
35
|
+
}
|
package/src/http-client.ts
CHANGED
|
@@ -1,23 +1,53 @@
|
|
|
1
1
|
// src/http-client.ts
|
|
2
|
-
import axios, {
|
|
2
|
+
import axios, {
|
|
3
|
+
type AxiosInstance,
|
|
4
|
+
type AxiosError,
|
|
5
|
+
type InternalAxiosRequestConfig,
|
|
6
|
+
} from "axios";
|
|
3
7
|
import type { PlataformaClient } from "./index.js";
|
|
4
8
|
import { handleAxiosError } from "./errors.js";
|
|
5
9
|
|
|
10
|
+
// Flag para evitar múltiplos refreshes simultâneos
|
|
11
|
+
let isRefreshing = false;
|
|
12
|
+
|
|
13
|
+
// Fila de requisições aguardando o refresh
|
|
14
|
+
let failedQueue: Array<{
|
|
15
|
+
resolve: (token: string) => void;
|
|
16
|
+
reject: (error: any) => void;
|
|
17
|
+
}> = [];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Processa a fila de requisições após o refresh.
|
|
21
|
+
*/
|
|
22
|
+
const processQueue = (error: any, token: string | null = null) => {
|
|
23
|
+
failedQueue.forEach((prom) => {
|
|
24
|
+
if (error) {
|
|
25
|
+
prom.reject(error);
|
|
26
|
+
} else {
|
|
27
|
+
prom.resolve(token!);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
failedQueue = [];
|
|
31
|
+
};
|
|
32
|
+
|
|
6
33
|
/**
|
|
7
34
|
* Cria uma instância do Axios pré-configurada.
|
|
8
35
|
* - Injeta automaticamente headers de Auth e Projeto.
|
|
36
|
+
* - Renova token automaticamente quando expira (401).
|
|
9
37
|
* - Converte erros em AetherError.
|
|
10
38
|
*/
|
|
11
39
|
export function createHttpClient(client: PlataformaClient): AxiosInstance {
|
|
12
40
|
const http = axios.create({
|
|
13
|
-
// Adiciona o /v1 automaticamente em todas as chamadas do SDK
|
|
14
41
|
baseURL: `${client.apiUrl}/v1`,
|
|
15
42
|
headers: {
|
|
16
43
|
"Content-Type": "application/json",
|
|
17
44
|
},
|
|
18
45
|
});
|
|
19
46
|
|
|
20
|
-
//
|
|
47
|
+
// ===========================================================================
|
|
48
|
+
// INTERCEPTOR DE REQUEST
|
|
49
|
+
// Injeta token e projectId em todas as requisições
|
|
50
|
+
// ===========================================================================
|
|
21
51
|
http.interceptors.request.use((config) => {
|
|
22
52
|
const token = client.getToken();
|
|
23
53
|
if (token) {
|
|
@@ -31,10 +61,92 @@ export function createHttpClient(client: PlataformaClient): AxiosInstance {
|
|
|
31
61
|
return config;
|
|
32
62
|
});
|
|
33
63
|
|
|
34
|
-
//
|
|
64
|
+
// ===========================================================================
|
|
65
|
+
// INTERCEPTOR DE RESPONSE
|
|
66
|
+
// Detecta 401 e tenta refresh automático do token
|
|
67
|
+
// ===========================================================================
|
|
35
68
|
http.interceptors.response.use(
|
|
36
69
|
(response) => response,
|
|
37
|
-
(error) =>
|
|
70
|
+
async (error: AxiosError) => {
|
|
71
|
+
const originalRequest = error.config as InternalAxiosRequestConfig & {
|
|
72
|
+
_retry?: boolean;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Verifica se é erro 401 (não autorizado) e não é retry
|
|
76
|
+
const isUnauthorized = error.response?.status === 401;
|
|
77
|
+
const isRetry = originalRequest?._retry;
|
|
78
|
+
const isAuthRoute = originalRequest?.url?.includes("/auth/");
|
|
79
|
+
|
|
80
|
+
// Não tenta refresh se:
|
|
81
|
+
// - Não é 401
|
|
82
|
+
// - Já é um retry
|
|
83
|
+
// - É uma rota de auth (login, register, refresh)
|
|
84
|
+
// - Não tem refresh token disponível
|
|
85
|
+
if (!isUnauthorized || isRetry || isAuthRoute) {
|
|
86
|
+
return handleAxiosError(error);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const refreshToken = client.getRefreshToken();
|
|
90
|
+
if (!refreshToken) {
|
|
91
|
+
// Sem refresh token, limpa sessão e propaga erro
|
|
92
|
+
client.clearSession();
|
|
93
|
+
return handleAxiosError(error);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Se já está fazendo refresh, aguarda na fila
|
|
97
|
+
if (isRefreshing) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
failedQueue.push({ resolve, reject });
|
|
100
|
+
})
|
|
101
|
+
.then((token) => {
|
|
102
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
103
|
+
return http(originalRequest);
|
|
104
|
+
})
|
|
105
|
+
.catch((err) => handleAxiosError(err));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Marca como retry e inicia refresh
|
|
109
|
+
originalRequest._retry = true;
|
|
110
|
+
isRefreshing = true;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// Chama endpoint de refresh diretamente (sem interceptor)
|
|
114
|
+
const { data } = await axios.post(
|
|
115
|
+
`${client.apiUrl}/v1/auth/refresh`,
|
|
116
|
+
{ refreshToken },
|
|
117
|
+
{ headers: { "Content-Type": "application/json" } }
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const newAccessToken = data.accessToken;
|
|
121
|
+
const newRefreshToken = data.refreshToken;
|
|
122
|
+
|
|
123
|
+
// Atualiza tokens no client (persiste no localStorage)
|
|
124
|
+
client.setToken(newAccessToken);
|
|
125
|
+
if (newRefreshToken) {
|
|
126
|
+
client.setRefreshToken(newRefreshToken);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Processa fila de requisições pendentes
|
|
130
|
+
processQueue(null, newAccessToken);
|
|
131
|
+
|
|
132
|
+
// Refaz a requisição original com novo token
|
|
133
|
+
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
|
|
134
|
+
return http(originalRequest);
|
|
135
|
+
} catch (refreshError: any) {
|
|
136
|
+
// Refresh falhou - sessão expirada
|
|
137
|
+
processQueue(refreshError, null);
|
|
138
|
+
client.clearSession();
|
|
139
|
+
|
|
140
|
+
// Emite evento de sessão expirada (se houver listener)
|
|
141
|
+
if (typeof window !== "undefined") {
|
|
142
|
+
window.dispatchEvent(new CustomEvent("aether:session-expired"));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return handleAxiosError(refreshError);
|
|
146
|
+
} finally {
|
|
147
|
+
isRefreshing = false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
38
150
|
);
|
|
39
151
|
|
|
40
152
|
return http;
|