Skip to content

Commit

Permalink
Run tools and fix feedbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
Quetzacoalt91 committed Jan 28, 2025
1 parent de2c2f0 commit 644059a
Show file tree
Hide file tree
Showing 18 changed files with 197 additions and 100 deletions.
10 changes: 5 additions & 5 deletions _dev/src/ts/api/RequestHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import baseApi from './baseApi';
import { ApiResponse, ApiResponseAction } from '../types/apiTypes';
import { ApiResponse, ApiResponseAction, ApiResponseUnknown } from '../types/apiTypes';
import Hydration from '../utils/Hydration';
import { AxiosError } from 'axios';

Expand Down Expand Up @@ -41,8 +41,8 @@ export class RequestHandler {
const responseData = response.data;
await this.#handleResponse(responseData, fromPopState);
} catch (error) {
if (error) {
await this.#handleError(error as AxiosError);
if (error instanceof AxiosError) {
await this.#handleError(error);
}
}
}
Expand Down Expand Up @@ -86,12 +86,12 @@ export class RequestHandler {
}
}

async #handleError(error: AxiosError<unknown, XMLHttpRequest>): Promise<void> {
async #handleError(error: AxiosError<ApiResponseUnknown, XMLHttpRequest>): Promise<void> {
new Hydration().hydrateError({
code: error.status,
type: error.code,
requestParams: error.request,
additionalContents: error.response?.data,
additionalContents: error.response?.data
});
}
}
Expand Down
4 changes: 2 additions & 2 deletions _dev/src/ts/api/baseApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const baseApi = axios.create({
Authorization: `Bearer ${() => window.AutoUpgradeVariables.token}`
},
transitional: {
clarifyTimeoutError: true,
},
clarifyTimeoutError: true
}
});

addRequestInterceptor(baseApi);
Expand Down
18 changes: 9 additions & 9 deletions _dev/src/ts/api/requestInterceptor.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { AxiosInstance, InternalAxiosRequestConfig } from "axios";
import { AxiosInstance, InternalAxiosRequestConfig } from 'axios';

const requestFulfilledInterceptor = (config: InternalAxiosRequestConfig<FormData>) => {
if (!config.data) {
config.data = new FormData();
}
config.data?.append('dir', window.AutoUpgradeVariables.admin_dir);
return config;
};
if (!config.data) {
config.data = new FormData();
}
config.data?.append('dir', window.AutoUpgradeVariables.admin_dir);
return config;
};

export const addRequestInterceptor = (axios: AxiosInstance): void => {
axios.interceptors.request.use(requestFulfilledInterceptor);
}
axios.interceptors.request.use(requestFulfilledInterceptor);
};
54 changes: 39 additions & 15 deletions _dev/src/ts/api/responseInterceptor.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,59 @@
import { AxiosError, AxiosInstance, AxiosResponse } from "axios";
import { APP_ERR_RESPONSE_BAD_TYPE, APP_ERR_RESPONSE_INVALID } from "../types/apiTypes";

const responseFulfilledInterceptor = (response: AxiosResponse<any, FormData>) => {
console.log('Checking response', response);
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
import {
ApiResponseUnknown,
ApiResponseUnknownObject,
APP_ERR_RESPONSE_BAD_TYPE,
APP_ERR_RESPONSE_EMPTY,
APP_ERR_RESPONSE_INVALID,
SilencedApiError
} from '../types/apiTypes';

const responseFulfilledInterceptor = (response: AxiosResponse<ApiResponseUnknown, FormData>) => {
if (!response?.data) {
throw new AxiosError(
'The response is empty',
APP_ERR_RESPONSE_EMPTY,
response.config,
response.request,
response
);
}
// All responses must be a parsed JSON. If we get another type of response,
// this means something went wrong, i.e Another software answered.
if (Object.prototype.toString.call(response.data) !== '[object Object]') {
throw new AxiosError('The response does not have a valid type', APP_ERR_RESPONSE_BAD_TYPE, response.config, response.request, response);
throw new AxiosError(
'The response does not have a valid type',
APP_ERR_RESPONSE_BAD_TYPE,
response.config,
response.request,
response
);
}

// Make sure the response contains the expected data
if (!response.data.kind) {
throw new AxiosError('The response contents is invalid', APP_ERR_RESPONSE_INVALID, response.config, response.request, response);
if (!(response.data as ApiResponseUnknownObject)?.kind) {
throw new AxiosError(
'The response contents is invalid',
APP_ERR_RESPONSE_INVALID,
response.config,
response.request,
response
);
}

return response;
};

const responseErroredInterceptor = (error: any) => {
const responseErroredInterceptor = (error: Error) => {
const errorSilenced = [AxiosError.ERR_CANCELED];
// Ignore some errors
if (error instanceof AxiosError) {
if (error.code && errorSilenced.includes(error.code)) {
return Promise.reject(null);
}
if (error instanceof AxiosError && error.code && errorSilenced.includes(error.code)) {
return Promise.reject(new SilencedApiError());
}

return Promise.reject(error);
};
};

export const addResponseInterceptor = (axios: AxiosInstance): void => {
axios.interceptors.response.use(responseFulfilledInterceptor, responseErroredInterceptor);
}
};
70 changes: 47 additions & 23 deletions _dev/src/ts/pages/ErrorPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import Hydration from '../utils/Hydration';
import PageAbstract from './PageAbstract';

export default class ErrorPage extends PageAbstract {
public static templateId: string = 'error-page-template';
// TODO: Improve this by putting the target in the template and sent it from the back end
public static targetElementIdToUpdate: string = 'ua_page';
public static readonly templateId: string = 'error-page-template';

isOnHomePage: boolean = false;

Expand All @@ -19,19 +17,25 @@ export default class ErrorPage extends PageAbstract {
}

public mount = (): void => {

// If the error page is already present on the DOM (For instance on a whole page refresh),
// initalize it at once instead of waiting for an event.
const errorPageFromBackEnd = document.querySelector('.error-page');
if (errorPageFromBackEnd) {
this.#mountErrorPage(errorPageFromBackEnd);
} else {
this.#errorTemplateElement.addEventListener(Hydration.hydrationEventName, this.#onError.bind(this), {once: true});
this.#errorTemplateElement.addEventListener(
Hydration.hydrationEventName,
this.#onError.bind(this),
{ once: true }
);
}
};

public beforeDestroy = (): void => {
this.#errorTemplateElement.removeEventListener(Hydration.hydrationEventName, this.#onError.bind(this));
this.#errorTemplateElement.removeEventListener(
Hydration.hydrationEventName,
this.#onError.bind(this)
);
this.#submitErrorReportForm?.removeEventListener('submit', this.#onSubmit);
logStore.clearLogs();
};
Expand All @@ -43,11 +47,13 @@ export default class ErrorPage extends PageAbstract {
throw new Error('Error template not found');
}

return element as HTMLTemplateElement;
}
['target'].forEach((data) => {
if (!element.dataset[data]) {
throw new Error(`Missing data ${data} from element dataset.`);
}
});

#onError = async (event: CustomEvent<ApiError>): Promise<void> => {
this.#createErrorPage(event);
return element as HTMLTemplateElement;
}

#createErrorPage(event: CustomEvent<ApiError>): void {
Expand All @@ -60,9 +66,13 @@ export default class ErrorPage extends PageAbstract {
errorChild.id = `ua_error_${event.detail.type}`;
}

const IsCodeAnHttpErrorCode = typeof event.detail.code === 'number' &&

Check failure on line 69 in _dev/src/ts/pages/ErrorPage.ts

View workflow job for this annotation

GitHub Actions / JS linter syntax check

Insert `⏎·····`
event.detail.code >= 300 &&
event.detail.code.toString().length === 3;

// If code is a HTTP error number (i.e 404, 500 etc.), let's change the text in the left column with it.
if (typeof event.detail.code === 'number' && event.detail.code >= 300 && event.detail.code.toString().length === 3) {
const strigifiedCode = event.detail.code.toString().replaceAll('0', 'O');
if (IsCodeAnHttpErrorCode) {
const strigifiedCode = (event.detail.code as number).toString().replaceAll('0', 'O');
const errorCodeSlotElements = errorElement.querySelectorAll('.error-page__code-char');
errorCodeSlotElements.forEach((element: Element, index: number) => {
element.innerHTML = strigifiedCode[index];
Expand All @@ -73,7 +83,9 @@ export default class ErrorPage extends PageAbstract {

// Display a user friendly text related to the code if it exists, otherwise write the error code.
const errorDescriptionElement = errorElement.querySelector('.error-page__desc');
const userFriendlyDescriptionElement = errorDescriptionElement?.querySelector(`.error-page__desc-${event.detail.code || event.detail.type}`);
const userFriendlyDescriptionElement = errorDescriptionElement?.querySelector(
`.error-page__desc-${IsCodeAnHttpErrorCode ? event.detail.code : event.detail.type}`
);
if (userFriendlyDescriptionElement) {
userFriendlyDescriptionElement.classList.remove('hidden');
} else if (errorDescriptionElement && event.detail.type) {
Expand All @@ -82,27 +94,28 @@ export default class ErrorPage extends PageAbstract {

// Store the contents in the logs so it can be used in the error reporting modal
if (event.detail.additionalContents) {
const logsContents = typeof event.detail.additionalContents === 'object'
? JSON.stringify(event.detail.additionalContents)
: event.detail.additionalContents;
const logsContents =
typeof event.detail.additionalContents === 'object'
? JSON.stringify(event.detail.additionalContents)
: event.detail.additionalContents;

logStore.addLog({
severity: Severity.SUCCESS,
height: 0,
offsetTop: 0,
message: logsContents,
message: logsContents
});
}

// Finally, append the result on the page
const targetElementToUpdate = document.getElementById(ErrorPage.targetElementIdToUpdate);
const targetElementToUpdate = document.getElementById(this.#errorTemplateElement.dataset.target!);

Check failure on line 111 in _dev/src/ts/pages/ErrorPage.ts

View workflow job for this annotation

GitHub Actions / JS linter syntax check

Replace `this.#errorTemplateElement.dataset.target!` with `⏎······this.#errorTemplateElement.dataset.target!⏎····`
if (!targetElementToUpdate) {
throw new Error('Target element cannot be found');
}
targetElementToUpdate.replaceChildren(errorElement);

// Retrieve the route we called to fill in the context.
let route: string|null = null;
let route: string | null = null;
if (event.detail.requestParams?.responseURL) {
const params = new URLSearchParams(new URL(event.detail.requestParams?.responseURL)?.search);
route = params?.get('route');
Expand All @@ -112,15 +125,15 @@ export default class ErrorPage extends PageAbstract {
severity: Severity.ERROR,
height: 0,
offsetTop: 0,
message: `An HTTP request failed on route ${route || 'N/A'}. Type: ${event.detail.type || 'N/A'} - Code ${event.detail.code || 'N/A'}`,
message: `HTTP request failed: Route ${route ?? 'N/A'} - Type: ${event.detail.type ?? 'N/A'} - Code ${event.detail.code ?? 'N/A'}`
});

// Enable events and page features
this.#mountErrorPage(document.querySelector('.error-page')!);
}

#mountErrorPage(errorPage: Element): void {
this.#form.addEventListener('submit', this.#onSubmit, {once: true});
this.#form.addEventListener('submit', this.#onSubmit, { once: true });

this.#submitErrorReportForm?.addEventListener('submit', this.#onSubmit);

Expand Down Expand Up @@ -149,13 +162,24 @@ export default class ErrorPage extends PageAbstract {
return form;
}

get #submitErrorReportForm(): HTMLFormElement|null {
get #submitErrorReportForm(): HTMLFormElement | null {
return document.forms.namedItem('submit-error-report');
}

readonly #onError = async (event: Event | CustomEvent<ApiError>): Promise<void> => {
if (!(event instanceof CustomEvent)) {
console.debug('Unexpected type of event received.');
return;
}
this.#createErrorPage(event);
};

readonly #onSubmit = async (event: SubmitEvent): Promise<void> => {
event.preventDefault();

await api.post((event.target as HTMLFormElement).dataset.routeToSubmit!, new FormData(this.#form));
await api.post(
(event.target as HTMLFormElement).dataset.routeToSubmit!,
new FormData(this.#form)
);
};
}
4 changes: 3 additions & 1 deletion _dev/src/ts/routing/ScriptHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ export default class ScriptHandler {
console.debug(`No matching class found for ID: ${scriptID}`);
// Outside a hydration, the scriptID matches the route query param.
// If it does not exist, we load the error management script instead.
this.loadScript('error-page');
if (!this.#currentScripts[ScriptType.PAGE]) {
this.loadScript('error-page');
}
return;
}

Expand Down
21 changes: 14 additions & 7 deletions _dev/src/ts/types/apiTypes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
interface ApiResponseHydration {
kind: 'hydrate',
kind: 'hydrate';
hydration: boolean;
new_content: string;
new_route?: string;
Expand All @@ -8,12 +8,12 @@ interface ApiResponseHydration {
}

interface ApiResponseNextRoute {
kind: 'next',
kind: 'next';
next_route: string;
}

interface ApiResponseAction {
kind: 'action',
kind: 'action';
error: null | boolean;
stepDone: null | boolean;
next: string;
Expand All @@ -28,15 +28,22 @@ interface ApiResponseAction {
}

export interface ApiError {
code?: number,
type?: string,
requestParams?: XMLHttpRequest,
additionalContents?: string|object
code?: number;
type?: string;
requestParams?: XMLHttpRequest;
additionalContents?: string | object;
}
export class SilencedApiError extends Error {};

Check failure on line 36 in _dev/src/ts/types/apiTypes.ts

View workflow job for this annotation

GitHub Actions / JS linter syntax check

Delete `;`

export type ApiResponseUnknownObject = {
kind?: Pick<ApiResponseHydration | ApiResponseNextRoute | ApiResponseAction, 'kind'>;
};
export type ApiResponseUnknown = string | ApiResponseUnknownObject | undefined;

type ApiResponse = ApiResponseHydration | ApiResponseNextRoute | ApiResponseAction;

export const APP_ERR_RESPONSE_BAD_TYPE = 'APP_ERR_RESPONSE_BAD_TYPE';
export const APP_ERR_RESPONSE_INVALID = 'APP_ERR_RESPONSE_INVALID';
export const APP_ERR_RESPONSE_EMPTY = 'APP_ERR_RESPONSE_EMPTY';

export type { ApiResponseHydration, ApiResponseNextRoute, ApiResponseAction, ApiResponse };
4 changes: 3 additions & 1 deletion _dev/src/ts/utils/Hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export default class Hydration {
scriptHandler.loadScript('error-page');

const elementToUpdate = document.getElementById(ErrorPage.templateId);
elementToUpdate?.dispatchEvent(new CustomEvent(Hydration.hydrationEventName, {detail: error}));
elementToUpdate?.dispatchEvent(
new CustomEvent(Hydration.hydrationEventName, { detail: error })
);
}
}
Loading

0 comments on commit 644059a

Please sign in to comment.