fix: "failed to fetch()" error (#491) (#632)

* 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 <v-stewro@microsoft.com>

Co-authored-by: stew-ro <v-stewro@microsoft.com>
This commit is contained in:
alex-krasn 2020-09-30 15:59:59 -07:00 коммит произвёл GitHub
Родитель ab86ed42e5
Коммит 3b2f40252b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 56 добавлений и 11 удалений

Просмотреть файл

@ -293,6 +293,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",

Просмотреть файл

@ -295,6 +295,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",

Просмотреть файл

@ -290,6 +290,7 @@ export interface IAppStrings {
incorrectFileExtension: {
attention: string,
text: string,
failedToFetch: string,
},
}
,

Просмотреть файл

@ -25,8 +25,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"],
@ -92,13 +92,14 @@ export class AssetService {
if (supportedImageFormats.hasOwnProperty(assetFormat)) {
const 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()}`);
}
}
@ -136,13 +137,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<string[]> {
const first4bytes: Response = await fetch(uri, { headers: { range: `bytes=0-${mimeBytesNeeded}` } });
const getFirst4bytes = (): Promise<Response> => this.pollForFetchAPI(() => fetch(uri, { headers: { range: `bytes=0-${mimeBytesNeeded}` } }), 1000, 200);
let first4bytes: Response;
try {
first4bytes = await getFirst4bytes()
} catch {
return new Promise<string[]>((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();
@ -403,4 +412,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<any> => {
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);
}
}