UNPKG

@xsprtd/nuxt-api

Version:

Nuxt API Authentication and Http Client

129 lines (128 loc) 3.85 kB
import { useApiOptions } from "../composables/useApiOptions.js"; import { useErrorBag } from "../composables/useErrorBag.js"; import { useTokenStorage } from "../composables/useTokenStorage.js"; import { useCookie, useRequestHeaders, useRequestURL } from "#app"; const getCredentials = () => { return "credentials" in Request.prototype ? "include" : void 0; }; const fetchCsrfCookie = async (config) => { try { await $fetch(config.endpoints.csrf, { baseURL: config.apiBaseURL, credentials: "include" }); } catch (error) { console.error("Failed to initialize CSRF cookie", error); } }; const attachCsrfHeader = async (headers, config) => { let csrfToken = useCookie(config.csrf.cookieName, { readonly: true }); if (!csrfToken.value) { await fetchCsrfCookie(config); csrfToken = useCookie(config.csrf.cookieName, { readonly: true }); } if (!csrfToken.value) { console.warn( `Unable to set ${config.csrf.headerName} header` ); return headers ?? {}; } return { ...headers, [config.csrf.headerName]: csrfToken.value }; }; const attachServerHeaders = (headers, config) => { const clientCookies = useRequestHeaders(["cookie"]); const origin = config.originUrl ?? useRequestURL().origin; return { ...headers, Referer: origin, Origin: origin, ...clientCookies }; }; const attachToken = async (headers) => { const token = await useTokenStorage().get(); if (!token) { console.debug("Authentication token is not set in the storage"); return headers; } return { ...headers, Authorization: `Bearer ${token}` }; }; const extractHeaders = (context) => { return context.options.headers instanceof Headers ? Object.fromEntries(context.options.headers.entries()) : context.options.headers; }; const prepareContext = async (context, config) => { const method = context.options.method?.toLowerCase() ?? "get"; context.options.headers = new Headers({ Accept: "application/json", ...config.headers, ...extractHeaders(context) }); if (context.options.body instanceof FormData) { context.options.method = "POST"; context.options.body.append("_method", method.toUpperCase()); } if (config.authMode === "cookie") { if (import.meta.server) { context.options.headers = new Headers( attachServerHeaders( Object.fromEntries(context.options.headers.entries()), config ) ); } if (["post", "delete", "put", "patch"].includes(method)) { context.options.headers = new Headers( await attachCsrfHeader( Object.fromEntries(context.options.headers.entries()), config ) ); } } else if (config.authMode === "token") { context.options.headers = new Headers( await attachToken( Object.fromEntries(context.options.headers.entries()) ) ); } }; export default (options) => { const config = useApiOptions(); options ||= {}; return { baseURL: config.apiBaseURL, credentials: getCredentials(), redirect: "manual", retry: config.fetchOptions.retryAttempts, onRequest: async (context) => { if (options.onRequest) { if (Array.isArray(options.onRequest)) { for (const hook of options.onRequest) { await hook(context); } } else { await options.onRequest(context); } } await prepareContext(context, config); }, onResponseError: async (context) => { useErrorBag().handle(context); if (options.onResponseError) { if (Array.isArray(options.onResponseError)) { for (const hook of options.onResponseError) { await hook(context); } } else { await options.onResponseError(context); } } } }; };