aboutsummaryrefslogtreecommitdiffstats
path: root/code/app/src/lib/api/_fetch.ts
diff options
context:
space:
mode:
Diffstat (limited to 'code/app/src/lib/api/_fetch.ts')
-rw-r--r--code/app/src/lib/api/_fetch.ts121
1 files changed, 121 insertions, 0 deletions
diff --git a/code/app/src/lib/api/_fetch.ts b/code/app/src/lib/api/_fetch.ts
new file mode 100644
index 0000000..b28e398
--- /dev/null
+++ b/code/app/src/lib/api/_fetch.ts
@@ -0,0 +1,121 @@
+import { Temporal } from "temporal-polyfill";
+import { clear_session_data } from "$lib/session";
+import type { Result } from "rustic";
+import { Err, Ok } from "rustic";
+import { redirect } from "@sveltejs/kit";
+import { browser } from "$app/environment";
+import { goto } from "$app/navigation";
+import { SignInPageMessage, signInPageMessageQueryKey } from "$routes/(main)/(public)/sign-in";
+import { log_error } from "$lib/logger";
+
+export async function http_post_async<T>(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<InternalFetchResponse<T>> {
+ const init = make_request_init("post", body, abort_signal);
+ const response = await internal_fetch_async({ url, init, timeout });
+ if (!skip_401_check && await redirect_if_401_async(response)) return Err("Server returned 401");
+ return make_response_async(response);
+}
+
+export async function http_get_async<T>(url: string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Result<InternalFetchResponse<T>, string>> {
+ const init = make_request_init("get", undefined, abort_signal);
+ const response = await internal_fetch_async({ url, init, timeout });
+ if (!skip_401_check && await redirect_if_401_async(response)) return Err("Server returned 401");
+ return make_response_async(response);
+}
+
+export async function http_delete_async<T>(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Result<InternalFetchResponse<T>, string>> {
+ const init = make_request_init("delete", body, abort_signal);
+ const response = await internal_fetch_async({ url, init, timeout });
+ if (!skip_401_check && await redirect_if_401_async(response)) return Err("Server returned 401");
+ return make_response_async(response);
+}
+
+async function internal_fetch_async(request: InternalFetchRequest): Promise<Response> {
+ if (!request.init) throw new Error("request.init is required");
+ const fetch_request = new Request(request.url, request.init);
+ let response: any;
+
+ try {
+ if (request.timeout && request.timeout > 500) {
+ response = await Promise.race([
+ fetch(fetch_request),
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), request.timeout))
+ ]);
+ } else {
+ response = await fetch(fetch_request);
+ }
+ } catch (error: any) {
+ log_error(error);
+ if (error.message === "Timeout") {
+ console.error("Request timed out");
+ } else if (error.message === "Network request failed") {
+ console.error("No internet connection");
+ } else {
+ throw error;
+ }
+ }
+
+ return response;
+}
+
+async function redirect_if_401_async(response: Response): Promise<boolean> {
+ if (response.status === 401) {
+ const redirectUrl = `/sign-in?${signInPageMessageQueryKey}=${SignInPageMessage.LOGGED_OUT}`;
+ clear_session_data();
+ if (browser) {
+ await goto(redirectUrl)
+ } else {
+ throw redirect(307, redirectUrl);
+ }
+ }
+ return false;
+}
+
+async function make_response_async<T>(response: Response): Promise<Result<InternalFetchResponse<T>, string>> {
+ const result = {
+ ok: response.ok,
+ status: response.status,
+ http_response: response,
+ } as InternalFetchResponse<T>;
+
+ if (response.status !== 204) {
+ try {
+ result.data = await response.json() as T;
+ } catch (error) {
+ log_error("", { error, result })
+ return Err("Deserialisation threw");
+ }
+ }
+ return Ok(result);
+}
+
+function make_request_init(method: string, body?: any, signal?: AbortSignal): RequestInit {
+ const init = {
+ method,
+ signal,
+ headers: {
+ "X-TimeZone": Temporal.Now.timeZone().id,
+ }
+ } as RequestInit;
+
+ if (body) {
+ init.body = JSON.stringify(body);
+ init.headers["Content-Type"] = "application/json;charset=UTF-8"
+ }
+
+ return init;
+}
+
+
+export type InternalFetchRequest = {
+ url: string,
+ init: RequestInit,
+ timeout?: number
+ retry_count?: number,
+}
+
+export type InternalFetchResponse<T> = {
+ ok: boolean,
+ status: number,
+ data: T | undefined,
+ http_response: Response
+} \ No newline at end of file