diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-10-25 11:51:37 +0200 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-10-25 11:51:37 +0200 |
| commit | 0005595703b2f3f7083ce4ba19bf5770057c75bd (patch) | |
| tree | 193a897f61a9a5e566961601de4cf42ae85984a0 /code/app/src/lib/api/_fetch.ts | |
| parent | 585c5c8537eb21dfc9f16108548e63d9ced3d971 (diff) | |
| download | greatoffice-0005595703b2f3f7083ce4ba19bf5770057c75bd.tar.xz greatoffice-0005595703b2f3f7083ce4ba19bf5770057c75bd.zip | |
.
Diffstat (limited to 'code/app/src/lib/api/_fetch.ts')
| -rw-r--r-- | code/app/src/lib/api/_fetch.ts | 121 |
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 |
