Files
carmanagement/apps/dashboard/src/lib/api.ts
T
root 7b8f81336a
Build & Push / Pipeline Tests (push) Failing after 2m0s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 53s
Test / API Unit Tests (push) Successful in 1m8s
Test / Homepage Unit Tests (push) Successful in 48s
Test / Carplace Unit Tests (push) Successful in 42s
Test / Admin Unit Tests (push) Successful in 41s
Test / Dashboard Unit Tests (push) Successful in 43s
Test / API Integration Tests (push) Failing after 1m7s
add subscription upgrade plan
2026-08-10 23:23:58 -04:00

161 lines
4.6 KiB
TypeScript

export function normalizeApiBase(value: string): string {
const base = value.replace(/\/+$/, '')
if (/\/api$/.test(base)) return `${base}/v1`
return /\/api\/v1$/.test(base) ? base : `${base}/api/v1`
}
const DASHBOARD_PROXY_API_BASE = '/dashboard/api/v1'
function isLocalBrowserHost(hostname: string): boolean {
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1'
}
function shouldUseDashboardProxy(configuredApiBase?: string): boolean {
if (typeof window === 'undefined') return false
if (!configuredApiBase) return true
if (configuredApiBase.startsWith('/')) return false
try {
const apiUrl = new URL(configuredApiBase)
const currentOrigin = window.location?.origin
const currentHostname = window.location?.hostname
if (!currentOrigin || !currentHostname) return false
if (isLocalBrowserHost(currentHostname) && isLocalBrowserHost(apiUrl.hostname)) {
return apiUrl.origin !== currentOrigin
}
if (isLocalBrowserHost(currentHostname)) return false
return apiUrl.origin !== currentOrigin
} catch {
return false
}
}
export function resolveApiBase(): string {
if (typeof window === 'undefined') {
return normalizeApiBase(process.env.API_INTERNAL_URL || 'http://localhost:4000/api/v1')
}
const configuredApiBase = process.env.NEXT_PUBLIC_API_URL
return normalizeApiBase(shouldUseDashboardProxy(configuredApiBase) ? DASHBOARD_PROXY_API_BASE : (configuredApiBase || DASHBOARD_PROXY_API_BASE))
}
export function resolveApiOrigin(): string | null {
if (typeof window === 'undefined') return null
try {
return new URL(resolveApiBase(), window.location.origin).origin
} catch {
return window.location.origin
}
}
export function resolveRealtimeSocketTarget(): { origin: string; path: string } | null {
if (typeof window === 'undefined') return null
const configuredApiBase = process.env.NEXT_PUBLIC_API_URL
if (configuredApiBase && !configuredApiBase.startsWith('/')) {
try {
return {
origin: new URL(normalizeApiBase(configuredApiBase)).origin,
path: '/socket.io',
}
} catch {
return null
}
}
const currentHostname = window.location?.hostname
if (currentHostname && isLocalBrowserHost(currentHostname)) {
return {
origin: 'http://localhost:4000',
path: '/socket.io',
}
}
return {
origin: resolveApiOrigin() ?? window.location.origin,
path: '/socket.io',
}
}
export const API_BASE = resolveApiBase()
export const EMPLOYEE_PROFILE_KEY = 'employee_profile'
export async function apiFetch<T>(path: string, options?: RequestInit): Promise<T> {
const isFormData = typeof FormData !== 'undefined' && options?.body instanceof FormData
const headers: Record<string, string> = {
...(options?.headers as Record<string, string> ?? {}),
}
if (!isFormData && options?.body !== undefined) {
headers['Content-Type'] = 'application/json'
}
const res = await fetch(`${resolveApiBase()}${path}`, {
...options,
headers,
credentials: 'include',
})
let json: any
try {
json = await res.json()
} catch {
throw new Error(`HTTP ${res.status}: ${res.statusText}`)
}
if (!res.ok) {
const issues = Array.isArray(json?.issues)
? json.issues
.map((issue: any) => {
const path = Array.isArray(issue?.path) && issue.path.length ? `${issue.path.join('.')}: ` : ''
return issue?.message ? `${path}${issue.message}` : null
})
.filter(Boolean)
: []
const message = issues.length
? `${json?.message ?? 'Invalid request'}: ${issues.join('; ')}`
: (json?.message ?? json?.error ?? `Request failed with status ${res.status}`)
const err = new Error(message) as any
err.code = json?.error
err.statusCode = res.status
err.issues = json?.issues
throw err
}
return (json?.data ?? json) as T
}
export async function apiFetchServer<T>(path: string, token: string, options?: RequestInit): Promise<T> {
const isFormData = typeof FormData !== 'undefined' && options?.body instanceof FormData
const res = await fetch(`${resolveApiBase()}${path}`, {
...options,
headers: {
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
'Authorization': `Bearer ${token}`,
...(options?.headers as Record<string, string> ?? {}),
},
cache: 'no-store',
})
let json: any
try {
json = await res.json()
} catch {
throw new Error(`HTTP ${res.status}: ${res.statusText}`)
}
if (!res.ok) {
const err = new Error(json?.message ?? `Request failed with status ${res.status}`) as any
err.statusCode = res.status
throw err
}
return (json?.data ?? json) as T
}