From 74cf4b7c50e88f8a3a98da518e0be314266f354c Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Thu, 28 Mar 2024 22:40:55 +0100 Subject: [PATCH] Add option to use img tag to fetch images MangaUpdates covers could not be fetched via the fetch api because there were cors issues --- src/components/tracker/TrackerMangaCard.tsx | 1 + src/components/util/SpinnerImage.tsx | 12 ++--- src/lib/requests/RequestManager.ts | 60 +++++++++++++++++++-- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/components/tracker/TrackerMangaCard.tsx b/src/components/tracker/TrackerMangaCard.tsx index 0ce47acd..196a08d4 100644 --- a/src/components/tracker/TrackerMangaCard.tsx +++ b/src/components/tracker/TrackerMangaCard.tsx @@ -145,6 +145,7 @@ export const TrackerMangaCard = ({ > void; + + useFetchApi?: boolean; } export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef) => { - const { src, alt, onImageLoad, spinnerStyle: { small, ...spinnerStyle } = {}, imgStyle } = props; + const { useFetchApi, src, alt, onImageLoad, spinnerStyle: { small, ...spinnerStyle } = {}, imgStyle } = props; const { t } = useTranslation(); @@ -55,8 +57,7 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef {}; } - let tmpImageSourceUrl: string; - const imageRequest = requestManager.requestImage(src, Priority.HIGH); + const imageRequest = requestManager.requestImage(src, Priority.HIGH, useFetchApi); let cacheTimeout: NodeJS.Timeout; const fetchImage = async () => { @@ -66,7 +67,6 @@ export const SpinnerImage = forwardRef((props: IProps, imgRef: ForwardedRef { - if (tmpImageSourceUrl) { - URL.revokeObjectURL(tmpImageSourceUrl); - } + imageRequest.cleanup(); clearTimeout(cacheTimeout); imageRequest.abortRequest(new Error('Component was unmounted')); }; diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index f94779d8..0c44e640 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -280,6 +280,7 @@ import { TRACKER_LOGOUT, TRACKER_UPDATE_BIND, } from '@/lib/graphql/mutations/TrackerMutation.ts'; +import { ControlledPromise } from '@/lib/ControlledPromise.ts'; enum GQLMethod { QUERY = 'QUERY', @@ -330,6 +331,8 @@ type SubscriptionHookOptions; cleanup: () => void } & AbortableRequest; + export type AbortabaleApolloQueryResponse = { response: Promise>; } & AbortableRequest; @@ -778,6 +781,39 @@ export class RequestManager { return `${this.getValidUrlFor(imageUrl, apiVersion)}`; } + private fetchImageViaTag(url: string, priority?: QueuePriority): ImageRequest { + const imgRequest = new ControlledPromise(); + imgRequest.promise.catch(defaultPromiseErrorHandler(`fetchImageViaTag(${url})`)); + + const img = new Image(); + const abortRequest = (reason?: any) => { + img.src = ''; + img.onload = null; + img.onerror = null; + img.onabort = null; + imgRequest.reject(reason); + }; + + const response = this.imageQueue.enqueue( + url, + async () => { + // throws error in case request was already aborted + await Promise.race([imgRequest.promise, Promise.resolve()]); + + img.src = url; + + img.onload = () => imgRequest.resolve(url); + img.onerror = (error) => imgRequest.reject(error); + img.onabort = (error) => imgRequest.reject(error); + + return imgRequest.promise; + }, + priority, + ); + + return { response, abortRequest, cleanup: () => {} }; + } + /** * After the image has been handled, {@see URL#revokeObjectURL} has to be called. * @@ -787,11 +823,12 @@ export class RequestManager { * const imageUrl = await imageRequest.response * * const img = new Image(); - * img.onLoad = () => URL.revokeObjectURL(imageUrl); + * img.onLoad = () => imageRequest.cleanup(); * img.src = imageUrl; * */ - public requestImage(url: string, priority?: QueuePriority): { response: Promise } & AbortableRequest { + private fetchImageViaFetchApi(url: string, priority?: QueuePriority): ImageRequest { + let objectUrl: string = ''; const { abortRequest, signal } = this.createAbortController(); const response = this.imageQueue.enqueue( url, @@ -806,11 +843,26 @@ export class RequestManager { }, }) .then((data) => data.blob()) - .then((data) => URL.createObjectURL(data)), + .then((data) => URL.createObjectURL(data)) + .then((imageUrl) => { + objectUrl = imageUrl; + return imageUrl; + }), priority, ); - return { response, abortRequest }; + return { response, abortRequest, cleanup: () => URL.revokeObjectURL(objectUrl) }; + } + + /** + * Make sure to call "cleanup" once the image is not needed anymore (only required if fetched via "fetch api") + */ + public requestImage(url: string, priority?: QueuePriority, useFetchApi: boolean = true): ImageRequest { + if (useFetchApi) { + return this.fetchImageViaFetchApi(url, priority); + } + + return this.fetchImageViaTag(url, priority); } private doRequest(