Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 15 commits into from
Aug 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/common/localization/en-us.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/common/localization/es-cl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/common/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ export interface IAppStrings {
incorrectFileExtension: {
attention: string,
text: string,
failedToFetch: string,
},
}
,
Expand Down
64 changes: 53 additions & 11 deletions src/services/assetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down Expand Up @@ -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()}`);
}
}

Expand Down Expand Up @@ -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<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();

Expand Down Expand Up @@ -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<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);
}
}