better-fe-error-handling (#1406)

* update error handling in HttpService to handle non-json responses better w/ burnettk

* fixed cypress tests

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
jasquat 2024-04-18 18:11:44 +00:00 committed by GitHub
parent 6643db9f2f
commit 91e988ff11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 33 additions and 21 deletions

View File

@ -1,3 +1,4 @@
/* eslint-disable max-classes-per-file */
import { BACKEND_BASE_URL } from '../config'; import { BACKEND_BASE_URL } from '../config';
import { objectIsEmpty } from '../helpers'; import { objectIsEmpty } from '../helpers';
import UserService from './UserService'; import UserService from './UserService';
@ -36,6 +37,13 @@ export class UnauthenticatedError extends Error {
} }
} }
export class UnexpectedResponseError extends Error {
constructor(message: string) {
super(message);
this.name = 'UnexpectedResponseError';
}
}
const makeCallToBackend = ({ const makeCallToBackend = ({
path, path,
successCallback, successCallback,
@ -74,47 +82,51 @@ backendCallProps) => {
const updatedPath = path.replace(/^\/v1\.0/, ''); const updatedPath = path.replace(/^\/v1\.0/, '');
let isSuccessful = true;
// this fancy 403 handling is like this because we want to get the response body.
// otherwise, we would just throw an exception.
let is403 = false;
fetch(`${BACKEND_BASE_URL}${updatedPath}`, httpArgs) fetch(`${BACKEND_BASE_URL}${updatedPath}`, httpArgs)
.then((response) => { .then((response) => {
if (response.status === 401) { if (response.status === 401) {
throw new UnauthenticatedError('You must be authenticated to do this.'); throw new UnauthenticatedError('You must be authenticated to do this.');
} else if (response.status === 403) {
is403 = true;
isSuccessful = false;
} else if (!response.ok) {
isSuccessful = false;
} }
return response.json(); return response.text().then((result: any) => {
return { response, text: result };
});
}) })
.then((result: any) => { .then((result: any) => {
if (isSuccessful) { let jsonResult = null;
successCallback(result); try {
} else if (is403) { jsonResult = JSON.parse(result.text);
} catch (error) {
if (error instanceof SyntaxError) {
throw new UnexpectedResponseError(
`Received unexpected response from server. Status: ${result.response.status}: ${result.response.statusText}. Body: ${result.text}`
);
}
throw error;
}
if (result.response.status === 403) {
if (onUnauthorized) { if (onUnauthorized) {
onUnauthorized(result); onUnauthorized(jsonResult);
} else if (UserService.isPublicUser()) { } else if (UserService.isPublicUser()) {
window.location.href = '/public/sign-out'; window.location.href = '/public/sign-out';
} else { } else {
// Hopefully we can make this service a hook and use the error message context directly // Hopefully we can make this service a hook and use the error message context directly
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
alert(result.message); alert(jsonResult.message);
}
} else {
let message = 'A server error occurred.';
if (result.message) {
message = result.message;
} }
} else if (!result.response.ok) {
if (failureCallback) { if (failureCallback) {
failureCallback(result); failureCallback(jsonResult);
} else { } else {
let message = 'A server error occurred.';
if (jsonResult.message) {
message = jsonResult.message;
}
console.error(message); console.error(message);
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
alert(message); alert(message);
} }
} else {
successCallback(jsonResult);
} }
}) })
.catch((error) => { .catch((error) => {