react-native-apikit
Version:
Modern API toolkit for React Native and Expo with automatic token management, smart response parsing, and built-in error handling
194 lines (193 loc) • 9.09 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.apiClient = void 0;
const config_1 = require("./config");
function request(methodOrConfig, urlOrConfig, data, config) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e;
let method = 'GET';
let url = '';
let finalConfig = {};
let body = undefined;
if (typeof methodOrConfig === 'string') {
method = methodOrConfig;
url = urlOrConfig;
body = data;
finalConfig = config || {};
}
else {
finalConfig = methodOrConfig;
method = (finalConfig.method || 'GET');
url = finalConfig.url || '';
body = finalConfig.data;
}
if (!url) {
return { data: null, error: { message: 'ApiKit: URL is required.' } };
}
const apiConfig = (0, config_1.getApiKitConfig)();
const engine = (0, config_1.getEngine)();
if (!apiConfig.baseUrl && !url.startsWith('http')) {
return { data: null, error: { message: 'ApiKit: baseUrl not configured. Please call configureApiKit first.' } };
}
const maxRetries = typeof apiConfig.retry === 'number' ? apiConfig.retry : 0;
let attempt = 0;
let lastError = null;
// --- Caching logic (GET only) ---
const isGet = (typeof methodOrConfig === 'string' ? methodOrConfig : methodOrConfig.method) === 'GET';
let cacheKey = '';
if (isGet && apiConfig.cache) {
// Build cache key from URL and params
let url = typeof methodOrConfig === 'string' ? urlOrConfig : methodOrConfig.url;
let params = (config && config.params) || (typeof methodOrConfig === 'object' && methodOrConfig.params);
cacheKey = url;
if (params) {
cacheKey += '?' + Object.entries(params).map(([k, v]) => `${k}=${v}`).join('&');
}
const cached = yield apiConfig.cache.get(cacheKey);
if (cached) {
return { data: cached.data, error: null, status: cached.status, raw: cached.raw };
}
}
while (attempt <= maxRetries) {
try {
// Get token if storage is configured
let token = null;
if (apiConfig.tokenStorage) {
try {
token = yield apiConfig.tokenStorage.getToken();
}
catch (error) {
// ignore
}
}
// Prepare headers
const headers = Object.assign(Object.assign({ 'Content-Type': 'application/json' }, apiConfig.headers), finalConfig.headers);
if (token) {
headers.Authorization = `Bearer ${token}`;
}
// Build full URL
const fullUrl = url.startsWith('http') ? url : `${apiConfig.baseUrl}${url}`;
let finalUrl = fullUrl;
if (finalConfig.params) {
const params = new URLSearchParams();
Object.entries(finalConfig.params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
params.append(key, String(value));
}
});
const queryString = params.toString();
if (queryString) {
finalUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
}
}
// Prepare request config
const requestConfig = {
url: finalUrl,
method,
data: method !== 'GET' ? body : undefined,
headers,
timeout: finalConfig.timeout || apiConfig.timeout,
signal: finalConfig.signal,
};
// --- Interceptor: onRequest ---
let interceptedRequestConfig = requestConfig;
if (apiConfig.onRequest) {
interceptedRequestConfig = yield apiConfig.onRequest(requestConfig);
}
// Make request
const response = yield engine.request(interceptedRequestConfig);
// --- Interceptor: onResponse ---
let interceptedResponse = response;
if (apiConfig.onResponse) {
interceptedResponse = yield apiConfig.onResponse(response);
}
// Handle 401 globally
if (interceptedResponse.status === 401 && apiConfig.onUnauthorized) {
apiConfig.onUnauthorized();
}
// After successful GET, store in cache
if (isGet && apiConfig.cache && interceptedResponse.data !== undefined) {
const ttl = (config && config.cacheTtlMs) || 60000; // 1 min default
yield apiConfig.cache.set(cacheKey, { data: interceptedResponse.data, status: interceptedResponse.status, raw: interceptedResponse.raw }, ttl);
}
return { data: interceptedResponse.data, error: null, status: interceptedResponse.status, raw: interceptedResponse.raw };
}
catch (error) {
let apiError;
if (error.name === 'AbortError') {
apiError = { message: 'Request cancelled', isNetworkError: false };
}
else if ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('Network request failed')) {
apiError = { message: 'No internet connection', isNetworkError: true };
}
else if (error.code === 'ECONNABORTED' || ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('timeout'))) {
apiError = { message: 'Request timeout', isTimeout: true };
}
else {
apiError = {
message: error.message || 'An error occurred',
status: (_c = error.response) === null || _c === void 0 ? void 0 : _c.status,
details: (_d = error.response) === null || _d === void 0 ? void 0 : _d.data,
isUnauthorized: ((_e = error.response) === null || _e === void 0 ? void 0 : _e.status) === 401,
};
}
// --- Interceptor: onError ---
if (apiConfig.onError) {
apiError = yield apiConfig.onError(apiError);
}
lastError = apiError;
// Only retry on network error or timeout
if ((apiError.isNetworkError || apiError.isTimeout) && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 200; // Exponential backoff: 200ms, 400ms, 800ms, ...
yield new Promise(res => setTimeout(res, delay));
attempt++;
continue;
}
return { data: null, error: apiError };
}
}
// If all retries failed
return { data: null, error: lastError };
});
}
exports.apiClient = {
get(url, config) {
return __awaiter(this, void 0, void 0, function* () {
return request('GET', url, undefined, config);
});
},
post(url, data, config) {
return __awaiter(this, void 0, void 0, function* () {
return request('POST', url, data, config);
});
},
put(url, data, config) {
return __awaiter(this, void 0, void 0, function* () {
return request('PUT', url, data, config);
});
},
patch(url, data, config) {
return __awaiter(this, void 0, void 0, function* () {
return request('PATCH', url, data, config);
});
},
del(url, config) {
return __awaiter(this, void 0, void 0, function* () {
return request('DELETE', url, undefined, config);
});
},
request(config) {
return __awaiter(this, void 0, void 0, function* () {
return request(config);
});
},
};