94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
|
|
import type { IFeeSchedulePayload } from './feeschedules.types.js';
|
||
|
|
|
||
|
|
export const FEE_SCHEDULE_DATA_URL = 'https://code.foss.global/fin.cx/fee-schedules/raw/branch/main/.onlygit/fee-schedules.json';
|
||
|
|
|
||
|
|
export async function loadFeeSchedulePayload(url = FEE_SCHEDULE_DATA_URL): Promise<IFeeSchedulePayload> {
|
||
|
|
let remoteError: unknown;
|
||
|
|
|
||
|
|
try {
|
||
|
|
return await fetchPayload(url);
|
||
|
|
} catch (error) {
|
||
|
|
remoteError = error;
|
||
|
|
}
|
||
|
|
|
||
|
|
const browserLocalPayload = await loadBrowserLocalPayload();
|
||
|
|
if (browserLocalPayload) {
|
||
|
|
return browserLocalPayload;
|
||
|
|
}
|
||
|
|
|
||
|
|
const nodeLocalPayload = await loadNodeLocalPayload();
|
||
|
|
if (nodeLocalPayload) {
|
||
|
|
return nodeLocalPayload;
|
||
|
|
}
|
||
|
|
|
||
|
|
throw new Error(`Failed to load fee schedule data from ${url}: ${formatError(remoteError)}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const fetchPayload = async (url: string): Promise<IFeeSchedulePayload> => {
|
||
|
|
const response = await fetch(url);
|
||
|
|
if (!response.ok) {
|
||
|
|
throw new Error(`${response.status} ${response.statusText}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return parsePayload(await response.json());
|
||
|
|
};
|
||
|
|
|
||
|
|
const loadBrowserLocalPayload = async (): Promise<IFeeSchedulePayload | undefined> => {
|
||
|
|
if (typeof location === 'undefined') {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
return await fetchPayload('/.onlygit/fee-schedules.json');
|
||
|
|
} catch {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const loadNodeLocalPayload = async (): Promise<IFeeSchedulePayload | undefined> => {
|
||
|
|
if (!isNodeRuntime()) {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const dynamicImport = new Function('specifier', 'return import(specifier)') as (specifier: string) => Promise<any>;
|
||
|
|
const [{ readFile }, { fileURLToPath }, path] = await Promise.all([
|
||
|
|
dynamicImport('node:fs/promises'),
|
||
|
|
dynamicImport('node:url'),
|
||
|
|
dynamicImport('node:path'),
|
||
|
|
]);
|
||
|
|
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
||
|
|
const filePath = path.resolve(currentDir, '../.onlygit/fee-schedules.json');
|
||
|
|
const fileContent = await readFile(filePath, 'utf8');
|
||
|
|
return parsePayload(JSON.parse(fileContent));
|
||
|
|
} catch {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const parsePayload = (payload: unknown): IFeeSchedulePayload => {
|
||
|
|
if (!payload || typeof payload !== 'object') {
|
||
|
|
throw new Error('Fee schedule payload is not an object.');
|
||
|
|
}
|
||
|
|
|
||
|
|
const typedPayload = payload as IFeeSchedulePayload;
|
||
|
|
if (!Array.isArray(typedPayload.catalogs) || !Array.isArray(typedPayload.scheduleData)) {
|
||
|
|
throw new Error('Fee schedule payload is missing catalogs or scheduleData arrays.');
|
||
|
|
}
|
||
|
|
|
||
|
|
return typedPayload;
|
||
|
|
};
|
||
|
|
|
||
|
|
const isNodeRuntime = (): boolean => {
|
||
|
|
return typeof process !== 'undefined' && Boolean(process.versions?.node);
|
||
|
|
};
|
||
|
|
|
||
|
|
const formatError = (error: unknown): string => {
|
||
|
|
return error instanceof Error ? error.message : String(error);
|
||
|
|
};
|
||
|
|
|
||
|
|
export const feeSchedulePayload = await loadFeeSchedulePayload();
|
||
|
|
export const FEE_SCHEDULE_CATALOGS = feeSchedulePayload.catalogs;
|
||
|
|
export const FEE_SCHEDULE_DATA = feeSchedulePayload.scheduleData;
|
||
|
|
export const SUPPORTED_COUNTRY_CODES = FEE_SCHEDULE_CATALOGS.map((catalog) => catalog.countryCode);
|