Skip to content

Commit

Permalink
Fixed Client-Side Error Handling
Browse files Browse the repository at this point in the history
  • Loading branch information
R1c4rdCo5t4 committed Jun 24, 2024
1 parent ce2f2f0 commit 5e89892
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 39 deletions.
16 changes: 15 additions & 1 deletion code/client/src/contexts/error/ErrorContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,38 @@ import ErrorComponent from '@ui/components/error/Error';

const ERROR_TIMEOUT = 5000;

export type ErrorHandler = <T>(fn: () => T) => Promise<T>;

export type ErrorContextType = {
publishError: (error: Error) => void;
errorHandler: ErrorHandler;
};

export const ErrorContext = createContext<ErrorContextType>({
publishError: () => {},
errorHandler: async fn => fn(),
});

export function ErrorProvider({ children }: { children: React.ReactNode }) {
const [error, setError] = useState<Error | undefined>();

async function errorHandler<T>(fn: () => T): Promise<T> {
try {
return await fn();
} catch (e) {
setError(e as Error);
throw e;
}
}

useEffect(() => {
if (!error) return;
console.error(error);
setTimeout(() => setError(undefined), ERROR_TIMEOUT);
}, [error]);

return (
<ErrorContext.Provider value={{ publishError: setError }}>
<ErrorContext.Provider value={{ publishError: setError, errorHandler }}>
{error && <ErrorComponent error={error} />}
{children}
</ErrorContext.Provider>
Expand Down
13 changes: 7 additions & 6 deletions code/client/src/services/auth/authService.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { User, UserData } from '@notespace/shared/src/users/types';
import { HttpCommunication } from '@services/communication/http/httpCommunication';
import { ErrorHandler } from '@/contexts/error/ErrorContext';

function authService(http: HttpCommunication, publishError: (error: Error) => void) {
function authService(http: HttpCommunication, errorHandler: ErrorHandler) {
async function sessionLogin(idToken: string) {
http.post('/users/login', { idToken }).catch(publishError);
return errorHandler(async () => await http.post('/users/login', { idToken }));
}

async function sessionLogout() {
http.post('/users/logout').catch(publishError);
return errorHandler(async () => await http.post('/users/logout'));
}

async function getUser(id: string): Promise<User> {
return http.get(`/users/${id}`).catch(publishError);
return errorHandler(async () => await http.get(`/users/${id}`));
}

async function updateUser(id: string, newProps: Partial<UserData>) {
http.put(`/users/${id}`, { id, ...newProps }).catch(publishError);
return errorHandler(async () => await http.put(`/users/${id}`, { id, ...newProps }));
}

async function deleteUser(id: string) {
http.delete(`/users/${id}`).catch(publishError);
return errorHandler(async () => await http.delete(`/users/${id}`));
}

return {
Expand Down
4 changes: 2 additions & 2 deletions code/client/src/services/auth/useAuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import useError from '@/contexts/error/useError';

function useAuthService() {
const { http } = useCommunication();
const { publishError } = useError();
return useMemo(() => authService(http, publishError), [http, publishError]);
const { errorHandler } = useError();
return useMemo(() => authService(http, errorHandler), [http, errorHandler]);
}

export default useAuthService;
13 changes: 7 additions & 6 deletions code/client/src/services/commits/commitsService.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { HttpCommunication } from '@services/communication/http/httpCommunication';
import { Commit, CommitData } from '@notespace/shared/src/document/types/commits';
import { ErrorHandler } from '@/contexts/error/ErrorContext';

function commitsService(http: HttpCommunication, publishError: (error: Error) => void, wid: string, id: string) {
function commitsService(http: HttpCommunication, errorHandler: ErrorHandler, wid: string, id: string) {
async function commit() {
http.post(`/workspaces/${wid}/${id}/commit`).catch(publishError);
return errorHandler(async () => await http.post(`/workspaces/${wid}/${id}/commit`));
}

async function rollback(commitId: string) {
http.post(`/workspaces/${wid}/${id}/rollback`, { commitId }).catch(publishError);
return errorHandler(async () => await http.post(`/workspaces/${wid}/${id}/rollback`, { commitId }));
}

async function fork(commitId: string) {
http.post(`/workspaces/${wid}/${id}/fork`, { commitId }).catch(publishError);
return errorHandler(async () => await http.post(`/workspaces/${wid}/${id}/fork`, { commitId }));
}

async function getCommits(): Promise<Commit[]> {
return http.get(`/workspaces/${wid}/${id}/commits`).catch(publishError);
return errorHandler(async () => await http.get(`/workspaces/${wid}/${id}/commits`));
}

async function getCommit(commitId: string): Promise<CommitData> {
return http.get(`/workspaces/${wid}/${id}/commits/${commitId}`).catch(publishError);
return errorHandler(async () => await http.get(`/workspaces/${wid}/${id}/commits/${commitId}`));
}

return {
Expand Down
4 changes: 2 additions & 2 deletions code/client/src/services/commits/useCommitsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import useError from '@/contexts/error/useError';
function useCommitsService() {
const { http } = useCommunication();
const { wid, id } = useParams();
const { publishError } = useError();
const { errorHandler } = useError();
if (!wid || !id) throw new Error('Cannot use commits service outside of a document');
return useMemo(() => commitsService(http, publishError, wid, id), [http, publishError, wid, id]);
return useMemo(() => commitsService(http, errorHandler, wid, id), [http, errorHandler, wid, id]);
}

export default useCommitsService;
17 changes: 9 additions & 8 deletions code/client/src/services/resource/resourcesService.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { HttpCommunication } from '@services/communication/http/httpCommunication';
import { ResourceInputModel, ResourceType, Resource } from '@notespace/shared/src/workspace/types/resource';
import { ErrorHandler } from '@/contexts/error/ErrorContext';

function resourcesService(http: HttpCommunication, publishError: (error: Error) => void, wid: string) {
function resourcesService(http: HttpCommunication, errorHandler: ErrorHandler, wid: string) {
async function getResource(id: string): Promise<Resource> {
return http.get(`/workspaces/${wid}/${id}`).catch(publishError);
return errorHandler(async () => await http.get(`/workspaces/${wid}/${id}`));
}

async function createResource(name: string, type: ResourceType, parent?: string): Promise<string> {
const resource: ResourceInputModel = { name, type, parent: parent || wid };
return http
.post(`/workspaces/${wid}`, resource)
.then(resource => resource.id)
.catch(publishError);
return errorHandler(async () => {
const { id } = await http.post(`/workspaces/${wid}`, resource);
return id;
});
}

async function deleteResource(id: string): Promise<void> {
http.delete(`/workspaces/${wid}/${id}`).catch(publishError);
return errorHandler(async () => await http.delete(`/workspaces/${wid}/${id}`));
}

async function updateResource(id: string, newProps: Partial<ResourceInputModel>): Promise<void> {
http.put(`/workspaces/${wid}/${id}`, newProps).catch(publishError);
return errorHandler(async () => await http.put(`/workspaces/${wid}/${id}`, newProps));
}

return {
Expand Down
4 changes: 2 additions & 2 deletions code/client/src/services/resource/useResourcesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import useError from '@/contexts/error/useError';
function useResourcesService() {
const { http } = useCommunication();
const { wid } = useParams();
const { publishError } = useError();
const { errorHandler } = useError();
if (!wid) throw new Error('Cannot use document service outside of a workspace');
return useMemo(() => resourcesService(http, publishError, wid), [http, publishError, wid]);
return useMemo(() => resourcesService(http, errorHandler, wid), [http, errorHandler, wid]);
}

export default useResourcesService;
4 changes: 2 additions & 2 deletions code/client/src/services/workspace/useWorkspaceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import useError from '@/contexts/error/useError';

function useWorkspaceService() {
const { http } = useCommunication();
const { publishError } = useError();
return useMemo(() => workspaceService(http, publishError), [http, publishError]);
const { errorHandler } = useError();
return useMemo(() => workspaceService(http, errorHandler), [http, errorHandler]);
}

export default useWorkspaceService;
21 changes: 11 additions & 10 deletions code/client/src/services/workspace/workspaceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,45 @@ import { HttpCommunication } from '@services/communication/http/httpCommunicatio
import { WorkspaceInputModel, WorkspaceMeta } from '@notespace/shared/src/workspace/types/workspace';
import { Workspace } from '@notespace/shared/src/workspace/types/workspace';
import { validateEmail } from '@services/workspace/utils';
import { ErrorHandler } from '@/contexts/error/ErrorContext';

function workspaceService(http: HttpCommunication, publishError: (error: Error) => void) {
function workspaceService(http: HttpCommunication, errorHandler: ErrorHandler) {
async function getWorkspace(id: string): Promise<Workspace> {
return http.get(`/workspaces/${id}`).catch(publishError);
return errorHandler(async () => await http.get(`/workspaces/${id}`));
}

async function getWorkspaces(): Promise<WorkspaceMeta[]> {
return http.get('/workspaces').catch(publishError);
return errorHandler(async () => await http.get('/workspaces'));
}

async function createWorkspace(workspace: WorkspaceInputModel): Promise<string> {
return http.post('/workspaces', workspace).catch(publishError);
return errorHandler(async () => await http.post('/workspaces', workspace));
}

async function deleteWorkspace(id: string): Promise<void> {
http.delete(`/workspaces/${id}`).catch(publishError);
return errorHandler(async () => await http.delete(`/workspaces/${id}`));
}

async function updateWorkspace(id: string, newProps: Partial<WorkspaceMeta>): Promise<void> {
http.put(`/workspaces/${id}`, newProps).catch(publishError);
return errorHandler(async () => await http.put(`/workspaces/${id}`, newProps));
}

async function addWorkspaceMember(id: string, email: string): Promise<void> {
validateEmail(email);
http.post(`/workspaces/${id}/members`, { email }).catch(publishError);
return errorHandler(async () => await http.post(`/workspaces/${id}/members`, { email }));
}

async function removeWorkspaceMember(id: string, email: string): Promise<void> {
validateEmail(email);
http.delete(`/workspaces/${id}/members`, { email }).catch(publishError);
return errorHandler(async () => await http.delete(`/workspaces/${id}/members`, { email }));
}

async function getWorkspacesFeed() {
return http.get('/workspaces/search').catch(publishError);
return errorHandler(async () => await http.get('/workspaces/search'));
}

async function searchWorkspaces(query: string, skip: number, limit: number): Promise<WorkspaceMeta[]> {
return http.get(`/workspaces/search?query=${query}&skip=${skip}&limit=${limit}`).catch(publishError);
return errorHandler(async () => await http.get(`/workspaces/search?query=${query}&skip=${skip}&limit=${limit}`));
}

return {
Expand Down

0 comments on commit 5e89892

Please sign in to comment.