magnetar/fe_calckey/frontend/magnetar-common/src/be-api.ts

127 lines
3.4 KiB
TypeScript

export type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
export interface BackendApiEndpoint<
M extends Method,
PP extends string[],
T,
R
> {
method: M;
endpoint: string;
pathParams: PP;
request?: T;
response?: R;
}
function nestedUrlSearchParams(data: any, topLevel: boolean = true): string {
switch (typeof data) {
case "string":
case "bigint":
case "boolean":
case "number":
case "symbol":
if (topLevel) return encodeURIComponent(data.toString()) + "=";
return data.toString();
case "object":
if (data === null) return "null";
if (Array.isArray(data))
return data
.map((d) => nestedUrlSearchParams(d, true))
.map(encodeURIComponent)
.join("&");
const inner = Object.entries(data).map(([k, v]) => [
k,
nestedUrlSearchParams(v, false),
]);
return new URLSearchParams(inner).toString();
default:
return "";
}
}
export type MagApiErrorCode = "Client:GenericApiError" | string;
export interface MagApiError {
status: number;
code: MagApiErrorCode;
message: string;
}
export class MagApiClient {
private readonly baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async call<
T extends BackendApiEndpoint<
T["method"] & Method,
T["pathParams"] & string[],
T["request"],
T["response"]
>
>(
{ endpoint, method }: T,
data: T["request"],
pathParams: {
[K in keyof T["pathParams"] as T["pathParams"][K] & string]:
| string
| number;
},
token?: string | null | undefined
): Promise<T["response"]> {
type Response = T["response"];
const authorizationToken = token ?? undefined;
const authorization = authorizationToken
? `Bearer ${authorizationToken}`
: undefined;
for (const name in pathParams) {
endpoint = endpoint.replace(`:${name}`, `${pathParams[name]}`);
}
const baseUrl = this.baseUrl.replace(/\/+$/, "");
let url = `${baseUrl}${endpoint}`;
if (method === "GET") {
const query = nestedUrlSearchParams(data as any);
if (query) {
url += `?${query}`;
}
}
return await fetch(url, {
method,
body: method !== "GET" ? JSON.stringify(data) : undefined,
credentials: "omit",
cache: "no-cache",
headers: authorization ? { authorization } : {},
})
.then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
return body as Response;
} else if (res.status === 204) {
return null as any as Response;
} else {
throw body as MagApiError;
}
})
.catch((e) => {
throw {
status: -1,
code: "Client:GenericApiError",
message: e,
} as MagApiError;
});
}
}