Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m52s
Docker images / frontend-image (push) Successful in 2m23s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 38s
Docker images / publish-release (push) Successful in 8s
JWT на backend, handoff/callback на UI, RBAC mm:*, AUTH_* в compose. Co-authored-by: Cursor <cursoragent@cursor.com>
73 lines
1.9 KiB
TypeScript
73 lines
1.9 KiB
TypeScript
import { configuredBackendUrl } from "@/lib/backend-url"
|
|
import {
|
|
getToken,
|
|
isAuthEnabled,
|
|
redirectToPortalLogin,
|
|
redirectToPortalLoginInteractive,
|
|
} from "@/lib/auth"
|
|
|
|
export class ApiClientError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public readonly status: number,
|
|
public readonly payload?: unknown,
|
|
) {
|
|
super(message)
|
|
this.name = "ApiClientError"
|
|
}
|
|
}
|
|
|
|
function trimBaseUrl(baseUrl: string): string {
|
|
return baseUrl.replace(/\/$/, "")
|
|
}
|
|
|
|
function resolveRequestUrl(baseUrl: string, path: string): string {
|
|
if (path.startsWith("/") && configuredBackendUrl().kind === "same-origin") {
|
|
return path
|
|
}
|
|
return trimBaseUrl(baseUrl) + path
|
|
}
|
|
|
|
export async function requestJson<T>(
|
|
baseUrl: string,
|
|
path: string,
|
|
init?: RequestInit,
|
|
): Promise<T> {
|
|
const hasBody = init?.body != null
|
|
const headers = new Headers(init?.headers)
|
|
if (hasBody && !headers.has("Content-Type")) {
|
|
headers.set("Content-Type", "application/json")
|
|
}
|
|
const token = typeof window !== "undefined" ? getToken() : null
|
|
if (token && !headers.has("Authorization")) {
|
|
headers.set("Authorization", `Bearer ${token}`)
|
|
}
|
|
|
|
const res = await fetch(resolveRequestUrl(baseUrl, path), {
|
|
...init,
|
|
headers,
|
|
})
|
|
|
|
if (res.status === 401 && typeof window !== "undefined" && isAuthEnabled()) {
|
|
const ok = redirectToPortalLogin()
|
|
if (!ok) redirectToPortalLoginInteractive()
|
|
throw new ApiClientError("Unauthorized", 401)
|
|
}
|
|
|
|
if (res.status === 204) return undefined as T
|
|
|
|
const payload = await res.json().catch(() => undefined)
|
|
if (!res.ok) {
|
|
const msg =
|
|
typeof payload === "object" &&
|
|
payload !== null &&
|
|
"error" in payload &&
|
|
typeof (payload as { error?: unknown }).error === "string"
|
|
? (payload as { error: string }).error
|
|
: res.statusText
|
|
throw new ApiClientError(msg, res.status, payload)
|
|
}
|
|
|
|
return payload as T
|
|
}
|