trainingpeaks-sdk
Version:
TypeScript SDK for TrainingPeaks API integration
314 lines (313 loc) • 11.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createHttpClient = exports.handleError = void 0;
const axios_1 = __importDefault(require("axios"));
const axios_cookiejar_support_1 = require("axios-cookiejar-support");
const tough_cookie_1 = require("tough-cookie");
const http_errors_1 = require("../../adapters/errors/http-errors.js");
const browser_utils_1 = require("../../shared/utils/browser-utils.js");
const curl_generator_1 = require("../../shared/utils/curl-generator.js");
const request_id_1 = require("../../shared/utils/request-id.js");
const request_with_refresh_1 = require("./request-with-refresh.js");
const retry_handler_1 = require("./retry-handler.js");
const normalizeHttpClientConfig = (config) => {
const browserHeaders = config.enableBrowserHeaders !== false
? (0, browser_utils_1.generateConsistentBrowserHeaders)()
: {};
return {
timeout: config.timeout ?? 30000,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
...browserHeaders,
...config.headers,
},
maxRedirects: config.maxRedirects ?? 5,
enableCookies: config.enableCookies,
enableBrowserHeaders: config.enableBrowserHeaders,
retryAttempts: config.retryAttempts ?? retry_handler_1.DEFAULT_RETRY_CONFIG.attempts,
retryDelay: config.retryDelay ?? retry_handler_1.DEFAULT_RETRY_CONFIG.delay,
retryBackoff: config.retryBackoff ?? retry_handler_1.DEFAULT_RETRY_CONFIG.backoff,
retryMaxDelay: config.retryMaxDelay ?? retry_handler_1.DEFAULT_RETRY_CONFIG.maxDelay,
retryJitter: config.retryJitter ?? retry_handler_1.DEFAULT_RETRY_CONFIG.jitter,
logger: config.logger,
sessionStorage: config.sessionStorage,
};
};
const createAxiosInstance = (config) => {
const normalizedConfig = normalizeHttpClientConfig(config);
const client = axios_1.default.create({
timeout: normalizedConfig.timeout,
headers: normalizedConfig.headers,
maxRedirects: normalizedConfig.maxRedirects,
});
let jar;
if (normalizedConfig.enableCookies) {
jar = new tough_cookie_1.CookieJar();
(0, axios_cookiejar_support_1.wrapper)(client);
client.defaults.jar = jar;
client.defaults.withCredentials = true;
}
client.interceptors.request.use((config) => {
return config;
}, (error) => {
if (error instanceof Error) {
return Promise.reject(error);
}
return Promise.reject(new Error(typeof error === 'string'
? error
: 'Unknown error in request interceptor'));
});
client.interceptors.response.use((response) => {
return response;
}, (error) => {
if (error instanceof Error) {
return Promise.reject(error);
}
return Promise.reject(new Error(typeof error === 'string'
? error
: 'Unknown error in response interceptor'));
});
return { client, jar };
};
const createAxiosConfig = async (baseConfig, config) => {
const axiosConfig = {
method: config.method.toLowerCase(),
url: config.url,
data: config.data,
params: config.options?.params,
headers: {
...baseConfig.headers,
...config.options?.headers,
},
timeout: config.options?.timeout ?? baseConfig.timeout,
withCredentials: baseConfig.enableCookies ?? false,
};
if (baseConfig.sessionStorage) {
try {
const session = await baseConfig.sessionStorage.get();
if (session?.token?.accessToken) {
const currentHeaders = axiosConfig.headers || {};
const authHeaderExists = Object.keys(currentHeaders).some((key) => key.toLowerCase() === 'authorization');
axiosConfig.headers = {
...currentHeaders,
...(authHeaderExists
? {}
: { Authorization: `Bearer ${session.token.accessToken}` }),
};
}
}
catch (error) {
if (baseConfig.logger) {
baseConfig.logger.warn('Failed to retrieve session for Bearer token', {
error,
});
}
}
}
if (config.options?.cookies && config.options.cookies.length > 0) {
const currentHeaders = axiosConfig.headers || {};
const existingCookieKey = Object.keys(currentHeaders).find((key) => key.toLowerCase() === 'cookie');
const existingCookies = existingCookieKey
? currentHeaders[existingCookieKey]
: undefined;
const newCookies = config.options.cookies.join('; ');
const cookieValue = existingCookies
? `${existingCookies}; ${newCookies}`
: newCookies;
axiosConfig.headers = {
...currentHeaders,
Cookie: cookieValue,
};
}
return axiosConfig;
};
const makeRequest = async (client, jar, baseConfig, config) => {
const initialAxiosConfig = await createAxiosConfig(baseConfig, config);
const retryHandler = new retry_handler_1.RetryHandler({
attempts: baseConfig.retryAttempts,
delay: baseConfig.retryDelay,
backoff: baseConfig.retryBackoff,
maxDelay: baseConfig.retryMaxDelay,
jitter: baseConfig.retryJitter,
});
if (baseConfig.logger) {
try {
const curlData = {
method: config.method,
url: config.url,
headers: initialAxiosConfig.headers,
data: initialAxiosConfig.data,
};
if (jar) {
const cookieStr = await jar.getCookieString(config.url);
if (cookieStr) {
curlData.cookies = cookieStr.split(';').map((c) => c.trim());
}
}
const curlCommand = (0, curl_generator_1.generateCurlCommand)(curlData);
baseConfig.logger.debug(`HTTP Request as cURL:\n${curlCommand}`);
}
catch (curlError) {
baseConfig.logger.warn('Failed to generate cURL command', {
error: curlError,
});
}
}
const requestId = (0, request_id_1.generateRequestId)();
if (baseConfig.logger) {
baseConfig.logger.debug('HTTP Request starting', {
requestId,
method: config.method,
url: config.url,
hasData: !!config.data,
hasParams: !!config.options?.params,
});
}
try {
const response = await retryHandler.execute(async () => {
const axiosConfig = await createAxiosConfig(baseConfig, config);
if (baseConfig.sessionStorage && baseConfig.logger) {
return (0, request_with_refresh_1.executeRequestWithRefresh)(axiosConfig, config, {
client,
sessionStorage: baseConfig.sessionStorage,
logger: baseConfig.logger,
maxRefreshRetries: 1,
});
}
else {
return client.request(axiosConfig);
}
}, {
url: config.url,
method: config.method,
requestId,
logger: baseConfig.logger,
});
let cookies = [];
if (jar) {
const cookieStr = await jar.getCookieString(config.url);
cookies = cookieStr ? cookieStr.split(';').map((c) => c.trim()) : [];
}
if (baseConfig.logger) {
baseConfig.logger.info('HTTP Request successful', {
requestId,
method: config.method,
url: config.url,
statusCode: response.status,
statusText: response.statusText,
hasData: !!response.data,
cookieCount: cookies.length,
});
}
return {
data: response.data,
success: true,
cookies,
};
}
catch (error) {
if (baseConfig.logger) {
baseConfig.logger.error('HTTP Request failed (final)', {
requestId,
method: config.method,
url: config.url,
error: error instanceof Error ? error.message : String(error),
errorCode: error && typeof error === 'object' && 'code' in error
? error.code
: undefined,
stack: error instanceof Error ? error.stack : undefined,
});
}
return {
data: null,
success: false,
error: (0, exports.handleError)(error, config, baseConfig.logger, requestId),
};
}
};
const handleError = (error, requestConfig, logger, requestId) => {
if (axios_1.default.isAxiosError(error)) {
const response = error.response;
const request = error.request;
if (response) {
const httpErrorResponse = {
status: response.status,
statusText: response.statusText,
data: response.data,
headers: response.headers,
};
return (0, http_errors_1.createHttpError)(httpErrorResponse, {
url: requestConfig.url,
method: requestConfig.method,
requestData: requestConfig.data,
requestId,
});
}
else if (request) {
const httpErrorResponse = {
status: 0,
statusText: 'Network Error',
data: { message: error.message },
};
return (0, http_errors_1.createHttpError)(httpErrorResponse, {
url: requestConfig.url,
method: requestConfig.method,
requestData: requestConfig.data,
requestId,
});
}
}
const httpErrorResponse = {
status: 0,
statusText: 'Unknown Error',
data: {
message: error instanceof Error ? error.message : 'Unknown error occurred',
},
};
return (0, http_errors_1.createHttpError)(httpErrorResponse, {
url: requestConfig.url,
method: requestConfig.method,
requestData: requestConfig.data,
requestId,
});
};
exports.handleError = handleError;
const createHttpClient = (config = {}) => {
const { client, jar } = createAxiosInstance(config);
const normalizedConfig = normalizeHttpClientConfig(config);
return {
get: (url, options) => makeRequest(client, jar, normalizedConfig, {
url,
method: 'GET',
options,
}),
post: (url, data, options) => makeRequest(client, jar, normalizedConfig, {
url,
method: 'POST',
data,
options,
}),
put: (url, data, options) => makeRequest(client, jar, normalizedConfig, {
url,
method: 'PUT',
data,
options,
}),
patch: (url, data, options) => makeRequest(client, jar, normalizedConfig, {
url,
method: 'PATCH',
data,
options,
}),
delete: (url, options) => makeRequest(client, jar, normalizedConfig, {
url,
method: 'DELETE',
options,
}),
};
};
exports.createHttpClient = createHttpClient;