UNPKG

@nostr-dev-kit/blossom

Version:

Blossom protocol support for NDK (Nostr Development Kit)

127 lines 4.94 kB
import { ErrorCodes } from "../types"; import { DEFAULT_HEADERS, DEFAULT_RETRY_OPTIONS, SERVER_ERROR_STATUS_CODES } from "./constants"; import { NDKBlossomServerError } from "./errors"; import { DebugLogger } from "./logger"; // Default logger const defaultLogger = new DebugLogger(); /** * Make an HTTP request with retry capability * * @param url URL to fetch * @param options Fetch options * @param retryOptions Retry configuration * @param logger Logger instance * @returns Fetch response */ export async function fetchWithRetry(url, options = {}, retryOptions = {}, logger = defaultLogger) { // Set up retry options with defaults const retry = { ...DEFAULT_RETRY_OPTIONS, ...retryOptions, }; // Set up headers with defaults const headers = { ...DEFAULT_HEADERS, ...(options.headers || {}), }; // Number of attempts made let attempts = 0; // Function to calculate the next delay using exponential backoff const getNextDelay = () => retry.retryDelay * retry.backoffFactor ** attempts; while (attempts <= retry.maxRetries) { try { // Make the request const response = await fetch(url, { ...options, headers, }); // Check for retryable status codes if (!response.ok && retry.retryableStatusCodes.includes(response.status) && attempts < retry.maxRetries) { attempts++; const delay = getNextDelay(); logger.warn(`Request failed with status ${response.status}, retrying in ${delay}ms (attempt ${attempts}/${retry.maxRetries})`, { url }); await new Promise((resolve) => setTimeout(resolve, delay)); continue; } // Return successful response or non-retryable error return response; } catch (error) { // Only retry on network errors if (attempts < retry.maxRetries) { attempts++; const delay = getNextDelay(); logger.warn(`Network error, retrying in ${delay}ms (attempt ${attempts}/${retry.maxRetries})`, { url, error, }); await new Promise((resolve) => setTimeout(resolve, delay)); } else { // Throw custom error after all retries failed throw new NDKBlossomServerError(`Network request failed after ${retry.maxRetries} retries: ${error.message}`, ErrorCodes.SERVER_UNAVAILABLE, url, undefined, error); } } } // This should never be reached but TS requires a return throw new NDKBlossomServerError(`Request failed after ${retry.maxRetries} retries`, ErrorCodes.SERVER_UNAVAILABLE, url); } /** * Make a HEAD request to check if a resource exists * * @param url URL to check * @param options Fetch options * @param retryOptions Retry configuration * @returns true if resource exists (2xx status), false otherwise */ export async function checkResourceExists(url, options = {}, retryOptions = {}) { try { const response = await fetchWithRetry(url, { ...options, method: "HEAD", }, retryOptions); return response.ok; } catch (error) { return false; } } /** * Check if a Blossom server has a blob by its hash * * @param serverUrl Base URL of the server * @param hash SHA256 hash of the blob * @param retryOptions Retry configuration * @returns true if the blob exists, false otherwise */ export async function checkBlobExists(serverUrl, hash, retryOptions = {}) { // Normalize the server URL const baseUrl = serverUrl.endsWith("/") ? serverUrl.slice(0, -1) : serverUrl; const url = `${baseUrl}/${hash}`; return checkResourceExists(url, { method: "HEAD" }, retryOptions); } /** * Extract JSON from a response with error handling * * @param response Fetch Response object * @param serverUrl Original server URL for error context * @returns Parsed JSON data */ export async function extractResponseJson(response, serverUrl) { if (!response.ok) { // Handle different error status codes if (SERVER_ERROR_STATUS_CODES.includes(response.status)) { throw new NDKBlossomServerError(`Server error: ${response.status} ${response.statusText}`, ErrorCodes.SERVER_ERROR, serverUrl, response.status); } else { throw new NDKBlossomServerError(`Request rejected: ${response.status} ${response.statusText}`, ErrorCodes.SERVER_REJECTED, serverUrl, response.status); } } try { return await response.json(); } catch (error) { throw new NDKBlossomServerError(`Invalid JSON response: ${error.message}`, ErrorCodes.SERVER_INVALID_RESPONSE, serverUrl, response.status, error); } } //# sourceMappingURL=http.js.map