diff --git a/src/common/axios.ts b/src/common/http.ts similarity index 62% rename from src/common/axios.ts rename to src/common/http.ts index 07e053f3..68072ba5 100644 --- a/src/common/axios.ts +++ b/src/common/http.ts @@ -14,34 +14,48 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { AxiosInstance, AxiosRequestHeaders } from 'axios'; +import { AxiosInstance } from 'axios'; +import { RetryConfig } from 'retry-axios'; import { Readable } from 'stream'; +import { ReadableStream } from 'stream/web'; +import { + TurboHTTPServiceInterface, + TurboSignedRequestHeaders, +} from '../types/turbo.js'; import { createAxiosInstance } from '../utils/axiosClient.js'; import { FailedRequestError } from '../utils/errors.js'; -export class TurboHTTPService implements TurboHTTPService { +export class TurboHTTPService implements TurboHTTPServiceInterface { protected axios: AxiosInstance; - constructor({ url = 'https://payment.ardrive.dev', retryConfig }: any) { + constructor({ + url, + retryConfig, + }: { + url: string; + retryConfig?: RetryConfig; + }) { this.axios = createAxiosInstance({ axiosConfig: { - baseURL: `${url}/v1`, + baseURL: url, }, retryConfig, }); } async get({ - url, + endpoint, + allowedStatuses = [200, 202], headers, }: { - url: string; - headers?: Record; + endpoint: string; + allowedStatuses?: number[]; + headers?: Partial; }): Promise { - const { status, statusText, data } = await this.axios.get(url, { + const { status, statusText, data } = await this.axios.get(endpoint, { headers, }); - if (status !== 200) { + if (!allowedStatuses.includes(status)) { throw new FailedRequestError(status, statusText); } @@ -49,24 +63,25 @@ export class TurboHTTPService implements TurboHTTPService { } async post({ - url, + endpoint, + allowedStatuses = [200, 202], headers, data, }: { - url: string; - headers: AxiosRequestHeaders; - data: Readable | ReadableStream | Buffer; + endpoint: string; + allowedStatuses?: number[]; + headers?: Partial & Record; + data: Readable | Buffer | ReadableStream; }): Promise { const { status, statusText, data: response, - } = await this.axios.post(url, { + } = await this.axios.post(endpoint, data, { headers, - data, }); - if (status !== 200) { + if (!allowedStatuses.includes(status)) { throw new FailedRequestError(status, statusText); } diff --git a/src/common/payment.ts b/src/common/payment.ts index f159e677..55e4a254 100644 --- a/src/common/payment.ts +++ b/src/common/payment.ts @@ -14,8 +14,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { AxiosInstance } from 'axios'; - import { Currency, TurboAuthenticatedPaymentServiceInterface, @@ -30,39 +28,27 @@ import { TurboUnauthenticatedPaymentServiceInterfaceConfiguration, TurboWalletSigner, } from '../types/turbo.js'; -import { createAxiosInstance } from '../utils/axiosClient.js'; -import { FailedRequestError } from '../utils/errors.js'; +import { TurboHTTPService } from './http.js'; export class TurboUnauthenticatedPaymentService implements TurboUnauthenticatedPaymentServiceInterface { - protected readonly axios: AxiosInstance; + protected readonly httpService: TurboHTTPService; constructor({ url = 'https://payment.ardrive.dev', retryConfig, }: TurboUnauthenticatedPaymentServiceInterfaceConfiguration) { - this.axios = createAxiosInstance({ - axiosConfig: { - baseURL: `${url}/v1`, - }, + this.httpService = new TurboHTTPService({ + url: `${url}/v1`, retryConfig, }); } async getFiatRates(): Promise { - const { - status, - statusText, - data: rates, - } = await this.axios.get('/rates'); - - if (status !== 200) { - throw new FailedRequestError(status, statusText); - } - - // TODO: should we return just the fiat rates instead of the whole response? - return rates; + return this.httpService.get({ + endpoint: '/rates', + }); } async getFiatToAR({ @@ -70,45 +56,21 @@ export class TurboUnauthenticatedPaymentService }: { currency: Currency; }): Promise { - const { - status, - statusText, - data: rate, - } = await this.axios.get(`/rates/${currency}`); - - if (status !== 200) { - throw new FailedRequestError(status, statusText); - } - - return rate; + return this.httpService.get({ + endpoint: `/rates/${currency}`, + }); } async getSupportedCountries(): Promise { - const { - status, - statusText, - data: countries, - } = await this.axios.get('/countries'); - - if (status !== 200) { - throw new FailedRequestError(status, statusText); - } - - return countries; + return this.httpService.get({ + endpoint: '/countries', + }); } async getSupportedCurrencies(): Promise { - const { - status, - statusText, - data: currencies, - } = await this.axios.get('/currencies'); - - if (status !== 200) { - throw new FailedRequestError(status, statusText); - } - - return currencies; + return this.httpService.get({ + endpoint: '/currencies', + }); } async getUploadCosts({ @@ -117,43 +79,19 @@ export class TurboUnauthenticatedPaymentService bytes: number[]; }): Promise { const fetchPricePromises = bytes.map((byteCount: number) => - this.axios.get(`/price/bytes/${byteCount}`), + this.httpService.get({ + endpoint: `/price/bytes/${byteCount}`, + }), ); - const responses = await Promise.all(fetchPricePromises); - const wincCostsForBytes = responses.map( - ({ - status, - statusText, - data, - }: { - status: number; - statusText: string; - data: TurboPriceResponse; - }) => { - if (status !== 200) { - throw new FailedRequestError(status, statusText); - } - return data; - }, - ); - + const wincCostsForBytes: TurboPriceResponse[] = + await Promise.all(fetchPricePromises); return wincCostsForBytes; } async getWincForFiat({ amount, currency }): Promise { - const { - status, - statusText, - data: wincForFiat, - } = await this.axios.get( - `/price/${currency}/${amount}`, - ); - - if (status !== 200) { - throw new FailedRequestError(status, statusText); - } - - return wincForFiat; + return this.httpService.get({ + endpoint: `/price/${currency}/${amount}`, + }); } } @@ -161,7 +99,7 @@ export class TurboUnauthenticatedPaymentService export class TurboAuthenticatedPaymentService implements TurboAuthenticatedPaymentServiceInterface { - protected readonly axios: AxiosInstance; + protected readonly httpService: TurboHTTPService; protected readonly signer: TurboWalletSigner; protected readonly publicPaymentService: TurboUnauthenticatedPaymentServiceInterface; @@ -171,18 +109,15 @@ export class TurboAuthenticatedPaymentService retryConfig, signer, }: TurboAuthenticatedPaymentServiceInterfaceConfiguration) { - this.signer = signer; - // TODO: abstract this away to TurboHTTPService class - this.axios = createAxiosInstance({ - axiosConfig: { - baseURL: `${url}/v1`, - }, + this.httpService = new TurboHTTPService({ + url: `${url}/v1`, retryConfig, }); this.publicPaymentService = new TurboUnauthenticatedPaymentService({ url, retryConfig, }); + this.signer = signer; } getFiatRates(): Promise { @@ -214,25 +149,13 @@ export class TurboAuthenticatedPaymentService async getBalance(): Promise { const headers = await this.signer.generateSignedRequestHeaders(); - - const { - status, - statusText, - data: balance, - } = await this.axios.get('/balance', { + const balance = await this.httpService.get({ + endpoint: '/balance', headers, + allowedStatuses: [200, 404], }); - if (status === 404) { - return { - winc: '0', - }; - } - - if (status !== 200) { - throw new FailedRequestError(status, statusText); - } - - return balance; + // 404's don't return a balance, so default to 0 + return balance.winc ? balance : { winc: '0' }; } } diff --git a/src/common/upload.ts b/src/common/upload.ts index 9c5dc2c4..bfba096b 100644 --- a/src/common/upload.ts +++ b/src/common/upload.ts @@ -14,8 +14,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { AxiosInstance } from 'axios'; - import { TurboAuthenticatedUploadServiceConfiguration, TurboAuthenticatedUploadServiceInterface, @@ -26,23 +24,19 @@ import { TurboUploadDataItemResponse, TurboWalletSigner, } from '../types/turbo.js'; -import { createAxiosInstance } from '../utils/axiosClient.js'; -import { FailedRequestError } from '../utils/errors.js'; +import { TurboHTTPService } from './http.js'; export class TurboUnauthenticatedUploadService implements TurboUnauthenticatedUploadServiceInterface { - protected axios: AxiosInstance; + protected httpService: TurboHTTPService; constructor({ url = 'https://upload.ardrive.dev', retryConfig, }: TurboUnauthenticatedUploadServiceInterfaceConfiguration) { - // TODO: abstract to TurboHTTPRequestService class - this.axios = createAxiosInstance({ - axiosConfig: { - baseURL: `${url}/v1`, - }, + this.httpService = new TurboHTTPService({ + url: `${url}/v1`, retryConfig, }); } @@ -51,28 +45,20 @@ export class TurboUnauthenticatedUploadService dataItemStreamFactory, }: TurboSignedDataItemFactory): Promise { // TODO: add p-limit constraint or replace with separate upload class - const { status, data, statusText } = - await this.axios.post( - `/tx`, - dataItemStreamFactory(), - { - headers: { - 'content-type': 'application/octet-stream', - }, - }, - ); - - if (![202, 200].includes(status)) { - throw new FailedRequestError(status, statusText); - } - return data; + return this.httpService.post({ + endpoint: `/tx`, + data: dataItemStreamFactory(), + headers: { + 'content-type': 'application/octet-stream', + }, + }); } } export class TurboAuthenticatedUploadService implements TurboAuthenticatedUploadServiceInterface { - protected axios: AxiosInstance; + protected httpService: TurboHTTPService; protected signer: TurboWalletSigner; constructor({ @@ -80,11 +66,8 @@ export class TurboAuthenticatedUploadService retryConfig, signer, }: TurboAuthenticatedUploadServiceConfiguration) { - // TODO: abstract to TurboHTTPRequestService class - this.axios = createAxiosInstance({ - axiosConfig: { - baseURL: `${url}/v1`, - }, + this.httpService = new TurboHTTPService({ + url: `${url}/v1`, retryConfig, }); this.signer = signer; @@ -94,21 +77,13 @@ export class TurboAuthenticatedUploadService dataItemStreamFactory, }: TurboSignedDataItemFactory): Promise { // TODO: add p-limit constraint or replace with separate upload class - const { status, data, statusText } = - await this.axios.post( - `/tx`, - dataItemStreamFactory(), - { - headers: { - 'content-type': 'application/octet-stream', - }, - }, - ); - - if (![202, 200].includes(status)) { - throw new FailedRequestError(status, statusText); - } - return data; + return this.httpService.post({ + endpoint: `/tx`, + data: dataItemStreamFactory(), + headers: { + 'content-type': 'application/octet-stream', + }, + }); } async uploadFile({ @@ -118,20 +93,12 @@ export class TurboAuthenticatedUploadService fileStreamFactory, }); // TODO: add p-limit constraint or replace with separate upload class - const { status, data, statusText } = - await this.axios.post( - `/tx`, - signedDataItem, - { - headers: { - 'content-type': 'application/octet-stream', - }, - }, - ); - - if (![202, 200].includes(status)) { - throw new FailedRequestError(status, statusText); - } - return data; + return this.httpService.post({ + endpoint: `/tx`, + data: signedDataItem, + headers: { + 'content-type': 'application/octet-stream', + }, + }); } } diff --git a/src/types/turbo.ts b/src/types/turbo.ts index dd534345..4007c802 100644 --- a/src/types/turbo.ts +++ b/src/types/turbo.ts @@ -114,6 +114,7 @@ export type FileStreamFactory = export type SignedDataStreamFactory = FileStreamFactory; export type TurboFileFactory = { fileStreamFactory: FileStreamFactory; // TODO: allow multiple files + abortController?: AbortController; // bundle?: boolean; // TODO: add bundling into BDIs }; @@ -121,9 +122,27 @@ export type TurboSignedDataItemFactory = { dataItemStreamFactory: SignedDataStreamFactory; // TODO: allow multiple data items }; -export interface TurboHTTPRequestService { - get(url: string): Promise; - post(url: string, data: Readable | Buffer): Promise; +export interface TurboHTTPServiceInterface { + get({ + endpoint, + headers, + allowedStatuses, + }: { + endpoint: string; + headers?: Partial & Record; + allowedStatuses?: number[]; + }): Promise; + post({ + endpoint, + headers, + allowedStatuses, + data, + }: { + endpoint: string; + headers?: Partial & Record; + allowedStatuses?: number[]; + data: Readable | ReadableStream | Buffer; + }): Promise; } export interface TurboWalletSigner {