113 lines
3.1 KiB
TypeScript
113 lines
3.1 KiB
TypeScript
|
import {FrontendApiEndpoint, FrontendApiEndpoints} from "./fe-api";
|
||
|
import {GetNoteById} from "./types/endpoints/GetNoteById";
|
||
|
|
||
|
type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
||
|
|
||
|
export interface BackendApiEndpoint<M extends Method, T, R> {
|
||
|
method: M;
|
||
|
endpoint: string;
|
||
|
pathParams: [string],
|
||
|
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 "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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<M extends Method, T extends BackendApiEndpoint<M, T["request"], T["response"]>>(
|
||
|
endpoint: T["endpoint"],
|
||
|
method: M,
|
||
|
data: T["request"],
|
||
|
pathParams: Record<string, string>,
|
||
|
token?: string | null | undefined
|
||
|
): Promise<T["response"]> {
|
||
|
type Response = T["response"];
|
||
|
|
||
|
const authorizationToken = token ?? undefined;
|
||
|
const authorization = authorizationToken
|
||
|
? `Bearer ${authorizationToken}`
|
||
|
: undefined;
|
||
|
|
||
|
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} : {},
|
||
|
})
|
||
|
.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;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
const a = new MagApiClient("https://aaa");
|
||
|
a.call<"GET", GetNoteById>("", "",{}, {})
|