106 lines
3.1 KiB
TypeScript
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 }
|
|
)
|
|
)
|
|
};
|
|
}
|