forked from phenom/healer-man
77 lines
3.2 KiB
TypeScript
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;
|
|
}
|