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 | 609x 609x 609x 71x 71x 71x 609x 3728x 3728x 3728x 3728x 2903x 3728x 3728x 59x 3647x 3647x 25x 25x 25x 25x 3622x 16x 12x 4x 4x 4x 3606x 3606x 3602x 609x 976x 2740x 2740x 530x 530x 2740x 12x | 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()
// A redirect response carries its target in a JSON body
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>>(() => {});
}
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);
},
};
|