Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | 521x 521x 521x 65x 65x 65x 521x 3276x 3276x 3276x 3276x 2479x 3276x 3276x 52x 3197x 3197x 27x 27x 28x 28x 3170x 16x 12x 4x 4x 4x 3154x 3154x 3141x 1x 521x 872x 2395x 2395x 463x 463x 2395x 9x | import { config } from "shared/config";
import {
ApiDevEndpoint,
ApiEndpoint,
AuthEndpoint,
} from "shared/constants/apiEndpoints";
import { ApiError } from "shared/types/api/errors";
import { fetchWithTimeout } from "client/utils/fetchWithTimeout";
import { getJWT } from "client/utils/getJWT";
import {
isBackgroundRequest,
onRequestFailure,
onRequestSuccess,
shouldTreatHttpErrorAsNetworkError,
showApiError,
} from "client/utils/networkErrorPolicy";
enum HttpMethod {
GET = "GET",
POST = "POST",
DELETE = "DELETE",
}
interface ApiResponse<T> {
data: T;
}
const baseURL = config.client().apiServerUrl;
const REQUEST_TIMEOUT_MS = 60000;
const networkErrorResponse = <T>(background: boolean): ApiResponse<T> => {
onRequestFailure(background);
const error: ApiError = {
errorId: "unknown",
message: "Network error",
status: "error",
};
return { data: error as T };
};
const apiFetch = async <T>(
url: string,
method: HttpMethod,
body?: unknown,
): Promise<ApiResponse<T>> => {
const background = isBackgroundRequest(method, url);
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
const authToken = getJWT();
if (authToken) {
headers.Authorization = `Bearer ${authToken}`;
}
let response: Response;
try {
response = await fetchWithTimeout(`${baseURL}${url}`, REQUEST_TIMEOUT_MS, {
method,
headers,
body: body === undefined ? undefined : JSON.stringify(body),
});
} catch {
return networkErrorResponse<T>(background);
}
onRequestSuccess();
// Reading the response body can reject with AbortError if the page unloads
// mid-response, so json() calls need the same error handling as fetch()
// Handle redirect responses (301/302 with JSON body containing location)
if ([301, 302].includes(response.status)) {
let data: { location: string };
try {
data = (await response.json()) as { location: string };
} catch {
return networkErrorResponse<T>(background);
}
location.href = data.location;
// eslint-disable-next-line @typescript-eslint/no-empty-function
return new Promise<ApiResponse<T>>(() => {});
}
// Handle errors
if (!response.ok) {
if (shouldTreatHttpErrorAsNetworkError(background)) {
return networkErrorResponse<T>(background);
}
showApiError(method, url, response.status);
const error: ApiError = {
errorId: "unknown",
message: "Invalid API response",
status: "error",
};
return { data: error as T };
}
try {
const data = (await response.json()) as T;
return { data };
} catch {
return networkErrorResponse<T>(background);
}
};
type Endpoint = ApiEndpoint | ApiDevEndpoint | AuthEndpoint;
export const api = {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
post: async <RES = never, REQ = never>(
url: Endpoint,
data?: REQ,
): Promise<ApiResponse<RES>> => {
return await apiFetch<RES>(url, HttpMethod.POST, data);
},
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
get: async <RES = never, REQ = never>(
url: Endpoint,
options?: { params?: REQ },
): Promise<ApiResponse<RES>> => {
let fetchUrl: string = url;
if (options?.params) {
const searchParams = new URLSearchParams(options.params);
fetchUrl = `${url}?${searchParams.toString()}`;
}
return await apiFetch<RES>(fetchUrl, HttpMethod.GET);
},
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
delete: async <RES = never, REQ = never>(
url: Endpoint,
options?: { data?: REQ },
): Promise<ApiResponse<RES>> => {
return await apiFetch<RES>(url, HttpMethod.DELETE, options?.data);
},
};
|