From e18c6b9818cb1b4deb8f43f5ba160c449e053513 Mon Sep 17 00:00:00 2001 From: Piyush Vijay Date: Wed, 13 Jun 2018 02:35:15 +0530 Subject: [PATCH] Fix error reporting functionality ajax callback errors . TODO: To find a way to wrap the exported functions containing PMA_ prefix into the error handler to generate backtrace. Signed-off-by: Piyush Vijay --- js/src/classes/ErrorReport.js | 338 ++++++++++++++++++++++++++++++++++ js/src/error_report.js | 8 + 2 files changed, 346 insertions(+) create mode 100644 js/src/classes/ErrorReport.js create mode 100644 js/src/error_report.js diff --git a/js/src/classes/ErrorReport.js b/js/src/classes/ErrorReport.js new file mode 100644 index 0000000000..0ac0b69f9f --- /dev/null +++ b/js/src/classes/ErrorReport.js @@ -0,0 +1,338 @@ +/** + * general function, usually for data manipulation pages + * + */ +import { AJAX } from '../ajax'; +import { PMA_Messages as PMA_messages } from '../variables/export_variables'; +import { PMA_commonParams } from '../variables/common_params'; +import { PMA_ajaxShowMessage } from '../utils/show_ajax_messages'; +import { PMA_getImage } from '../functions/get_image'; +import TraceKit from 'tracekit'; +import { jQuery as $ } from '../utils/extend_jquery'; + +/** + * This Object uses the library TraceKit to generate the backtrace of the + * error but since we are using webpack and the development and production + * mode have different url leading to cross origin query, the error + * report cannot be generated in development mode with context. + * The context will be null in deveopment mode. + * + * @see TraceKit url checking implemented to avoid Cross Origin + * https://github.com/csnover/TraceKit/blob/d38d48765f089309e4f4c7a1baacd96dd0534187/tracekit.js#L445 + * + * @see TraceKit How context of the error is being gennerated + * https://github.com/csnover/TraceKit/blob/d38d48765f089309e4f4c7a1baacd96dd0534187/tracekit.js#L499 + * + * Due to usage of webpack, the production output is creating backtrace context + * containing single line only and that single line is the complete generated + * webpack bundle in the uglify mode. + */ + +var ErrorReport = { + /** + * @var object stores the last exception info + */ + _last_exception: null, + /** + * handles thrown error exceptions based on user preferences + * + * @return void + */ + error_handler: function (exception) { + if (exception.name === null || typeof(exception.name) === 'undefined') { + exception.name = ErrorReport._extractExceptionName(exception); + } + ErrorReport._last_exception = exception; + $.get('error_report.php', { + ajax_request: true, + server: PMA_commonParams.get('server'), + get_settings: true, + exception_type: 'js' + }, function (data) { + if (data.success !== true) { + PMA_ajaxShowMessage(data.error, false); + return; + } + if (data.report_setting === 'ask') { + ErrorReport._showErrorNotification(); + } else if (data.report_setting === 'always') { + var report_data = ErrorReport._get_report_data(exception); + var post_data = $.extend(report_data, { + send_error_report: true, + automatic: true + }); + $.post('error_report.php', post_data, function (data) { + if (data.success === false) { + // in the case of an error, show the error message returned. + PMA_ajaxShowMessage(data.error, false); + } else { + PMA_ajaxShowMessage(data.message, false); + } + }); + } + }); + }, + /** + * Shows the modal dialog previewing the report + * + * @param exception object error report info + * + * @return void + */ + _showReportDialog: function (exception) { + var report_data = ErrorReport._get_report_data(exception); + + /* Remove the hidden dialogs if there are*/ + if ($('#error_report_dialog').length !== 0) { + $('#error_report_dialog').remove(); + } + var $div = $('
'); + $div.css('z-index', '1000'); + + var button_options = {}; + button_options[PMA_messages.strSendErrorReport] = function () { + var $dialog = $(this); + var post_data = $.extend(report_data, { + send_error_report: true, + description: $('#report_description').val(), + always_send: $('#always_send_checkbox')[0].checked + }); + $.post('error_report.php', post_data, function (data) { + $dialog.dialog('close'); + if (data.success === false) { + // in the case of an error, show the error message returned. + PMA_ajaxShowMessage(data.error, false); + } else { + PMA_ajaxShowMessage(data.message, 3000); + } + }); + }; + + button_options[PMA_messages.strCancel] = function () { + $(this).dialog('close'); + }; + + $.post('error_report.php', report_data, function (data) { + if (data.success === false) { + // in the case of an error, show the error message returned. + PMA_ajaxShowMessage(data.error, false); + } else { + // Show dialog if the request was successful + $div + .append(data.message) + .dialog({ + title: PMA_messages.strSubmitErrorReport, + width: 650, + modal: true, + buttons: button_options, + close: function () { + $(this).remove(); + } + }); + } + }); // end $.get() + }, + /** + * Shows the small notification that asks for user permission + * + * @return void + */ + _showErrorNotification: function () { + ErrorReport._removeErrorNotification(); + + var $div = $( + '
' + ).append( + PMA_getImage('s_error') + PMA_messages.strErrorOccurred + ); + + var $buttons = $('
'); + + var button_html = ''; + + button_html += ''; + button_html += PMA_getImage('s_cog', PMA_messages.strChangeReportSettings); + button_html += ''; + + button_html += ''; + button_html += PMA_getImage('b_close', PMA_messages.strIgnore); + button_html += ''; + + $buttons.html(button_html); + + $div.append($buttons); + $div.appendTo(document.body); + $(document).on('click', '#change_error_settings', ErrorReport._redirect_to_settings); + $(document).on('click', '#show_error_report', ErrorReport._createReportDialog); + $(document).on('click', '#ignore_error', ErrorReport._removeErrorNotification); + }, + /** + * Removes the notification if it was displayed before + * + * @return void + */ + _removeErrorNotification: function (e) { + if (e) { + // don't remove the hash fragment by navigating to # + e.preventDefault(); + } + $('#error_notification').fadeOut(function () { + $(this).remove(); + }); + }, + /** + * Extracts Exception name from message if it exists + * + * @return String + */ + _extractExceptionName: function (exception) { + if (exception.message === null || typeof(exception.message) === 'undefined') { + return ''; + } + + var reg = /([a-zA-Z]+):/; + var regex_result = reg.exec(exception.message); + if (regex_result && regex_result.length === 2) { + return regex_result[1]; + } + + return ''; + }, + /** + * Shows the modal dialog previewing the report + * + * @return void + */ + _createReportDialog: function () { + ErrorReport._removeErrorNotification(); + ErrorReport._showReportDialog(ErrorReport._last_exception); + }, + /** + * Redirects to the settings page containing error report + * preferences + * + * @return void + */ + _redirect_to_settings: function () { + window.location.href = 'prefs_forms.php?form=Features'; + }, + /** + * Returns the report data to send to the server + * + * @param exception object exception info + * + * @return object + */ + _get_report_data: function (exception) { + var report_data = { + 'ajax_request': true, + 'exception': exception, + 'current_url': window.location.href, + 'exception_type': 'js' + }; + if (AJAX.scriptHandler._scripts.length > 0) { + report_data.scripts = AJAX.scriptHandler._scripts.map( + function (script) { + return script; + } + ); + } + return report_data; + }, + /** + * Wraps all global functions that start with PMA_ + * + * @return void + * + * This function needs to be changes as no objects are available + * in the global scope so window does not contain any function + */ + wrap_global_functions: function () { + for (var key in window) { + if (key.indexOf('PMA_') === 0) { + var global = window[key]; + if (typeof(global) === 'function') { + window[key] = ErrorReport.wrap_function(global); + } + } + } + }, + /** + * Wraps given function in error reporting code and returns wrapped function + * + * @param func function to be wrapped + * + * @return function + */ + wrap_function: function (func) { + // console.log(func.wrapped); + if (!func.wrapped) { + var new_func = function () { + try { + return func.apply(this, arguments); + } catch (x) { + // console.log(new Error(x)); + TraceKit.report(x); + } + }; + new_func.wrapped = true; + // Set guid of wrapped function same as original function, so it can be removed + // See bug#4146 (problem with jquery draggable and sortable) + new_func.guid = func.guid = func.guid || new_func.guid || jQuery.guid++; + return new_func; + } else { + // console.log(func); + return func; + } + }, + /** + * Automatically wraps the callback in AJAX.registerOnload + * + * @return void + */ + _wrap_ajax_onload_callback: function () { + var oldOnload = AJAX.registerOnload; + AJAX.registerOnload = function (file, func) { + func = ErrorReport.wrap_function(func); + oldOnload.call(this, file, func); + }; + }, + /** + * Automatically wraps the callback in $.fn.on + * + * @return void + * + * Since we are exporting the instance of jQuery from + * '/util/extend_jquery', therefore this method will work + * fine and wrap the callback of $.fn.on of jQuery instance. + */ + _wrap_$_on_callback: function () { + var oldOn = $.fn.on; + $.fn.on = function () { + for (var i = 1; i <= 3; i++) { + if (typeof(arguments[i]) === 'function') { + arguments[i] = ErrorReport.wrap_function(arguments[i]); + break; + } + } + return oldOn.apply(this, arguments); + }; + }, + /** + * Wraps all global functions that start with PMA_ + * also automatically wraps the callback in AJAX.registerOnload + * + * @return void + */ + set_up_error_reporting: function () { + ErrorReport.wrap_global_functions(); + ErrorReport._wrap_ajax_onload_callback(); + ErrorReport._wrap_$_on_callback(); + } + +}; + +export default ErrorReport; diff --git a/js/src/error_report.js b/js/src/error_report.js new file mode 100644 index 0000000000..32b84464b5 --- /dev/null +++ b/js/src/error_report.js @@ -0,0 +1,8 @@ +import ErrorReport from './classes/ErrorReport'; +import TraceKit from 'tracekit'; + +export function onload1 () { + TraceKit.report.subscribe(ErrorReport.error_handler); + ErrorReport.set_up_error_reporting(); + ErrorReport.wrap_global_functions(); +}