Hangar/frontend/plugins/api.ts

157 lines
5.8 KiB
TypeScript
Raw Normal View History

2021-01-21 11:36:18 +08:00
import { Context } from '@nuxt/types';
import { Inject } from '@nuxt/types/app';
import { AxiosError, AxiosRequestConfig } from 'axios';
import jwtDecode, { JwtPayload } from 'jwt-decode';
2021-02-05 08:22:22 +08:00
import qs from 'qs';
2021-02-09 02:26:18 +08:00
const createApi = ({ $axios, store, app: { $cookies } }: Context) => {
2021-01-21 11:36:18 +08:00
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);
2021-01-21 11:36:18 +08:00
} else {
return this.refreshToken();
2021-01-21 11:36:18 +08:00
}
} else if (forceFetch) {
2021-02-03 07:47:15 +08:00
return this.refreshToken();
} else {
return Promise.resolve(null);
}
}
private refreshToken(): Promise<string | null> {
2021-02-03 07:47:15 +08:00
return new Promise<string | null>((resolve) => {
return $axios
.get<{ token: string; refreshToken: string; cookieName: string; expiresIn: number }>('/refresh')
2021-02-03 07:47:15 +08:00
.then((value) => {
store.commit('auth/SET_TOKEN', value.data.token);
2021-02-05 01:49:02 +08:00
$cookies.set(value.data.cookieName, value.data.refreshToken, {
path: '/',
expires: new Date(Date.now() + value.data.expiresIn * 1000),
sameSite: 'strict',
2021-03-17 01:43:58 +08:00
secure: process.env.nodeEnv === 'production',
});
resolve(value.data.token);
2021-02-03 07:47:15 +08:00
})
.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);
});
}
2021-02-09 02:26:18 +08:00
requestInternal<T = void>(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);
2021-02-03 07:47:15 +08:00
});
}
requestInternalWithToken<T>(
url: string,
token: string | null,
authed: boolean = true,
method: AxiosRequestConfig['method'] = 'get',
data: object = {}
): Promise<T> {
let tokenPromise: Promise<string | null>;
if (token && !API.validateToken(token)) {
tokenPromise = this.getToken(true);
} else {
tokenPromise = Promise.resolve(token);
}
return tokenPromise.then((token) => {
if (authed && !token) {
2021-03-22 13:03:41 +08:00
// TODO figure out how to not have to make the request if you know its going to fail
}
2021-03-22 13:03:41 +08:00
// if (authed && !token) {
// // TODO this return doesn't match others
// return Promise.reject({
// isAxiosError: true,
// respose: {
// data: {
// isHangarApiException: true,
// httpError: {
// statusCode: 401,
// },
// message: 'You must be logged in',
// },
// },
// });
// } else {
// return this._request(`internal/${url}`, token, method, data);
// }
return this._request(`internal/${url}`, token, method, data);
2021-02-03 07:47:15 +08:00
});
2021-01-21 11:36:18 +08:00
}
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> {
2021-02-10 16:31:43 +08:00
const headers: Record<string, string> = token ? { Authorization: `HangarAuth ${token}` } : {};
2021-01-21 11:36:18 +08:00
return new Promise<T>((resolve, reject) => {
return $axios
.request<T>({
method,
url: `/api/${url}`,
headers,
2021-02-05 08:22:22 +08:00
data: method?.toLowerCase() !== 'get' ? data : {},
params: method?.toLowerCase() === 'get' ? data : {},
paramsSerializer: (params) => {
return qs.stringify(params, {
arrayFormat: 'repeat',
});
},
2021-01-22 11:11:24 +08:00
})
.then(({ data }) => resolve(data))
.catch((error: AxiosError) => {
reject(error);
2021-01-22 15:27:42 +08:00
});
2021-01-21 11:36:18 +08:00
});
}
}
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' {
2021-01-31 10:00:11 +08:00
// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars
2021-01-21 11:36:18 +08:00
interface Store<S> {
$api: apiType;
}
}
export default (ctx: Context, inject: Inject) => {
inject('api', createApi(ctx));
2021-01-21 11:36:18 +08:00
};