Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 7 additions & 17 deletions realtime/src/services/exchanges/currencybeacon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import {
UnknownExchangeServiceError,
InvalidExchangeConfigError,
} from "@domain/exchanges"
import { CacheKeys } from "@domain/cache"
import { toPrice, toSeconds, toTimestamp } from "@domain/primitives"

import { LocalCacheService } from "@services/cache"
import { CacheKeys } from "@domain/cache"
import { baseLogger } from "@services/logger"

import { cleanRatesObject, isRatesObjectValid } from "@utils"

const mutex = new Mutex()
export const CurrencyBeaconExchangeService = async ({
base,
Expand Down Expand Up @@ -68,8 +71,9 @@ export const CurrencyBeaconExchangeService = async ({
},
},
)
const rates = data?.response?.rates
if (status >= 400 || !isRatesObjectValid(rates)) {
const rates = cleanRatesObject(data?.response?.rates)

if (status >= 400 || !isRatesObjectValid<CurrencyBeaconRates>(rates)) {
await LocalCacheService().set<number>({
key: cacheKeyStatus,
value: status,
Expand All @@ -96,20 +100,6 @@ export const CurrencyBeaconExchangeService = async ({
}
}

const isRatesObjectValid = (rates: unknown): rates is CurrencyBeaconRates => {
if (!rates || typeof rates !== "object") return false

let keyCount = 0
for (const key in rates) {
if (typeof key !== "string" || typeof rates[key] !== "number") {
return false
}
keyCount++
}

return !!keyCount
}

const tickerFromRaw = ({
rate,
timestamp,
Expand Down
31 changes: 13 additions & 18 deletions realtime/src/services/exchanges/exchangeratehost/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import {
UnknownExchangeServiceError,
InvalidExchangeConfigError,
} from "@domain/exchanges"
import { CacheKeys } from "@domain/cache"
import { toPrice, toSeconds, toTimestamp } from "@domain/primitives"

import { LocalCacheService } from "@services/cache"
import { CacheKeys } from "@domain/cache"
import { baseLogger } from "@services/logger"

import { cleanRatesObject, isRatesObjectValid } from "@utils"

const mutex = new Mutex()
export const ExchangeRateHostService = async ({
base,
Expand Down Expand Up @@ -69,7 +72,13 @@ export const ExchangeRateHostService = async ({
)

const { success, quotes, timestamp } = data
if (!success || status >= 400 || !isRatesObjectValid(quotes)) {
const rates = cleanRatesObject(quotes)

if (
!success ||
status >= 400 ||
!isRatesObjectValid<ExchangeRateHostRates>(rates)
) {
await LocalCacheService().set<number>({
key: cacheKeyStatus,
value: status,
Expand All @@ -80,11 +89,11 @@ export const ExchangeRateHostService = async ({

await LocalCacheService().set<ExchangeRateHostRates>({
key: cacheKey,
value: quotes,
value: rates,
ttlSecs: toSeconds(cacheTtlSecs > 0 ? cacheTtlSecs : 300),
})

return tickerFromRaw({ rate: quotes[symbol], timestamp })
return tickerFromRaw({ rate: rates[symbol], timestamp })
} catch (error) {
baseLogger.error({ error }, "ExchangeRateHost unknown error")
return new UnknownExchangeServiceError(error.message || error)
Expand All @@ -96,20 +105,6 @@ export const ExchangeRateHostService = async ({
}
}

const isRatesObjectValid = (rates: unknown): rates is ExchangeRateHostRates => {
if (!rates || typeof rates !== "object") return false

let keyCount = 0
for (const key in rates) {
if (typeof key !== "string" || typeof rates[key] !== "number") {
return false
}
keyCount++
}

return !!keyCount
}

const tickerFromRaw = ({
rate,
timestamp,
Expand Down
24 changes: 7 additions & 17 deletions realtime/src/services/exchanges/free-currency-rates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import {
UnknownExchangeServiceError,
InvalidExchangeConfigError,
} from "@domain/exchanges"
import { CacheKeys } from "@domain/cache"
import { toPrice, toSeconds, toTimestamp } from "@domain/primitives"

import { LocalCacheService } from "@services/cache"
import { CacheKeys } from "@domain/cache"
import { baseLogger } from "@services/logger"

import { cleanRatesObject, isRatesObjectValid } from "@utils"

const mutex = new Mutex()
export const FreeCurrencyRatesExchangeService = async ({
base,
Expand Down Expand Up @@ -110,24 +113,11 @@ const getWithFallback = async ({
for (const url of urls) {
try {
const { status, data } = await axios.get(url, params)
const rates = data ? data[baseCurrency] : undefined
if (status === 200 && isRatesObjectValid(rates)) return rates
const rates = cleanRatesObject(data ? data[baseCurrency] : undefined)
if (status === 200 && isRatesObjectValid<FreeCurrencyRatesRates>(rates))
return rates
} catch {}
}

return new UnknownExchangeServiceError(`Invalid response.`)
}

const isRatesObjectValid = (rates: unknown): rates is FreeCurrencyRatesRates => {
if (!rates || typeof rates !== "object") return false

let keyCount = 0
for (const key in rates) {
if (typeof key !== "string" || typeof rates[key] !== "number") {
return false
}
keyCount++
}

return !!keyCount
}
27 changes: 10 additions & 17 deletions realtime/src/services/exchanges/yadio/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import {
InvalidExchangeConfigError,
InvalidExchangeResponseError,
} from "@domain/exchanges"
import { CacheKeys } from "@domain/cache"
import { toPrice, toSeconds, toTimestamp } from "@domain/primitives"

import { LocalCacheService } from "@services/cache"
import { CacheKeys } from "@domain/cache"
import { baseLogger } from "@services/logger"

import { cleanRatesObject, isRatesObjectValid } from "@utils"

const mutex = new Mutex()
export const YadioExchangeService = async ({
base,
Expand Down Expand Up @@ -51,10 +54,14 @@ export const YadioExchangeService = async ({
},
)

const rates = data && data[base]
if (status >= 400 || !isRatesObjectValid(rates))
const rawRates = data && data[base]
if (status >= 400 || !rawRates)
return new InvalidExchangeResponseError(`Invalid response. Error ${status}`)

const rates = cleanRatesObject(rawRates)
if (!isRatesObjectValid<YadioRates>(rates))
return new InvalidExchangeResponseError(`No valid rates found in response`)

await LocalCacheService().set<YadioRates>({
key: cacheKey,
value: rates,
Expand All @@ -73,20 +80,6 @@ export const YadioExchangeService = async ({
}
}

const isRatesObjectValid = (rates: unknown): rates is YadioRates => {
if (!rates || typeof rates !== "object") return false

let keyCount = 0
for (const key in rates) {
if (typeof key !== "string" || typeof rates[key] !== "number") {
return false
}
keyCount++
}

return !!keyCount
}

const tickerFromRaw = ({
rate,
timestamp,
Expand Down
35 changes: 35 additions & 0 deletions realtime/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,38 @@ export const round = (number: number): number =>
+(Math.round(Number(number + "e+2")) + "e-2")

export const unixTimestamp = (date: Date) => Math.floor(date.getTime() / 1000)

export const isRatesObjectValid = <T extends RatesObject>(rates: unknown): rates is T => {
if (!rates || typeof rates !== "object" || Array.isArray(rates)) return false

let keyCount = 0
for (const key in rates) {
const value = (rates as Record<string, unknown>)[key]
if (typeof key !== "string" || typeof value !== "number") {
return false
}
keyCount++
}

return !!keyCount
}

export const cleanRatesObject = (rates: unknown): RatesObject => {
if (!rates || typeof rates !== "object") return {}

return Object.entries(rates).reduce((cleaned, [key, value]) => {
// Handle string conversion
if (typeof value === "string") {
const parsed = parseFloat(value)
if (!isNaN(parsed) && isFinite(parsed)) {
cleaned[key] = parsed
}
}
// Handle direct number values
else if (typeof value === "number" && !isNaN(value) && isFinite(value)) {
cleaned[key] = value
}

return cleaned
}, {} as RatesObject)
}
1 change: 1 addition & 0 deletions realtime/src/utils/index.types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
type RatesObject = { [key: string]: number }
Loading