Skip to content

Instantly share code, notes, and snippets.

@acidjazz
Created February 8, 2022 08:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save acidjazz/5d6a6a041090e9c5206c9919a2a9fb79 to your computer and use it in GitHub Desktop.
Save acidjazz/5d6a6a041090e9c5206c9919a2a9fb79 to your computer and use it in GitHub Desktop.
api plugin in laranuxt
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
}
}
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