@stratosphere-network/wallet
Version:
Wallet module for StratoSphere SDK
156 lines • 5.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MpesaHttpClient = void 0;
class MpesaHttpClient {
config;
baseUrl;
constructor(config) {
this.config = config;
this.baseUrl = this.getMpesaBaseUrl();
}
getMpesaBaseUrl() {
// Always use the production Mpesa service URL as specified
return "https://mpesa-offramping-onramping-production.up.railway.app";
}
async request(requestConfig) {
const { method, url, data, params, headers = {} } = requestConfig;
// Build URL with query parameters
const fullUrl = new URL(url, this.baseUrl);
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
fullUrl.searchParams.append(key, String(value));
}
});
}
// Build headers
const requestHeaders = {
"Content-Type": "application/json",
...headers,
};
// Add API key if available
if (this.config.apiKey) {
requestHeaders["X-API-Key"] = this.config.apiKey;
}
// Build fetch options
const fetchOptions = {
method,
headers: requestHeaders,
};
// Add body for POST/PUT requests
if (data && (method === "POST" || method === "PUT")) {
fetchOptions.body = JSON.stringify(data);
}
// Add timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, this.config.timeout || 30000);
fetchOptions.signal = controller.signal;
try {
const response = await this.executeWithRetry(fullUrl.toString(), fetchOptions);
clearTimeout(timeoutId);
return await this.handleResponse(response);
}
catch (error) {
clearTimeout(timeoutId);
throw this.handleError(error);
}
}
async executeWithRetry(url, options) {
const maxRetries = this.config.retries || 3;
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
// Don't retry on client errors (4xx), only on server errors (5xx) and network errors
if (response.ok || (response.status >= 400 && response.status < 500)) {
return response;
}
if (attempt === maxRetries) {
return response;
}
// Wait before retrying (exponential backoff)
await this.delay(Math.pow(2, attempt) * 1000);
}
catch (error) {
lastError = error;
if (attempt === maxRetries) {
throw lastError;
}
// Wait before retrying
await this.delay(Math.pow(2, attempt) * 1000);
}
}
throw lastError;
}
async handleResponse(response) {
const contentType = response.headers.get("content-type");
if (!response.ok) {
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
let errorDetails = {};
try {
if (contentType?.includes("application/json")) {
errorDetails = await response.json();
errorMessage =
errorDetails.message || errorDetails.error || errorMessage;
}
else {
errorMessage = (await response.text()) || errorMessage;
}
}
catch {
// If we can't parse the error response, use the default message
}
const apiError = {
message: errorMessage,
error: errorDetails.error,
status: response.status,
};
throw apiError;
}
// Handle different response types
if (response.status === 204) {
return {};
}
if (contentType?.includes("application/json")) {
return await response.json();
}
// For non-JSON responses, return as text
return (await response.text());
}
handleError(error) {
if (error.name === "AbortError") {
return {
message: "Request timeout",
error: "The request took too long to complete",
status: 408,
};
}
if (error.message && error.status) {
// Already an ApiError
return error;
}
if (error instanceof TypeError && error.message.includes("fetch")) {
return {
message: "Network error",
error: "Unable to connect to the server. Please check your internet connection.",
status: 0,
};
}
return {
message: error.message || "An unexpected error occurred",
error: error.toString(),
status: 500,
};
}
delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Method to update API key
setApiKey(apiKey) {
this.config.apiKey = apiKey;
}
}
exports.MpesaHttpClient = MpesaHttpClient;
//# sourceMappingURL=mpesa-http-client.js.map