-
Notifications
You must be signed in to change notification settings - Fork 117
/
Copy pathindex.ts
125 lines (103 loc) · 3.31 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import fetch, { Response, RequestInit } from 'node-fetch';
import { AppConfig } from '@utils/config';
import { v4 as uuidv4 } from 'uuid';
import type { Options } from '@src/types';
interface RestClientConfig extends Options {
queryParams?: Record<string, string | number>;
retries?: number;
}
class RestClient {
private static generateIdempotencyKey(): string {
return uuidv4();
}
static appendQueryParamsToUrl(url: string, queryParams?: Record<string, string | number>): string {
if (!queryParams) return url;
const searchParams = new URLSearchParams();
for (const key in queryParams) {
if (Object.prototype.hasOwnProperty.call(queryParams, key)) {
searchParams.append(key, queryParams[key].toString());
}
}
return url.includes('?') ? `${url}&${searchParams.toString()}` : `${url}?${searchParams.toString()}`;
}
private static async retryWithExponentialBackoff<T>(
fn: () => Promise<T>,
retries: number,
): Promise<T> {
let attempt = 1;
const execute = async () => {
try {
return await fn();
} catch (error) {
if (attempt >= retries || (error.status < 500)) {
throw error;
}
const delayMs = AppConfig.BASE_DELAY_MS * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
attempt++;
return execute();
}
};
return execute();
}
static async fetch<T>(
endpoint: string,
config?: RestClientConfig & RequestInit
): Promise<T> {
const {
timeout = AppConfig.DEFAULT_TIMEOUT,
idempotencyKey = RestClient.generateIdempotencyKey(),
queryParams,
method = 'GET',
retries = AppConfig.DEFAULT_RETRIES,
corporationId,
integratorId,
plataformId,
meliSessionId,
expandResponseNodes,
cardValidation,
...customConfig
} = config || {};
const url = RestClient.appendQueryParamsToUrl(`${AppConfig.BASE_URL}${endpoint}`, queryParams);
customConfig.headers = {
...customConfig.headers,
[AppConfig.Headers.CONTENT_TYPE]: 'application/json',
[AppConfig.Headers.PRODUCT_ID]: AppConfig.PRODUCT_ID,
[AppConfig.Headers.TRACKING_ID]: AppConfig.getTrackingId(),
[AppConfig.Headers.USER_AGENT]: AppConfig.getUserAgent(),
...(corporationId ? { [AppConfig.Headers.CORPORATION_ID]: corporationId } : {}),
...(integratorId ? { [AppConfig.Headers.INTEGRATOR_ID]: integratorId } : {}),
...(plataformId ? { [AppConfig.Headers.PLATFORM_ID]: plataformId } : {}),
...(meliSessionId ? { [AppConfig.Headers.MELI_SESSION_ID]: meliSessionId } : {}),
...(expandResponseNodes ? { [AppConfig.Headers.EXPAND_RESPONDE_NODES]: expandResponseNodes } : {}),
...(cardValidation ? { [AppConfig.Headers.CARD_VALIDATION]: cardValidation } : {}),
};
if (method && method !== 'GET') {
customConfig.headers = {
...customConfig.headers,
[AppConfig.Headers.IDEMPOTENCY_KEY]: idempotencyKey,
};
}
let response: Response;
const fetchFn = async () => {
response = await fetch(url, {
...customConfig,
method,
timeout,
});
if (response.ok) {
const data = await response.json();
const api_response = {
status: response.status,
headers: response.headers.raw(),
};
data.api_response = api_response;
return data as T;
} else {
throw await response.json();
}
};
return await RestClient.retryWithExponentialBackoff(fetchFn, retries);
}
}
export { RestClient };