Files
healer-man/src/app/arenaClient.ts
T
2026-09-05 20:42:38 -04:00

77 lines
3.2 KiB
TypeScript

import type { PlayerSession } from "./types";
import { onlineApiOrigin, requestOnlineJson } from "./onlineAccountClient";
import type { ArenaLoadout, ArenaQueueRequest, ArenaQueueState, ArenaStageManifest } from "../game/arenaTypes";
export interface ArenaStateResponse {
characterId: string | null;
loadouts: ArenaLoadout[];
ratings: Record<"2v2" | "3v3", { bracket: "2v2" | "3v3"; rating: number; title: string; updatedAt: number | null } | null>;
queue: ArenaQueueState;
stages: ArenaStageManifest[];
}
function token(session: PlayerSession): string {
if (session.kind !== "account" || !session.accessToken) throw new Error("Rated arenas require an online account.");
return session.accessToken;
}
export function fetchArenaState(session: PlayerSession, characterId: string): Promise<ArenaStateResponse> {
return requestOnlineJson(`/api/arena/state?characterId=${encodeURIComponent(characterId)}`, {}, token(session));
}
export function putArenaLoadout(
session: PlayerSession,
characterId: string,
slot: number,
loadout: Omit<ArenaLoadout, "slot" | "characterId" | "level" | "version" | "selected" | "updatedAt"> & { selected?: boolean },
): Promise<ArenaLoadout> {
return requestOnlineJson(`/api/arena/loadouts/${slot}`, {
method: "PUT",
body: JSON.stringify({ ...loadout, characterId }),
}, token(session));
}
export function deleteArenaLoadout(session: PlayerSession, characterId: string, slot: number) {
return requestOnlineJson<{ ok: true }>(`/api/arena/loadouts/${slot}`, {
method: "DELETE",
body: JSON.stringify({ characterId }),
}, token(session));
}
export function enterArenaQueue(session: PlayerSession, request: ArenaQueueRequest): Promise<ArenaQueueState> {
return requestOnlineJson("/api/arena/queue", { method: "POST", body: JSON.stringify(request) }, token(session));
}
export function leaveArenaQueue(session: PlayerSession): Promise<ArenaQueueState> {
return requestOnlineJson("/api/arena/queue", { method: "DELETE" }, token(session));
}
export function acceptArenaReadyCheck(session: PlayerSession): Promise<ArenaQueueState> {
return requestOnlineJson("/api/arena/ready", { method: "POST" }, token(session));
}
interface ArenaSocketTicket { ticket: string; expiresAt: number; path: string }
export async function openArenaSocket(
session: PlayerSession,
onMessage: (message: unknown) => void,
onClose: () => void,
): Promise<WebSocket> {
const response = await requestOnlineJson<ArenaSocketTicket>("/api/arena/socket-ticket", { method: "POST" }, token(session));
const origin = onlineApiOrigin();
const base = origin || window.location.origin;
const url = new URL(response.path, base);
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
url.searchParams.set("ticket", response.ticket);
const socket = new WebSocket(url);
socket.addEventListener("message", (event) => {
try { onMessage(JSON.parse(String(event.data))); } catch { /* Ignore malformed server messages. */ }
});
socket.addEventListener("close", onClose);
await new Promise<void>((resolve, reject) => {
socket.addEventListener("open", () => resolve(), { once: true });
socket.addEventListener("error", () => reject(new Error("Could not connect to the arena server.")), { once: true });
});
return socket;
}