mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
228 lines
5.7 KiB
TypeScript
228 lines
5.7 KiB
TypeScript
import {
|
|
useOidc,
|
|
useOidcAccessToken,
|
|
useOidcIdToken,
|
|
} from "@axa-fr/react-oidc";
|
|
import loadConfig from "@utils/config";
|
|
import { sleep } from "@utils/helpers";
|
|
import { usePathname } from "next/navigation";
|
|
import { isExpired } from "react-jwt";
|
|
import useSWR from "swr";
|
|
import { useErrorBoundary } from "@/contexts/ErrorBoundary";
|
|
|
|
type Method = "GET" | "POST" | "PUT" | "DELETE";
|
|
|
|
export type ErrorResponse = {
|
|
code: number;
|
|
message: string;
|
|
};
|
|
|
|
const config = loadConfig();
|
|
|
|
type RequestOptions = {
|
|
signal?: AbortSignal;
|
|
origin?: string;
|
|
};
|
|
|
|
async function apiRequest<T>(
|
|
oidcFetch: (input: RequestInfo, init?: RequestInit) => Promise<Response>,
|
|
method: Method,
|
|
url: string,
|
|
data?: any,
|
|
options?: RequestOptions,
|
|
) {
|
|
const origin = options?.origin ? options?.origin : config.apiOrigin + "/api";
|
|
|
|
const res = await oidcFetch(`${origin}${url}`, {
|
|
method,
|
|
body: JSON.stringify(data),
|
|
signal: options?.signal,
|
|
});
|
|
|
|
try {
|
|
if (!res.ok) {
|
|
const error = (await res.json()) as ErrorResponse;
|
|
return Promise.reject(error);
|
|
}
|
|
return (await res.json()) as T;
|
|
} catch (e) {
|
|
if (!res.ok) {
|
|
const error = {
|
|
code: res.status,
|
|
message: res.statusText,
|
|
} as ErrorResponse;
|
|
return Promise.reject(error);
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
export function useNetBirdFetch(ignoreError: boolean = false): {
|
|
fetch: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
|
} {
|
|
const tokenSource = config.tokenSource || "accessToken";
|
|
const { idToken } = useOidcIdToken();
|
|
const { accessToken } = useOidcAccessToken();
|
|
const token = tokenSource.toLowerCase() == "idtoken" ? idToken : accessToken;
|
|
const handleErrors = useApiErrorHandling(ignoreError);
|
|
|
|
const isTokenExpired = async () => {
|
|
let attempts = 20;
|
|
while (isExpired(token) && attempts > 0) {
|
|
await sleep(500);
|
|
attempts = attempts - 1;
|
|
}
|
|
return isExpired(token);
|
|
};
|
|
|
|
const nativeFetch = async (input: RequestInfo, init?: RequestInit) => {
|
|
const tokenExpired = await isTokenExpired();
|
|
if (tokenExpired) {
|
|
return handleErrors({
|
|
code: 401,
|
|
message: "token expired",
|
|
} as ErrorResponse);
|
|
}
|
|
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
};
|
|
|
|
return fetch(input, {
|
|
...init,
|
|
headers,
|
|
});
|
|
};
|
|
|
|
return {
|
|
fetch: nativeFetch as (
|
|
input: RequestInfo,
|
|
init?: RequestInit,
|
|
) => Promise<Response>,
|
|
};
|
|
}
|
|
|
|
export default function useFetchApi<T>(
|
|
url: string,
|
|
ignoreError = false,
|
|
revalidate = true,
|
|
allowFetch = true,
|
|
options?: RequestOptions,
|
|
) {
|
|
const { fetch } = useNetBirdFetch(ignoreError);
|
|
const handleErrors = useApiErrorHandling(ignoreError);
|
|
|
|
const { data, error, isLoading, isValidating, mutate } = useSWR(
|
|
url,
|
|
async (url) => {
|
|
if (!allowFetch) return;
|
|
return apiRequest<T>(fetch, "GET", url, undefined, options).catch((err) =>
|
|
handleErrors(err as ErrorResponse),
|
|
);
|
|
},
|
|
{
|
|
keepPreviousData: true,
|
|
revalidateOnFocus: revalidate,
|
|
revalidateIfStale: revalidate,
|
|
revalidateOnReconnect: revalidate,
|
|
},
|
|
);
|
|
|
|
return {
|
|
data: data as T | undefined,
|
|
error,
|
|
isLoading,
|
|
isValidating,
|
|
mutate,
|
|
} as const;
|
|
}
|
|
|
|
export function useApiCall<T>(
|
|
url: string,
|
|
ignoreError = false,
|
|
requestOptions?: RequestOptions,
|
|
) {
|
|
const { fetch } = useNetBirdFetch(ignoreError);
|
|
const handleErrors = useApiErrorHandling(ignoreError);
|
|
|
|
return {
|
|
post: async (data: any, suffix = "", options?: RequestOptions) => {
|
|
return apiRequest<T>(
|
|
fetch,
|
|
"POST",
|
|
url + suffix,
|
|
data,
|
|
options || requestOptions,
|
|
)
|
|
.then((res) => Promise.resolve(res as T))
|
|
.catch((err) => handleErrors(err as ErrorResponse)) as Promise<T>;
|
|
},
|
|
put: async (data: any, suffix = "", options?: RequestOptions) => {
|
|
return apiRequest<T>(
|
|
fetch,
|
|
"PUT",
|
|
url + suffix,
|
|
data,
|
|
options || requestOptions,
|
|
)
|
|
.then((res) => Promise.resolve(res as T))
|
|
.catch((err) => handleErrors(err as ErrorResponse)) as Promise<T>;
|
|
},
|
|
del: async (data: any = "", suffix = "", options?: RequestOptions) => {
|
|
return apiRequest<T>(
|
|
fetch,
|
|
"DELETE",
|
|
url + suffix,
|
|
data,
|
|
options || requestOptions,
|
|
)
|
|
.then((res) => Promise.resolve(res as T))
|
|
.catch((err) => handleErrors(err as ErrorResponse)) as Promise<T>;
|
|
},
|
|
get: async (suffix = "", options?: RequestOptions) => {
|
|
return apiRequest<T>(
|
|
fetch,
|
|
"GET",
|
|
url + suffix,
|
|
undefined,
|
|
options || requestOptions,
|
|
)
|
|
.then((res) => Promise.resolve(res as T))
|
|
.catch((err) => handleErrors(err as ErrorResponse)) as Promise<T>;
|
|
},
|
|
};
|
|
}
|
|
|
|
export function useApiErrorHandling(ignoreError = false) {
|
|
const { login } = useOidc();
|
|
const currentPath = usePathname();
|
|
const { setError } = useErrorBoundary();
|
|
if (ignoreError)
|
|
return (err: ErrorResponse) => {
|
|
console.log(err);
|
|
return Promise.reject(err);
|
|
};
|
|
|
|
return (err: ErrorResponse) => {
|
|
if (err.code == 401 && err.message == "no valid authentication provided") {
|
|
return login(currentPath);
|
|
}
|
|
if (err.code == 401 && err.message == "token expired") {
|
|
return login(currentPath);
|
|
}
|
|
if (err.code == 401 && err.message == "token invalid") {
|
|
setError(err);
|
|
}
|
|
if (err.code == 500 && err.message == "internal server error") {
|
|
setError(err);
|
|
}
|
|
if (err.code > 400 && err.code <= 500) {
|
|
setError(err);
|
|
}
|
|
|
|
return Promise.reject(err);
|
|
};
|
|
}
|