Created
February 8, 2022 08:04
-
-
Save acidjazz/5d6a6a041090e9c5206c9919a2a9fb79 to your computer and use it in GitHub Desktop.
api plugin in laranuxt
This file contains 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 characters
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 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 characters
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 | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment