diff --git a/gulpfile.js b/gulpfile.js index 5da74aaeeb..f0d105deae 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -109,7 +109,6 @@ var backgroundInclude = [ 'cachedTypes.js', 'zotero/date.js', 'zotero/debug.js', - 'errors_webkit.js', "zotero/xregexp/xregexp.js", "zotero/xregexp/addons/build.js", "zotero/xregexp/addons/matchrecursive.js", @@ -117,6 +116,7 @@ var backgroundInclude = [ "zotero/xregexp/addons/unicode/unicode-categories.js", "zotero/xregexp/addons/unicode/unicode-zotero.js", 'zotero/openurl.js', + 'reports.js', 'repo.js', 'zotero/translation/tlds.js', 'zotero/translation/translator.js', diff --git a/src/common/connector.js b/src/common/connector.js index 1f6f4b3030..525dd669ce 100644 --- a/src/common/connector.js +++ b/src/common/connector.js @@ -44,6 +44,25 @@ Zotero.Connector = { Object.assign(this.selected, data); Zotero.Connector_Browser._updateExtensionUI(); }.bind(this)}); + this.addEventListener('reports', {notify: async function(data) { + if ('errors' in data && 'get' in data.errors) { + let sysInfo = await Zotero.getSystemInfo(); + let errors = await Zotero.Errors.getErrors(); + Zotero.Connector.callMethod('reports', {report: `${sysInfo}\n\n${errors.join('\n\n')}`}); + } + else if ('debug' in data) { + if ('get' in data.debug) { + let debug = await Zotero.Debug.get(); + Zotero.Connector.callMethod('reports', {report: debug}); + } + else if ('store' in data.debug) { + Zotero.Debug.setStore(data.debug.store) + } + else if ('clear' in data.debug) { + Zotero.Debug.clear(); + } + } + }}); Zotero.Connector.SSE.init(); }, @@ -311,46 +330,3 @@ Zotero.Connector.SSE = { }; Zotero.Connector.addEventListener = Zotero.Connector.SSE._addEventListener.bind(Zotero.Connector.SSE); Zotero.Connector.removeEventListener = Zotero.Connector.SSE._removeEventListener.bind(Zotero.Connector.SSE); - - -// TODO: this does not belong here in the slightest -Zotero.Connector_Debug = new function() { - /** - * Call a callback depending upon whether debug output is being stored - */ - this.storing = function() { - return Zotero.Debug.storing; - } - - /** - * Call a callback with the lines themselves - */ - this.get = function() { - return Zotero.Debug.get(); - }; - - /** - * Call a callback with the number of lines of output - */ - this.count = function() { - return Zotero.Debug.count(); - } - - /** - * Submit data to the server - */ - this.submitReport = function() { - return Zotero.Debug.get().then(function(body){ - return Zotero.HTTP.request("POST", ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1", {body}); - }).then(function(xmlhttp) { - if (!xmlhttp.responseXML) { - throw new Error('Invalid response from server'); - } - var reported = xmlhttp.responseXML.getElementsByTagName('reported'); - if (reported.length != 1) { - throw new Error('The server returned an error. Please try again.'); - } - return reported[0].getAttribute('reportID'); - }); - }; -} diff --git a/src/common/errors_webkit.js b/src/common/errors_webkit.js deleted file mode 100644 index cd0ca1cdf5..0000000000 --- a/src/common/errors_webkit.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This file is part of Zotero. - - Zotero is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zotero is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zotero. If not, see . - - ***** END LICENSE BLOCK ***** -*/ - -Zotero.Errors = new function() { - var _output = []; - - /** - * Error handler - * @param {String} string Error string - * @param {String} url URL of error - * @param {Number} line Line where error occurred - */ - this.log = function(string, url, line) { - var err = ['[JavaScript Error: "', string, '"']; - if(url || line) { - var info = []; - if(url) info.push('file: "'+url+'"'); - if(line) info.push('line: '+line); - err.push(" {"+info.join(" ")+"}"); - } - err.push("]"); - err = err.join(""); - _output.push(err); - } - - /** - * Gets errors as an array of strings - */ - this.getErrors = Zotero.Promise.method(function() { - return _output.slice(); - }) - - /** - * Sends an error report to the server - */ - this.sendErrorReport = function() { - return Zotero.getSystemInfo().then(function(info) { - var parts = { - error: "true", - errorData: _output.join('\n'), - extraData: '', - diagnostic: info - }; - - var body = ''; - for (var key in parts) { - body += key + '=' + encodeURIComponent(parts[key]) + '&'; - } - body = body.substr(0, body.length - 1); - let options = {body, headers: {'Content-Type': 'application/x-www-form-urlencoded'}}; - return Zotero.HTTP.request("POST", "https://www.zotero.org/repo/report", options).then(function(xmlhttp) { - var reported = xmlhttp.responseXML.getElementsByTagName('reported'); - if (reported.length != 1) { - throw new Error('Invalid response from repository'); - } - return reported[0].getAttribute('reportID'); - }); - }); - } -} - -Zotero.Debug.bgInit = Zotero.Debug.init; diff --git a/src/common/messages.js b/src/common/messages.js index c724aa9f6b..37ffdf71dd 100644 --- a/src/common/messages.js +++ b/src/common/messages.js @@ -115,7 +115,11 @@ var MESSAGES = { minArgs: 4 } }, - setStore: false + setStore: false, + isStoring: true, + get: true, + count: true, + submitToZotero: true }, Connector: { checkIsOnline: true, @@ -133,16 +137,10 @@ var MESSAGES = { openConfigEditor: false, openPreferences: false }, - Connector_Debug: { - storing: true, - get: true, - count: true, - submitReport: true - }, Errors: { log: false, getErrors: true, - sendErrorReport: true + submitToZotero: true }, Messaging: { sendMessage: true diff --git a/src/common/preferences/preferences.jsx b/src/common/preferences/preferences.jsx index cb2dccf061..acd5af5de4 100644 --- a/src/common/preferences/preferences.jsx +++ b/src/common/preferences/preferences.jsx @@ -113,7 +113,7 @@ var Zotero_Preferences = { document.getElementById('advanced-textarea-errors').textContent = errors.join("\n\n"); } // get debug logging info - return Zotero.Connector_Debug.count(); + return Zotero.Debug.count(); }).then(function(count) { document.getElementById('advanced-span-lines-logged').textContent = count.toString(); toggleDisabled(document.getElementById('advanced-button-view-output'), !count); @@ -208,7 +208,7 @@ Zotero_Preferences.Advanced = { init: function() { document.getElementById("advanced-checkbox-enable-logging").onchange = - function() { Zotero.Debug.setStore(this.checked); }; + function() { Zotero.Debug.setStore(this.checked, true); }; document.getElementById("advanced-checkbox-enable-at-startup").onchange = function() { Zotero.Prefs.set('debug.store', this.checked); }; document.getElementById("advanced-checkbox-show-in-console").onchange = function() { @@ -245,7 +245,7 @@ Zotero_Preferences.Advanced = { }; // get preference values - Zotero.Connector_Debug.storing(function(status) { + Zotero.Debug.isStoring().then(function(status) { document.getElementById('advanced-checkbox-enable-logging').checked = !!status; }); Zotero.Prefs.getAsync("debug.store").then(function(status) { @@ -263,7 +263,7 @@ Zotero_Preferences.Advanced = { * Opens a new window to view debug output. */ viewDebugOutput: function() { - Zotero.Connector_Debug.get(function(log) { + Zotero.Debug.get().then(function(log) { var textarea = document.getElementById("advanced-textarea-debug"); textarea.textContent = log; textarea.style.display = ""; @@ -274,7 +274,7 @@ Zotero_Preferences.Advanced = { * Clears stored debug output. */ clearDebugOutput: function() { - Zotero.Debug.clear(); + Zotero.Debug.clear(true); Zotero_Preferences.refreshData(); var textarea = document.getElementById("advanced-textarea-debug"); textarea.style.display = 'none'; @@ -287,9 +287,9 @@ Zotero_Preferences.Advanced = { var submitOutputButton = document.getElementById('advanced-button-submit-output'); toggleDisabled(submitOutputButton, true); - return Zotero.Connector_Debug.submitReport().then(function(reportID) { + return Zotero.Debug.submitToZotero().then(function(reportID) { alert("Your debug output has been submitted.\n\n" - + `The Debug ID is D${reportID}.`); + + `The Debug ID is ${reportID}.`); }, function(e) { alert(`An error occurred submitting your debug output.\n\n${e.message}\n\n`+ 'Please check your internet connection. If the problem persists, '+ @@ -304,7 +304,7 @@ Zotero_Preferences.Advanced = { var reportErrorsButton = document.getElementById('advanced-button-report-errors'); toggleDisabled(reportErrorsButton, true); - return Zotero.Errors.sendErrorReport().then(function(reportID) { + return Zotero.Errors.submitToZotero().then(function(reportID) { alert(`Your error report has been submitted.\n\nReport ID: ${reportID}\n\n`+ 'Please post a message to the Zotero Forums (forums.zotero.org) with this Report '+ 'ID, a description of the problem, and any steps necessary to reproduce it.\n\n'+ diff --git a/src/common/reports.js b/src/common/reports.js new file mode 100644 index 0000000000..df4a1cd207 --- /dev/null +++ b/src/common/reports.js @@ -0,0 +1,138 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2017 Center for History and New Media + George Mason University, Fairfax, Virginia, USA + http://zotero.org + + This file is part of Zotero. + + Zotero is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zotero is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zotero. If not, see . + + ***** END LICENSE BLOCK ***** +*/ + +(function() { + +var setStore = Zotero.Debug.setStore; +Zotero.Debug.setStore = function(val, fetchFromClient) { + if (typeof fetchFromClient != 'boolean') fetchFromClient = false; + setStore.apply(this, arguments); + if (fetchFromClient) { + Zotero.Connector.callMethod('reports', {debug: {store: val}}); + } +}; + +var clear = Zotero.Debug.clear; +Zotero.Debug.clear = function(fetchFromClient) { + if (typeof fetchFromClient != 'boolean') fetchFromClient = false; + clear.apply(this, arguments); + if (fetchFromClient) { + Zotero.Connector.callMethod('reports', {debug: {clear: true}}); + } +}; + +// Only there to expose the pref to injected pages +Zotero.Debug.isStoring = function() { + return Zotero.Debug.storing; +}; + +/** + * Submit data to the server + */ +Zotero.Debug.submitToZotero = async function() { + return Zotero.Errors.submitToZotero(true); +}; + +Zotero.Errors = new function() { + var _output = []; + + /** + * Error handler + * @param {String} string Error string + * @param {String} url URL of error + * @param {Number} line Line where error occurred + */ + this.log = function(string, url, line) { + var err = ['Error: ', string]; + if(url || line) { + var info = []; + if(url) info.push('file: "'+url+'"'); + if(line) info.push('line: '+line); + err.push(" {"+info.join(" ")+"}"); + } + err = err.join(""); + _output.push(err); + }; + + /** + * Gets errors as an array of strings + */ + this.getErrors = async function() { + return _output.slice(); + }; + + this.generateReport = async function() { + let sysInfo = await Zotero.getSystemInfo(); + return sysInfo + "\n\n" + (await this.getErrors()).join('\n\n') + "\n\n" + } + + /** + * Sends an error report to the server + */ + this.submitToZotero = async function(debug) { + var connectorBody, zoteroBody, url; + if (debug) { + url = ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1"; + connectorBody = await Zotero.Debug.get(); + } else { + url = ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1"; + connectorBody = await this.generateReport(); + } + + // If zotero unavailable -- ignore + try { + if (debug) { + zoteroBody = await Zotero.Connector.callMethod('reports', {debug: {get: true}}); + } else { + zoteroBody = await Zotero.Connector.callMethod('reports', {errors: {get: true}}); + } + } catch (e) {} + + let date = (new Date()).toUTCString(); + let type = debug ? "Debug" : "Report"; + let body = `----------------------------- Connector ${type}: ${date} --------------------------------\n\n`; + body += connectorBody; + if (zoteroBody) { + body += `\n\n----------------------------- Zotero ${type} --------------------------------\n\n`; + body += zoteroBody; + } + let headers = {'Content-Type': 'text/plain'}; + let xmlhttp = await Zotero.HTTP.request("POST", url, {body, headers}); + + if (!xmlhttp.responseXML) { + throw new Error('Invalid response from server'); + } + var reported = xmlhttp.responseXML.getElementsByTagName('reported'); + if (reported.length != 1) { + throw new Error('The server returned an error. Please try again.'); + } + if (debug) { + return 'D' + reported[0].getAttribute('reportID'); + } + return 'D' + reported[0].getAttribute('reportID'); + } +}; + +}()); \ No newline at end of file diff --git a/src/common/test/tests/connectorTest.js b/src/common/test/tests/connectorTest.js index d4a1f5b338..7681b95509 100644 --- a/src/common/test/tests/connectorTest.js +++ b/src/common/test/tests/connectorTest.js @@ -46,18 +46,26 @@ describe('Connector', function() { })); it('responds with false when Zotero is offline', Promise.coroutine(function*() { - let status = yield background(function() { + let status = yield background(async function() { Zotero.HTTP.request.resolves({status: 0}); - return Zotero.Connector.checkIsOnline(); + let sseStatus = Zotero.Connector.SSE.available; + Zotero.Connector.SSE.available = false; + let status = await Zotero.Connector.checkIsOnline(); + Zotero.Connector.SSE.available = sseStatus; + return status }); assert.isNotOk(status); })); it('throws when Zotero responds with a non-200 status', Promise.coroutine(function* () { try { - yield background(function() { + yield background(async function() { Zotero.HTTP.request.resolves({status: 500, getResponseHeader: () => '', responseText: 'Error'}); - return Zotero.Connector.checkIsOnline(); + let sseStatus = Zotero.Connector.SSE.available; + Zotero.Connector.SSE.available = false; + let status = await Zotero.Connector.checkIsOnline(); + Zotero.Connector.SSE.available = sseStatus; + return status }); } catch (e) { return diff --git a/src/common/test/tests/preferencesTest.js b/src/common/test/tests/preferencesTest.js index 6af85e2fbd..2da17c7333 100644 --- a/src/common/test/tests/preferencesTest.js +++ b/src/common/test/tests/preferencesTest.js @@ -113,22 +113,23 @@ describe('Preferences', function() { } })); - it('submits a debug log to Zotero.org', Promise.coroutine(function * () { + it('submits a debug log to Zotero.org', async function () { var debugId = '1234567890'; var testDebugLine = 'testDebugLine'; - yield background(function(debugId) { + await background(function(debugId) { sinon.stub(Zotero.HTTP, 'request').resolves( {responseXML: {getElementsByTagName: () => [{getAttribute: () => debugId}]}} ); + sinon.stub(Zotero.Connector, 'callMethod').resolves(new Zotero.Connector.CommunicationError('stub')); }, debugId); try { - yield tab.run(function(testDebugLine) { + await tab.run(function(testDebugLine) { document.getElementById('advanced-checkbox-enable-logging').click(); Zotero.debug(testDebugLine); return Zotero_Preferences.refreshData(); }, testDebugLine); - var message = yield tab.run(function() { + var message = await tab.run(function() { var deferred = Zotero.Promise.defer(); sinon.stub(window, 'alert').callsFake(deferred.resolve); document.getElementById('advanced-checkbox-enable-logging').click(); @@ -141,13 +142,16 @@ describe('Preferences', function() { }); assert.include(message, `D${debugId}`); - var debugLogBody = yield background(function() { + var debugLogBody = await background(function() { return Zotero.HTTP.request.firstCall.args[2].body; }); assert.include(debugLogBody, testDebugLine); } finally { - yield background(() => Zotero.HTTP.request.restore()); - } - })); + await background(function() { + Zotero.HTTP.request.restore(); + Zotero.Connector.callMethod.restore(); + }); + } + }); }); }); \ No newline at end of file diff --git a/src/common/zotero.js b/src/common/zotero.js index bddc9efbad..7612f35fc2 100644 --- a/src/common/zotero.js +++ b/src/common/zotero.js @@ -157,7 +157,7 @@ var Zotero = new function() { /** * Get versions, platform, etc. */ - this.getSystemInfo = function() { + this.getSystemInfo = async function() { var info = { connector: "true", version: this.version, @@ -169,12 +169,7 @@ var Zotero = new function() { info.appName = Zotero.clientName; info.zoteroAvailable = Zotero.Connector.isOnline; - var str = ''; - for (var key in info) { - str += key + ' => ' + info[key] + ', '; - } - str = str.substr(0, str.length - 2); - return Promise.resolve(str); + return JSON.stringify(info, null, ' '); }; /**