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

126 lines
3.3 KiB
TypeScript
Raw Normal View History

export type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
2023-11-03 12:17:53 +00:00
export interface BackendApiEndpoint<
M extends Method,
PP extends string[],
T,
R
> {
2023-11-03 12:17:53 +00:00
method: M;
endpoint: string;
pathParams: PP;
2023-11-03 12:17:53 +00:00
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()) + "=";
2023-11-03 12:17:53 +00:00
return data.toString();
case "object":
if (data === null) return "null";
2023-11-03 12:17:53 +00:00
if (Array.isArray(data))
return data
.map((d) => nestedUrlSearchParams(d, true))
2023-11-03 12:17:53 +00:00
.map(encodeURIComponent)
.join("&");
const inner = Object.entries(data).map(([k, v]) => [
k,
nestedUrlSearchParams(v, false),
]);
2023-11-03 12:17:53 +00:00
return new URLSearchParams(inner).toString();
default:
return "";
}
}
export type MagApiErrorCode = "Client:GenericApiError" | string;
2023-11-03 12:17:53 +00:00
export interface MagApiError {
status: number;
code: MagApiErrorCode;
message: string;
2023-11-03 12:17:53 +00:00
}
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,
2023-11-03 12:17:53 +00:00
data: T["request"],
pathParams: {
[K in keyof T["pathParams"] as T["pathParams"][K] & string]:
| string
| number;
},
2023-11-03 12:17:53 +00:00
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]}`);
}
2023-11-03 12:17:53 +00:00
let url = `${this.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 } : {},
2023-11-03 12:17:53 +00:00
})
.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 {
2023-11-03 12:17:53 +00:00
status: -1,
code: "Client:GenericApiError",
message: e,
} as MagApiError;
2023-11-03 12:17:53 +00:00
});
}
}