Refactor runtime bootstrap and ship control flows
This commit is contained in:
@@ -2,7 +2,12 @@ import type { WorldDelta, WorldSnapshot } from "./contracts";
|
||||
import type { TelemetrySnapshot } from "./contractsTelemetry";
|
||||
import type { BalanceSettings } from "./contractsBalance";
|
||||
import type { PlayerFactionSnapshot } from "./contractsPlayerFaction";
|
||||
import type { AuthSessionResponse, ForgotPasswordResponse } from "./contractsAuth";
|
||||
import type { ShipAutomationCatalogSnapshot } from "./contractsShipAutomation";
|
||||
import type { FactionSnapshot } from "./contractsFactions";
|
||||
import type { ShipSnapshot } from "./contractsShips";
|
||||
import type { StationSnapshot } from "./contractsInfrastructure";
|
||||
import { clearAuthSession, getAuthSession, setAuthSession } from "./authSession";
|
||||
import type {
|
||||
PlayerAssetAssignmentCommandRequest,
|
||||
PlayerAutomationPolicyCommandRequest,
|
||||
@@ -23,16 +28,54 @@ export interface WorldStreamScope {
|
||||
bubbleId?: string | null;
|
||||
}
|
||||
|
||||
async function fetchJson<T>(input: RequestInfo | URL, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(input, init);
|
||||
async function fetchJson<T>(input: RequestInfo | URL, init?: RequestInit, options?: { skipAuth?: boolean; skipRefresh?: boolean }): Promise<T> {
|
||||
const headers = new Headers(init?.headers);
|
||||
if (!options?.skipAuth) {
|
||||
const session = getAuthSession();
|
||||
if (session?.accessToken) {
|
||||
headers.set("Authorization", `Bearer ${session.accessToken}`);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(input, {
|
||||
...init,
|
||||
headers,
|
||||
});
|
||||
if (response.status === 401 && !options?.skipAuth && !options?.skipRefresh) {
|
||||
const refreshed = await tryRefreshSession();
|
||||
if (refreshed) {
|
||||
return fetchJson<T>(input, init, { skipRefresh: true });
|
||||
}
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(`${init?.method ?? "GET"} ${typeof input === "string" ? input : input.toString()} failed with ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
async function tryRefreshSession(): Promise<boolean> {
|
||||
const session = getAuthSession();
|
||||
if (!session?.refreshToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch("/api/auth/refresh", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ refreshToken: session.refreshToken }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
clearAuthSession();
|
||||
return false;
|
||||
}
|
||||
|
||||
const nextSession = await response.json() as AuthSessionResponse;
|
||||
setAuthSession(nextSession);
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function fetchWorldSnapshot(signal?: AbortSignal) {
|
||||
return fetchJson<WorldSnapshot>("/api/world", { signal });
|
||||
return fetchJson<WorldSnapshot>("/api/world", { signal }, { skipAuth: true });
|
||||
}
|
||||
|
||||
export function openWorldStream(
|
||||
@@ -86,16 +129,80 @@ export async function updateBalance(settings: BalanceSettings) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function createFaction(request: { factionId: string }) {
|
||||
return fetchJson<FactionSnapshot>("/api/gm/factions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function spawnShip(request: { factionId: string; systemId: string; shipId?: string | null; behaviorKind?: string | null }) {
|
||||
return fetchJson<ShipSnapshot>("/api/gm/ships", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function spawnStation(request: { factionId: string; systemId: string; objective?: string | null; label?: string | null }) {
|
||||
return fetchJson<StationSnapshot>("/api/gm/stations", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function resetWorld() {
|
||||
return fetchJson<WorldSnapshot>("/api/world/reset", {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export async function register(request: { email: string; password: string }) {
|
||||
const session = await fetchJson<AuthSessionResponse>("/api/auth/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
}, { skipAuth: true, skipRefresh: true });
|
||||
setAuthSession(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function login(request: { email: string; password: string }) {
|
||||
const session = await fetchJson<AuthSessionResponse>("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
}, { skipAuth: true, skipRefresh: true });
|
||||
setAuthSession(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function forgotPassword(request: { email: string }) {
|
||||
return fetchJson<ForgotPasswordResponse>("/api/auth/forgot-password", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
}, { skipAuth: true, skipRefresh: true });
|
||||
}
|
||||
|
||||
export async function resetPassword(request: { token: string; newPassword: string }) {
|
||||
await fetchJson<void>("/api/auth/reset-password", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
}, { skipAuth: true, skipRefresh: true });
|
||||
}
|
||||
|
||||
export async function fetchPlayerFaction(signal?: AbortSignal) {
|
||||
return fetchJson<PlayerFactionSnapshot>("/api/player-faction", { signal });
|
||||
}
|
||||
|
||||
export async function fetchShipAutomationCatalog(signal?: AbortSignal) {
|
||||
return fetchJson<ShipAutomationCatalogSnapshot>("/api/ships/catalog", { signal }, { skipAuth: true });
|
||||
}
|
||||
|
||||
export async function createPlayerOrganization(request: PlayerOrganizationCommandRequest) {
|
||||
return fetchJson<PlayerFactionSnapshot>("/api/player-faction/organizations", {
|
||||
method: "POST",
|
||||
@@ -182,3 +289,9 @@ export async function updateShipDefaultBehavior(shipId: string, request: ShipDef
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeShipOrder(shipId: string, orderId: string) {
|
||||
return fetchJson<ShipSnapshot>(`/api/ships/${shipId}/orders/${orderId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user