/
Muip
/
ProBody
Обзор
Документация
Войти
/
Muip
/
ProBody
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
api/ApiClient.ts
171 строка
5 KB
chmerev
new
14 авг 2024, 12:02
14 авг 2024, 12:02
595f266
Код
Авторство
О чём код?
/* eslint-disable no-console */ import axios, { AxiosInstance } from 'axios'; import ApiClientProps, { ApiClientEventsEnum } from './ApiClientProps'; import { UserApi } from './user/User'; import { ErrorResponse } from './dto/Error'; import { UserAuthResponse } from './user/dto/UserAuthResponse.dto'; import { NiceResponse } from './dto/NiceResponse'; import { CoursesApi } from './courses/Courses'; import { AppSettings } from '../appSettings'; export default class ApiClient { client: AxiosInstance; baseUrl: string; private props: ApiClientProps; constructor(props: ApiClientProps) { this.props = props; this.baseUrl = this.props.baseUrl; this.client = axios.create({ baseURL: this.baseUrl, headers: { 'Referer': this.props.baseUrl } }); this.setInterceptors(); } public async getAccessToken(): Promise<string | undefined | null> { return this.props.getToken(this.props.auth.access.name) || null; } public async getRefreshToken(): Promise<string | undefined | null> { return this.props.getToken(this.props.auth.refresh.name) || null; } public async saveTokens(accessToken: string | null, refreshToken: string | null) { if (accessToken != null) { await this.props.saveToken(this.props.auth.access.name, accessToken, { expires: this.props.auth.access.durationDays, httpOnly: false }); } else { await this.props.deleteToken(this.props.auth.access.name); } if (refreshToken != null) { await this.props.saveToken(this.props.auth.refresh.name, refreshToken, { expires: this.props.auth.refresh.durationDays, httpOnly: false }); } else { await this.props.deleteToken(this.props.auth.refresh.name); } } public async Unauthorize() { await this.saveTokens(null, null); this.run(ApiClientEventsEnum.Unauthorize) } private listeners: {evt: ApiClientEventsEnum, callback: () => void | Promise<void>}[] = []; public async on(evt: ApiClientEventsEnum, fn: () => void | Promise<void>) { this.listeners.push({evt: evt, callback: fn}) } public async off(evt: ApiClientEventsEnum, fn: () => void | Promise<void>) { this.listeners = this.listeners.filter((e) => e.evt !== evt && e.callback !== fn); } private async run(evt: ApiClientEventsEnum) { this.listeners.filter((x) => x.evt === evt).forEach((x) => x.callback()); } public async isAuthorized() { return await this.getAccessToken() != null && await this.getRefreshToken() != null; } private async setInterceptors() { this.client.interceptors.request.use( async config => { config.headers['Accept'] = 'application/json'; const accessToken = await this.getAccessToken(); const refreshToken = await this.getRefreshToken(); if (this.props.debug) { const { url, data, headers } = config; console.log('REQUEST:', url, JSON.stringify(data, null, 4), headers); } if (accessToken) {config.headers['Authorization'] = `Bearer ${accessToken}`;} //we dont have access BUT we have refresh token if (!accessToken && refreshToken) { const result = await this.refreshAccessTokenAndSave(); if (result) { config.headers['Authorization'] = `Bearer ${result}`; return config; } } return config; }, error => { Promise.reject(error) }); this.client.interceptors.response.use( (response) => { if (this.props.debug) { const { status, data } = response; console.log('RESPONSE:', JSON.stringify(data, null, 4), 'STATUS:', status); } return response }, // eslint-disable-next-line @typescript-eslint/no-explicit-any async (error: any) => { const originalRequest = error.config; const refreshToken = await this.getRefreshToken(); const errors = error?.response.data?.errors as ErrorResponse[] const needToUpdateToken = errors.some(x => x.code == 1007) || error?.response?.status === 401; if (needToUpdateToken && refreshToken && !originalRequest._retry) { const result = await this.refreshAccessTokenAndSave(); originalRequest._retry = true; if (result) { return this.client(originalRequest); } } if (this.props.debug) { for (const err of errors) { console.error('RESPONSE ERROR:', err.message, 'CODE:', err.code); } } return Promise.reject({ success: false, error: error?.response?.data?.error, data: error, code: error?.response?.status }); }); } private async refreshAccessTokenAndSave(): Promise<boolean> { if (this.props.debug) { console.log('REFRESHING TOKEN...'); } const refreshToken = await this.getRefreshToken(); if (refreshToken) { try { //need to use axios directly because we dont want to use interceptor const res = await axios.put<NiceResponse<UserAuthResponse>>(this.props.baseUrl + '/authorization/refresh', { token: refreshToken }, { headers: { 'Content-Type': 'application/json', 'Referer': AppSettings.schoolURL } }); if (res.data.success) { await this.saveTokens(res.data?.body?.accessToken || null, res.data?.body?.refreshToken || null); return true; } throw new Error } catch (e) { await this.Unauthorize(); return false; } } else { return false; } } user: UserApi = new UserApi(this) courses: CoursesApi = new CoursesApi(this) }