Test Failed
Push — main ( e93202...ec58f7 )
by Yuri
01:18
created

index.ts ➔ getCachedRate   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 5
rs 10
c 0
b 0
f 0
ccs 3
cts 3
cp 1
cc 2
crap 2
1 2
import { BackendError, FetchError, MalformedError } from './errors'
2
3
export type CurrencyCode = string;
4
5
interface YFResponse {
6
  chart: {
7
    result: {
8
      meta: {
9
        regularMarketPrice: number
10
      }
11
    }[]
12
  }
13
}
14
15
interface CachedRate {
16
  value: number;
17
  timestamp: number;
18
}
19
20
interface ExchangeRateOptions {
21
  cacheDurationMs?: number; // Defaults to undefined (no caching)
22
}
23
24 2
const YF_BASE = 'https://query1.finance.yahoo.com/v8/finance/chart/'
25 2
const YF_PARAMS = '=X?region=US&lang=en-US&includePrePost=false&interval=2m&useYfid=true&range=1d&corsDomain=finance.yahoo.com&.tsrc=finance'
26
27 2
const rateCache: Record<string, CachedRate> = {}
28
29
function getRateUrl (from: CurrencyCode, to: CurrencyCode): string {
30 8
  return `${YF_BASE}${from.toUpperCase()}${to.toUpperCase()}${YF_PARAMS}`
31
}
32
33
function isCacheValid (cachedRate: CachedRate, cacheDurationMs: number): boolean {
34 3
  return Date.now() - cachedRate.timestamp < cacheDurationMs
35
}
36
37
function getCachedRate (from: CurrencyCode, to: CurrencyCode, cacheDurationMs?: number): number | null {
38 9
  const cacheKey = `${from}-${to}`
39 9
  const cachedRate = rateCache[cacheKey]
40 9
  return (cacheDurationMs && cachedRate && isCacheValid(cachedRate, cacheDurationMs)) ? cachedRate.value : null
41
}
42
43
async function fetchRate (rateUrl: string): Promise<number> {
44
  let response: Response
45
46 8
  try {
47 8
    response = await fetch(rateUrl)
48 7
    if (!response.ok) {
49 1
      throw new BackendError(`Service did not return HTTP 200 response. Status: ${response.status}`)
50
    }
51
52 6
    const result: YFResponse = await response.json()
53 6
    const rate = result.chart?.result[0]?.meta?.regularMarketPrice
54 6
    if (!rate) {
55 1
      throw new MalformedError('Unexpected response structure. Missing "regularMarketPrice".')
56
    }
57
58 5
    return rate
59
  } catch (error) {
60 3
    if (error instanceof BackendError || error instanceof MalformedError) {
61 2
      throw error
62
    }
63 1
    throw new FetchError(`Failed to fetch data from ${rateUrl}`)
64
  }
65
}
66
67 2
export async function getExchangeRate (
68
  from: CurrencyCode,
69
  to: CurrencyCode,
70
  options: ExchangeRateOptions = {}
71
): Promise<number> {
72 9
  const cachedRate = getCachedRate(from, to, options.cacheDurationMs)
73 9
  if (cachedRate !== null) {
74 1
    return cachedRate
75
  }
76
77 8
  const rateUrl = getRateUrl(from, to)
78 8
  const rate = await fetchRate(rateUrl)
79
80 5
  if (options.cacheDurationMs) {
81 3
    const cacheKey = `${from}-${to}`
82 3
    rateCache[cacheKey] = { value: rate, timestamp: Date.now() } // Cache the result with a timestamp
83
  }
84
85 5
  return rate
86
}
87
88
89
export * from './errors'
90