From 530545c7cd2b4a3ff444e9c7e1f40c68d4a7376c Mon Sep 17 00:00:00 2001 From: alex-krasn <64093224+alex-krasn@users.noreply.github.com> Date: Fri, 14 Aug 2020 15:50:24 -0700 Subject: [PATCH] fix: "failed to fetch()" error (#491) * add try-catch for fetch() * use poll function instead try-catch * style: naming * fix: poll and resolve for fetched failed * add try-catch for fetch() * use poll function instead try-catch * style: naming * merge + misc * deletes unused import * fix: check for failed fetch promise * since it's not blocking error - console.err(message) instead 'toast' * switch to console.err for spooded mime type too Co-authored-by: stew-ro --- src/common/localization/en-us.ts | 1 + src/common/localization/es-cl.ts | 1 + src/common/strings.ts | 1 + src/services/assetService.ts | 64 ++++++++++++++++++++++++++------ 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/common/localization/en-us.ts b/src/common/localization/en-us.ts index 59cb71c33..2f0488df0 100644 --- a/src/common/localization/en-us.ts +++ b/src/common/localization/en-us.ts @@ -374,6 +374,7 @@ export const english: IAppStrings = { incorrectFileExtension: { attention: "Attention!", text: "- extension of this file doesn't correspond MIME type. Please check file:", + failedToFetch: "Failed to fetch ${fileName} for mime type validation", }, }, assetError: "Unable to load asset", diff --git a/src/common/localization/es-cl.ts b/src/common/localization/es-cl.ts index 163be946e..ecdd4b254 100644 --- a/src/common/localization/es-cl.ts +++ b/src/common/localization/es-cl.ts @@ -375,6 +375,7 @@ export const spanish: IAppStrings = { incorrectFileExtension: { attention: "¡Atención!", text: "-- la extensión de este archivo no corresponde al tipo MIME. Por favor revise el archivo:", + failedToFetch: "No se pudo recuperar ${fileName} para la validación del tipo de mímica", }, }, assetError: "No se puede mostrar el activo", diff --git a/src/common/strings.ts b/src/common/strings.ts index cc70ba07f..84cf69439 100644 --- a/src/common/strings.ts +++ b/src/common/strings.ts @@ -370,6 +370,7 @@ export interface IAppStrings { incorrectFileExtension: { attention: string, text: string, + failedToFetch: string, }, } , diff --git a/src/services/assetService.ts b/src/services/assetService.ts index 095316068..4d502d192 100644 --- a/src/services/assetService.ts +++ b/src/services/assetService.ts @@ -26,8 +26,8 @@ interface IMime { pattern: (number|undefined)[]; } - // tslint:disable number-literal-format - // tslint:disable no-magic-numbers +// tslint:disable number-literal-format +// tslint:disable no-magic-numbers const imageMimes: IMime[] = [ { types: ["bmp"], @@ -100,13 +100,14 @@ export class AssetService { } else { types = await this.getMimeType(filePath); } - + const corruptFileName = fileName.split("%2F").pop().replace(/%20/g, " "); + if (!types) { + console.error(interpolate(strings.editorPage.assetWarning.incorrectFileExtension.failedToFetch, { fileName: corruptFileName.toLocaleUpperCase() })); + } // If file was renamed/spoofed - fix file extension to true MIME type and show message - if (!types.includes(assetFormat)) { + else if (!types.includes(assetFormat)) { assetFormat = types[0]; - const corruptFileName = fileName.split("%2F").pop().replace(/%20/g, " "); - - toast.info(`${strings.editorPage.assetWarning.incorrectFileExtension.attention} ${corruptFileName.toLocaleUpperCase()} ${strings.editorPage.assetWarning.incorrectFileExtension.text} ${corruptFileName.toLocaleUpperCase()}`, { delay: 3000 }); + console.error(`${strings.editorPage.assetWarning.incorrectFileExtension.attention} ${corruptFileName.toLocaleUpperCase()} ${strings.editorPage.assetWarning.incorrectFileExtension.text} ${corruptFileName.toLocaleUpperCase()}`); } } @@ -144,13 +145,21 @@ export class AssetService { } } - // If extension of a file was spoofed, we fetch only first 4 bytes of the file and read MIME type + // If extension of a file was spoofed, we fetch only first 4 or needed amount of bytes of the file and read MIME type public static async getMimeType(uri: string): Promise { - const first4bytes: Response = await fetch(uri, { headers: { range: `bytes=0-${mimeBytesNeeded}` } }); + const getFirst4bytes = (): Promise => this.pollForFetchAPI(() => fetch(uri, { headers: { range: `bytes=0-${mimeBytesNeeded}` } }), 1000, 200); + let first4bytes: Response; + try { + first4bytes = await getFirst4bytes() + } catch { + return new Promise((resolve) => { + resolve(null); + }); + } const arrayBuffer: ArrayBuffer = await first4bytes.arrayBuffer(); - const blob = new Blob([new Uint8Array(arrayBuffer).buffer]); + const blob: Blob = new Blob([new Uint8Array(arrayBuffer).buffer]); const isMime = (bytes: Uint8Array, mime: IMime): boolean => { - return mime.pattern.every((p, i) => !p || bytes[i] === p); + return mime.pattern.every((p, i) => !p || bytes[i] === p); }; const fileReader: FileReader = new FileReader(); @@ -430,4 +439,37 @@ export class AssetService { const startsWithFolderPath = assetName.indexOf(`${normalizedPath}/`) === 0; return startsWithFolderPath && assetName.lastIndexOf("/") === normalizedPath.length; } + + /** + * Poll function to repeatedly check if request succeeded + * @param func - function that will be called repeatedly + * @param timeout - timeout + * @param interval - interval + */ + private static pollForFetchAPI = (func, timeout, interval): Promise => { + const endTime = Number(new Date()) + (timeout || 10000); + interval = interval || 100; + + const checkSucceeded = (resolve, reject) => { + const ajax = func(); + ajax.then( + response => { + if (response.ok) { + resolve(response); + } else { + // Didn't succeeded after too much time, reject + reject("Timed out, please try other file or check your network setting."); + } + }).catch(() => { + if (Number(new Date()) < endTime) { + // If the request isn't succeeded and the timeout hasn't elapsed, go again + setTimeout(checkSucceeded, interval, resolve, reject); + } else { + // Didn't succeeded after too much time, reject + reject("Timed out fetching file."); + } + }); + }; + return new Promise(checkSucceeded); + } }