175 lines
4.6 KiB
TypeScript
175 lines
4.6 KiB
TypeScript
interface RequestParams {
|
|
uri: string;
|
|
method: "GET" | "POST" | "DELETE" | "PATCH" | "PUT";
|
|
allowFail?: boolean;
|
|
jsonData?: any;
|
|
formData?: FormData;
|
|
upProgress?: (progress: number) => void;
|
|
downProgress?: (e: { progress: number; total: number }) => void;
|
|
}
|
|
|
|
interface APIResponse {
|
|
data: any;
|
|
status: number;
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
constructor(message: string, public code: number, public data: any) {
|
|
super(`HTTP status: ${code}\nMessage: ${message}\nData=${data}`);
|
|
}
|
|
}
|
|
|
|
export class APIClient {
|
|
/**
|
|
* Get backend URL
|
|
*/
|
|
static backendURL(): string {
|
|
const URL = import.meta.env.VITE_APP_BACKEND ?? "";
|
|
if (URL.length === 0) throw new Error("Backend URL undefined!");
|
|
return URL;
|
|
}
|
|
|
|
/**
|
|
* Check out whether the backend is accessed through
|
|
* HTTPS or not
|
|
*/
|
|
static IsBackendSecure(): boolean {
|
|
return this.backendURL().startsWith("https");
|
|
}
|
|
|
|
/**
|
|
* Perform a request on the backend
|
|
*/
|
|
static async exec(args: RequestParams): Promise<APIResponse> {
|
|
let body: string | undefined | FormData = undefined;
|
|
let headers: any = {};
|
|
|
|
// JSON request
|
|
if (args.jsonData) {
|
|
headers["Content-Type"] = "application/json";
|
|
body = JSON.stringify(args.jsonData);
|
|
}
|
|
|
|
// Form data request
|
|
else if (args.formData) {
|
|
body = args.formData;
|
|
}
|
|
|
|
const url = this.backendURL() + args.uri;
|
|
|
|
let data;
|
|
let status: number;
|
|
|
|
// Make the request with XMLHttpRequest
|
|
if (args.upProgress) {
|
|
const res: XMLHttpRequest = await new Promise((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.upload.addEventListener("progress", (e) =>
|
|
args.upProgress!(e.loaded / e.total)
|
|
);
|
|
xhr.addEventListener("load", () => resolve(xhr));
|
|
xhr.addEventListener("error", () =>
|
|
reject(new Error("File upload failed"))
|
|
);
|
|
xhr.addEventListener("abort", () =>
|
|
reject(new Error("File upload aborted"))
|
|
);
|
|
xhr.addEventListener("timeout", () =>
|
|
reject(new Error("File upload timeout"))
|
|
);
|
|
xhr.open(args.method, url, true);
|
|
xhr.withCredentials = true;
|
|
for (const key in headers) {
|
|
if (headers.hasOwnProperty(key))
|
|
xhr.setRequestHeader(key, headers[key]);
|
|
}
|
|
xhr.send(body);
|
|
});
|
|
|
|
status = res.status;
|
|
if (res.responseType === "json") data = JSON.parse(res.responseText);
|
|
else data = res.response;
|
|
}
|
|
|
|
// Make the request with fetch
|
|
else {
|
|
const res = await fetch(url, {
|
|
method: args.method,
|
|
body: body,
|
|
headers: headers,
|
|
credentials: "include",
|
|
});
|
|
|
|
// Process response
|
|
// JSON response
|
|
if (res.headers.get("content-type") === "application/json")
|
|
data = await res.json();
|
|
// Text / XML response
|
|
else if (
|
|
["application/xml", "text/plain"].includes(
|
|
res.headers.get("content-type") ?? ""
|
|
)
|
|
)
|
|
data = await res.text();
|
|
// Binary file, tracking download progress
|
|
else if (res.body !== null && args.downProgress) {
|
|
// Track download progress
|
|
const contentEncoding = res.headers.get("content-encoding");
|
|
const contentLength = contentEncoding
|
|
? null
|
|
: res.headers.get("content-length");
|
|
|
|
const total = parseInt(contentLength ?? "0", 10);
|
|
let loaded = 0;
|
|
|
|
const resInt = new Response(
|
|
new ReadableStream({
|
|
start(controller) {
|
|
const reader = res.body!.getReader();
|
|
|
|
const read = async () => {
|
|
try {
|
|
const ret = await reader.read();
|
|
if (ret.done) {
|
|
controller.close();
|
|
return;
|
|
}
|
|
loaded += ret.value.byteLength;
|
|
args.downProgress!({ progress: loaded, total });
|
|
controller.enqueue(ret.value);
|
|
read();
|
|
} catch (e) {
|
|
console.error(e);
|
|
controller.error(e);
|
|
}
|
|
};
|
|
|
|
read();
|
|
},
|
|
})
|
|
);
|
|
|
|
data = await resInt.blob();
|
|
}
|
|
|
|
// Do not track progress (binary file)
|
|
else data = await res.blob();
|
|
|
|
status = res.status;
|
|
}
|
|
|
|
// Handle expired tokens
|
|
if (status === 412) {
|
|
window.location.href = "/";
|
|
}
|
|
|
|
if (!args.allowFail && (status < 200 || status > 299))
|
|
throw new ApiError("Request failed!", status, data);
|
|
|
|
return {
|
|
data: data,
|
|
status: status,
|
|
};
|
|
}
|
|
}
|