LeLab / src /contexts /ApiContext.tsx
GitHub CI
Sync from leLab @ 7317f7103e3a9d7f45fe4c0d6e4660a8f9d295e3
fc9bd9f
import React, { createContext, useContext, ReactNode, useState, useCallback, useMemo } from "react";
interface ApiContextType {
baseUrl: string;
wsBaseUrl: string;
fetchWithHeaders: (url: string, options?: RequestInit) => Promise<Response>;
}
const ApiContext = createContext<ApiContextType | undefined>(undefined);
const STORAGE_KEY = "lelab.apiBaseUrl";
const DEFAULT_LOCALHOST = "http://localhost:8000";
const httpToWs = (url: string): string => url.replace(/^http(s?):/, "ws$1:");
const resolveInitialBaseUrl = (): string => {
if (typeof window === "undefined") return DEFAULT_LOCALHOST;
const fromQuery = new URLSearchParams(window.location.search).get("api");
if (fromQuery) {
try {
new URL(fromQuery);
const clean = fromQuery.replace(/\/$/, "");
window.localStorage.setItem(STORAGE_KEY, clean);
return clean;
} catch {
console.warn("Invalid `api` query param, ignoring:", fromQuery);
}
}
return window.localStorage.getItem(STORAGE_KEY) || DEFAULT_LOCALHOST;
};
export const ApiProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const [baseUrl] = useState<string>(resolveInitialBaseUrl);
const wsBaseUrl = httpToWs(baseUrl);
const fetchWithHeaders = useCallback(async (url: string, options: RequestInit = {}): Promise<Response> => {
return fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
}, []);
const value = useMemo(
() => ({ baseUrl, wsBaseUrl, fetchWithHeaders }),
[baseUrl, wsBaseUrl, fetchWithHeaders]
);
return <ApiContext.Provider value={value}>{children}</ApiContext.Provider>;
};
export const useApi = (): ApiContextType => {
const context = useContext(ApiContext);
if (context === undefined) {
throw new Error("useApi must be used within an ApiProvider");
}
return context;
};