mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-12-09 06:32:43 +08:00
147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
import { Context } from '@nuxt/types';
|
|
import { Inject } from '@nuxt/types/app';
|
|
import { AxiosError, AxiosRequestConfig } from 'axios';
|
|
import jwtDecode, { JwtPayload } from 'jwt-decode';
|
|
import qs from 'qs';
|
|
|
|
const createApi = ({ $axios, store, app: { $cookies }, error }: Context) => {
|
|
class API {
|
|
getToken(forceFetch: boolean = true): Promise<string | null> {
|
|
if (store.state.auth.token) {
|
|
if (API.validateToken(store.state.auth.token)) {
|
|
return Promise.resolve(store.state.auth.token);
|
|
} else {
|
|
return this.refreshToken();
|
|
}
|
|
} else if (forceFetch) {
|
|
return this.refreshToken();
|
|
} else {
|
|
return Promise.resolve(null);
|
|
}
|
|
}
|
|
|
|
private refreshToken(): Promise<string | null> {
|
|
return new Promise<string | null>((resolve) => {
|
|
return $axios
|
|
.get<{ token: string; refreshToken: string; cookieName: string; expiresIn: number }>('/refresh')
|
|
.then((value) => {
|
|
store.commit('auth/SET_TOKEN', value.data.token);
|
|
$cookies.set(value.data.cookieName, value.data.refreshToken, {
|
|
path: '/',
|
|
expires: new Date(Date.now() + value.data.expiresIn * 1000),
|
|
sameSite: 'strict',
|
|
secure: process.env.NODE_ENV === 'production',
|
|
});
|
|
resolve(value.data.token);
|
|
})
|
|
.catch(() => {
|
|
resolve(null);
|
|
});
|
|
});
|
|
}
|
|
|
|
request<T>(url: string, authed: boolean = true, method: AxiosRequestConfig['method'] = 'get', data: object = {}): Promise<T> {
|
|
return this.getToken(authed).then((token) => {
|
|
return this._request(`v1/${url}`, token, method, data);
|
|
});
|
|
}
|
|
|
|
requestInternal<T>(url: string, authed: boolean = true, method: AxiosRequestConfig['method'] = 'get', data: object = {}): Promise<T> {
|
|
return this.getToken(authed).then((token) => {
|
|
return this.requestInternalWithToken<T>(url, token, authed, method, data);
|
|
});
|
|
}
|
|
|
|
requestInternalWithToken<T>(
|
|
url: string,
|
|
token: string | null,
|
|
authed: boolean = true,
|
|
method: AxiosRequestConfig['method'] = 'get',
|
|
data: object = {}
|
|
): Promise<T> {
|
|
let promise: Promise<string | null>;
|
|
if (token && !API.validateToken(token)) {
|
|
promise = this.getToken(true);
|
|
} else {
|
|
promise = Promise.resolve(token);
|
|
}
|
|
|
|
return promise.then((token) => {
|
|
if (authed && !token) {
|
|
error({
|
|
statusCode: 401,
|
|
message: 'Requires authorization',
|
|
});
|
|
return Promise.reject(new Error('Requires authorization'));
|
|
} else {
|
|
return this._request(`internal/${url}`, token, method, data);
|
|
}
|
|
});
|
|
}
|
|
|
|
private static validateToken(token: string): boolean {
|
|
const decodedToken = jwtDecode<JwtPayload>(token);
|
|
if (!decodedToken.exp) {
|
|
return false;
|
|
}
|
|
return decodedToken.exp * 1000 > Date.now() - 10 * 1000; // check against 10 seconds earlier to mitigate tokens expiring mid-request
|
|
}
|
|
|
|
private _request<T>(url: string, token: string | null, method: AxiosRequestConfig['method'], data: object): Promise<T> {
|
|
const headers = token ? { Authorization: `HangarAuth ${token}` } : {};
|
|
|
|
return new Promise<T>((resolve, reject) => {
|
|
return $axios
|
|
.request<T>({
|
|
method,
|
|
url: `/api/${url}`,
|
|
headers,
|
|
data: method?.toLowerCase() !== 'get' ? data : {},
|
|
params: method?.toLowerCase() === 'get' ? data : {},
|
|
paramsSerializer: (params) => {
|
|
return qs.stringify(params, {
|
|
arrayFormat: 'repeat',
|
|
});
|
|
},
|
|
})
|
|
.then(({ data }) => resolve(data))
|
|
.catch((error: AxiosError) => {
|
|
// TODO error popup
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
return new API();
|
|
};
|
|
|
|
type apiType = ReturnType<typeof createApi>;
|
|
|
|
declare module 'vue/types/vue' {
|
|
interface Vue {
|
|
$api: apiType;
|
|
}
|
|
}
|
|
|
|
declare module '@nuxt/types' {
|
|
interface NuxtAppOptions {
|
|
$api: apiType;
|
|
}
|
|
|
|
interface Context {
|
|
$api: apiType;
|
|
}
|
|
}
|
|
|
|
declare module 'vuex/types/index' {
|
|
// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars
|
|
interface Store<S> {
|
|
$api: apiType;
|
|
}
|
|
}
|
|
|
|
export default (ctx: Context, inject: Inject) => {
|
|
inject('api', createApi(ctx));
|
|
};
|