Skip to content

Commit

Permalink
feat: abstract axios to TurboHTTPService class
Browse files Browse the repository at this point in the history
Implement TurboHTTPServiceInterface and update payment and upload service to use it
  • Loading branch information
dtfiedler committed Sep 5, 2023
1 parent 97fd29b commit 6592ac8
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 190 deletions.
47 changes: 31 additions & 16 deletions src/common/axios.ts → src/common/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,59 +14,74 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<T>({
url,
endpoint,
allowedStatuses = [200, 202],
headers,
}: {
url: string;
headers?: Record<string, string>;
endpoint: string;
allowedStatuses?: number[];
headers?: Partial<TurboSignedRequestHeaders>;
}): Promise<T> {
const { status, statusText, data } = await this.axios.get<T>(url, {
const { status, statusText, data } = await this.axios.get<T>(endpoint, {
headers,
});

if (status !== 200) {
if (!allowedStatuses.includes(status)) {
throw new FailedRequestError(status, statusText);
}

return data;
}

async post<T>({
url,
endpoint,
allowedStatuses = [200, 202],
headers,
data,
}: {
url: string;
headers: AxiosRequestHeaders;
data: Readable | ReadableStream | Buffer;
endpoint: string;
allowedStatuses?: number[];
headers?: Partial<TurboSignedRequestHeaders> & Record<string, string>;
data: Readable | Buffer | ReadableStream;
}): Promise<T> {
const {
status,
statusText,
data: response,
} = await this.axios.post<T>(url, {
} = await this.axios.post<T>(endpoint, data, {
headers,
data,
});

if (status !== 200) {
if (!allowedStatuses.includes(status)) {
throw new FailedRequestError(status, statusText);
}

Expand Down
143 changes: 33 additions & 110 deletions src/common/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { AxiosInstance } from 'axios';

import {
Currency,
TurboAuthenticatedPaymentServiceInterface,
Expand All @@ -30,85 +28,49 @@ 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<TurboRatesResponse> {
const {
status,
statusText,
data: rates,
} = await this.axios.get<TurboRatesResponse>('/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<TurboRatesResponse>({
endpoint: '/rates',
});
}

async getFiatToAR({
currency,
}: {
currency: Currency;
}): Promise<TurboFiatToArResponse> {
const {
status,
statusText,
data: rate,
} = await this.axios.get<TurboFiatToArResponse>(`/rates/${currency}`);

if (status !== 200) {
throw new FailedRequestError(status, statusText);
}

return rate;
return this.httpService.get<TurboFiatToArResponse>({
endpoint: `/rates/${currency}`,
});
}

async getSupportedCountries(): Promise<TurboCountriesResponse> {
const {
status,
statusText,
data: countries,
} = await this.axios.get<TurboCountriesResponse>('/countries');

if (status !== 200) {
throw new FailedRequestError(status, statusText);
}

return countries;
return this.httpService.get<TurboCountriesResponse>({
endpoint: '/countries',
});
}

async getSupportedCurrencies(): Promise<TurboCurrenciesResponse> {
const {
status,
statusText,
data: currencies,
} = await this.axios.get<TurboCurrenciesResponse>('/currencies');

if (status !== 200) {
throw new FailedRequestError(status, statusText);
}

return currencies;
return this.httpService.get<TurboCurrenciesResponse>({
endpoint: '/currencies',
});
}

async getUploadCosts({
Expand All @@ -117,51 +79,27 @@ export class TurboUnauthenticatedPaymentService
bytes: number[];
}): Promise<TurboPriceResponse[]> {
const fetchPricePromises = bytes.map((byteCount: number) =>
this.axios.get<TurboPriceResponse>(`/price/bytes/${byteCount}`),
this.httpService.get<TurboPriceResponse>({
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<TurboPriceResponse> {
const {
status,
statusText,
data: wincForFiat,
} = await this.axios.get<TurboPriceResponse>(
`/price/${currency}/${amount}`,
);

if (status !== 200) {
throw new FailedRequestError(status, statusText);
}

return wincForFiat;
return this.httpService.get<TurboPriceResponse>({
endpoint: `/price/${currency}/${amount}`,
});
}
}

// NOTE: we could use an abstract class here, but for consistency sake we'll directly call the public payment service APIs
export class TurboAuthenticatedPaymentService
implements TurboAuthenticatedPaymentServiceInterface
{
protected readonly axios: AxiosInstance;
protected readonly httpService: TurboHTTPService;
protected readonly signer: TurboWalletSigner;
protected readonly publicPaymentService: TurboUnauthenticatedPaymentServiceInterface;

Expand All @@ -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<TurboRatesResponse> {
Expand Down Expand Up @@ -214,25 +149,13 @@ export class TurboAuthenticatedPaymentService

async getBalance(): Promise<TurboBalanceResponse> {
const headers = await this.signer.generateSignedRequestHeaders();

const {
status,
statusText,
data: balance,
} = await this.axios.get<TurboBalanceResponse>('/balance', {
const balance = await this.httpService.get<TurboBalanceResponse>({
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' };
}
}
Loading

0 comments on commit 6592ac8

Please sign in to comment.