Files
weirsoe-party-protocol/frontend/src/api/angular-client.ts
DEV-bot 58f7f02af3
All checks were successful
CI / test-and-quality (push) Successful in 2m11s
CI / test-and-quality (pull_request) Successful in 2m11s
fix(spa): normalize angular api client base URL for django endpoints
2026-03-01 13:52:32 +00:00

106 lines
3.1 KiB
TypeScript

import type {
ApiFailure,
ApiResult,
HealthResponse,
JoinSessionRequest,
JoinSessionResponse,
SessionDetailResponse,
StartRoundRequest,
StartRoundResponse
} from './types';
export interface AngularHttpError {
status?: number;
message?: string;
error?: unknown;
}
export interface AngularHttpClientLike {
get<T>(url: string, options?: { withCredentials?: boolean }): Promise<T>;
post<T>(url: string, body: unknown, options?: { withCredentials?: boolean }): Promise<T>;
}
export interface AngularApiClient {
health(): Promise<ApiResult<HealthResponse>>;
getSession(code: string): Promise<ApiResult<SessionDetailResponse>>;
joinSession(payload: JoinSessionRequest): Promise<ApiResult<JoinSessionResponse>>;
startRound(code: string, payload: StartRoundRequest): Promise<ApiResult<StartRoundResponse>>;
}
function toFailure(error: unknown): ApiFailure {
const candidate = (error ?? {}) as AngularHttpError;
const status = typeof candidate.status === 'number' ? candidate.status : 0;
const payload = candidate.error;
if (status === 0) {
return {
kind: 'network',
status: 0,
message: candidate.message ?? 'Network error while contacting API'
};
}
return {
kind: 'http',
status,
message: candidate.message ?? `HTTP ${status}`,
...(payload === undefined ? {} : { payload })
};
}
function normalizeCode(code: string): string {
return code.trim().toUpperCase();
}
function normalizeBaseUrl(baseUrl: string): string {
return baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
}
function buildUrl(baseUrl: string, path: string): string {
return `${normalizeBaseUrl(baseUrl)}${path}`;
}
async function wrap<T>(call: () => Promise<T>): Promise<ApiResult<T>> {
try {
const data = await call();
return { ok: true, status: 200, data };
} catch (error: unknown) {
return {
ok: false,
status: typeof (error as AngularHttpError)?.status === 'number' ? (error as AngularHttpError).status! : 0,
error: toFailure(error)
};
}
}
export function createAngularApiClient(http: AngularHttpClientLike, baseUrl = ''): AngularApiClient {
return {
health: () => wrap(() => http.get<HealthResponse>(buildUrl(baseUrl, '/healthz'), { withCredentials: true })),
getSession: (code: string) =>
wrap(() =>
http.get<SessionDetailResponse>(buildUrl(baseUrl, `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}`), {
withCredentials: true
})
),
joinSession: (payload: JoinSessionRequest) =>
wrap(() =>
http.post<JoinSessionResponse>(
buildUrl(baseUrl, '/lobby/sessions/join'),
{
code: normalizeCode(payload.code),
nickname: payload.nickname.trim()
},
{ withCredentials: true }
)
),
startRound: (code: string, payload: StartRoundRequest) =>
wrap(() =>
http.post<StartRoundResponse>(
buildUrl(baseUrl, `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/rounds/start`),
payload,
{ withCredentials: true }
)
)
};
}