From daf6b5024929a0a78ec79e84d9604ec6d84d9fbf Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Tue, 5 Mar 2024 02:07:05 +0100 Subject: [PATCH] Fix/deprioritize image requests (#638) * Use "fetch" instead of "axios" Axios does not support prioritizing requests * Deprioritize image requests The browser requests images () with low priority by default. Due to switching to loading images via axios (f852ce70e7b98c35f3a31e6981d5135b4ae50c8e), the image requests had the same priority as other requests and thus blocked e.g. xhr requests --- package.json | 1 - src/components/util/SpinnerImage.tsx | 3 +- src/lib/requests/RequestManager.ts | 12 ++-- src/lib/requests/client/RestClient.ts | 80 +++++++++++-------------- src/screens/settings/ServerSettings.tsx | 1 - yarn.lock | 57 ------------------ 6 files changed, 45 insertions(+), 109 deletions(-) diff --git a/package.json b/package.json index 936814ef..190246ae 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "@mui/x-date-pickers": "^6.19.2", "@vitejs/plugin-react-swc": "^3.5.0", "apollo-upload-client": "^17.0.0", - "axios": "^1.6.7", "dayjs": "^1.11.10", "file-selector": "^0.6.0", "graphql-tag": "^2.12.6", diff --git a/src/components/util/SpinnerImage.tsx b/src/components/util/SpinnerImage.tsx index a36269b3..eb17e207 100644 --- a/src/components/util/SpinnerImage.tsx +++ b/src/components/util/SpinnerImage.tsx @@ -14,7 +14,6 @@ import { Theme, SxProps, Stack, Button } from '@mui/material'; import BrokenImageIcon from '@mui/icons-material/BrokenImage'; import RefreshIcon from '@mui/icons-material/Refresh'; import { useTranslation } from 'react-i18next'; -import { CanceledError } from 'axios'; import { requestManager } from '@/lib/requests/RequestManager.ts'; import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts'; @@ -72,7 +71,7 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef): void { + public updateClient(config: RequestInit): void { this.restClient.updateConfig(config); this.graphQLClient.updateConfig(); } public getBaseUrl(): string { - return this.restClient.getClient().defaults.baseURL!; + return this.restClient.getBaseUrl(); } public getValidUrlFor(endpoint: string, apiVersion: string = RequestManager.API_VERSION): string { @@ -764,8 +763,13 @@ export class RequestManager { const response = this.restClient .fetcher(url, { checkResponseIsJson: false, - config: { signal, responseType: 'blob' }, + config: { + signal, + // @ts-ignore - typing has not been updated yet + priority: 'low', + }, }) + .then((data) => data.blob()) .then((data) => URL.createObjectURL(data)); return { response, abortRequest }; diff --git a/src/lib/requests/client/RestClient.ts b/src/lib/requests/client/RestClient.ts index a21adef1..b70da094 100644 --- a/src/lib/requests/client/RestClient.ts +++ b/src/lib/requests/client/RestClient.ts @@ -6,33 +6,30 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import { BaseClient } from '@/lib/requests/client/BaseClient.ts'; export enum HttpMethod { - GET = 'get', - POST = 'post', - PATCH = 'patch', - DELETE = 'delete', + GET = 'GET', + POST = 'POST', + PATCH = 'PATCH', + DELETE = 'DELETE', } -type SimpleRestResponse = { - data: Data; -}; - export interface IRestClient { - get>(url: string): Promise; - delete>(url: string): Promise; - post>(url: string, data?: any): Promise; - put>(url: string, data?: any): Promise; - patch>(url: string, data?: any): Promise; + get(url: string): Promise; + delete(url: string): Promise; + post(url: string, data?: any): Promise; + put(url: string, data?: any): Promise; + patch(url: string, data?: any): Promise; } export class RestClient - extends BaseClient(url: string, data: any) => Promise> + extends BaseClient Promise> implements IRestClient { - public readonly fetcher = async ( + private config: RequestInit = {}; + + public readonly fetcher = async ( url: string, { data, @@ -42,20 +39,27 @@ export class RestClient }: { data?: any; httpMethod?: HttpMethod; - config?: AxiosRequestConfig; + config?: RequestInit; checkResponseIsJson?: boolean; } = {}, - ): Promise => { - let result: AxiosResponse; + ): Promise => { + const updatedUrl = url.startsWith('http') ? url : `${this.getBaseUrl()}${url}`; + + let result: Response; switch (httpMethod) { case HttpMethod.GET: - result = await this.client[httpMethod](url, config); + result = await this.client(updatedUrl, { ...this.config, ...config, method: httpMethod }); break; case HttpMethod.POST: case HttpMethod.PATCH: case HttpMethod.DELETE: - result = await this.client[httpMethod](url, data, config); + result = await this.client(updatedUrl, { + ...this.config, + ...config, + method: httpMethod, + body: JSON.stringify(data), + }); break; default: throw new Error(`Unexpected HttpMethod "${httpMethod}"`); @@ -65,54 +69,42 @@ export class RestClient throw new Error(result.statusText); } - if (checkResponseIsJson && result.headers['content-type'] !== 'application/json') { + if (checkResponseIsJson && result.headers.get('content-type') !== 'application/json') { throw new Error('Response is not json'); } - return result.data; + return result; }; protected override createClient(): void { - const baseURL = this.getBaseUrl(); - - this.client = axios.create({ - // baseURL must not have trailing slash - baseURL, - }); - - this.client.interceptors.request.use((config) => { - if (config.data instanceof FormData) { - Object.assign(config.headers, { 'Content-Type': 'multipart/form-data' }); - } - return config; - }); + this.client = fetch.bind(window); } - public updateConfig(config: Partial): void { - this.client.defaults = { ...this.client.defaults, ...config }; + public updateConfig(config: RequestInit): void { + this.config = { ...this.config, ...config }; } - public getClient(): AxiosInstance { + public getClient(): typeof fetch { return this.client; } get get() { - return this.client.get; + return (url: string) => this.fetcher(url); } get post() { - return this.client.post; + return (url: string, data?: any) => this.fetcher(url, { data, httpMethod: HttpMethod.POST }); } get put() { - return this.client.put; + return (url: string, data?: any) => this.fetcher(url, { data, httpMethod: HttpMethod.POST }); } get patch() { - return this.client.patch; + return (url: string, data?: any) => this.fetcher(url, { data, httpMethod: HttpMethod.PATCH }); } get delete() { - return this.client.delete; + return (url: string) => this.fetcher(url, { httpMethod: HttpMethod.DELETE }); } } diff --git a/src/screens/settings/ServerSettings.tsx b/src/screens/settings/ServerSettings.tsx index 0b9cddff..dd0a4e21 100644 --- a/src/screens/settings/ServerSettings.tsx +++ b/src/screens/settings/ServerSettings.tsx @@ -88,7 +88,6 @@ export const ServerSettings = () => { const handleServerAddressChange = (address: string) => { const serverBaseUrl = address.replaceAll(/(\/)+$/g, ''); setServerAddress(serverBaseUrl); - requestManager.updateClient({ baseURL: serverBaseUrl }); }; const updateSetting = ( diff --git a/yarn.lock b/yarn.lock index 11147c1d..f3020d8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2249,11 +2249,6 @@ asynciterator.prototype@^1.0.0: dependencies: has-symbols "^1.0.3" -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - auto-bind@~4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" @@ -2269,15 +2264,6 @@ axe-core@=4.7.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== -axios@^1.6.7: - version "1.6.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" - integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== - dependencies: - follow-redirects "^1.15.4" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -2610,13 +2596,6 @@ colorette@^2.0.16: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -2786,11 +2765,6 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - dependency-graph@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27" @@ -3412,11 +3386,6 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== -follow-redirects@^1.15.4: - version "1.15.5" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" - integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== - for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -3424,15 +3393,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -4314,18 +4274,6 @@ micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -4724,11 +4672,6 @@ prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"