- typed client and useFaq/useFaqApi (SSR, faq:<slug>:<locale> key, server and public bases, x-forwarded-for forwarded also on cached renders) - FaqBlock, FaqList and FaqItem: native details, exclusive and openFirst, server-sanitized description and answers, nothing or #fallback on 404, error or no items - FAQ tokens and base CSS with the .faq-html rules, README - node client tests and a playground SSR smoke against a fixture API
29 lines
1.4 KiB
TypeScript
29 lines
1.4 KiB
TypeScript
// The FAQ API client. On the server it calls the internal base
|
|
// (runtimeConfig.faq.apiBase) and forwards the visitor's x-forwarded-for,
|
|
// plus the address of the peer that reached Nuxt, so the Go rate limiter
|
|
// keeps one bucket per visitor, also on route-cached renders. In the browser
|
|
// it calls the same-origin public base. The base is always runtime config,
|
|
// never request input.
|
|
|
|
import { createFaqClient, type FaqClient, type FaqFetcher } from '../utils/client.ts'
|
|
|
|
export function useFaqApi(): FaqClient {
|
|
const config = useRuntimeConfig()
|
|
|
|
if (import.meta.server) {
|
|
const base = (config.faq as { apiBase: string }).apiBase
|
|
const event = useRequestEvent()
|
|
const recorded = event?.context.faqForwardedFor as string | undefined
|
|
const incoming = useRequestHeaders(['x-forwarded-for'])['x-forwarded-for']
|
|
const peer = event?.node?.req?.socket?.remoteAddress
|
|
const forwarded = recorded || [incoming, peer].filter((v): v is string => typeof v === 'string' && v !== '').join(', ')
|
|
const headers: Record<string, string> = forwarded ? { 'x-forwarded-for': forwarded } : {}
|
|
const fetcher: FaqFetcher = (path, query) => $fetch(path, { baseURL: base, query, headers })
|
|
return createFaqClient(fetcher)
|
|
}
|
|
|
|
const base = (config.public.faq as { apiBase: string }).apiBase
|
|
const fetcher: FaqFetcher = (path, query) => $fetch(path, { baseURL: base, query })
|
|
return createFaqClient(fetcher)
|
|
}
|