feat: refactor api

This commit is contained in:
Zamitto 2024-06-13 21:08:30 -03:00
parent f21a8bf784
commit ba08e0b112
4 changed files with 65 additions and 57 deletions

View file

@ -43,4 +43,7 @@ export class UserPreferences {
@Column("text", { default: "" }) @Column("text", { default: "" })
refreshToken: string; refreshToken: string;
@Column("int", { default: 0 })
tokenExpirationTimestamp: number;
} }

View file

@ -12,3 +12,8 @@ export const downloadSourceSchema = z.object({
}) })
), ),
}); });
export const refreshTokenSchema = z.object({
accessToken: z.string(),
expiresIn: z.number(),
});

View file

@ -21,7 +21,7 @@ const loadState = async (userPreferences: UserPreferences | null) => {
if (userPreferences?.realDebridApiToken) if (userPreferences?.realDebridApiToken)
RealDebridClient.authorize(userPreferences?.realDebridApiToken); RealDebridClient.authorize(userPreferences?.realDebridApiToken);
HydraApi.createInstance(); HydraApi.setupApi();
const [nextQueueItem] = await downloadQueueRepository.find({ const [nextQueueItem] = await downloadQueueRepository.find({
order: { order: {

View file

@ -1,13 +1,19 @@
import { refreshTokenSchema } from "@main/events/helpers/validators";
import { userPreferencesRepository } from "@main/repository"; import { userPreferencesRepository } from "@main/repository";
import axios, { AxiosInstance } from "axios"; import axios, { AxiosInstance } from "axios";
export class HydraApi { export class HydraApi {
private static instance: AxiosInstance; private static instance: AxiosInstance;
static authToken = ""; private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5;
static refreshToken = "";
static async createInstance() { private static userAuth = {
authToken: "",
refreshToken: "",
expirationTimestamp: 0,
};
static async setupApi() {
this.instance = axios.create({ this.instance = axios.create({
baseURL: import.meta.env.MAIN_VITE_API_URL, baseURL: import.meta.env.MAIN_VITE_API_URL,
}); });
@ -16,72 +22,66 @@ export class HydraApi {
where: { id: 1 }, where: { id: 1 },
}); });
this.authToken = userPreferences?.accessToken ?? ""; this.userAuth = {
this.refreshToken = userPreferences?.refreshToken ?? ""; authToken: userPreferences?.accessToken ?? "",
refreshToken: userPreferences?.refreshToken ?? "",
this.instance.interceptors.request.use( expirationTimestamp: userPreferences?.tokenExpirationTimestamp ?? 0,
(config) => { };
config.headers.Authorization = `Bearer ${this.authToken}`;
return config;
},
(error) => {
return Promise.reject(error);
} }
private static async revalidateAccessTokenIfExpired() {
const now = new Date();
if (this.userAuth.expirationTimestamp > now.getTime()) {
const response = await this.instance.post(`/auth/refresh`, {
refreshToken: this.userAuth.refreshToken,
});
const { accessToken, expiresIn } = refreshTokenSchema.parse(
response.data
); );
this.instance.interceptors.response.use( const tokenExpirationTimestamp =
(response) => response, now.getTime() + expiresIn - this.EXPIRATION_OFFSET_IN_MS;
async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshToken = this.refreshToken;
if (!refreshToken) return error; this.userAuth.authToken = accessToken;
this.userAuth.expirationTimestamp = tokenExpirationTimestamp;
try {
const response = await axios.post(
`${import.meta.env.MAIN_VITE_API_URL}/auth/refresh`,
{ refreshToken }
);
const newAccessToken = response.data.accessToken;
this.authToken = newAccessToken;
userPreferencesRepository.upsert( userPreferencesRepository.upsert(
{ {
id: 1, id: 1,
accessToken: newAccessToken, accessToken,
tokenExpirationTimestamp,
}, },
["id"] ["id"]
); );
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
return axios(originalRequest); //recall Api with new token
} catch (err) {
this.authToken = "";
this.refreshToken = "";
return error;
} }
} }
return error; private static getAxiosConfig() {
} return {
); headers: {
Authorization: `Bearer ${this.userAuth.authToken}`,
},
};
} }
static async get(url: string) { static async get(url: string) {
return this.instance.get(url); this.revalidateAccessTokenIfExpired();
return this.instance.get(url, this.getAxiosConfig());
} }
static async post(url: string, data?: any) { static async post(url: string, data?: any) {
return this.instance.post(url, data); this.revalidateAccessTokenIfExpired();
return this.instance.post(url, data, this.getAxiosConfig());
} }
static async put(url, data?: any) { static async put(url, data?: any) {
return this.instance.put(url, data); this.revalidateAccessTokenIfExpired();
return this.instance.put(url, data, this.getAxiosConfig());
} }
static async patch(url, data?: any) { static async patch(url, data?: any) {
return this.instance.patch(url, data); this.revalidateAccessTokenIfExpired();
return this.instance.patch(url, data, this.getAxiosConfig());
} }
} }