2023-11-05 14:28:55 +00:00
|
|
|
export type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
2023-11-03 12:17:53 +00:00
|
|
|
|
2023-11-03 20:14:17 +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;
|
2023-11-03 20:14:17 +00:00
|
|
|
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":
|
2023-11-03 20:14:17 +00:00
|
|
|
if (topLevel) return encodeURIComponent(data.toString()) + "=";
|
2023-11-03 12:17:53 +00:00
|
|
|
|
|
|
|
return data.toString();
|
|
|
|
case "object":
|
2023-11-03 20:14:17 +00:00
|
|
|
if (data === null) return "null";
|
2023-11-03 12:17:53 +00:00
|
|
|
|
|
|
|
if (Array.isArray(data))
|
2023-11-03 20:14:17 +00:00
|
|
|
return data
|
|
|
|
.map((d) => nestedUrlSearchParams(d, true))
|
2023-11-03 12:17:53 +00:00
|
|
|
.map(encodeURIComponent)
|
|
|
|
.join("&");
|
|
|
|
|
2023-11-03 20:14:17 +00:00
|
|
|
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 "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-05 14:28:55 +00:00
|
|
|
export type MagApiErrorCode = "Client:GenericApiError" | string;
|
2023-11-03 12:17:53 +00:00
|
|
|
|
|
|
|
export interface MagApiError {
|
|
|
|
status: number;
|
2023-11-03 20:14:17 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-11-03 20:14:17 +00:00
|
|
|
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"],
|
2023-11-03 20:14:17 +00:00
|
|
|
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;
|
|
|
|
|
2023-11-03 20:14:17 +00:00
|
|
|
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",
|
2023-11-03 20:14:17 +00:00
|
|
|
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) => {
|
2023-11-03 20:14:17 +00:00
|
|
|
throw {
|
2023-11-03 12:17:53 +00:00
|
|
|
status: -1,
|
|
|
|
code: "Client:GenericApiError",
|
2023-11-03 20:14:17 +00:00
|
|
|
message: e,
|
|
|
|
} as MagApiError;
|
2023-11-03 12:17:53 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|