UNPKG

@mixxtor/currencyx-js

Version:

Modern TypeScript currency converter with type inference and multiple providers (Google Finance, Fixer.io). Framework agnostic with clean architecture.

199 lines (171 loc) 5.06 kB
/** * Fixer.io Exchange */ import type { CurrencyCode, ConversionResult, ExchangeRatesResult, FixerConfig, ExchangeRatesParams, ConvertParams, } from '../types/index.js' import { BaseCurrencyExchange } from './base_exchange.js' interface FixerResponse { success: boolean timestamp?: number base?: string date?: string rates?: Record<string, number> error?: { code: number info: string type: string } } interface FixerConvertResponse { success: boolean query?: { from: string to: string amount: number } info?: { timestamp: number rate: number } date?: string result?: number error?: { code: number info: string type: string } } export class FixerExchange extends BaseCurrencyExchange { readonly name = 'fixer' private baseUrl = 'http://data.fixer.io/api' private accessKey: string private timeout: number constructor(config: FixerConfig) { super() this.accessKey = config.accessKey this.base = config.base || 'EUR' // Fixer.io default base is EUR this.timeout = config.timeout || 5000 } /** * Set API key */ setKey(key: string): this { this.accessKey = key return this } /** * Get latest exchange rates */ async latestRates(params?: ExchangeRatesParams): Promise<ExchangeRatesResult> { const codes = params?.codes || this.currencies try { const url = new URL(`${this.baseUrl}/latest`) url.searchParams.set('access_key', this.accessKey) url.searchParams.set('base', this.base) if (codes && codes.length > 0) { url.searchParams.set('symbols', codes.join(',')) } const response = await fetch(url.toString(), { signal: AbortSignal.timeout(this.timeout), }) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } const data: FixerResponse = await response.json() if (!data.success) { return this.createExchangeRatesResult( this.base, {}, { code: data.error?.code, info: data.error?.info || 'Unknown error from Fixer.io', type: data.error?.type || 'API_ERROR', } ) } return this.createExchangeRatesResult(this.base, data.rates || {}) } catch (error) { return this.createExchangeRatesResult( this.base, {}, { info: error instanceof Error ? error.message : 'Failed to fetch exchange rates', type: 'FETCH_ERROR', } ) } } /** * Convert currency amount */ async convert(params: ConvertParams): Promise<ConversionResult> { const { amount, from, to } = params try { if (from === to) { return this.createConversionResult(amount, from, to, amount, 1.0) } const url = new URL(`${this.baseUrl}/convert`) url.searchParams.set('access_key', this.accessKey) url.searchParams.set('from', from) url.searchParams.set('to', to) url.searchParams.set('amount', amount.toString()) const response = await fetch(url.toString(), { signal: AbortSignal.timeout(this.timeout), }) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } const data: FixerConvertResponse = await response.json() if (!data.success) { return this.createConversionResult(amount, from, to, undefined, undefined, { code: data.error?.code, info: data.error?.info || 'Unknown error from Fixer.io', type: data.error?.type || 'API_ERROR', }) } return this.createConversionResult(amount, from, to, data.result, data.info?.rate) } catch (error) { return this.createConversionResult(amount, from, to, undefined, undefined, { info: error instanceof Error ? error.message : 'Conversion failed', type: 'CONVERSION_ERROR', }) } } /** * Get conversion rate between two currencies */ async getConvertRate(from: CurrencyCode, to: CurrencyCode): Promise<number | undefined> { try { if (from === to) { return 1.0 } // If one of the currencies is the base currency, we can use latest rates if (from === this.base) { const rates = await this.latestRates({ codes: [to] }) return rates.rates[to] } if (to === this.base) { const rates = await this.latestRates({ codes: [from] }) const rate = rates.rates[from] return rate ? 1 / rate : undefined } // For cross-currency conversion, get both rates against base const rates = await this.latestRates({ codes: [from, to] }) const fromRate = rates.rates[from] const toRate = rates.rates[to] if (fromRate && toRate) { return toRate / fromRate } return undefined } catch (error) { console.error(`Fixer: Failed to get conversion rate for ${from}-${to}:`, error) return undefined } } }