# Configuraited http service
###### tags: `Typescript`
> Пример конфигурироваемого Http service для фронтенд проекта.
Сначала надо определить конфиг для апишки
```typescript=
interface IApiUrls {
baseApiUrl: string;
[key: string]: string;
}
interface IConfig {
[key: string]: {
host: string;
[key: string]: string;
};
}
export interface IApiConfig {
api: IConfig;
}
function getConfig(urls: IApiUrls, cachingLifeTime?: number): IApiConfig {
return {
api: Object.assign({
caching: {
lifetime: cachingLifeTime !== undefined ? cachingLifeTime : 5 * 1000, // 5 sec
},
}, getApiConfig(urls)),
};
}
function getApiConfig({ baseApiUrl }: IApiUrls): IConfig {
return {
signing: {
host: `${baseApiUrl}/signing/v2/`,
statusByAccessToken: 'sessions/status/:accessToken',
updateSubjectInfo: 'sessions/:sessionId/tasks/updateSubjectInfo',
sessionTask: 'sessions/:sessionId/tasks/:taskId',
uploadSignature: 'signature',
signingTaskExit: 'sessions/status/:accessToken/exit',
},
};
}
export default {
getConfig,
};
```
Потом определяем файл с конфигом для всего проекта и импортим туда конфиг апишки
```typescript=
import _merge from 'lodash/merge';
import defaultConfig from './default';
import defaultApi from './default-api';
module.exports = _merge(
{},
defaultConfig,
defaultApi.getConfig({ baseApiUrl: defaultConfig.baseApiUrl }),
{
isSsr: true,
},
);
```
Далее делаем api service который будет работать с этим конифгом. Вам понадобиться HttpService это [классический сервис описанный в этой статейке](https://hackmd.io/@devhouse/classic-http-service)
```typescript=
import { HttpService } from './http.service';
import { AxiosError, AxiosRequestConfig, AxiosResponse, Method } from 'axios';
import * as Sentry from '@sentry/browser';
import _forOwn from 'lodash/forOwn';
import _merge from 'lodash/merge';
import _get from 'lodash/get';
import _isUndefined from 'lodash/isUndefined';
import { ENVS } from '../constants/common';
class ApiServiceClass {
private readonly _http = HttpService;
public defaultOptions: AxiosRequestConfig;
constructor(private _appConfig: any) {
if (!this._appConfig) {
throw new Error('Api config is not defined');
} else {
this.defaultOptions = this._appConfig.api.options;
}
}
public get<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, options?: AxiosRequestConfig): Promise<T> {
const url = this.buildUrl(apiKey, pathKey, urlParams);
if (!url) {
throw new Error(`url is not found please check keys, ${apiKey}, ${pathKey}`);
}
return this.handleResponse<T>(this._http.get<T>(url, this.getOptions(options)));
}
public post<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, body?: {}, options?: AxiosRequestConfig): Promise<T> {
return this._payloadRequest.apply(this, ['post', apiKey, pathKey, urlParams, body, options]) as Promise<T>;
}
public patch<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, body?: {}, options?: AxiosRequestConfig): Promise<T> {
return this._payloadRequest.apply(this, ['patch', apiKey, pathKey, urlParams, body, options]) as Promise<T>;
}
public put<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, body?: {}, options?: AxiosRequestConfig): Promise<T> {
return this._payloadRequest.apply(this, ['put', apiKey, pathKey, urlParams, body, options]) as Promise<T>;
}
public remove<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, options?: AxiosRequestConfig): Promise<T> {
const url = this.buildUrl(apiKey, pathKey, urlParams);
if (!url) {
throw new Error(`url is not found please check keys, ${apiKey}, ${pathKey}`);
}
return this.handleResponse(this._http.delete(url, this.getOptions(options)));
}
public getOptions(options: AxiosRequestConfig = {}): AxiosRequestConfig {
let requestOptions = this.defaultOptions;
if (options) {
requestOptions = _merge({}, this.defaultOptions, options);
}
return requestOptions;
}
public handleResponse<T>(promise: Promise<AxiosResponse<T>>): Promise<T> {
return promise
.then((res: AxiosResponse<T>) => {
return res.data;
})
.catch((err: AxiosError) => {
Sentry.captureException(err);
throw err;
});
}
public buildUrl(apiKey: string, pathKey: string, urlParams: {} = {}): string {
if (apiKey.indexOf('api.') !== 0) {
apiKey = 'api.' + apiKey;
}
const api = _get(this._appConfig, apiKey);
const path = _get(api, pathKey);
if (_isUndefined(api) || _isUndefined(path)) {
console
.error(`apiKey: '${apiKey}' or pathKey '${pathKey}' were not found in config, ${JSON.stringify(this._appConfig)}`);
Sentry
.captureException(`apiKey: '${apiKey}' or pathKey '${pathKey}' were not found in config, ${JSON.stringify(this._appConfig)}`);
return '';
}
return api.host + this._fillUrl(path, urlParams);
}
private _payloadRequest<T>(method: Method, apiKey: string,
pathKey: string, urlParams?: {}, body?: any, options?: AxiosRequestConfig): Promise<T> {
const url = this.buildUrl(apiKey, pathKey, urlParams);
if (!url) {
throw new Error(`url is not found please check keys, ${apiKey}, ${pathKey}`);
}
return this.handleResponse(
this._http.request(
this.getOptions({
method,
url,
data: body,
...options,
}),
),
);
}
private _fillUrl(url: string, params: {}): string {
_forOwn(params, (value, key) => {
url = url.replace(new RegExp(':' + key), value);
});
return url;
}
}
/***
* TODO
* Improve get configs by env
*/
let environment_path = 'environment';
if (process.env.ENV === ENVS.DEV) {
environment_path += '.dev';
}
if (process.env.ENV === ENVS.STAGE) {
environment_path += '.stage';
}
if (process.env.ENV === ENVS.PROD) {
environment_path += '.prod';
}
const environment = require(`../../environments/${environment_path}`);
export const getSigningApiAuthToken = (): string => {
return process.env.SIGNING_API_AUTH_TOKEN || 'XXXXX';
};
export const ApiService = new ApiServiceClass(environment);
```
И потом это можно вот так использовать
```typescript=
import { ApiService, getSigningApiAuthToken } from '../services/api.service';
import { ISession } from './interfaces/i-session';
import { IUpdateSignerInfoPayload } from './interfaces/i-update-signer-info-payload';
import { IUploadSignaturePayload } from './interfaces/i-upload-signature-payload';
export class SigningApiService {
public static getSessionByAccessToken(accessToken: string, remoteIpAddress?: string): Promise<ISession> {
return ApiService.get('signing', 'statusByAccessToken', { accessToken }, {
headers: {
Authorization: 'Bearer ' + getSigningApiAuthToken(),
'X-Forwarded-For': remoteIpAddress
}
});
}
public static updateSignerInfo(sessionId: string, payload: IUpdateSignerInfoPayload): Promise<ISession> {
return ApiService.put('signing', 'updateSubjectInfo', { sessionId }, payload, {
headers: {
Authorization: 'Bearer ' + getSigningApiAuthToken(),
}
});
}
public static uploadSignature(payload: IUploadSignaturePayload): Promise<{redirectUrl: string}> {
const formData = new FormData();
formData.append('signatureFile', payload.signatureFile, payload.signatureFile.name);
formData.append('signatureInput', JSON.stringify({
accessToken: payload.accessToken,
signerName: payload.signerName,
personalNumber: payload.personalNumber,
}));
return ApiService.post('signing', 'uploadSignature', {}, formData, {
headers: {
'Content-Type': 'multipart/form-data',
Accept: 'application/json',
Authorization: 'Bearer ' + getSigningApiAuthToken(),
}
});
}
public static taskExit(accessToken: string): Promise<{ redirectUrl: string }> {
return ApiService.post('signing', 'signingTaskExit', { accessToken }, {}, {
headers: {
Authorization: 'Bearer ' + getSigningApiAuthToken(),
}
});
}
}
```