Created
February 8, 2022 08:04
-
-
Save acidjazz/5d6a6a041090e9c5206c9919a2a9fb79 to your computer and use it in GitHub Desktop.
Revisions
-
acidjazz created this gist
Feb 8, 2022 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,214 @@ import { FetchError, FetchOptions, SearchParams } from 'ohmyfetch' import { reactive, ref } from '@vue/reactivity' import { IncomingMessage, ServerResponse } from 'http' import { useCookie } from 'h3' import { TailvueToast } from 'tailvue' import { Router } from 'vue-router' import Cookies from 'universal-cookie' export interface UserLogin { token: string user: models.User provider: string error?: string action?: LoginAction } export interface AuthConfig { fetchOptions: FetchOptions req?: IncomingMessage res?: ServerResponse redirect: { logout: string login: undefined|string } } export interface LoginAction { action: string url: string } const authConfigDefaults:AuthConfig = { fetchOptions: {}, req: undefined, redirect: { logout: '/', login: undefined, }, } export default class Api { public token = ref<string|undefined>(undefined) private cookies:Cookies = new Cookies(); public config: AuthConfig public $user = reactive<models.User|Record<string, unknown>>({}) public $toast:TailvueToast public loggedIn = ref<boolean>(false) public modal = ref<boolean>(false) public redirect = ref<boolean>(false) public action = ref<null|LoginAction>(null) public callback = undefined constructor(config: AuthConfig, toast: TailvueToast) { this.$toast = toast this.config = { ...authConfigDefaults,...config } this.checkUser() } on(redirect: boolean, action: LoginAction|null) { this.redirect.value = redirect this.modal.value = true this.action.value = action } off() { this.modal.value = false } checkUser() { this.token.value = this.getToken() if (this.token.value) { this.loggedIn.value = true this.setUser().then() } else this.loggedIn.value = false } async login (result: UserLogin): Promise<undefined|string> { this.loggedIn.value = true this.token.value = result.token Object.assign(this.$user, result.user) this.cookies.set('token', this.token.value, { path: '/', maxAge: 60*60*24*30 }) this.$toast.show({ type: 'success', message: 'Login Successful', timeout: 1 }) if (result.action && result.action.action === 'redirect') return result.action.url if (this.callback) this.callback() return this.config.redirect.login } private getToken(): string { if (this.config.req) return useCookie(this.config.req, 'token') return this.cookies.get('token') } private fetchOptions(params?: SearchParams, method = 'GET'): FetchOptions { const fetchOptions = this.config.fetchOptions fetchOptions.headers = { Accept: 'application/json', Authorization: `Bearer ${this.token.value}`, } fetchOptions.method = method delete this.config.fetchOptions.body delete this.config.fetchOptions.params if (params) if (method === 'POST' || method === 'PUT') this.config.fetchOptions.body = params else this.config.fetchOptions.params = params return this.config.fetchOptions } private async setUser(): Promise<void> { try { const result = await $fetch<api.MetApiResponse & { data: models.User }>('/me', this.fetchOptions()) Object.assign(this.$user, result.data) } catch (e) { await this.invalidate() } } public async index <Results>(endpoint: string, params?: SearchParams): Promise<api.MetApiResults & { data: Results }> { try { return await $fetch<api.MetApiResults & { data: Results }>(endpoint, this.fetchOptions(params)) } catch (error) { await this.toastError(error) } } public async get <Result>(endpoint: string, params?: SearchParams): Promise<api.MetApiResponse & { data: Result }> { try { return await $fetch<api.MetApiResponse & { data: Result }>(endpoint, this.fetchOptions(params)) } catch (error) { await this.toastError(error) } } public async update (endpoint: string, params?: SearchParams): Promise<api.MetApiResponse> { try { return (await $fetch<api.MetApiResults & { data: api.MetApiResponse}>(endpoint, this.fetchOptions(params, 'PUT'))).data } catch (error) { await this.toastError(error) } } public async store <Result>(endpoint: string, params?: SearchParams): Promise<api.MetApiResponse & { data: Result }> { try { return (await $fetch<api.MetApiResults & { data: api.MetApiResponse & { data: Result } }>(endpoint, this.fetchOptions(params, 'POST'))).data } catch (error) { await this.toastError(error) } } public async delete (endpoint: string, params?: SearchParams): Promise<api.MetApiResponse> { try { return (await $fetch<api.MetApiResults & { data: api.MetApiResponse}>(endpoint, this.fetchOptions(params, 'DELETE'))).data } catch (error) { await this.toastError(error) } } public async attempt (token: string | string[]): Promise<UserLogin> { try { return (await $fetch<api.MetApiResponse & { data: UserLogin }>('/login', this.fetchOptions({ token }, 'POST'))).data } catch (error) { await this.toastError(error) } } private async toastError (error: FetchError): Promise<void> { if (error.response?.status === 401) return await this.invalidate() if (!this.$toast) throw error if (error.response._data && error.response._data.errors) for (const err of error.response._data.errors) this.$toast.show({ type: 'danger', message: err.detail ?? err.message ?? '', timeout: 12, }) if (error.response?.status === 403) return this.$toast.show({ type: 'denied', message: error.response._data.message, timeout: 0, }) if (error.response._data.exception) this.$toast.show({ type: 'danger', message: `<b>[${error.response._data.exception}]</b> <br /> ${error.response._data.message} <br /> <a href="phpstorm://open?file=/${error.response._data.file}&line=${error.response._data.line}">${error.response._data.file}:${error.response._data.line}</a>`, timeout: 0, }) } public async logout (router: Router): Promise<void> { const response = (await $fetch<api.MetApiResults>('/logout', this.fetchOptions())) this.$toast.show(Object.assign(response.data, { timeout: 1 })) await this.invalidate(router) } public async invalidate (router?: Router): Promise<void> { this.token.value = undefined this.loggedIn.value = false Object.assign(this.$user, {}) this.cookies.remove('token') if (router) await router.push(this.config.redirect.logout) else if (process.client) document.location.href = this.config.redirect.logout } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,33 @@ import { defineNuxtPlugin, useNuxtApp, useRuntimeConfig } from '#app' import Api from '~/lib/api' export default defineNuxtPlugin((nuxtApp) => { const config = useRuntimeConfig() const { $toast } = useNuxtApp() nuxtApp.provide('api', new Api({ req: nuxtApp.ssrContext?.req, res: nuxtApp.ssrContext?.res, fetchOptions: { baseURL: config.apiURL, }, redirect: { logout: '/', login: '/home', }, }, $toast), ) }) declare module '#app' { interface NuxtApp { $api: Api } } declare module '@vue/runtime-core' { interface ComponentCustomProperties { $api: Api } }