UNPKG

@profplum700/etsy-v3-api-client

Version:

JavaScript/TypeScript client for the Etsy Open API v3 with OAuth 2.0 authentication

951 lines (942 loc) 32.3 kB
class EtsyApiError extends Error { constructor(message, _statusCode, _response) { super(message); this._statusCode = _statusCode; this._response = _response; this.name = 'EtsyApiError'; } get statusCode() { return this._statusCode; } get response() { return this._response; } } class EtsyAuthError extends Error { constructor(message, _code) { super(message); this._code = _code; this.name = 'EtsyAuthError'; } get code() { return this._code; } } class EtsyRateLimitError extends Error { constructor(message, _retryAfter) { super(message); this._retryAfter = _retryAfter; this.name = 'EtsyRateLimitError'; } get retryAfter() { return this._retryAfter; } } const isBrowser$1 = typeof window !== 'undefined' && typeof window.document !== 'undefined'; const isNode$1 = typeof process !== 'undefined' && !!process.versions?.node; const isWebWorker = typeof globalThis.importScripts === 'function' && typeof navigator !== 'undefined'; const hasFetch = typeof fetch !== 'undefined'; const hasWebCrypto = typeof crypto !== 'undefined' && typeof crypto.subtle !== 'undefined'; const hasLocalStorage = (() => { try { return typeof localStorage !== 'undefined'; } catch { return false; } })(); const hasSessionStorage = (() => { try { return typeof sessionStorage !== 'undefined'; } catch { return false; } })(); function getEnvironmentInfo() { return { isBrowser: isBrowser$1, isNode: Boolean(isNode$1), isWebWorker, hasFetch, hasWebCrypto, hasLocalStorage, hasSessionStorage, userAgent: isBrowser$1 ? navigator.userAgent : 'Node.js', nodeVersion: isNode$1 ? process.version : undefined, }; } function assertFetchSupport() { if (!hasFetch) { throw new Error('Fetch API is not available. Please use Node.js 18+ or a modern browser.'); } } function getAvailableStorage() { if (isNode$1) { return 'file'; } else if (hasLocalStorage) { return 'localStorage'; } else if (hasSessionStorage) { return 'sessionStorage'; } else { return 'memory'; } } class MemoryTokenStorage { constructor() { this.tokens = null; } async save(tokens) { this.tokens = { ...tokens }; } async load() { return this.tokens ? { ...this.tokens } : null; } async clear() { this.tokens = null; } } class TokenManager { constructor(config, storage) { this.currentTokens = null; this.keystring = config.keystring; this.refreshCallback = config.refreshSave; this.storage = storage; this.currentTokens = { access_token: config.accessToken, refresh_token: config.refreshToken, expires_at: config.expiresAt, token_type: 'Bearer', scope: '' }; } async getAccessToken() { if (!this.currentTokens) { if (this.storage) { this.currentTokens = await this.storage.load(); } if (!this.currentTokens) { throw new EtsyAuthError('No tokens available', 'NO_TOKENS'); } } const now = new Date(); const expiresAt = new Date(this.currentTokens.expires_at); const bufferTime = 60 * 1000; if (now.getTime() >= (expiresAt.getTime() - bufferTime)) { await this.refreshToken(); } return this.currentTokens.access_token; } async refreshToken() { if (this.refreshPromise) { return this.refreshPromise; } if (!this.currentTokens) { throw new EtsyAuthError('No tokens available to refresh', 'NO_REFRESH_TOKEN'); } this.refreshPromise = this.performTokenRefresh(); try { const newTokens = await this.refreshPromise; this.currentTokens = newTokens; if (this.storage) { await this.storage.save(newTokens); } if (this.refreshCallback) { this.refreshCallback(newTokens.access_token, newTokens.refresh_token, newTokens.expires_at); } return newTokens; } finally { this.refreshPromise = undefined; } } async performTokenRefresh() { if (!this.currentTokens) { throw new EtsyAuthError('No tokens available', 'NO_TOKENS'); } const body = new URLSearchParams({ grant_type: 'refresh_token', client_id: this.keystring, refresh_token: this.currentTokens.refresh_token }); try { const response = await this.fetch('https://api.etsy.com/v3/public/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' }, body: body.toString() }); if (!response.ok) { const errorText = await response.text(); throw new EtsyAuthError(`Token refresh failed: ${response.status} ${response.statusText} - ${errorText}`, 'TOKEN_REFRESH_FAILED'); } const tokenResponse = await response.json(); return { access_token: tokenResponse.access_token, refresh_token: tokenResponse.refresh_token, expires_at: new Date(Date.now() + (tokenResponse.expires_in * 1000)), token_type: tokenResponse.token_type, scope: tokenResponse.scope }; } catch (_error) { if (_error instanceof EtsyAuthError) { throw _error; } throw new EtsyAuthError(`Token refresh failed: ${_error instanceof Error ? _error.message : 'Unknown error'}`, 'TOKEN_REFRESH_ERROR'); } } getCurrentTokens() { return this.currentTokens ? { ...this.currentTokens } : null; } updateTokens(tokens) { this.currentTokens = { ...tokens }; } isTokenExpired() { if (!this.currentTokens) { return true; } const now = new Date(); const expiresAt = new Date(this.currentTokens.expires_at); return now.getTime() >= expiresAt.getTime(); } willTokenExpireSoon(minutes = 5) { if (!this.currentTokens) { return true; } const now = new Date(); const expiresAt = new Date(this.currentTokens.expires_at); const bufferTime = minutes * 60 * 1000; return now.getTime() >= (expiresAt.getTime() - bufferTime); } async clearTokens() { this.currentTokens = null; if (this.storage) { await this.storage.clear(); } } getTimeUntilExpiration() { if (!this.currentTokens) { return null; } const now = new Date(); const expiresAt = new Date(this.currentTokens.expires_at); return expiresAt.getTime() - now.getTime(); } async fetch(url, options) { assertFetchSupport(); return fetch(url, options); } } function createDefaultTokenStorage(options) { if (isNode$1) { return new FileTokenStorage(options?.filePath || './etsy-tokens.json'); } else if (options?.preferSession && hasSessionStorage) { return new SessionStorageTokenStorage(options?.storageKey); } else if (hasLocalStorage) { return new LocalStorageTokenStorage(options?.storageKey); } else if (hasSessionStorage) { return new SessionStorageTokenStorage(options?.storageKey); } else { return new MemoryTokenStorage(); } } class LocalStorageTokenStorage { constructor(storageKey = 'etsy_tokens') { if (!hasLocalStorage) { throw new Error('localStorage is not available in this environment'); } this.storageKey = storageKey; } async save(tokens) { const data = JSON.stringify(tokens); localStorage.setItem(this.storageKey, data); } async load() { try { const data = localStorage.getItem(this.storageKey); if (!data) return null; const tokens = JSON.parse(data); if (tokens.expires_at) { tokens.expires_at = new Date(tokens.expires_at); } return tokens; } catch { return null; } } async clear() { localStorage.removeItem(this.storageKey); } } class SessionStorageTokenStorage { constructor(storageKey = 'etsy_tokens') { if (!hasSessionStorage) { throw new Error('sessionStorage is not available in this environment'); } this.storageKey = storageKey; } async save(tokens) { const data = JSON.stringify(tokens); sessionStorage.setItem(this.storageKey, data); } async load() { try { const data = sessionStorage.getItem(this.storageKey); if (!data) return null; const tokens = JSON.parse(data); if (tokens.expires_at) { tokens.expires_at = new Date(tokens.expires_at); } return tokens; } catch { return null; } } async clear() { sessionStorage.removeItem(this.storageKey); } } class FileTokenStorage { constructor(filePath) { if (!isNode$1) { throw new Error('FileTokenStorage is only available in Node.js environments'); } this.filePath = filePath; } async save(tokens) { if (!isNode$1) { throw new Error('FileTokenStorage is only available in Node.js'); } try { const data = JSON.stringify(tokens, null, 2); await this._writeFile(this.filePath, data); } catch { throw new Error('Failed to save tokens to file'); } } async load() { if (!isNode$1) { return null; } try { const data = await this._readFile(this.filePath); const tokens = JSON.parse(data); if (tokens.expires_at) { tokens.expires_at = new Date(tokens.expires_at); } return tokens; } catch { return null; } } async clear() { if (!isNode$1) { return; } try { await this._deleteFile(this.filePath); } catch { } } async _writeFile(filePath, data) { if (typeof process === 'undefined') return; const fs = await import('fs'); await fs.promises.writeFile(filePath, data, 'utf8'); } async _readFile(filePath) { if (typeof process === 'undefined') throw new Error('Not available'); const fs = await import('fs'); return await fs.promises.readFile(filePath, 'utf8'); } async _deleteFile(filePath) { if (typeof process === 'undefined') return; const fs = await import('fs'); await fs.promises.unlink(filePath); } } class EtsyRateLimiter { constructor(config) { this.requestCount = 0; this.dailyReset = new Date(); this.lastRequestTime = 0; this.config = { maxRequestsPerDay: 10000, maxRequestsPerSecond: 10, minRequestInterval: 100, ...config }; this.setNextDailyReset(); } setNextDailyReset() { const now = new Date(); this.dailyReset = new Date(now); this.dailyReset.setUTCDate(this.dailyReset.getUTCDate() + 1); this.dailyReset.setUTCHours(0, 0, 0, 0); } async waitForRateLimit() { const now = Date.now(); if (now >= this.dailyReset.getTime()) { this.requestCount = 0; this.setNextDailyReset(); } if (this.requestCount >= this.config.maxRequestsPerDay) { const timeUntilReset = this.dailyReset.getTime() - now; throw new EtsyRateLimitError(`Daily rate limit of ${this.config.maxRequestsPerDay} requests exceeded. Reset in ${Math.ceil(timeUntilReset / 1000 / 60)} minutes.`, timeUntilReset); } const timeSinceLastRequest = now - this.lastRequestTime; if (timeSinceLastRequest < this.config.minRequestInterval) { const waitTime = this.config.minRequestInterval - timeSinceLastRequest; await new Promise(resolve => setTimeout(resolve, waitTime)); } this.requestCount++; this.lastRequestTime = Date.now(); } getRateLimitStatus() { const now = Date.now(); if (now >= this.dailyReset.getTime()) { this.requestCount = 0; this.setNextDailyReset(); } return { remainingRequests: Math.max(0, this.config.maxRequestsPerDay - this.requestCount), resetTime: this.dailyReset, canMakeRequest: this.requestCount < this.config.maxRequestsPerDay && (now - this.lastRequestTime) >= this.config.minRequestInterval }; } getRemainingRequests() { return Math.max(0, this.config.maxRequestsPerDay - this.requestCount); } reset() { this.requestCount = 0; this.lastRequestTime = 0; this.setNextDailyReset(); } canMakeRequest() { const now = Date.now(); if (now >= this.dailyReset.getTime()) { this.requestCount = 0; this.setNextDailyReset(); } if (this.requestCount >= this.config.maxRequestsPerDay) { return false; } return (now - this.lastRequestTime) >= this.config.minRequestInterval; } getTimeUntilNextRequest() { const now = Date.now(); const timeSinceLastRequest = now - this.lastRequestTime; if (timeSinceLastRequest >= this.config.minRequestInterval) { return 0; } return this.config.minRequestInterval - timeSinceLastRequest; } getConfig() { return { ...this.config }; } } const defaultRateLimiter = new EtsyRateLimiter(); class DefaultLogger { debug(message, ...args) { const isDevelopment = isNode$1 ? process.env.NODE_ENV === 'development' : window.location?.hostname === 'localhost' || window.location?.hostname === '127.0.0.1'; if (isDevelopment) { console.log(`[DEBUG] ${message}`, ...args); } } info(message, ...args) { console.log(`[INFO] ${message}`, ...args); } warn(message, ...args) { console.warn(`[WARN] ${message}`, ...args); } error(message, ...args) { console.error(`[ERROR] ${message}`, ...args); } } class MemoryCache { constructor() { this.cache = new Map(); } async get(key) { const entry = this.cache.get(key); if (!entry) return null; if (Date.now() > entry.expires) { this.cache.delete(key); return null; } return entry.value; } async set(key, value, ttl = 3600) { const expires = Date.now() + (ttl * 1000); this.cache.set(key, { value, expires }); } async delete(key) { this.cache.delete(key); } async clear() { this.cache.clear(); } } class EtsyClient { constructor(config) { this.tokenManager = new TokenManager(config); this.baseUrl = config.baseUrl || 'https://api.etsy.com/v3/application'; this.logger = new DefaultLogger(); this.keystring = config.keystring; if (config.rateLimiting?.enabled !== false) { this.rateLimiter = new EtsyRateLimiter({ maxRequestsPerDay: config.rateLimiting?.maxRequestsPerDay || 10000, maxRequestsPerSecond: config.rateLimiting?.maxRequestsPerSecond || 10, minRequestInterval: config.rateLimiting?.minRequestInterval ?? 100 }); } else { this.rateLimiter = new EtsyRateLimiter(undefined); } if (config.caching?.enabled !== false) { this.cache = config.caching?.storage || new MemoryCache(); this.cacheTtl = config.caching?.ttl || 3600; } } async makeRequest(endpoint, options = {}, useCache = true) { const url = `${this.baseUrl}${endpoint}`; const requestOptions = { method: 'GET', ...options }; const cacheKey = `${url}:${JSON.stringify(requestOptions)}`; if (useCache && this.cache && requestOptions.method === 'GET') { const cached = await this.cache.get(cacheKey); if (cached) { return JSON.parse(cached); } } await this.rateLimiter.waitForRateLimit(); const accessToken = await this.tokenManager.getAccessToken(); const headers = { 'Authorization': `Bearer ${accessToken}`, 'x-api-key': this.getApiKey(), 'Content-Type': 'application/json', 'Accept': 'application/json', ...requestOptions.headers }; try { const response = await this.fetch(url, { ...requestOptions, headers }); if (!response.ok) { const errorText = await response.text(); throw new EtsyApiError(`Etsy API error: ${response.status} ${response.statusText}`, response.status, errorText); } const data = await response.json(); if (useCache && this.cache && requestOptions.method === 'GET') { await this.cache.set(cacheKey, JSON.stringify(data), this.cacheTtl); } return data; } catch (error) { if (error instanceof EtsyApiError || error instanceof EtsyAuthError) { throw error; } throw new EtsyApiError(`Request failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 0, error); } } getApiKey() { return this.keystring; } async getUser() { return this.makeRequest('/users/me'); } async getShop(shopId) { if (shopId) { return this.makeRequest(`/shops/${shopId}`); } const user = await this.getUser(); if (!user.shop_id) { throw new EtsyApiError('User does not have a shop', 404); } return this.makeRequest(`/shops/${user.shop_id}`); } async getShopByOwnerUserId(userId) { return this.makeRequest(`/users/${userId}/shops`); } async getShopSections(shopId) { let targetShopId = shopId; if (!targetShopId) { const user = await this.getUser(); if (!user.shop_id) { throw new EtsyApiError('User does not have a shop', 404); } targetShopId = user.shop_id.toString(); } const response = await this.makeRequest(`/shops/${targetShopId}/sections`); this.logger.info(`Found ${response.results.length} shop sections`); return response.results; } async getShopSection(shopId, sectionId) { return this.makeRequest(`/shops/${shopId}/sections/${sectionId}`); } async getListingsByShop(shopId, params = {}) { let targetShopId = shopId; if (!targetShopId) { const user = await this.getUser(); if (!user.shop_id) { throw new EtsyApiError('User does not have a shop', 404); } targetShopId = user.shop_id.toString(); } const searchParams = new URLSearchParams(); searchParams.set('state', params.state || 'active'); if (params.limit !== undefined) searchParams.set('limit', params.limit.toString()); if (params.offset !== undefined) searchParams.set('offset', params.offset.toString()); if (params.sort_on) searchParams.set('sort_on', params.sort_on); if (params.sort_order) searchParams.set('sort_order', params.sort_order); if (params.includes) searchParams.set('includes', params.includes.join(',')); const response = await this.makeRequest(`/shops/${targetShopId}/listings?${searchParams.toString()}`); return response.results; } async getListing(listingId, includes) { const params = includes ? `?includes=${includes.join(',')}` : ''; return this.makeRequest(`/listings/${listingId}${params}`); } async findAllListingsActive(params = {}) { const searchParams = new URLSearchParams(); if (params.keywords) searchParams.set('keywords', params.keywords); if (params.category) searchParams.set('category', params.category); if (params.limit !== undefined) searchParams.set('limit', params.limit.toString()); if (params.offset !== undefined) searchParams.set('offset', params.offset.toString()); if (params.sort_on) searchParams.set('sort_on', params.sort_on); if (params.sort_order) searchParams.set('sort_order', params.sort_order); if (params.min_price !== undefined) searchParams.set('min_price', params.min_price.toString()); if (params.max_price !== undefined) searchParams.set('max_price', params.max_price.toString()); if (params.tags) searchParams.set('tags', params.tags.join(',')); if (params.location) searchParams.set('location', params.location); if (params.shop_location) searchParams.set('shop_location', params.shop_location); const response = await this.makeRequest(`/listings/active?${searchParams.toString()}`); return response.results; } async getListingImages(listingId) { const response = await this.makeRequest(`/listings/${listingId}/images`); return response.results; } async getListingInventory(listingId) { return this.makeRequest(`/listings/${listingId}/inventory`); } async getSellerTaxonomyNodes() { const response = await this.makeRequest('/seller-taxonomy/nodes'); return response.results; } async getUserShops() { const response = await this.makeRequest('/users/me/shops'); return response.results || []; } getRemainingRequests() { return this.rateLimiter.getRemainingRequests(); } getRateLimitStatus() { return this.rateLimiter.getRateLimitStatus(); } async clearCache() { if (this.cache) { await this.cache.clear(); } } getCurrentTokens() { return this.tokenManager.getCurrentTokens(); } isTokenExpired() { return this.tokenManager.isTokenExpired(); } async refreshToken() { return this.tokenManager.refreshToken(); } async fetch(url, options) { assertFetchSupport(); return fetch(url, options); } } const isBrowser = typeof window !== 'undefined'; const isNode = typeof process !== 'undefined' && process.versions?.node; async function generateRandomBytes(length) { if (isBrowser) { const array = new Uint8Array(length); crypto.getRandomValues(array); return array; } else if (isNode) { try { const crypto = await import('crypto'); return new Uint8Array(crypto.randomBytes(length)); } catch { throw new Error('Node.js crypto module not available'); } } else { throw new Error('Crypto functions are not available in this environment'); } } async function generateRandomBase64Url(length) { const bytes = await generateRandomBytes(length); return base64UrlEncode(bytes); } async function sha256(data) { const encoder = new TextEncoder(); const dataBytes = typeof data === 'string' ? encoder.encode(data) : data; if (isBrowser) { const hashBuffer = await crypto.subtle.digest('SHA-256', dataBytes); return new Uint8Array(hashBuffer); } else if (isNode) { try { const crypto = await import('crypto'); const hash = crypto.createHash('sha256'); hash.update(dataBytes); return new Uint8Array(hash.digest()); } catch { throw new Error('Node.js crypto module not available'); } } else { throw new Error('SHA256 is not available in this environment'); } } async function sha256Base64Url(data) { const hash = await sha256(data); return base64UrlEncode(hash); } function base64UrlEncode(bytes) { if (isBrowser) { const binaryString = String.fromCharCode(...bytes); const base64 = btoa(binaryString); return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); } else if (isNode) { const base64 = Buffer.from(bytes).toString('base64'); return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); } else { throw new Error('Base64URL encoding is not available in this environment'); } } async function generateCodeVerifier() { return generateRandomBase64Url(32); } async function generateState() { return generateRandomBase64Url(32); } async function createCodeChallenge(codeVerifier) { return sha256Base64Url(codeVerifier); } class AuthHelper { constructor(config) { this.keystring = config.keystring; this.redirectUri = config.redirectUri; this.scopes = config.scopes; this.codeVerifier = config.codeVerifier || ''; this.state = config.state || ''; this.initialized = this.initialize(config); } async initialize(config) { if (!config.codeVerifier) { this.codeVerifier = await generateCodeVerifier(); } if (!config.state) { this.state = await generateState(); } } async getAuthUrl() { await this.initialized; const codeChallenge = await createCodeChallenge(this.codeVerifier); const params = new URLSearchParams({ response_type: 'code', client_id: this.keystring, redirect_uri: this.redirectUri, scope: this.scopes.join(' '), state: this.state, code_challenge: codeChallenge, code_challenge_method: 'S256' }); return `https://www.etsy.com/oauth/connect?${params.toString()}`; } async setAuthorizationCode(code, state) { await this.initialized; if (state !== this.state) { throw new EtsyAuthError('State parameter mismatch', 'INVALID_STATE'); } this.authorizationCode = code; this.receivedState = state; } async getAccessToken() { if (!this.authorizationCode) { throw new EtsyAuthError('Authorization code not set. Call setAuthorizationCode() first.', 'NO_AUTH_CODE'); } const body = new URLSearchParams({ grant_type: 'authorization_code', client_id: this.keystring, redirect_uri: this.redirectUri, code: this.authorizationCode, code_verifier: this.codeVerifier }); try { assertFetchSupport(); const response = await fetch('https://api.etsy.com/v3/public/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' }, body: body.toString() }); if (!response.ok) { const errorText = await response.text(); throw new EtsyAuthError(`Token exchange failed: ${response.status} ${response.statusText} - ${errorText}`, 'TOKEN_EXCHANGE_FAILED'); } const tokenResponse = await response.json(); return { access_token: tokenResponse.access_token, refresh_token: tokenResponse.refresh_token, expires_at: new Date(Date.now() + (tokenResponse.expires_in * 1000)), token_type: tokenResponse.token_type, scope: tokenResponse.scope }; } catch (error) { if (error instanceof EtsyAuthError) { throw error; } throw new EtsyAuthError(`Token exchange failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'TOKEN_EXCHANGE_ERROR'); } } async getState() { await this.initialized; return this.state; } async getCodeVerifier() { await this.initialized; return this.codeVerifier; } getScopes() { return [...this.scopes]; } getRedirectUri() { return this.redirectUri; } } const ETSY_SCOPES = { LISTINGS_READ: 'listings_r', SHOPS_READ: 'shops_r', PROFILE_READ: 'profile_r', FAVORITES_READ: 'favorites_r', FEEDBACK_READ: 'feedback_r', TREASURY_READ: 'treasury_r', LISTINGS_WRITE: 'listings_w', SHOPS_WRITE: 'shops_w', PROFILE_WRITE: 'profile_w', FAVORITES_WRITE: 'favorites_w', FEEDBACK_WRITE: 'feedback_w', TREASURY_WRITE: 'treasury_w', LISTINGS_DELETE: 'listings_d', SHOPS_DELETE: 'shops_d', PROFILE_DELETE: 'profile_d', FAVORITES_DELETE: 'favorites_d', FEEDBACK_DELETE: 'feedback_d', TREASURY_DELETE: 'treasury_d', TRANSACTIONS_READ: 'transactions_r', TRANSACTIONS_WRITE: 'transactions_w', BILLING_READ: 'billing_r', CART_READ: 'cart_r', CART_WRITE: 'cart_w', RECOMMEND_READ: 'recommend_r', RECOMMEND_WRITE: 'recommend_w', ADDRESS_READ: 'address_r', ADDRESS_WRITE: 'address_w', EMAIL_READ: 'email_r' }; const COMMON_SCOPE_COMBINATIONS = { SHOP_READ_ONLY: [ ETSY_SCOPES.SHOPS_READ, ETSY_SCOPES.LISTINGS_READ, ETSY_SCOPES.PROFILE_READ ], SHOP_MANAGEMENT: [ ETSY_SCOPES.SHOPS_READ, ETSY_SCOPES.SHOPS_WRITE, ETSY_SCOPES.LISTINGS_READ, ETSY_SCOPES.LISTINGS_WRITE, ETSY_SCOPES.LISTINGS_DELETE, ETSY_SCOPES.PROFILE_READ, ETSY_SCOPES.TRANSACTIONS_READ ], BASIC_ACCESS: [ ETSY_SCOPES.SHOPS_READ, ETSY_SCOPES.LISTINGS_READ ] }; function createEtsyClient(config) { return new EtsyClient(config); } function createAuthHelper(config) { return new AuthHelper(config); } function createTokenManager(config, storage) { return new TokenManager(config, storage); } function createRateLimiter(config) { return new EtsyRateLimiter(config); } const VERSION = '1.0.2'; const LIBRARY_NAME = 'etsy-v3-api-client'; function getLibraryInfo() { return { name: LIBRARY_NAME, version: VERSION, description: 'JavaScript/TypeScript client for the Etsy Open API v3 with OAuth 2.0 authentication', author: 'profplum700', license: 'MIT', homepage: 'https://github.com/ForestHillArtsHouse/etsy-v3-api-client#readme' }; } export { AuthHelper, COMMON_SCOPE_COMBINATIONS, ETSY_SCOPES, EtsyApiError, EtsyAuthError, EtsyClient, EtsyRateLimitError, EtsyRateLimiter, FileTokenStorage, LIBRARY_NAME, LocalStorageTokenStorage, MemoryTokenStorage, SessionStorageTokenStorage, TokenManager, VERSION, createAuthHelper, createCodeChallenge, createDefaultTokenStorage, createEtsyClient, createRateLimiter, createTokenManager, EtsyClient as default, defaultRateLimiter, generateCodeVerifier, generateRandomBase64Url, generateState, getAvailableStorage, getEnvironmentInfo, getLibraryInfo, hasLocalStorage, hasSessionStorage, isBrowser$1 as isBrowser, isNode$1 as isNode, sha256, sha256Base64Url }; //# sourceMappingURL=node.esm.js.map