currzy
Version:
Free, open-source library for fetching, managing, and converting up-to-date currency rates from multiple reliable sources with the ability to easily choose the source.
244 lines (239 loc) • 5.97 kB
JavaScript
// src/providers/cbrf/index.ts
import { ofetch } from "ofetch";
import { XMLParser } from "fast-xml-parser";
// src/cache/drivers.ts
import fs from "fs/promises";
import path from "path";
var FileCacheDriver = class {
constructor(providerName) {
this.cacheFile = path.resolve("cache", `${providerName}-cache.json`);
}
async load() {
try {
const raw = await fs.readFile(this.cacheFile, "utf-8");
return JSON.parse(raw);
} catch {
return null;
}
}
async save(data) {
await fs.mkdir(path.dirname(this.cacheFile), { recursive: true });
await fs.writeFile(this.cacheFile, JSON.stringify(data, null, 2), "utf-8");
}
async clear() {
try {
await fs.unlink(this.cacheFile);
} catch {
}
}
};
var LocalStorageCacheDriver = class {
constructor(providerName) {
this.key = `currenzy-${providerName}-cache`;
}
async load() {
const raw = localStorage.getItem(this.key);
return raw ? JSON.parse(raw) : null;
}
async save(data) {
localStorage.setItem(this.key, JSON.stringify(data));
}
async clear() {
localStorage.removeItem(this.key);
}
};
// src/cache/index.ts
function createCacheDriver(providerName) {
if (typeof window !== "undefined" && "localStorage" in window) {
return new LocalStorageCacheDriver(providerName);
} else {
return new FileCacheDriver(providerName);
}
}
// src/providers/cbrf/types/currency.ts
var AVAILABLE_CURRENCIES = [
"AUD",
"AZN",
"DZD",
"GBP",
"AMD",
"BHD",
"BYN",
"BGN",
"BOB",
"BRL",
"HUF",
"VND",
"HKD",
"GEL",
"DKK",
"AED",
"USD",
"EUR",
"EGP",
"INR",
"IDR",
"IRR",
"KZT",
"CAD",
"QAR",
"KGS",
"CNY",
"CUP",
"MDL",
"MNT",
"NGN",
"NZD",
"NOK",
"OMR",
"PLN",
"SAR",
"RON",
"XDR",
"SGD",
"TJS",
"THB",
"BDT",
"TRY",
"TMT",
"UZS",
"UAH",
"CZK",
"SEK",
"CHF",
"ETB",
"RSD",
"ZAR",
"KRW",
"JPY",
"MMK",
"RUB"
];
function assertCurrency(code) {
if (!AVAILABLE_CURRENCIES.includes(code)) {
throw new Error(`Unknown currency: ${code}`);
}
}
// src/providers/cbrf/index.ts
var CbrfProvider = class {
constructor() {
this.url = "https://api.allorigins.win/raw?url=https://www.cbr.ru/scripts/XML_daily.asp";
this.rates = {};
this.lastUpdate = null;
this.initialized = false;
this.cacheTTL = 1e3 * 60 * 60;
this.cache = createCacheDriver("cbrf");
this.availableCurrencies = AVAILABLE_CURRENCIES;
}
async loadCache() {
const data = await this.cache.load();
if (data) {
this.rates = data.payload.rates;
this.lastUpdate = data.lastUpdate ? new Date(data.lastUpdate) : null;
this.initialized = true;
}
}
async saveCache() {
await this.cache.save({
payload: { rates: this.rates },
lastUpdate: this.lastUpdate ? this.lastUpdate.toISOString() : null
});
}
async clearCache() {
await this.cache.clear();
this.rates = {};
this.lastUpdate = null;
this.initialized = false;
}
async fetchRates() {
try {
const xml = await ofetch(this.url, { responseType: "text" });
const parser = new XMLParser();
const data = parser.parse(xml);
const items = Array.isArray(data.ValCurs.Valute) ? data.ValCurs.Valute : [data.ValCurs.Valute];
this.rates = {};
for (const item of items) {
const nominal = Number(item.Nominal);
const value = parseFloat(item.Value.replace(",", "."));
const vunitRate = value / nominal;
const rate = { code: item.CharCode, nominal, value, vunitRate };
if (this.availableCurrencies.includes(rate.code)) {
this.rates[rate.code] = rate;
}
}
this.rates["RUB"] = { code: "RUB", nominal: 1, value: 1, vunitRate: 1 };
this.lastUpdate = /* @__PURE__ */ new Date();
this.initialized = true;
await this.saveCache();
} catch (e) {
console.error("Error fetching CBRF rates:", e);
if (!this.initialized) {
await this.loadCache();
if (!this.initialized) throw new Error("No CBRF data and no cache");
}
}
}
async ensureInitialized() {
if (!this.initialized) await this.loadCache();
const expired = this.lastUpdate === null || (/* @__PURE__ */ new Date()).getTime() - this.lastUpdate.getTime() > this.cacheTTL;
if (!this.initialized || expired) {
await this.fetchRates();
}
}
async getRate(code, base = "USD") {
await this.ensureInitialized();
assertCurrency(code);
assertCurrency(base);
const rateInRub = this.rates[code].vunitRate;
const baseRateInRub = this.rates[base].vunitRate;
return baseRateInRub / rateInRub;
}
async convert(amount, from, to) {
await this.ensureInitialized();
assertCurrency(from);
assertCurrency(to);
const fromRateInRub = this.rates[from].vunitRate;
const toRateInRub = this.rates[to].vunitRate;
return amount * fromRateInRub / toRateInRub;
}
async getAllRates(base = "USD") {
await this.ensureInitialized();
assertCurrency(base);
const result = {};
for (const code of this.availableCurrencies) {
if (code === base) continue;
result[code] = await this.getRate(code, base);
}
return result;
}
getLastUpdate() {
return this.lastUpdate;
}
};
// src/index.ts
var Currzy = class {
constructor(providerName) {
if (providerName === "cbrf") this.provider = new CbrfProvider();
else throw new Error("Unknown provider");
}
async getRate(code) {
return await this.provider.getRate(code);
}
async convert(amount, from, to) {
return await this.provider.convert(amount, from, to);
}
async getAllRatesTo(code) {
return await this.provider.getAllRates(code);
}
async clearCache() {
return this.provider.clearCache();
}
getLastUpdate() {
return this.provider.getLastUpdate();
}
};
export {
CbrfProvider,
Currzy
};
//# sourceMappingURL=index.js.map