308 lines
10 KiB
TypeScript
308 lines
10 KiB
TypeScript
import { useUserStore } from "../store/UserStore";
|
|
import { useApplicationStore } from "../store/ApplicationStore";
|
|
import { ImportMetaExtras } from "../../../import-meta";
|
|
|
|
interface ApiProgressData {
|
|
loaded: number;
|
|
total: number;
|
|
}
|
|
|
|
type ApiProgressCallback = (progress: ApiProgressData) => void;
|
|
|
|
interface ApiOptions {
|
|
url: string;
|
|
params?: object;
|
|
method?: string;
|
|
headers?: HeadersInit;
|
|
body?: string | object | FormData | ArrayBuffer | Blob;
|
|
signal?: AbortSignal | null;
|
|
progress?: ApiProgressCallback;
|
|
}
|
|
|
|
export interface ApiResponse {
|
|
status: number;
|
|
message: string;
|
|
data: unknown;
|
|
json?: Record<string, unknown>;
|
|
}
|
|
|
|
const apiDefaultHeaders = {
|
|
Accept: "application/json",
|
|
"Content-Type": "application/json;charset=UTF-8",
|
|
};
|
|
|
|
export const api = {
|
|
timeout: 8000,
|
|
baseUrl: (import.meta as ImportMetaExtras).env.APP_URL_API,
|
|
|
|
send: function (options: ApiOptions) {
|
|
return new Promise((resolve, reject) => {
|
|
let url = this.baseUrl + options.url;
|
|
|
|
if (options.params) {
|
|
let params = "";
|
|
|
|
for (const [key, value] of Object.entries(options.params)) {
|
|
const placeholder = `{${key}}`;
|
|
if (url.includes(placeholder)) {
|
|
url = url.replace(
|
|
placeholder,
|
|
encodeURIComponent(value)
|
|
);
|
|
} else {
|
|
params += `&${encodeURIComponent(
|
|
key
|
|
)}=${encodeURIComponent(value)}`;
|
|
}
|
|
}
|
|
|
|
url = url.replace(/{(.*?)}/g, "$1");
|
|
if (params.length > 0) {
|
|
url += (url.includes("?") ? "" : "?") + params.substring(1);
|
|
}
|
|
}
|
|
|
|
options.headers = {
|
|
...apiDefaultHeaders,
|
|
...(options.headers || {}),
|
|
};
|
|
|
|
const userStore = useUserStore();
|
|
if (userStore.id) {
|
|
options.headers["Authorization"] = `Bearer ${userStore.token}`;
|
|
}
|
|
|
|
options.method = options.method.toUpperCase() || "GET";
|
|
|
|
if (options.body && typeof options.body === "object") {
|
|
if (options.body instanceof FormData) {
|
|
if (
|
|
Object.prototype.hasOwnProperty.call(
|
|
options.headers,
|
|
"Content-Type"
|
|
)
|
|
) {
|
|
// remove the "Content-Type" key from the headers object
|
|
delete options.headers["Content-Type"];
|
|
}
|
|
|
|
if (options.method != "POST") {
|
|
options.body.append("_method", options.method);
|
|
options.method = "POST";
|
|
}
|
|
} else if (
|
|
options.body instanceof Blob ||
|
|
options.body instanceof ArrayBuffer
|
|
) {
|
|
// do nothing, let XHR handle these types of bodies without a Content-Type header
|
|
} else {
|
|
options.body = JSON.stringify(options.body);
|
|
options.headers["Content-Type"] = "application/json";
|
|
}
|
|
}
|
|
|
|
if (
|
|
(options.method == "POST" ||
|
|
options.method == "PUT" ||
|
|
options.method == "PATCH") &&
|
|
options.progress
|
|
) {
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
xhr.upload.onprogress = function (event) {
|
|
if (event.lengthComputable) {
|
|
options.progress({
|
|
loaded: event.loaded,
|
|
total: event.total,
|
|
});
|
|
}
|
|
};
|
|
|
|
xhr.open(options.method, url);
|
|
for (const header in options.headers) {
|
|
xhr.setRequestHeader(header, options.headers[header]);
|
|
}
|
|
xhr.send(options.body as XMLHttpRequestBodyInit);
|
|
|
|
xhr.onload = function () {
|
|
const result = {
|
|
status: xhr.status,
|
|
statusText: xhr.statusText,
|
|
url: url,
|
|
headers: {},
|
|
data: "",
|
|
};
|
|
|
|
const headersString = xhr.getAllResponseHeaders();
|
|
const headersArray = headersString.trim().split("\n");
|
|
headersArray.forEach((header) => {
|
|
const [name, value] = header.trim().split(":");
|
|
result.headers[name] = value.trim();
|
|
});
|
|
|
|
if (
|
|
xhr.response &&
|
|
result.headers["content-type"] == "application/json"
|
|
) {
|
|
try {
|
|
result.data = JSON.parse(xhr.response);
|
|
} catch (error) {
|
|
result.data = xhr.response;
|
|
}
|
|
} else {
|
|
result.data = xhr.response;
|
|
}
|
|
|
|
if (xhr.status < 300) {
|
|
resolve(result);
|
|
} else {
|
|
useApplicationStore().unavailable = true;
|
|
reject(result);
|
|
}
|
|
};
|
|
} else {
|
|
const fetchOptions: RequestInit = {
|
|
method: options.method.toUpperCase() || "GET",
|
|
headers: options.headers,
|
|
signal: options.signal || null,
|
|
};
|
|
|
|
if (
|
|
(typeof options.body == "string" &&
|
|
options.body.length > 0) ||
|
|
options.body instanceof FormData
|
|
) {
|
|
fetchOptions.body = options.body;
|
|
}
|
|
|
|
fetch(url, fetchOptions)
|
|
.then(async (response) => {
|
|
let data: string | object = "";
|
|
if (response.headers.get("content-length") !== "0") {
|
|
if (
|
|
response &&
|
|
response.headers.get("content-type") == null
|
|
) {
|
|
try {
|
|
data = response.json
|
|
? await response.json()
|
|
: {};
|
|
} catch (error) {
|
|
try {
|
|
data = response.text
|
|
? await response.text()
|
|
: "";
|
|
} catch (error) {
|
|
data = "";
|
|
}
|
|
}
|
|
} else {
|
|
data =
|
|
response && response.json
|
|
? await response.json()
|
|
: {};
|
|
}
|
|
}
|
|
|
|
const result = {
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
url: response.url,
|
|
headers: response.headers,
|
|
data: data,
|
|
};
|
|
|
|
if (response.status >= 300) {
|
|
useApplicationStore().unavailable = true;
|
|
reject(result);
|
|
}
|
|
|
|
resolve(result);
|
|
})
|
|
.catch((error) => {
|
|
// Handle any errors thrown during the fetch process
|
|
const { response, ...rest } = error;
|
|
reject({
|
|
...rest,
|
|
response: response && response.json(),
|
|
});
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
get: async function (options: ApiOptions | string): Promise<ApiResponse> {
|
|
let apiOptions = {} as ApiOptions;
|
|
|
|
if (typeof options == "string") {
|
|
apiOptions.url = options;
|
|
} else {
|
|
apiOptions = options;
|
|
}
|
|
|
|
apiOptions.method = "GET";
|
|
return await this.send(apiOptions);
|
|
},
|
|
|
|
post: async function (options: ApiOptions | string): Promise<ApiResponse> {
|
|
let apiOptions = {} as ApiOptions;
|
|
|
|
if (typeof options == "string") {
|
|
apiOptions.url = options;
|
|
} else {
|
|
apiOptions = options;
|
|
}
|
|
|
|
apiOptions.method = "POST";
|
|
return await this.send(options);
|
|
},
|
|
|
|
put: async function (options: ApiOptions | string): Promise<ApiResponse> {
|
|
let apiOptions = {} as ApiOptions;
|
|
|
|
if (typeof options == "string") {
|
|
apiOptions.url = options;
|
|
} else {
|
|
apiOptions = options;
|
|
}
|
|
|
|
apiOptions.method = "PUT";
|
|
return await this.send(options);
|
|
},
|
|
|
|
delete: async function (
|
|
options: ApiOptions | string
|
|
): Promise<ApiResponse> {
|
|
let apiOptions = {} as ApiOptions;
|
|
|
|
if (typeof options == "string") {
|
|
apiOptions.url = options;
|
|
} else {
|
|
apiOptions = options;
|
|
}
|
|
|
|
apiOptions.method = "DELETE";
|
|
return await this.send(options);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Get an api result data as type.
|
|
*
|
|
* @param result The api result object.
|
|
* @param defaultValue The default data to return if no result exists.
|
|
* @returns Data object.
|
|
*/
|
|
export function getApiResultData<T>(
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
result: any,
|
|
defaultValue: T | null = null
|
|
): T | null {
|
|
if (!result || !Object.prototype.hasOwnProperty.call(result, "data")) {
|
|
return defaultValue;
|
|
}
|
|
|
|
const data = result.data as T;
|
|
return data instanceof Object ? data : defaultValue;
|
|
}
|