From fc5d030ef2695f7d2daeb87db9697240be4b99d4 Mon Sep 17 00:00:00 2001 From: Adrien Dieudonne Date: Wed, 13 Jun 2018 15:21:24 +0200 Subject: [PATCH] [FIX] web: checks rpc responses in web.ajax Before this commit, load_action and load_views were never resolved in case of internet connection loss. It's because the call is triggered by web.rpc direcly who uses ajax.rpc without any response check. Now the rpc response is checked in web.ajax. Thereby all responses are handled whether they come from session.rpc or rpc.query. Plus, it was not the responsibility of the session to check the rpc responses. Steps to reproduce the issue: (1) load_action: - Go to the app switcher for the first time - Force disconnected from the network -> No notification appears and the user aren't notify (2) load_views (mobile only): - Go to Sales -> Quotation (In this case, the first call is 'load_views' because we need to force to load some views like kanban view) - Force disconnected from the network -> No notification appears and the user aren't notify Note: It's now important to notify the user about connection loss because a recent improvement in mobile app replaces the 'retry screen' and lets the webclient handle connection issues by itself. The single odoo mobile app is supposed to work with all databases from 10.0. --- .../src/js/chrome/abstract_web_client.js | 2 +- addons/web/static/src/js/chrome/loading.js | 6 +- addons/web/static/src/js/core/ajax.js | 62 +++++++++++++++-- addons/web/static/src/js/core/session.js | 67 +++++++------------ 4 files changed, 85 insertions(+), 52 deletions(-) diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js index 7d3f19c1639..bce77a352ea 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -170,7 +170,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi core.bus.on('connection_restored', this, this._onConnectionRestored); // crash manager integration - session.on('error', crash_manager, crash_manager.rpc_error); + core.bus.on('rpc_error', crash_manager, crash_manager.rpc_error); window.onerror = function (message, file, line, col, error) { // Scripts injected in DOM (eg: google API's js files) won't return a clean error on window.onerror. // The browser will just give you a 'Script error.' as message and nothing else for security issue. diff --git a/addons/web/static/src/js/chrome/loading.js b/addons/web/static/src/js/chrome/loading.js index 0c1b5e5025f..6c7f102231b 100644 --- a/addons/web/static/src/js/chrome/loading.js +++ b/addons/web/static/src/js/chrome/loading.js @@ -26,9 +26,9 @@ var Loading = Widget.extend({ this._super(parent); this.count = 0; this.blocked_ui = false; - session.on("request", this, this.request_call); - session.on("response", this, this.response_call); - session.on("response_failed", this, this.response_call); + core.bus.on('rpc_request', this, this.request_call); + core.bus.on("rpc_response", this, this.response_call); + core.bus.on("rpc_response_failed", this, this.response_call); }, destroy: function() { this.on_rpc_event(-this.count); diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js index d6c67eacaec..fa79c65ef45 100644 --- a/addons/web/static/src/js/core/ajax.js +++ b/addons/web/static/src/js/core/ajax.js @@ -5,7 +5,15 @@ var core = require('web.core'); var utils = require('web.utils'); var time = require('web.time'); -function genericJsonRpc (fct_name, params, fct) { +function genericJsonRpc (fct_name, params, settings, fct) { + var shadow = settings.shadow || false; + delete settings.shadow; + if (!shadow) { + core.bus.trigger('rpc_request'); + } + + var deferred = $.Deferred(); + var data = { jsonrpc: "2.0", method: fct_name, @@ -29,12 +37,56 @@ function genericJsonRpc (fct_name, params, fct) { return def.reject.apply(def, ["communication"].concat(_.toArray(arguments))); }); // FIXME: jsonp? - result.abort = function () { if (xhr.abort) xhr.abort(); }; - return result; + deferred.abort = function () { + deferred.reject('communication', $.Event(), 'abort', 'abort'); + if (xhr.abort) { + xhr.abort(); + } + }; + + result.then(function (result) { + if (!shadow) { + core.bus.trigger('rpc_response'); + } + deferred.resolve(result); + }, function (type, error, textStatus, errorThrown) { + if (type === "server") { + if (!shadow) { + core.bus.trigger('rpc_response'); + } + if (error.code === 100) { + core.bus.trigger('invalidate_session'); + } + deferred.reject(error, $.Event()); + } else { + if (!shadow) { + core.bus.trigger('rpc_response_failed'); + } + var nerror = { + code: -32098, + message: "XmlHttpRequestError " + errorThrown, + data: { + type: "xhr"+textStatus, + debug: error.responseText, + objects: [error, errorThrown] + }, + }; + deferred.reject(nerror, $.Event()); + } + }); + deferred.fail(function () { // Allow deferred user to disable rpc_error call in fail + deferred.fail(function (error, event) { + if (!event.isDefaultPrevented()) { + core.bus.trigger('rpc_error', error, event); + } + }); + }); + return deferred; } function jsonRpc(url, fct_name, params, settings) { - return genericJsonRpc(fct_name, params, function(data) { + settings = settings || {}; + return genericJsonRpc(fct_name, params, settings, function(data) { return $.ajax(url, _.extend({}, settings, { url: url, dataType: 'json', @@ -47,7 +99,7 @@ function jsonRpc(url, fct_name, params, settings) { function jsonpRpc(url, fct_name, params, settings) { settings = settings || {}; - return genericJsonRpc(fct_name, params, function(data) { + return genericJsonRpc(fct_name, params, settings, function(data) { var payload_str = JSON.stringify(data, time.date_to_utc); var payload_url = $.param({r:payload_str}); var force2step = settings.force2step || false; diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js index 11423eec909..09b65357404 100644 --- a/addons/web/static/src/js/core/session.js +++ b/addons/web/static/src/js/core/session.js @@ -49,6 +49,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { this.qweb_mutex = new concurrency.Mutex(); this.currencies = {}; this._groups_def = {}; + core.bus.on('invalidate_session', this, this._onInvalidateSession); }, setup: function (origin, options) { // must be able to customize server @@ -327,31 +328,31 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { rpc: function (url, params, options) { var self = this; options = _.clone(options || {}); - var shadow = options.shadow || false; options.headers = _.extend({}, options.headers); if (odoo.debug) { options.headers["X-Debug-Mode"] = $.deparam($.param.querystring()).debug; } - delete options.shadow; - - var deferred = $.Deferred(); + var deferred = self.check_session_id(); var aborted = false; + var xhrDef; deferred.abort = function () { - aborted = true; + if (xhrDef) { + xhrDef.abort(); + } else { + aborted = true; + } }; - self.check_session_id().then(function () { + + return deferred.then(function () { if (aborted) { - return deferred.reject('communication', $.Event(), 'abort', 'abort'); + return $.Deferred().reject('communication', $.Event(), 'abort', 'abort'); } // TODO: remove if (! _.isString(url)) { _.extend(options, url); url = url.url; } - // TODO correct handling of timeouts - if (! shadow) - self.trigger('request'); var fct; if (self.origin_server) { fct = ajax.jsonRpc; @@ -370,40 +371,9 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { url = self.url(url, null); options.session_id = self.session_id || ''; } - var p = fct(url, "call", params, options); - deferred.abort = p.abort.bind(p); // Allow to abort the rpc call - p = p.then(function (result) { - if (! shadow) - self.trigger('response'); - return deferred.resolve(result); - }, function (type, error, textStatus, errorThrown) { - if (type === "server") { - if (! shadow) - self.trigger('response'); - if (error.code === 100) { - self.uid = false; - } - return deferred.reject(error, $.Event()); - } else { - if (! shadow) - self.trigger('response_failed'); - var nerror = { - code: -32098, - message: "XmlHttpRequestError " + errorThrown, - data: {type: "xhr"+textStatus, debug: error.responseText, objects: [error, errorThrown] } - }; - return deferred.reject(nerror, $.Event()); - } - }); - return deferred.fail(function () { // Allow deferred user to disable rpc_error call in fail - deferred.fail(function (error, event) { - if (!event.isDefaultPrevented()) { - self.trigger('error', error, event); - } - }); - }); + xhrDef = fct(url, "call", params, options); + return xhrDef; }); - return deferred; }, url: function (path, params) { params = _.extend(params || {}); @@ -428,6 +398,17 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { getTZOffset: function (date) { return -new Date(date).getTimezoneOffset(); }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onInvalidateSession: function () { + this.uid = false; + }, }); return Session;