Files
OpenFrontIO/src/client/Api.ts
T
AlexBesios dbaa61318e - Update the newbox to fetch news from the api
- Fix the comment recoms and the minor issues
2026-04-07 14:29:06 +03:00

291 lines
7.5 KiB
TypeScript

import newsItemsFallback from "resources/news.json";
import { z } from "zod";
import type { NewsItem } from "../core/ApiSchemas";
import {
ClanLeaderboardResponse,
ClanLeaderboardResponseSchema,
NewsItemSchema,
PlayerProfile,
PlayerProfileSchema,
RankedLeaderboardResponse,
RankedLeaderboardResponseSchema,
UserMeResponse,
UserMeResponseSchema,
} from "../core/ApiSchemas";
import { AnalyticsRecord, AnalyticsRecordSchema } from "../core/Schemas";
import { getAuthHeader, logOut, userAuth } from "./Auth";
export async function fetchPlayerById(
playerId: string,
): Promise<PlayerProfile | false> {
try {
const userAuthResult = await userAuth();
if (!userAuthResult) return false;
const { jwt } = userAuthResult;
const url = `${getApiBase()}/player/${encodeURIComponent(playerId)}`;
const res = await fetch(url, {
headers: {
Accept: "application/json",
Authorization: `Bearer ${jwt}`,
},
});
if (res.status !== 200) {
console.warn(
"fetchPlayerById: unexpected status",
res.status,
res.statusText,
);
return false;
}
const json = await res.json();
const parsed = PlayerProfileSchema.safeParse(json);
if (!parsed.success) {
console.warn("fetchPlayerById: Zod validation failed", parsed.error);
return false;
}
return parsed.data;
} catch (err) {
console.warn("fetchPlayerById: request failed", err);
return false;
}
}
let __userMe: Promise<UserMeResponse | false> | null = null;
export async function getUserMe(): Promise<UserMeResponse | false> {
if (__userMe !== null) {
return __userMe;
}
__userMe = (async () => {
try {
const userAuthResult = await userAuth();
if (!userAuthResult) return false;
const { jwt } = userAuthResult;
// Get the user object
const response = await fetch(getApiBase() + "/users/@me", {
headers: {
authorization: `Bearer ${jwt}`,
},
});
if (response.status === 401) {
await logOut();
return false;
}
if (response.status !== 200) return false;
const body = await response.json();
const result = UserMeResponseSchema.safeParse(body);
if (!result.success) {
const error = z.prettifyError(result.error);
console.error("Invalid response", error);
return false;
}
return result.data;
} catch (e) {
return false;
}
})();
return __userMe;
}
export async function createCheckoutSession(
priceId: string,
colorPaletteName?: string,
): Promise<string | false> {
try {
const response = await fetch(
`${getApiBase()}/stripe/create-checkout-session`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: await getAuthHeader(),
},
body: JSON.stringify({
priceId: priceId,
hostname: window.location.origin,
colorPaletteName: colorPaletteName,
}),
},
);
if (!response.ok) {
console.error(
"createCheckoutSession: request failed",
response.status,
response.statusText,
);
return false;
}
const json = await response.json();
return json.url;
} catch (e) {
console.error("createCheckoutSession: request failed", e);
return false;
}
}
export function getApiBase() {
const domainname = getAudience();
if (domainname === "localhost") {
const apiDomain = process?.env?.API_DOMAIN;
if (apiDomain) {
return `https://${apiDomain}`;
}
return localStorage.getItem("apiHost") ?? "http://localhost:8787";
}
return `https://api.${domainname}`;
}
export function getAudience() {
const { hostname } = new URL(window.location.href);
const domainname = hostname.split(".").slice(-2).join(".");
return domainname;
}
// Check if the user's account is linked to a Discord or email account.
export function hasLinkedAccount(
userMeResponse: UserMeResponse | false,
): boolean {
return (
userMeResponse !== false &&
(userMeResponse.user?.discord !== undefined ||
userMeResponse.user?.email !== undefined)
);
}
export async function fetchGameById(
gameId: string,
): Promise<AnalyticsRecord | false> {
try {
const url = `${getApiBase()}/game/${encodeURIComponent(gameId)}`;
const res = await fetch(url, {
headers: {
Accept: "application/json",
},
});
if (res.status !== 200) {
console.warn(
"fetchGameById: unexpected status",
res.status,
res.statusText,
);
return false;
}
const json = await res.json();
const parsed = AnalyticsRecordSchema.safeParse(json);
if (!parsed.success) {
console.warn("fetchGameById: Zod validation failed", parsed.error);
return false;
}
return parsed.data;
} catch (err) {
console.warn("fetchGameById: request failed", err);
return false;
}
}
export async function fetchClanLeaderboard(): Promise<
ClanLeaderboardResponse | false
> {
try {
const res = await fetch(`${getApiBase()}/public/clans/leaderboard`, {
headers: { Accept: "application/json" },
});
if (!res.ok) {
console.warn(
"fetchClanLeaderboard: unexpected status",
res.status,
res.statusText,
);
return false;
}
const json = await res.json();
const parsed = ClanLeaderboardResponseSchema.safeParse(json);
if (!parsed.success) {
console.warn(
"fetchClanLeaderboard: Zod validation failed",
parsed.error.toString(),
);
return false;
}
return parsed.data;
} catch (err) {
console.warn("fetchClanLeaderboard: request failed", err);
return false;
}
}
export async function fetchPlayerLeaderboard(
page: number,
): Promise<RankedLeaderboardResponse | "reached_limit" | false> {
try {
const url = new URL(`${getApiBase()}/leaderboard/ranked`);
url.searchParams.set("page", String(page));
const res = await fetch(url.toString(), {
headers: { Accept: "application/json" },
});
if (!res.ok) {
console.warn(
"fetchPlayerLeaderboard: unexpected status",
res.status,
res.statusText,
);
return false;
}
const json = await res.json();
const parsed = RankedLeaderboardResponseSchema.safeParse(json);
if (!parsed.success) {
// Handle "Page must be between X and Y" error as end of list
if (json?.message?.includes?.("Page must be between")) {
return "reached_limit";
}
console.warn(
"fetchPlayerLeaderboard: Zod validation failed",
parsed.error.toString(),
);
return false;
}
return parsed.data;
} catch (err) {
console.error("fetchPlayerLeaderboard: request failed", err);
return false;
}
}
export async function getNews(): Promise<NewsItem[]> {
try {
const res = await fetch(`${getApiBase()}/news`, {
headers: { Accept: "application/json" },
});
if (res.status !== 200) {
console.warn("getNews: unexpected status", res.status);
return newsItemsFallback as NewsItem[];
}
const json = await res.json();
const parsed = z.array(NewsItemSchema).safeParse(json);
if (!parsed.success) {
console.warn("getNews: Zod validation failed", parsed.error);
return newsItemsFallback as NewsItem[];
}
return parsed.data;
} catch (err) {
console.warn("getNews: request failed, using fallback", err);
return newsItemsFallback as NewsItem[];
}
}