'use strict' import { validationParts } from './uploader_constants'; import { deepMatchObject } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; /** * ErrorClasses * ============ * Known error classes based on groupings (ie. where they happened, which component, etc). * * Error classes have a test object that can be used to test whether or not an error * object matches that specific class. Properties in the test object will be recursively * checked in the error object, and the error object is only matched to the class if all * tests succeed. See testErrorAgainstClass() below for the implementation of the matching. * * ErrorClasses.default.default is the generic error for errors not identified under any * grouping and class. * * Format: * ErrorClasses = { * 'errorGrouping': { * 'errorClassName': ErrorClass * ... * }, * ... * 'default': { * ... * 'default': generic error for errors that don't fall under any grouping and class * } * } * * Each class is of the format: * ErrorClass = { * 'name': name of the class * 'group': grouping of the error, * 'prettifiedText': prettified text for the class * 'test': { * prop1: property in the error object to recursively match against using * either === or, if the property is a string, substring match * (ie. indexOf() >= 0) * ... * }, * } * * Test object examples * ==================== * A class like this: * * 'errorClass': { * 'test': { * 'reason': 'Invalid server response', * 'xhr': { * 'response': 'Internal error', * 'status': 500 * } * } * } * * will match this error object: * * error = { * 'reason': 'Invalid server response', * 'xhr': { // Simplified version of the XMLHttpRequest object responsible for the failure * 'response': 'Internal error', * 'status': 500 * } * } * * but will *NOT* match this error object: * * error = { * 'reason': 'Invalid server response', * 'xhr': { * 'response': 'Unauthorized', * 'status': 401 * } * } * * A common use case is for the test to just be against the error.reason string. * In these cases, setting the test object to be just a string will enforce this test, * so something like this: * * 'errorClass': { * 'test': { * 'reason': 'Invalid server response' * } * } * * is the same as: * * 'errorClass': { * 'test': 'Invalid server response' * } */ const ErrorClasses = { 'upload': { 'requestTimeTooSkewed': { 'prettifiedText': getLangText('Check your time and date preferences and select "set date and time ' + 'automatically." Being off by a few minutes from our servers can ' + 'prevent your upload.'), 'test': { 'xhr': { 'response': 'RequestTimeTooSkewed' } } }, 'chunkSignatureError': { 'prettifiedText': getLangText("We're experiencing some problems with uploads at the moment and " + 'are working to resolve them. Please try again in a few hours.'), 'test': 'Problem signing the chunk' }, // Fallback error tips 'largeFileSize': { 'prettifiedText': getLangText(`We handle files up to ${validationParts.sizeLimit.default / 1000000000}GB ` + 'but your Internet connection may not. With large files and limited ' + 'bandwith, it may take some time to complete. If it doesn’t seem to ' + 'progress at all, try restarting the process.') }, 'tryDifferentBrowser': { 'prettifiedText': getLangText("We're still having trouble uploading your file. It might be your " + "browser; try a different browser or make sure you’re using the " + 'latest version.') }, 'contactUs': { 'prettifiedText': getLangText("We're having a really hard time with your upload. Please contact us for more help.") }, 'offline': { 'prettifiedText': getLangText('It looks like your Internet connection might have gone down during the upload. Please check your connection and try again.') } }, 'default': { 'default': { 'prettifiedText': getLangText("It looks like there's been a problem on our end. If you keep experiencing this error, please contact us.") } } }; // Dynamically inject the name and group properties into the classes Object.keys(ErrorClasses).forEach((errorGroupKey) => { const errorGroup = ErrorClasses[errorGroupKey]; Object.keys(errorGroup).forEach((errorClassKey) => { const errorClass = errorGroup[errorClassKey]; errorClass.name = errorGroupKey + '-' + errorClassKey; errorClass.group = errorGroupKey; }); }); /** * Returns prettified text for a given error by trying to match it to * a known error in ErrorClasses or the given class. * * One should provide a class (eg. ErrorClasses.upload.requestTimeTooSkewed) * if they already have an error in mind that they want to match against rather * than all the available error classes. * * @param {object} error An error with the following: * @param {string} error.type Type of error * @param {string} error.reason Reason of error * @param {(XMLHttpRequest)} error.xhr XHR associated with the error * @param {(any)} error.* Any other property as necessary * * @param {(object)} errorClass ErrorClass to match against the given error. * Signature should be similar to ErrorClasses' classes (see above). * @param {object|string} errorClass.test Test object to recursively match against the given error * @param {string} errorClass.prettifiedText Prettified text to return if the test matches * * @return {string} Prettified error string. Returns the default error string if no * error class was matched to the given error. */ function getPrettifiedError(error, errorClass) { const matchedClass = errorClass ? testErrorAgainstClass(error, errorClass) : testErrorAgainstAll(error); return (matchedClass && matchedClass.prettifiedText) || ErrorClasses.default.default.prettifiedText; } /** * Tests the given error against all items in ErrorClasses and returns * the matching class if available. * See getPrettifiedError() for the signature of @param error. * @return {(object)} Matched error class */ function testErrorAgainstAll(error) { const type = error.type || 'default'; const errorGroup = ErrorClasses[type]; return Object .keys(errorGroup) .reduce((result, key) => { return result || testErrorAgainstClass(error, errorGroup[key]); }, null); } /** * Tests the error against the class by recursively testing the * class's test object against the error. * Implements the test matching behaviour described in ErrorClasses. * * See getPrettifiedError() for the signatures of @param error and @param errorClass. * @return {(object)} Returns the given class if the test succeeds. */ function testErrorAgainstClass(error, errorClass) { // Automatically fail classes if no tests present, since some of the error classes // may not have an error to test against. if (!errorClass.test) { return; } if (typeof errorClass.test === 'string') { errorClass.test = { reason: errorClass.test }; } return deepMatchObject(error, errorClass.test, (objProp, matchProp) => { return (objProp === matchProp || (typeof objProp === 'string' && objProp.indexOf(matchProp) >= 0)); }) ? errorClass : null; } // Need to export with the clause syntax as we change ErrorClasses after its declaration. export { ErrorClasses, getPrettifiedError, testErrorAgainstAll, testErrorAgainstClass };