UNPKG

ak-fetch

Version:

Production-ready HTTP client for bulk operations with connection pooling, exponential backoff, streaming, and comprehensive error handling

302 lines (259 loc) 9.12 kB
/** * Cookie jar implementation for session management */ import { CookieJar } from 'tough-cookie'; class AkCookieJar { constructor(options = {}) { this.jar = new CookieJar(options.store, options); this.enabled = options.enabled !== false; this.rejectPublicSuffixes = options.rejectPublicSuffixes !== false; this.allowSpecialUseDomain = options.allowSpecialUseDomain || false; } /** * Set a cookie from a Set-Cookie header * @param {string} cookieString - Cookie string from Set-Cookie header * @param {string} url - URL where the cookie was set * @returns {Promise<Cookie|null>} The cookie that was set */ async setCookie(cookieString, url) { if (!this.enabled) return null; try { return await this.jar.setCookie(cookieString, url); } catch (error) { console.warn(`Failed to set cookie: ${error.message}`); return null; } } /** * Set multiple cookies from Set-Cookie headers * @param {string[]} cookieStrings - Array of cookie strings * @param {string} url - URL where the cookies were set * @returns {Promise<Cookie[]>} Array of cookies that were set */ async setCookies(cookieStrings, url) { if (!this.enabled || !Array.isArray(cookieStrings)) return []; const cookies = []; for (const cookieString of cookieStrings) { const cookie = await this.setCookie(cookieString, url); if (cookie) cookies.push(cookie); } return cookies; } /** * Get cookies for a URL as a Cookie header value * @param {string} url - URL to get cookies for * @returns {Promise<string>} Cookie header value */ async getCookieString(url) { if (!this.enabled) return ''; try { return await this.jar.getCookieString(url); } catch (error) { console.warn(`Failed to get cookies: ${error.message}`); return ''; } } /** * Get cookies for a URL as Cookie objects * @param {string} url - URL to get cookies for * @returns {Promise<Cookie[]>} Array of cookies */ async getCookies(url) { if (!this.enabled) return []; try { return await this.jar.getCookies(url); } catch (error) { console.warn(`Failed to get cookies: ${error.message}`); return []; } } /** * Remove cookies that match the criteria * @param {string} domain - Domain to remove cookies from * @param {string} path - Path to remove cookies from * @param {string} name - Name of cookie to remove * @returns {Promise<number>} Number of cookies removed */ async removeCookies(domain, path, name) { if (!this.enabled) return 0; try { const cookies = await this.jar.getCookies(`http://${domain}${path || '/'}`); let removed = 0; for (const cookie of cookies) { if (!name || cookie.key === name) { await this.jar.store.removeCookie(cookie.domain, cookie.path, cookie.key); removed++; } } return removed; } catch (error) { console.warn(`Failed to remove cookies: ${error.message}`); return 0; } } /** * Clear all cookies * @returns {Promise<void>} */ async clear() { if (!this.enabled) return; try { // Use removeAllCookies if available const store = this.jar.store; if (store.removeAllCookies) { return await new Promise((resolve, reject) => { store.removeAllCookies((err) => { if (err) reject(err); else resolve(); }); }); } else { // Fallback: get all cookies and remove them const domains = await this.getAllDomains(); for (const domain of domains) { await this.removeCookies(domain, '/', null); } } } catch (error) { console.warn(`Failed to clear cookies: ${error.message}`); } } /** * Get all domains that have cookies * @returns {Promise<string[]>} Array of domains */ async getAllDomains() { if (!this.enabled) return []; try { const domains = new Set(); const allCookies = await this.getAllCookies(); for (const cookie of allCookies) { domains.add(cookie.domain); } return Array.from(domains); } catch (error) { console.warn(`Failed to get domains: ${error.message}`); return []; } } /** * Get all cookies from the jar * @returns {Promise<Cookie[]>} Array of all cookies */ async getAllCookies() { if (!this.enabled) return []; try { // This is a workaround since tough-cookie doesn't have a direct getAllCookies method const cookies = []; const store = this.jar.store; if (store.getAllCookies) { return await new Promise((resolve, reject) => { store.getAllCookies((err, allCookies) => { if (err) reject(err); else resolve(allCookies || []); }); }); } return cookies; } catch (error) { console.warn(`Failed to get all cookies: ${error.message}`); return []; } } /** * Export cookies to JSON * @returns {Promise<Object>} Serialized cookies */ async serialize() { if (!this.enabled) return {}; try { return await new Promise((resolve, reject) => { this.jar.serialize((err, serialized) => { if (err) reject(err); else resolve(serialized); }); }); } catch (error) { console.warn(`Failed to serialize cookies: ${error.message}`); return {}; } } /** * Import cookies from JSON * @param {Object} serialized - Serialized cookies * @returns {Promise<void>} */ async deserialize(serialized) { if (!this.enabled || !serialized) return; try { this.jar = await CookieJar.deserialize(serialized); } catch (error) { console.warn(`Failed to deserialize cookies: ${error.message}`); } } /** * Process response headers to extract and store cookies * @param {Object} headers - Response headers * @param {string} url - URL of the response * @returns {Promise<Cookie[]>} Cookies that were set */ async processResponseHeaders(headers, url) { if (!this.enabled || !headers) return []; const setCookieHeaders = []; // Handle different header formats if (headers['set-cookie']) { if (Array.isArray(headers['set-cookie'])) { setCookieHeaders.push(...headers['set-cookie']); } else { setCookieHeaders.push(headers['set-cookie']); } } if (setCookieHeaders.length === 0) return []; return await this.setCookies(setCookieHeaders, url); } /** * Add cookies to request headers * @param {Object} headers - Request headers object * @param {string} url - URL of the request * @returns {Promise<Object>} Modified headers */ async addCookiesToHeaders(headers, url) { if (!this.enabled) return headers; const cookieString = await this.getCookieString(url); if (cookieString) { headers = { ...headers }; headers['Cookie'] = cookieString; } return headers; } /** * Get cookie statistics * @returns {Promise<Object>} Cookie statistics */ async getStats() { if (!this.enabled) return { enabled: false, count: 0, domains: 0 }; const allCookies = await this.getAllCookies(); const domains = await this.getAllDomains(); return { enabled: true, count: allCookies.length, domains: domains.length, domainList: domains }; } /** * Enable or disable the cookie jar * @param {boolean} enabled - Whether to enable cookies */ setEnabled(enabled) { this.enabled = enabled; } /** * Check if cookies are enabled * @returns {boolean} True if enabled */ isEnabled() { return this.enabled; } } export default AkCookieJar;