UNPKG

mnotify-ts-sdk

Version:

Modern, zero-dependency TypeScript SDK for mNotify BMS API - Type-safe SMS, contacts, and account management with Railway-Oriented Programming

173 lines 7.41 kB
"use strict"; 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.HttpClient = void 0; const MNotifyError_1 = require("../errors/MNotifyError"); const Result_1 = require("../types/Result"); /** * Core HTTP client for mNotify API communication using native fetch * * @remarks * This class handles all low-level HTTP requests to the mNotify API, * including authentication, retries, and error handling. */ class HttpClient { /** * Creates a new HttpClient instance * @param config - Configuration object * @param config.apiKey - Your mNotify API key * @param config.baseUrl - Base API URL (default: 'https://api.mnotify.com/api') * @param config.timeout - Request timeout in ms (default: 10000) * @param config.maxRetries - Maximum retry attempts for failed requests (default: 3) */ constructor(config) { this.apiKey = config.apiKey; this.baseUrl = config.baseUrl || "https://api.mnotify.com/api"; this.timeout = config.timeout || 10000; this.maxRetries = config.maxRetries || 3; } /** * Makes an HTTP request to the mNotify API with Result type (railway-oriented programming) * @param config - Request configuration * @param retryCount - Current retry attempt (used internally) * @returns Promise with Result containing either the response data or an error * * @example * ```typescript * const client = new HttpClient({ apiKey: 'your-key' }); * const result = await client.requestSafe<T>({ * method: 'GET', * url: '/account/balance' * }); * * if (result.isOk()) { * console.log(result.value); * } else { * console.error(result.error); * } * ``` */ requestSafe(config_1) { return __awaiter(this, arguments, void 0, function* (config, retryCount = 0) { const url = this.buildUrl(config.url, config.params); const requestContext = { service: "HttpClient", operation: "requestSafe", stage: "request", method: config.method, path: config.url, url: this.redactUrl(url), retryCount, }; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); try { const response = yield fetch(url, { method: config.method, headers: { Authorization: this.apiKey, "Content-Type": "application/json", Accept: "application/json", }, body: config.data ? JSON.stringify(config.data) : undefined, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { // Handle rate limiting with retry if (response.status === 429 && retryCount < this.maxRetries) { const retryAfter = parseInt(response.headers.get("retry-after") || "1") * 1000; yield this.sleep(retryAfter); return this.requestSafe(config, retryCount + 1); } const errorData = yield response.json().catch(() => ({})); return (0, Result_1.err)(new MNotifyError_1.MNotifyError(errorData.message || response.statusText, response.status, errorData, Object.assign(Object.assign({}, requestContext), { stage: "response" }))); } const data = (yield response.json()); return (0, Result_1.ok)(data); } catch (error) { clearTimeout(timeoutId); if (error instanceof MNotifyError_1.MNotifyError) { return (0, Result_1.err)(error.withContext(Object.assign(Object.assign({}, requestContext), { stage: "network" }))); } // Handle timeout if (error instanceof Error && error.name === "AbortError") { return (0, Result_1.err)(MNotifyError_1.MNotifyError.fromUnknown(error, "Request timeout", 408, Object.assign(Object.assign({}, requestContext), { stage: "network" }))); } // Handle network errors return (0, Result_1.err)(MNotifyError_1.MNotifyError.fromUnknown(error, "Network error", 0, Object.assign(Object.assign({}, requestContext), { stage: "network" }))); } }); } /** * Makes an HTTP request to the mNotify API (throws on error - legacy API) * @param config - Request configuration * @param retryCount - Current retry attempt (used internally) * @returns Promise with the parsed response data * @throws {MNotifyError} When API returns an error response * * @example * ```typescript * const client = new HttpClient({ apiKey: 'your-key' }); * const response = await client.request<T>({ * method: 'GET', * url: '/account/balance' * }); * ``` */ request(config_1) { return __awaiter(this, arguments, void 0, function* (config, retryCount = 0) { const result = yield this.requestSafe(config, retryCount); return result.unwrap(); }); } /** * Builds the full URL with query parameters */ buildUrl(path, params) { // Ensure API base paths like ".../api" are preserved even when path starts with "/" const normalizedBase = this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`; const normalizedPath = path.startsWith("/") ? path.slice(1) : path; const url = new URL(normalizedPath, normalizedBase); // Always add API key as query param url.searchParams.set("key", this.apiKey); // Add additional params if provided if (params) { Object.entries(params).forEach(([key, value]) => { url.searchParams.set(key, value); }); } return url.toString(); } redactUrl(rawUrl) { try { const url = new URL(rawUrl); if (url.searchParams.has("key")) { url.searchParams.set("key", "***"); } return url.toString(); } catch (_a) { return rawUrl; } } /** * Sleep utility for retry logic */ sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } } exports.HttpClient = HttpClient; //# sourceMappingURL=HttpClient.js.map