[IMP] mail, *: adapt bus service to wowl env.
*: bus, calendar, iap_mail, im_livechat, project, web. In order to ease the PR introducing the websockets in Odoo, the bus service has to be updated to be a wowl service. This PR takes care of it. task-2053917 closes odoo/odoo#95824 Related: odoo/enterprise#29361 Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
committed by
tsm-odoo
parent
8467f7b9ff
commit
3ae11c87e8
@@ -13,11 +13,7 @@
|
||||
'bus/static/src/**/*',
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
'bus/static/src/js/longpolling_bus.js',
|
||||
'bus/static/src/js/crosstab_bus.js',
|
||||
'bus/static/src/js/services/bus_service.js',
|
||||
'bus/static/src/js/services/legacy/*.js',
|
||||
'bus/static/src/js/*.js',
|
||||
'bus/static/src/**/*.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'bus/static/tests/*.js',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
odoo.define('bus.CrossTab', function (require) {
|
||||
"use strict";
|
||||
/** @odoo-module **/
|
||||
|
||||
var Longpolling = require('bus.Longpolling');
|
||||
import { browser } from '@web/core/browser/browser';
|
||||
import session from 'web.session';
|
||||
|
||||
var session = require('web.session');
|
||||
import { Longpolling } from '@bus/longpolling_bus';
|
||||
|
||||
/**
|
||||
* CrossTab
|
||||
@@ -26,23 +26,20 @@ var session = require('web.session');
|
||||
* - become_master : when this tab became the master
|
||||
* - no_longer_master : when this tab is not longer the master (the user swith tab)
|
||||
*/
|
||||
var CrossTabBus = Longpolling.extend({
|
||||
// constants
|
||||
TAB_HEARTBEAT_PERIOD: 10000, // 10 seconds
|
||||
MASTER_TAB_HEARTBEAT_PERIOD: 1500, // 1.5 seconds
|
||||
HEARTBEAT_OUT_OF_DATE_PERIOD: 5000, // 5 seconds
|
||||
HEARTBEAT_KILL_OLD_PERIOD: 15000, // 15 seconds
|
||||
LOCAL_STORAGE_PREFIX: 'bus',
|
||||
export class CrossTab extends Longpolling {
|
||||
constructor(env, services) {
|
||||
super(env, services);
|
||||
// constants
|
||||
this.TAB_HEARTBEAT_PERIOD = 10000; // 10 seconds
|
||||
this.MASTER_TAB_HEARTBEAT_PERIOD = 1500; // 1.5 seconds
|
||||
this.HEARTBEAT_OUT_OF_DATE_PERIOD = 5000; // 5 seconds
|
||||
this.HEARTBEAT_KILL_OLD_PERIOD = 15000; // 15 seconds
|
||||
this.LOCAL_STORAGE_PREFIX = 'bus';
|
||||
|
||||
// properties
|
||||
_isMasterTab: false,
|
||||
_isRegistered: false,
|
||||
// properties
|
||||
this._isMasterTab = false;
|
||||
this._isRegistered = false;
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var now = new Date().getTime();
|
||||
// used to prefix localStorage keys
|
||||
this._sanitizedOrigin = session.origin.replace(/:\/{0,2}/g, '_');
|
||||
@@ -53,55 +50,57 @@ var CrossTabBus = Longpolling.extend({
|
||||
this._callLocalStorage('removeItem', 'last');
|
||||
}
|
||||
this._lastNotificationID = this._callLocalStorage('getItem', 'last', 0);
|
||||
this.call('local_storage', 'onStorage', this, this._onStorage);
|
||||
},
|
||||
destroy: function () {
|
||||
this._super();
|
||||
clearTimeout(this._heartbeatTimeout);
|
||||
},
|
||||
browser.addEventListener('storage', this._onStorage.bind(this));
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Share the bus channels with the others tab by the local storage
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
addChannel: function (channel) {
|
||||
addChannel(channel) {
|
||||
this._currentTabChannels.add(channel);
|
||||
this._super.apply(this, arguments);
|
||||
super.addChannel(channel);
|
||||
this._updateChannels();
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Share the bus channels with the others tab by the local storage
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
deleteChannel: function (channel) {
|
||||
deleteChannel(channel) {
|
||||
this._currentTabChannels.delete(channel);
|
||||
this._super.apply(this, arguments);
|
||||
super.deleteChannel(channel);
|
||||
this._updateChannels();
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
getTabId: function () {
|
||||
getTabId() {
|
||||
return this._id;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether this bus is related to the master tab.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isMasterTab: function () {
|
||||
isMasterTab() {
|
||||
return this._isMasterTab;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the local storage to share the long polling from the master tab.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
startPolling: function () {
|
||||
startPolling() {
|
||||
if (this._isActive === null) {
|
||||
this._heartbeat = this._heartbeat.bind(this);
|
||||
}
|
||||
@@ -130,23 +129,26 @@ var CrossTabBus = Longpolling.extend({
|
||||
}
|
||||
|
||||
if (this._isMasterTab) {
|
||||
this._super.apply(this, arguments);
|
||||
super.startPolling();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Share the option with the local storage
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
updateOption: function () {
|
||||
this._super.apply(this, arguments);
|
||||
updateOption(key, value) {
|
||||
super.updateOption(key, value);
|
||||
this._callLocalStorage('setItem', 'options', this._options);
|
||||
},
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Call local_storage service
|
||||
* Call browser localStorage.
|
||||
*
|
||||
* @private
|
||||
* @param {string} method (getItem, setItem, removeItem, on)
|
||||
@@ -154,9 +156,17 @@ var CrossTabBus = Longpolling.extend({
|
||||
* @param {any} param
|
||||
* @returns service information
|
||||
*/
|
||||
_callLocalStorage: function (method, key, param) {
|
||||
return this.call('local_storage', method, this._generateKey(key), param);
|
||||
},
|
||||
_callLocalStorage(method, key, param) {
|
||||
if (method === 'setItem') {
|
||||
param = JSON.stringify(param);
|
||||
}
|
||||
const result = browser.localStorage[method](this._generateKey(key), param);
|
||||
if (method === 'getItem') {
|
||||
return result ? JSON.parse(result) : param;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates localStorage keys prefixed by bus. (LOCAL_STORAGE_PREFIX = the name
|
||||
* of this addon), and the sanitized origin, to prevent keys from
|
||||
@@ -167,16 +177,18 @@ var CrossTabBus = Longpolling.extend({
|
||||
* @param {string} key
|
||||
* @returns key prefixed with the origin
|
||||
*/
|
||||
_generateKey: function (key) {
|
||||
_generateKey(key) {
|
||||
return this.LOCAL_STORAGE_PREFIX + '.' + this._sanitizedOrigin + '.' + key;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @returns {integer} number of milliseconds since 1 January 1970 00:00:00
|
||||
*/
|
||||
_getLastPresence: function () {
|
||||
return this._callLocalStorage('getItem', 'lastPresence') || this._super();
|
||||
},
|
||||
_getLastPresence() {
|
||||
return this._callLocalStorage('getItem', 'lastPresence') || super._getLastPresence();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all the time (according to the constants) if the tab is the master tab and
|
||||
* check if it is active. Use the local storage for this checks.
|
||||
@@ -184,7 +196,7 @@ var CrossTabBus = Longpolling.extend({
|
||||
* @private
|
||||
* @see _startElection method
|
||||
*/
|
||||
_heartbeat: function () {
|
||||
_heartbeat() {
|
||||
var now = new Date().getTime();
|
||||
var heartbeatValue = parseInt(this._callLocalStorage('getItem', 'heartbeat', 0));
|
||||
var peers = this._callLocalStorage('getItem', 'peers', {});
|
||||
@@ -229,22 +241,23 @@ var CrossTabBus = Longpolling.extend({
|
||||
if (this._lastPresenceTime + hbPeriod > now) {
|
||||
this._callLocalStorage('setItem', 'lastPresence', this._lastPresenceTime);
|
||||
}
|
||||
this._heartbeatTimeout = browser.setTimeout(this._heartbeat.bind(this), hbPeriod);
|
||||
}
|
||||
|
||||
this._heartbeatTimeout = setTimeout(this._heartbeat.bind(this), hbPeriod);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_registerWindowUnload: function () {
|
||||
$(window).on('unload.' + this._id, this._onUnload.bind(this));
|
||||
},
|
||||
_registerWindowUnload() {
|
||||
browser.addEventListener('unload', this._onUnload.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check with the local storage if the current tab is the master tab.
|
||||
* If this tab became the master, trigger 'become_master' event
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_startElection: function () {
|
||||
_startElection() {
|
||||
if (this._isMasterTab) {
|
||||
return;
|
||||
}
|
||||
@@ -275,14 +288,15 @@ var CrossTabBus = Longpolling.extend({
|
||||
delete peers[newMaster];
|
||||
this._callLocalStorage('setItem', 'peers', peers);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Update localstorage channels of with the channels of this tab.
|
||||
*
|
||||
* @private
|
||||
* @return {boolean} true if the aggregated channels has changed.
|
||||
*/
|
||||
_updateChannels: function () {
|
||||
_updateChannels() {
|
||||
const currentPeerIds = new Set(Object.keys(this._callLocalStorage('getItem', 'peers') || {}));
|
||||
const peerChannels = this._callLocalStorage('getItem', 'channels') || {};
|
||||
const peerChannelsBefore = JSON.stringify(peerChannels);
|
||||
@@ -314,31 +328,35 @@ var CrossTabBus = Longpolling.extend({
|
||||
}
|
||||
this._channels = Array.from(allChannels);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onFocusChange: function (params) {
|
||||
this._super.apply(this, arguments);
|
||||
_onFocusChange(params) {
|
||||
super._onFocusChange(params);
|
||||
this._callLocalStorage('setItem', 'focus', params.focus);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* If it's the master tab, the notifications ares broadcasted to other tabs by the
|
||||
* local storage.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_onPoll: function (notifications) {
|
||||
var notifs = this._super(notifications);
|
||||
_onPoll(notifications) {
|
||||
var notifs = super._onPoll(notifications);
|
||||
if (this._isMasterTab && notifs.length) {
|
||||
this._callLocalStorage('setItem', 'last', this._lastNotificationID);
|
||||
this._callLocalStorage('setItem', 'last_ts', new Date().getTime());
|
||||
this._callLocalStorage('setItem', 'notification', notifs);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler when the local storage is updated
|
||||
*
|
||||
@@ -347,7 +365,10 @@ var CrossTabBus = Longpolling.extend({
|
||||
* @param {string} event.key
|
||||
* @param {string} event.newValue
|
||||
*/
|
||||
_onStorage: function (e) {
|
||||
_onStorage(e) {
|
||||
if (!e.key || !e.key.includes(this.LOCAL_STORAGE_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
var value = JSON.parse(e.newValue);
|
||||
var key = e.key;
|
||||
|
||||
@@ -381,13 +402,14 @@ var CrossTabBus = Longpolling.extend({
|
||||
this._isOdooFocused = value;
|
||||
this.trigger('window_focus', this._isOdooFocused);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler when unload the window
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onUnload: function () {
|
||||
_onUnload() {
|
||||
// unload peer
|
||||
var peers = this._callLocalStorage('getItem', 'peers') || {};
|
||||
delete peers[this._id];
|
||||
@@ -399,10 +421,5 @@ var CrossTabBus = Longpolling.extend({
|
||||
if (this._isMasterTab) {
|
||||
this._callLocalStorage('removeItem', 'master');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return CrossTabBus;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from '@web/core/registry';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export const legacyBusService = {
|
||||
start() {
|
||||
return Component.env.services.bus_service;
|
||||
},
|
||||
};
|
||||
|
||||
registry.category('services').add('legacy_bus_service', legacyBusService);
|
||||
+66
-75
@@ -1,9 +1,8 @@
|
||||
odoo.define('bus.Longpolling', function (require) {
|
||||
"use strict";
|
||||
/** @odoo-module **/
|
||||
|
||||
var Bus = require('web.Bus');
|
||||
var ServicesMixin = require('web.ServicesMixin');
|
||||
import { browser } from '@web/core/browser/browser';
|
||||
|
||||
const { EventBus } = owl;
|
||||
|
||||
/**
|
||||
* Event Longpolling bus used to bind events on the server long polling return
|
||||
@@ -11,27 +10,23 @@ var ServicesMixin = require('web.ServicesMixin');
|
||||
* trigger:
|
||||
* - window_focus : when the window focus change (true for focused, false for blur)
|
||||
* - notification : when a notification is receive from the long polling
|
||||
*
|
||||
* @class Longpolling
|
||||
*/
|
||||
var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
// constants
|
||||
PARTNERS_PRESENCE_CHECK_PERIOD: 30000, // don't check presence more than once every 30s
|
||||
ERROR_RETRY_DELAY: 10000, // 10 seconds
|
||||
POLL_ROUTE: '/longpolling/poll',
|
||||
export class Longpolling extends EventBus {
|
||||
constructor(env) {
|
||||
super();
|
||||
this.env = env;
|
||||
|
||||
// properties
|
||||
_isActive: null,
|
||||
_lastNotificationID: 0,
|
||||
_isOdooFocused: true,
|
||||
_pollRetryTimeout: null,
|
||||
// CONSTANTS
|
||||
this.PARTNERS_PRESENCE_CHECK_PERIOD = 30000; // don't check presence more than once every 30s
|
||||
this.ERROR_RETRY_DELAY = 10000; // 10 seconds
|
||||
this.POLL_ROUTE = '/longpolling/poll';
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function (parent, params) {
|
||||
this._super.apply(this, arguments);
|
||||
// PROPERTIES
|
||||
this._isActive = null;
|
||||
this._id = _.uniqueId('bus');
|
||||
this._lastNotificationID = 0;
|
||||
this._isOdooFocused = true;
|
||||
this._pollRetryTimeout = null;
|
||||
|
||||
// the _id is modified by crosstab_bus, so we can't use it to unbind the events in the destroy.
|
||||
this._longPollingBusId = this._id;
|
||||
@@ -40,30 +35,19 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
|
||||
// bus presence
|
||||
this._lastPresenceTime = new Date().getTime();
|
||||
$(window).on("focus." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: true}));
|
||||
$(window).on("blur." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false}));
|
||||
$(window).on("unload." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false}));
|
||||
|
||||
$(window).on("click." + this._longPollingBusId, this._onPresence.bind(this));
|
||||
$(window).on("keydown." + this._longPollingBusId, this._onPresence.bind(this));
|
||||
$(window).on("keyup." + this._longPollingBusId, this._onPresence.bind(this));
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
destroy: function () {
|
||||
this.stopPolling();
|
||||
$(window).off("focus." + this._longPollingBusId);
|
||||
$(window).off("blur." + this._longPollingBusId);
|
||||
$(window).off("unload." + this._longPollingBusId);
|
||||
$(window).off("click." + this._longPollingBusId);
|
||||
$(window).off("keydown." + this._longPollingBusId);
|
||||
$(window).off("keyup." + this._longPollingBusId);
|
||||
this._super();
|
||||
},
|
||||
browser.addEventListener("focus", this._onFocusChange.bind(this, { focus: true }));
|
||||
browser.addEventListener("blur", this._onFocusChange.bind(this, { focus: false }));
|
||||
browser.addEventListener("unload", this._onFocusChange.bind(this, { focus: false }));
|
||||
browser.addEventListener("click", this._onPresence.bind(this));
|
||||
browser.addEventListener("keydown", this._onPresence.bind(this));
|
||||
browser.addEventListener("keyup", this._onPresence.bind(this));
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Register a new channel to listen on the longpoll (ignore if already
|
||||
* listening on this channel).
|
||||
@@ -72,12 +56,13 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
*
|
||||
* @param {string} channel
|
||||
*/
|
||||
addChannel: function (channel) {
|
||||
addChannel(channel) {
|
||||
if (this._channels.indexOf(channel) === -1) {
|
||||
this._channels.push(channel);
|
||||
this._restartPolling();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a channel from listening on the longpoll.
|
||||
*
|
||||
@@ -87,7 +72,7 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
*
|
||||
* @param {string} channel
|
||||
*/
|
||||
deleteChannel: function (channel) {
|
||||
deleteChannel(channel) {
|
||||
var index = this._channels.indexOf(channel);
|
||||
if (index !== -1) {
|
||||
this._channels.splice(index, 1);
|
||||
@@ -95,20 +80,22 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
this._pollRpc.abort();
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell whether odoo is focused or not
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isOdooFocused: function () {
|
||||
isOdooFocused() {
|
||||
return this._isOdooFocused;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a long polling, i.e. it continually opens a long poll
|
||||
* connection as long as it is not stopped (@see `stopPolling`)
|
||||
*/
|
||||
startPolling: function () {
|
||||
startPolling() {
|
||||
if (this._isActive === null) {
|
||||
this._poll = this._poll.bind(this);
|
||||
}
|
||||
@@ -116,21 +103,23 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
this._isActive = true;
|
||||
this._poll();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any started long polling
|
||||
*
|
||||
* Aborts a pending longpoll so that we immediately remove ourselves
|
||||
* from listening on notifications on this channel.
|
||||
*/
|
||||
stopPolling: function () {
|
||||
stopPolling() {
|
||||
this._isActive = false;
|
||||
this._channels = [];
|
||||
clearTimeout(this._pollRetryTimeout);
|
||||
if (this._pollRpc) {
|
||||
this._pollRpc.abort();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or update an option on the longpoll bus.
|
||||
* Stored options are sent to the server whenever a poll is started.
|
||||
@@ -138,21 +127,25 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
* @param {string} key
|
||||
* @param {any} value
|
||||
*/
|
||||
updateOption: function (key, value) {
|
||||
updateOption(key, value) {
|
||||
this._options[key] = value;
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* returns the last recorded presence
|
||||
*
|
||||
* @private
|
||||
* @returns {integer} number of milliseconds since 1 January 1970 00:00:00
|
||||
*/
|
||||
_getLastPresence: function () {
|
||||
_getLastPresence() {
|
||||
return this._lastPresenceTime;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Continually start a poll:
|
||||
*
|
||||
@@ -167,7 +160,7 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_poll: function () {
|
||||
_poll() {
|
||||
var self = this;
|
||||
if (!this._isActive) {
|
||||
return;
|
||||
@@ -183,10 +176,8 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
self._pollRpc = false;
|
||||
self._onPoll(result);
|
||||
self._poll();
|
||||
}).guardedCatch(function (result) {
|
||||
}).catch(function (result) {
|
||||
self._pollRpc = false;
|
||||
// no error popup if request is interrupted or fails for any reason
|
||||
result.event.preventDefault();
|
||||
if (result.message === "XmlHttpRequestError abort") {
|
||||
self._poll();
|
||||
} else {
|
||||
@@ -194,19 +185,20 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
self._pollRetryTimeout = setTimeout(self._poll, self.ERROR_RETRY_DELAY + (Math.floor((Math.random()*20)+1)*1000));
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param data: object with poll parameters
|
||||
*/
|
||||
_makePoll: function(data) {
|
||||
return this._rpc({route: this.POLL_ROUTE, params: data}, {shadow : true, timeout: 60000});
|
||||
},
|
||||
_makePoll(params) {
|
||||
return this.env.services.rpc(this.POLL_ROUTE, params, { silent: true, timeout: 60000 });
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Handler when the focus of the window change.
|
||||
* Trigger the 'window_focus' event.
|
||||
@@ -215,13 +207,14 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
* @param {Object} params
|
||||
* @param {Boolean} params.focus
|
||||
*/
|
||||
_onFocusChange: function (params) {
|
||||
_onFocusChange(params) {
|
||||
this._isOdooFocused = params.focus;
|
||||
if (params.focus) {
|
||||
this._lastPresenceTime = new Date().getTime();
|
||||
this.trigger('window_focus', this._isOdooFocused);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler when the long polling receive the new notifications
|
||||
* Update the last notification id received.
|
||||
@@ -231,7 +224,7 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
* @param {Object[]} notifications, Input notifications have an id, channel, message
|
||||
* @returns {Array[]} Output arrays have notification's channel and message
|
||||
*/
|
||||
_onPoll: function (notifications) {
|
||||
_onPoll(notifications) {
|
||||
var self = this;
|
||||
var notifs = _.map(notifications, function (notif) {
|
||||
if (notif.id > self._lastNotificationID) {
|
||||
@@ -241,16 +234,18 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
});
|
||||
this.trigger("notification", notifs);
|
||||
return notifs;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler when they are an activity on the window (click, keydown, keyup)
|
||||
* Update the last presence date.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onPresence: function () {
|
||||
_onPresence() {
|
||||
this._lastPresenceTime = new Date().getTime();
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart polling.
|
||||
*
|
||||
@@ -262,9 +257,5 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
|
||||
} else {
|
||||
this.startPolling();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return LongpollingBus;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -4,19 +4,16 @@ import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { session } from "@web/session";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export const assetsWatchdogService = {
|
||||
dependencies: ["notification"],
|
||||
dependencies: ["bus_service", "notification"],
|
||||
|
||||
start(env, { notification }) {
|
||||
start(env, { bus_service, notification }) {
|
||||
let isNotificationDisplayed = false;
|
||||
let bundleNotifTimerID = null;
|
||||
|
||||
env.bus.on("WEB_CLIENT_READY", null, async () => {
|
||||
const legacyEnv = Component.env;
|
||||
legacyEnv.services.bus_service.onNotification(this, onNotification);
|
||||
legacyEnv.services.bus_service.startPolling();
|
||||
bus_service.onNotification(this, onNotification);
|
||||
bus_service.startPolling();
|
||||
});
|
||||
|
||||
/**
|
||||
+30
-65
@@ -1,55 +1,17 @@
|
||||
odoo.define('bus.BusService', function (require) {
|
||||
"use strict";
|
||||
/** @odoo-module **/
|
||||
|
||||
var CrossTab = require('bus.CrossTab');
|
||||
var core = require('web.core');
|
||||
var ServicesMixin = require('web.ServicesMixin');
|
||||
const session = require('web.session');
|
||||
import { CrossTab } from '@bus/crosstab_bus';
|
||||
|
||||
var BusService = CrossTab.extend(ServicesMixin, {
|
||||
dependencies : ['local_storage'],
|
||||
import { registry } from '@web/core/registry';
|
||||
import session from 'web.session';
|
||||
|
||||
// properties
|
||||
_audio: null,
|
||||
export class BusService extends CrossTab {
|
||||
constructor(env, services) {
|
||||
super(env, services);
|
||||
|
||||
/**
|
||||
* As the BusService doesn't extend AbstractService, we have to replicate
|
||||
* here what is done in AbstractService
|
||||
*
|
||||
* @param {Object} env
|
||||
*/
|
||||
init: function (env) {
|
||||
this.env = env;
|
||||
this._super();
|
||||
},
|
||||
|
||||
/**
|
||||
* Replicate the behavior of AbstractService:
|
||||
*
|
||||
* Directly calls the requested service, instead of triggering a
|
||||
* 'call_service' event up, which wouldn't work as services have no parent.
|
||||
*
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_trigger_up: function (ev) {
|
||||
if (ev.name === 'call_service') {
|
||||
const payload = ev.data;
|
||||
let args = payload.args || [];
|
||||
if (payload.service === 'ajax' && payload.method === 'rpc') {
|
||||
// ajax service uses an extra 'target' argument for rpc
|
||||
args = args.concat(ev.target);
|
||||
}
|
||||
const service = this.env.services[payload.service];
|
||||
const result = service[payload.method].apply(service, args);
|
||||
payload.callback(result);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* This method is necessary in order for this Class to be used to instantiate services
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
start: function () {},
|
||||
// properties
|
||||
this._audio = null;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
@@ -71,10 +33,10 @@ var BusService = CrossTab.extend(ServicesMixin, {
|
||||
this._sendNativeNotification(options.title, options.message, callback);
|
||||
} catch (error) {
|
||||
// Notification without Serviceworker in Chrome Android doesn't works anymore
|
||||
// So we fallback to displayNotification() in this case
|
||||
// So we fallback to the notification service in this case
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=481856
|
||||
if (error.message.indexOf('ServiceWorkerRegistration') > -1) {
|
||||
this.displayNotification(options);
|
||||
this.env.services['notification'].add(options.message, options);
|
||||
this._beep();
|
||||
} else {
|
||||
throw error;
|
||||
@@ -82,21 +44,22 @@ var BusService = CrossTab.extend(ServicesMixin, {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.displayNotification(options);
|
||||
this.env.services['notification'].add(options.message, options);
|
||||
if (this.isMasterTab()) {
|
||||
this._beep();
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Register listeners on notifications received on this bus service
|
||||
*
|
||||
* @param {Object} receiver
|
||||
* @param {function} func
|
||||
*/
|
||||
onNotification: function () {
|
||||
onNotification() {
|
||||
this.on.apply(this, ["notification"].concat(Array.prototype.slice.call(arguments)));
|
||||
},
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -107,7 +70,7 @@ var BusService = CrossTab.extend(ServicesMixin, {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_beep: function () {
|
||||
_beep() {
|
||||
if (typeof(Audio) !== "undefined") {
|
||||
if (!this._audio) {
|
||||
this._audio = new Audio();
|
||||
@@ -116,7 +79,8 @@ var BusService = CrossTab.extend(ServicesMixin, {
|
||||
}
|
||||
Promise.resolve(this._audio.play()).catch(_.noop);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a browser notification
|
||||
*
|
||||
@@ -125,7 +89,7 @@ var BusService = CrossTab.extend(ServicesMixin, {
|
||||
* @param {string} content
|
||||
* @param {function} [callback] if given callback will be called when user clicks on notification
|
||||
*/
|
||||
_sendNativeNotification: function (title, content, callback) {
|
||||
_sendNativeNotification(title, content, callback) {
|
||||
var notification = new Notification(
|
||||
// The native Notification API works with plain text and not HTML
|
||||
// unescaping is safe because done only at the **last** step
|
||||
@@ -145,12 +109,13 @@ var BusService = CrossTab.extend(ServicesMixin, {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const busService = {
|
||||
dependencies: ['notification'],
|
||||
start(env, services) {
|
||||
return new BusService(env, services);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
core.serviceRegistry.add('bus_service', BusService);
|
||||
|
||||
return BusService;
|
||||
|
||||
});
|
||||
};
|
||||
registry.category('services').add('bus_service', busService);
|
||||
@@ -0,0 +1,14 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from '@web/core/registry';
|
||||
|
||||
export function makeBusServiceToLegacyEnv(legacyEnv) {
|
||||
return {
|
||||
dependencies: ['bus_service'],
|
||||
start(_, { bus_service }) {
|
||||
legacyEnv.services['bus_service'] = bus_service;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
registry.category('wowlToLegacyServiceMappers').add('bus_service_to_legacy_env', makeBusServiceToLegacyEnv);
|
||||
@@ -1,33 +1,24 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import * as legacyRegistry from "web.Registry";
|
||||
import * as BusService from "bus.BusService";
|
||||
import * as RamStorage from "web.RamStorage";
|
||||
import * as AbstractStorageService from "web.AbstractStorageService";
|
||||
import { busService } from "@bus/services/bus_service";
|
||||
|
||||
import { createWebClient } from "@web/../tests/webclient/helpers";
|
||||
import { assetsWatchdogService } from "@bus/js/services/assets_watchdog_service";
|
||||
import { assetsWatchdogService } from "@bus/services/assets_watchdog_service";
|
||||
import { click, getFixture, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const LocalStorageService = AbstractStorageService.extend({
|
||||
storage: new RamStorage(),
|
||||
});
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
QUnit.module("Bus Assets WatchDog", (hooks) => {
|
||||
let legacyServicesRegistry;
|
||||
let target;
|
||||
hooks.beforeEach((assert) => {
|
||||
legacyServicesRegistry = new legacyRegistry();
|
||||
legacyServicesRegistry.add("bus_service", BusService);
|
||||
legacyServicesRegistry.add("local_storage", LocalStorageService);
|
||||
|
||||
serviceRegistry.add("assetsWatchdog", assetsWatchdogService);
|
||||
serviceRegistry.add("bus_service", busService);
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
clearTimeout: () => {},
|
||||
setTimeout(fn) {
|
||||
return this._super(fn, 0);
|
||||
},
|
||||
location: {
|
||||
reload: () => assert.step("reloadPage"),
|
||||
},
|
||||
@@ -56,7 +47,6 @@ QUnit.module("Bus Assets WatchDog", (hooks) => {
|
||||
};
|
||||
|
||||
await createWebClient({
|
||||
legacyParams: { serviceRegistry: legacyServicesRegistry },
|
||||
mockRPC,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,45 +1,35 @@
|
||||
odoo.define('web.bus_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var BusService = require('bus.BusService');
|
||||
var CrossTabBus = require('bus.CrossTab');
|
||||
var AbstractStorageService = require('web.AbstractStorageService');
|
||||
var RamStorage = require('web.RamStorage');
|
||||
var { busService } = require('@bus/services/bus_service');
|
||||
var { CrossTab } = require('@bus/crosstab_bus');
|
||||
var testUtils = require('web.test_utils');
|
||||
var Widget = require('web.Widget');
|
||||
const LegacyRegistry = require("web.Registry");
|
||||
const { browser } = require("@web/core/browser/browser");
|
||||
const { ConnectionLostError } = require("@web/core/network/rpc_service");
|
||||
const { registry } = require("@web/core/registry");
|
||||
const { patchWithCleanup, nextTick } = require("@web/../tests/helpers/utils");
|
||||
const { createWebClient } = require('@web/../tests/webclient/helpers');
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
var LocalStorageServiceMock;
|
||||
|
||||
BusService = BusService.extend({
|
||||
TAB_HEARTBEAT_PERIOD: 10,
|
||||
MASTER_TAB_HEARTBEAT_PERIOD: 1,
|
||||
});
|
||||
|
||||
const { makeTestEnv } = require('@web/../tests/helpers/mock_env');
|
||||
|
||||
QUnit.module('Bus', {
|
||||
beforeEach: function () {
|
||||
LocalStorageServiceMock = AbstractStorageService.extend({storage: new RamStorage()});
|
||||
const customBusService = {
|
||||
...busService,
|
||||
start() {
|
||||
const originalBusService = busService.start(...arguments);
|
||||
originalBusService.TAB_HEARTBEAT_PERIOD = 10;
|
||||
originalBusService.MASTER_TAB_HEARTBEAT_PERIOD = 1;
|
||||
return originalBusService;
|
||||
},
|
||||
};
|
||||
registry.category('services').add('bus_service', customBusService);
|
||||
},
|
||||
}, function () {
|
||||
QUnit.test('notifications received from the longpolling channel', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
var pollPromise = testUtils.makeTestPromise();
|
||||
|
||||
var parent = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
data: {},
|
||||
services: {
|
||||
bus_service: BusService,
|
||||
local_storage: LocalStorageServiceMock,
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
const env = await makeTestEnv({
|
||||
mockRPC(route, args) {
|
||||
if (route === '/longpolling/poll') {
|
||||
assert.step(route + ' - ' + args.channels.join(','));
|
||||
|
||||
@@ -49,17 +39,12 @@ QUnit.module('Bus', {
|
||||
}).bind(pollPromise);
|
||||
return pollPromise;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var widget = new Widget(parent);
|
||||
await widget.appendTo($('#qunit-fixture'));
|
||||
|
||||
widget.call('bus_service', 'onNotification', this, function (notifications) {
|
||||
env.services['bus_service'].onNotification(null, notifications => {
|
||||
assert.step('notification - ' + notifications.toString());
|
||||
});
|
||||
widget.call('bus_service', 'addChannel', 'lambda');
|
||||
env.services['bus_service'].addChannel('lambda');
|
||||
|
||||
pollPromise.resolve([{
|
||||
message: 'beta',
|
||||
@@ -78,15 +63,10 @@ QUnit.module('Bus', {
|
||||
'notification - epsilon',
|
||||
'/longpolling/poll - lambda',
|
||||
]);
|
||||
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('longpolling restarts when connection is lost', async function (assert) {
|
||||
assert.expect(4);
|
||||
const legacyRegistry = new LegacyRegistry();
|
||||
legacyRegistry.add("bus_service", BusService);
|
||||
legacyRegistry.add("local_storage", LocalStorageServiceMock);
|
||||
|
||||
const oldSetTimeout = window.setTimeout;
|
||||
patchWithCleanup(
|
||||
@@ -99,23 +79,21 @@ QUnit.module('Bus', {
|
||||
|
||||
let busService;
|
||||
let rpcCount = 0;
|
||||
// Using createWebclient to get the compatibility layer between the old services and the new
|
||||
await createWebClient({
|
||||
const env = await makeTestEnv({
|
||||
mockRPC(route) {
|
||||
if (route === '/longpolling/poll') {
|
||||
rpcCount++;
|
||||
assert.step(`polling ${rpcCount}`);
|
||||
if (rpcCount == 1) {
|
||||
return Promise.reject(new ConnectionLostError());
|
||||
return new ConnectionLostError();
|
||||
}
|
||||
assert.equal(rpcCount, 2, "Should not be called after stopPolling");
|
||||
busService.stopPolling();
|
||||
return Promise.reject(new ConnectionLostError());
|
||||
return new ConnectionLostError();
|
||||
}
|
||||
},
|
||||
legacyParams: { serviceRegistry: legacyRegistry },
|
||||
});
|
||||
busService = Component.env.services.bus_service;
|
||||
busService = env.services['bus_service'];
|
||||
busService.startPolling();
|
||||
// Give longpolling bus a tick to try to restart polling
|
||||
await nextTick();
|
||||
@@ -136,48 +114,32 @@ QUnit.module('Bus', {
|
||||
assert.expect(3);
|
||||
|
||||
// Simulate no ID of last notification in the local storage
|
||||
testUtils.mock.patch(LocalStorageServiceMock, {
|
||||
getItem: function (key) {
|
||||
patchWithCleanup(browser, {
|
||||
getItem(key) {
|
||||
if (key === 'last_ts') {
|
||||
return 0;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
var pollPromise = testUtils.makeTestPromise();
|
||||
var parent = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parent, {
|
||||
data: {},
|
||||
services: {
|
||||
bus_service: BusService,
|
||||
local_storage: LocalStorageServiceMock,
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
const env = await makeTestEnv({
|
||||
mockRPC(route, args) {
|
||||
if (route === '/longpolling/poll') {
|
||||
assert.step(route);
|
||||
assert.strictEqual(args.last, 0,
|
||||
"provided last notification ID should be 0");
|
||||
|
||||
pollPromise = testUtils.makeTestPromise();
|
||||
pollPromise.abort = (function () {
|
||||
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
|
||||
}).bind(pollPromise);
|
||||
return pollPromise;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
var widget = new Widget(parent);
|
||||
await widget.appendTo($('#qunit-fixture'));
|
||||
|
||||
// trigger longpolling poll RPC
|
||||
widget.call('bus_service', 'addChannel', 'lambda');
|
||||
env.services['bus_service'].addChannel('lambda');
|
||||
assert.verifySteps(['/longpolling/poll']);
|
||||
|
||||
testUtils.mock.unpatch(LocalStorageServiceMock);
|
||||
parent.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('cross tab bus share message from a channel', async function (assert) {
|
||||
@@ -186,60 +148,33 @@ QUnit.module('Bus', {
|
||||
// master
|
||||
|
||||
var pollPromiseMaster = testUtils.makeTestPromise();
|
||||
|
||||
var parentMaster = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parentMaster, {
|
||||
data: {},
|
||||
services: {
|
||||
bus_service: BusService,
|
||||
local_storage: LocalStorageServiceMock,
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
const masterEnv = await makeTestEnv({
|
||||
mockRPC(route, args) {
|
||||
if (route === '/longpolling/poll') {
|
||||
assert.step('master' + ' - ' + route + ' - ' + args.channels.join(','));
|
||||
|
||||
pollPromiseMaster = testUtils.makeTestPromise();
|
||||
pollPromiseMaster.abort = (function () {
|
||||
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
|
||||
}).bind(pollPromiseMaster);
|
||||
return pollPromiseMaster;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
var master = new Widget(parentMaster);
|
||||
await master.appendTo($('#qunit-fixture'));
|
||||
|
||||
master.call('bus_service', 'onNotification', master, function (notifications) {
|
||||
masterEnv.services['bus_service'].onNotification(null, notifications => {
|
||||
assert.step('master - notification - ' + notifications.toString());
|
||||
});
|
||||
master.call('bus_service', 'addChannel', 'lambda');
|
||||
masterEnv.services['bus_service'].addChannel('lambda');
|
||||
|
||||
// slave
|
||||
await testUtils.nextTick();
|
||||
var parentSlave = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parentSlave, {
|
||||
data: {},
|
||||
services: {
|
||||
bus_service: BusService,
|
||||
local_storage: LocalStorageServiceMock,
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
const slaveEnv = await makeTestEnv({
|
||||
mockRPC(route, args) {
|
||||
if (route === '/longpolling/poll') {
|
||||
throw new Error("Can not use the longpolling of the slave client");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
var slave = new Widget(parentSlave);
|
||||
await slave.appendTo($('#qunit-fixture'));
|
||||
|
||||
slave.call('bus_service', 'onNotification', slave, function (notifications) {
|
||||
slaveEnv.services['bus_service'].onNotification(null, notifications => {
|
||||
assert.step('slave - notification - ' + notifications.toString());
|
||||
});
|
||||
slave.call('bus_service', 'addChannel', 'lambda');
|
||||
slaveEnv.services['bus_service'].addChannel('lambda');
|
||||
|
||||
pollPromiseMaster.resolve([{
|
||||
id: 1,
|
||||
@@ -254,9 +189,6 @@ QUnit.module('Bus', {
|
||||
'slave - notification - beta',
|
||||
'master - /longpolling/poll - lambda',
|
||||
]);
|
||||
|
||||
parentMaster.destroy();
|
||||
parentSlave.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('cross tab bus elect new master on master unload', async function (assert) {
|
||||
@@ -265,67 +197,38 @@ QUnit.module('Bus', {
|
||||
// master
|
||||
var pollPromiseMaster = testUtils.makeTestPromise();
|
||||
|
||||
var parentMaster = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parentMaster, {
|
||||
data: {},
|
||||
services: {
|
||||
bus_service: BusService,
|
||||
local_storage: LocalStorageServiceMock,
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
const masterEnv = await makeTestEnv({
|
||||
mockRPC(route, args) {
|
||||
if (route === '/longpolling/poll') {
|
||||
assert.step('master - ' + route + ' - ' + args.channels.join(','));
|
||||
|
||||
pollPromiseMaster = testUtils.makeTestPromise();
|
||||
pollPromiseMaster.abort = (function () {
|
||||
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
|
||||
}).bind(pollPromiseMaster);
|
||||
return pollPromiseMaster;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
var master = new Widget(parentMaster);
|
||||
await master.appendTo($('#qunit-fixture'));
|
||||
|
||||
master.call('bus_service', 'onNotification', master, function (notifications) {
|
||||
masterEnv.services['bus_service'].onNotification(null, notifications => {
|
||||
assert.step('master - notification - ' + notifications.toString());
|
||||
});
|
||||
master.call('bus_service', 'addChannel', 'lambda');
|
||||
masterEnv.services['bus_service'].addChannel('lambda');
|
||||
|
||||
// slave
|
||||
await testUtils.nextTick();
|
||||
var parentSlave = new Widget();
|
||||
var pollPromiseSlave = testUtils.makeTestPromise();
|
||||
await testUtils.mock.addMockEnvironment(parentSlave, {
|
||||
data: {},
|
||||
services: {
|
||||
bus_service: BusService,
|
||||
local_storage: LocalStorageServiceMock,
|
||||
},
|
||||
mockRPC: function (route, args) {
|
||||
const slaveEnv = await makeTestEnv({
|
||||
mockRPC(route, args) {
|
||||
if (route === '/longpolling/poll') {
|
||||
assert.step('slave - ' + route + ' - ' + args.channels.join(','));
|
||||
|
||||
pollPromiseSlave = testUtils.makeTestPromise();
|
||||
pollPromiseSlave.abort = (function () {
|
||||
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
|
||||
}).bind(pollPromiseSlave);
|
||||
return pollPromiseSlave;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
var slave = new Widget(parentSlave);
|
||||
await slave.appendTo($('#qunit-fixture'));
|
||||
|
||||
slave.call('bus_service', 'onNotification', slave, function (notifications) {
|
||||
slaveEnv.services['bus_service'].onNotification(null, notifications => {
|
||||
assert.step('slave - notification - ' + notifications.toString());
|
||||
});
|
||||
slave.call('bus_service', 'addChannel', 'lambda');
|
||||
|
||||
slaveEnv.services['bus_service'].addChannel('lambda');
|
||||
pollPromiseMaster.resolve([{
|
||||
id: 1,
|
||||
channel: 'lambda',
|
||||
@@ -334,7 +237,7 @@ QUnit.module('Bus', {
|
||||
await testUtils.nextTick();
|
||||
|
||||
// simulate unloading master
|
||||
master.call('bus_service', '_onUnload');
|
||||
masterEnv.services['bus_service']._onUnload();
|
||||
|
||||
pollPromiseSlave.resolve([{
|
||||
id: 2,
|
||||
@@ -352,73 +255,46 @@ QUnit.module('Bus', {
|
||||
'slave - notification - gamma',
|
||||
'slave - /longpolling/poll - lambda',
|
||||
]);
|
||||
|
||||
parentMaster.destroy();
|
||||
parentSlave.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('two tabs calling addChannel simultaneously', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
let id = 1;
|
||||
testUtils.mock.patch(CrossTabBus, {
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.__tabId__ = id++;
|
||||
},
|
||||
addChannel: function (channel) {
|
||||
patchWithCleanup(CrossTab.prototype, {
|
||||
addChannel(channel) {
|
||||
assert.step('Tab ' + this.__tabId__ + ': addChannel ' + channel);
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
deleteChannel: function (channel) {
|
||||
deleteChannel(channel) {
|
||||
assert.step('Tab ' + this.__tabId__ + ': deleteChannel ' + channel);
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
let pollPromise;
|
||||
const parentTab1 = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parentTab1, {
|
||||
data: {},
|
||||
services: {
|
||||
local_storage: LocalStorageServiceMock,
|
||||
},
|
||||
mockRPC: function (route) {
|
||||
const firstTabEnv = await makeTestEnv({
|
||||
mockRPC(route) {
|
||||
if (route === '/longpolling/poll') {
|
||||
pollPromise = testUtils.makeTestPromise();
|
||||
pollPromise.abort = (function () {
|
||||
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
|
||||
}).bind(pollPromise);
|
||||
return pollPromise;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
const parentTab2 = new Widget();
|
||||
await testUtils.mock.addMockEnvironment(parentTab2, {
|
||||
data: {},
|
||||
services: {
|
||||
local_storage: LocalStorageServiceMock,
|
||||
},
|
||||
mockRPC: function (route) {
|
||||
if (route === '/longpolling/poll') {
|
||||
pollPromise = testUtils.makeTestPromise();
|
||||
pollPromise.abort = (function () {
|
||||
this.reject({message: "XmlHttpRequestError abort"}, $.Event());
|
||||
}).bind(pollPromise);
|
||||
return pollPromise;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
const tab1 = new CrossTabBus(parentTab1);
|
||||
const tab2 = new CrossTabBus(parentTab2);
|
||||
|
||||
tab1.addChannel("alpha");
|
||||
tab2.addChannel("alpha");
|
||||
tab1.addChannel("beta");
|
||||
tab2.addChannel("beta");
|
||||
const secondTabEnv = await makeTestEnv({
|
||||
mockRPC(route) {
|
||||
if (route === '/longpolling/poll') {
|
||||
pollPromise = testUtils.makeTestPromise();
|
||||
return pollPromise;
|
||||
}
|
||||
}
|
||||
});
|
||||
firstTabEnv.services['bus_service'].__tabId__ = 1;
|
||||
secondTabEnv.services['bus_service'].__tabId__ = 2;
|
||||
firstTabEnv.services['bus_service'].addChannel('alpha');
|
||||
secondTabEnv.services['bus_service'].addChannel('alpha');
|
||||
firstTabEnv.services['bus_service'].addChannel('beta');
|
||||
secondTabEnv.services['bus_service'].addChannel('beta');
|
||||
|
||||
assert.verifySteps([
|
||||
"Tab 1: addChannel alpha",
|
||||
@@ -426,10 +302,6 @@ QUnit.module('Bus', {
|
||||
"Tab 1: addChannel beta",
|
||||
"Tab 2: addChannel beta",
|
||||
]);
|
||||
|
||||
testUtils.mock.unpatch(CrossTabBus);
|
||||
parentTab1.destroy();
|
||||
parentTab2.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,26 +4,23 @@ import { browser } from "@web/core/browser/browser";
|
||||
import { ConnectionLostError } from "@web/core/network/rpc_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export const calendarNotificationService = {
|
||||
dependencies: ["action", "notification", "rpc"],
|
||||
dependencies: ["action", "bus_service", "notification", "rpc"],
|
||||
|
||||
start(env, { action, notification, rpc }) {
|
||||
start(env, { action, bus_service, notification, rpc }) {
|
||||
let calendarNotifTimeouts = {};
|
||||
let nextCalendarNotifTimeout = null;
|
||||
const displayedNotifications = new Set();
|
||||
|
||||
env.bus.on("WEB_CLIENT_READY", null, async () => {
|
||||
const legacyEnv = Component.env;
|
||||
legacyEnv.services.bus_service.onNotification(this, (notifications) => {
|
||||
bus_service.onNotification(this, (notifications) => {
|
||||
for (const { payload, type } of notifications) {
|
||||
if (type === "calendar.alarm") {
|
||||
displayCalendarNotification(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
legacyEnv.services.bus_service.startPolling();
|
||||
bus_service.startPolling();
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import * as legacyRegistry from "web.Registry";
|
||||
import * as BusService from "bus.BusService";
|
||||
import * as RamStorage from "web.RamStorage";
|
||||
import * as AbstractStorageService from "web.AbstractStorageService";
|
||||
import { busService } from "@bus/services/bus_service";
|
||||
|
||||
import { createWebClient } from "@web/../tests/webclient/helpers";
|
||||
import { calendarNotificationService } from "@calendar/js/services/calendar_notification_service";
|
||||
@@ -11,9 +8,6 @@ import { click, getFixture, nextTick, patchWithCleanup } from "@web/../tests/hel
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const LocalStorageService = AbstractStorageService.extend({
|
||||
storage: new RamStorage(),
|
||||
});
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
QUnit.module("Calendar Notification", (hooks) => {
|
||||
@@ -22,15 +16,12 @@ QUnit.module("Calendar Notification", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
|
||||
legacyServicesRegistry = new legacyRegistry();
|
||||
legacyServicesRegistry.add("bus_service", BusService);
|
||||
legacyServicesRegistry.add("local_storage", LocalStorageService);
|
||||
|
||||
serviceRegistry.add("calendarNotification", calendarNotificationService);
|
||||
|
||||
serviceRegistry.add("bus_service", busService);
|
||||
patchWithCleanup(browser, {
|
||||
setTimeout: (fn) => fn(),
|
||||
clearTimeout: () => {},
|
||||
setTimeout(fn) {
|
||||
this._super(fn, 0);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,12 +4,11 @@ import { Markup } from 'web.utils';
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export const iapNotificationService = {
|
||||
dependencies: ["notification"],
|
||||
dependencies: ["bus_service", "notification"],
|
||||
|
||||
start(env, { notification }) {
|
||||
start(env, { bus_service, notification }) {
|
||||
env.bus.on("WEB_CLIENT_READY", null, async () => {
|
||||
const legacyEnv = owl.Component.env;
|
||||
legacyEnv.services.bus_service.onNotification(this, (notifications) => {
|
||||
bus_service.onNotification(this, (notifications) => {
|
||||
for (const { payload, type } of notifications) {
|
||||
if (type === 'iap_notification') {
|
||||
if (payload.error_type == 'success') {
|
||||
@@ -20,7 +19,7 @@ export const iapNotificationService = {
|
||||
}
|
||||
}
|
||||
});
|
||||
legacyEnv.services.bus_service.startPolling();
|
||||
bus_service.startPolling();
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -182,9 +182,9 @@ Help your customers with this chat, and analyse their feedback.
|
||||
'web/static/src/legacy/js/services/ajax_service.js',
|
||||
'web/static/src/legacy/js/services/local_storage_service.js',
|
||||
# Bus, Mail, Livechat
|
||||
'bus/static/src/js/longpolling_bus.js',
|
||||
'bus/static/src/js/crosstab_bus.js',
|
||||
'bus/static/src/js/services/bus_service.js',
|
||||
'bus/static/src/longpolling_bus.js',
|
||||
'bus/static/src/crosstab_bus.js',
|
||||
'bus/static/src/services/bus_service.js',
|
||||
'mail/static/src/js/utils.js',
|
||||
'im_livechat/static/src/legacy/public_livechat_history_tracking.js',
|
||||
'im_livechat/static/src/legacy/public_livechat_chatbot.js',
|
||||
@@ -207,9 +207,9 @@ Help your customers with this chat, and analyse their feedback.
|
||||
('include', 'mail.assets_messaging'),
|
||||
'mail/static/src/services/messaging_service.js',
|
||||
# Framework JS
|
||||
'bus/static/src/js/*.js',
|
||||
'bus/static/src/js/services/bus_service.js',
|
||||
'bus/static/src/js/services/legacy/legacy_bus_service.js',
|
||||
'bus/static/src/*.js',
|
||||
'bus/static/src/services/bus_service.js',
|
||||
'bus/static/src/services/legacy/make_bus_service_to_legacy_env.js',
|
||||
'web/static/lib/luxon/luxon.js',
|
||||
'web/static/src/core/**/*',
|
||||
# FIXME: debug menu currently depends on webclient, once it doesn't we don't need to remove the contents of the debug folder
|
||||
|
||||
@@ -22,5 +22,6 @@ const messagingValuesService = {
|
||||
const serviceRegistry = registry.category('services');
|
||||
serviceRegistry.add('messaging', messagingService);
|
||||
serviceRegistry.add('messagingValues', messagingValuesService);
|
||||
serviceRegistry.add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv(owl.Component.env));
|
||||
serviceRegistry.add('public_livechat_service', publicLivechatService);
|
||||
|
||||
registry.category('wowlToLegacyServiceMappers').add('make_messaging_to_legacy_env', makeMessagingToLegacyEnv);
|
||||
|
||||
@@ -95,9 +95,9 @@
|
||||
'mail/static/src/services/*.js',
|
||||
'mail/static/src/utils/*.js',
|
||||
# Framework JS
|
||||
'bus/static/src/js/*.js',
|
||||
'bus/static/src/js/services/bus_service.js',
|
||||
'bus/static/src/js/services/legacy/legacy_bus_service.js',
|
||||
'bus/static/src/*.js',
|
||||
'bus/static/src/services/bus_service.js',
|
||||
'bus/static/src/services/legacy/make_bus_service_to_legacy_env.js',
|
||||
'web/static/lib/luxon/luxon.js',
|
||||
'web/static/src/core/**/*',
|
||||
# FIXME: debug menu currently depends on webclient, once it doesn't we don't need to remove the contents of the debug folder
|
||||
|
||||
@@ -31,7 +31,7 @@ export class NotificationRequest extends Component {
|
||||
_handleResponseNotificationPermission(value) {
|
||||
this.messaging.refreshIsNotificationPermissionDefault();
|
||||
if (value !== 'granted') {
|
||||
this.env.services['legacy_bus_service'].sendNotification({
|
||||
this.env.services['bus_service'].sendNotification({
|
||||
message: this.env._t("Odoo will not have the permission to send native notifications on this device."),
|
||||
title: this.env._t("Permission denied"),
|
||||
});
|
||||
|
||||
@@ -18,8 +18,8 @@ registerModel({
|
||||
odoo.__DEBUG__.messaging = this;
|
||||
},
|
||||
_willDelete() {
|
||||
if (this.env.services['legacy_bus_service']) {
|
||||
this.env.services['legacy_bus_service'].off('window_focus', null, this._handleGlobalWindowFocus);
|
||||
if (this.env.services['bus_service']) {
|
||||
this.env.services['bus_service'].off('window_focus', null, this._handleGlobalWindowFocus);
|
||||
}
|
||||
delete odoo.__DEBUG__.messaging;
|
||||
},
|
||||
@@ -29,7 +29,7 @@ registerModel({
|
||||
* Starts messaging and related records.
|
||||
*/
|
||||
async start() {
|
||||
this.env.services['legacy_bus_service'].on('window_focus', null, this._handleGlobalWindowFocus);
|
||||
this.env.services['bus_service'].on('window_focus', null, this._handleGlobalWindowFocus);
|
||||
await this.initializer.start();
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
|
||||
@@ -16,9 +16,9 @@ registerModel({
|
||||
identifyingFields: ['messaging'],
|
||||
lifecycleHooks: {
|
||||
_willDelete() {
|
||||
if (this.env.services['legacy_bus_service']) {
|
||||
this.env.services['legacy_bus_service'].off('notification');
|
||||
this.env.services['legacy_bus_service'].stopPolling();
|
||||
if (this.env.services['bus_service']) {
|
||||
this.env.services['bus_service'].off('notification');
|
||||
this.env.services['bus_service'].stopPolling();
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -28,8 +28,8 @@ registerModel({
|
||||
* the current users. This includes pinned channels for instance.
|
||||
*/
|
||||
start() {
|
||||
this.env.services.legacy_bus_service.onNotification(null, notifs => this._handleNotifications(notifs));
|
||||
this.env.services.legacy_bus_service.startPolling();
|
||||
this.env.services['bus_service'].onNotification(null, notifs => this._handleNotifications(notifs));
|
||||
this.env.services['bus_service'].startPolling();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -269,7 +269,7 @@ registerModel({
|
||||
channel.correspondent === this.messaging.partnerRoot
|
||||
);
|
||||
if (!isChatWithOdooBot) {
|
||||
const isOdooFocused = this.env.services['legacy_bus_service'].isOdooFocused();
|
||||
const isOdooFocused = this.env.services['bus_service'].isOdooFocused();
|
||||
// Notify if out of focus
|
||||
if (!isOdooFocused && channel.isChatChannel) {
|
||||
this._notifyNewChannelMessageWhileOutOfFocus({
|
||||
@@ -620,7 +620,7 @@ registerModel({
|
||||
// then open a chat for the current user with the new user.
|
||||
const message = sprintf(this.env._t('%s connected'), username);
|
||||
const title = this.env._t("This is their first connection. Wish them luck.");
|
||||
this.env.services['legacy_bus_service'].sendNotification({ message, title, type: 'info' });
|
||||
this.env.services['bus_service'].sendNotification({ message, title, type: 'info' });
|
||||
const chat = await this.messaging.getChat({ partnerId });
|
||||
if (!this.exists() || !chat || this.messaging.device.isSmall) {
|
||||
return;
|
||||
@@ -662,7 +662,7 @@ registerModel({
|
||||
const notificationContent = escape(
|
||||
htmlToTextContentInline(message.body).substr(0, PREVIEW_MSG_MAX_SIZE)
|
||||
);
|
||||
this.env.services['legacy_bus_service'].sendNotification({
|
||||
this.env.services['bus_service'].sendNotification({
|
||||
message: notificationContent,
|
||||
title: notificationTitle,
|
||||
type: 'info',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { data } from 'mail.discuss_public_channel_template';
|
||||
import { makeMessagingToLegacyEnv } from '@mail/utils/make_messaging_to_legacy_env';
|
||||
import { messagingToLegacyEnv } from '@mail/utils/make_messaging_to_legacy_env';
|
||||
|
||||
import { DialogManagerContainer } from '@mail/components/dialog_manager_container/dialog_manager_container';
|
||||
import { DiscussPublicViewContainer } from '@mail/components/discuss_public_view_container/discuss_public_view_container';
|
||||
@@ -46,11 +46,12 @@ Component.env = legacyEnv;
|
||||
serviceRegistry.add('legacy_notification', makeLegacyNotificationService(Component.env));
|
||||
serviceRegistry.add('legacy_crash_manager', makeLegacyCrashManagerService(Component.env));
|
||||
serviceRegistry.add('legacy_dialog_mapping', makeLegacyDialogMappingService(Component.env));
|
||||
serviceRegistry.add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv(Component.env));
|
||||
|
||||
serviceRegistry.add('messaging', messagingService);
|
||||
serviceRegistry.add('messagingValues', messagingValuesService);
|
||||
|
||||
registry.category('wowlToLegacyServiceMappers').add('make_messaging_to_legacy_env', messagingToLegacyEnv);
|
||||
|
||||
const mainComponentsRegistry = registry.category('main_components');
|
||||
mainComponentsRegistry.add('DiscussPublicViewContainer', { Component: DiscussPublicViewContainer });
|
||||
// needed by the attachment viewer
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ModelManager } from '@mail/model/model_manager';
|
||||
export const messagingService = {
|
||||
dependencies: [
|
||||
'effect',
|
||||
'legacy_bus_service',
|
||||
'bus_service',
|
||||
'localization',
|
||||
'messagingValues',
|
||||
'orm',
|
||||
|
||||
@@ -566,7 +566,6 @@ async function start(param0 = {}) {
|
||||
registerCleanup(async () => {
|
||||
await webClient.env.services.messaging.modelManager.messagingInitializedPromise;
|
||||
webClient.env.services.messaging.modelManager.destroy();
|
||||
webClient.env.services.legacy_bus_service.destroy();
|
||||
delete webClient.env.services.messaging;
|
||||
delete owl.Component.env.services.messaging;
|
||||
delete owl.Component.env[wowlServicesSymbol].messaging;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { legacyBusService } from '@bus/js/services/legacy/legacy_bus_service';
|
||||
|
||||
import BusService from 'bus.BusService';
|
||||
import { busService } from '@bus/services/bus_service';
|
||||
import { makeBusServiceToLegacyEnv } from '@bus/services/legacy/make_bus_service_to_legacy_env';
|
||||
|
||||
import { ChatWindowManagerContainer } from '@mail/components/chat_window_manager_container/chat_window_manager_container';
|
||||
import { DialogManagerContainer } from '@mail/components/dialog_manager_container/dialog_manager_container';
|
||||
@@ -16,10 +15,6 @@ import { registry } from '@web/core/registry';
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { createWebClient } from "@web/../tests/webclient/helpers";
|
||||
|
||||
import AbstractStorageService from 'web.AbstractStorageService';
|
||||
import RamStorage from 'web.RamStorage';
|
||||
import LegacyRegistry from 'web.Registry';
|
||||
|
||||
const WEBCLIENT_LOAD_ROUTES = [
|
||||
'/web/webclient/load_menus',
|
||||
'/web/dataset/call_kw/res.users/load_views',
|
||||
@@ -46,7 +41,6 @@ const SERVICES_PARAMETER_NAMES = new Set([
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {Object} [param0.services]
|
||||
* @param {Object} [param0.legacyServices]
|
||||
* @param {number} [param0.loadingBaseDelayDuration=0]
|
||||
* @param {Promise} [param0.messagingBeforeCreationDeferred=Promise.resolve()]
|
||||
* Deferred that let tests block messaging creation and simulate resolution.
|
||||
@@ -58,7 +52,6 @@ const SERVICES_PARAMETER_NAMES = new Set([
|
||||
* to the webClient as a legacy parameter.
|
||||
*/
|
||||
function setupMessagingServiceRegistries({
|
||||
legacyServices,
|
||||
loadingBaseDelayDuration = 0,
|
||||
messagingBeforeCreationDeferred = Promise.resolve(),
|
||||
messagingBus,
|
||||
@@ -66,20 +59,6 @@ function setupMessagingServiceRegistries({
|
||||
testSetupDoneDeferred,
|
||||
}) {
|
||||
const serviceRegistry = registry.category('services');
|
||||
const legacyServiceRegistry = new LegacyRegistry();
|
||||
legacyServices = {
|
||||
bus_service: BusService.extend({
|
||||
_beep() {}, // Do nothing
|
||||
_poll() {}, // Do nothing
|
||||
_registerWindowUnload() {}, // Do nothing
|
||||
isOdooFocused() {
|
||||
return true;
|
||||
},
|
||||
updateOption() {},
|
||||
}),
|
||||
local_storage: AbstractStorageService.extend({ storage: new RamStorage() }),
|
||||
...legacyServices,
|
||||
};
|
||||
|
||||
patchWithCleanup(messagingService, {
|
||||
async _startModelManager() {
|
||||
@@ -101,31 +80,36 @@ function setupMessagingServiceRegistries({
|
||||
}
|
||||
};
|
||||
|
||||
const customBusService = {
|
||||
start() {
|
||||
const originalService = busService.start(...arguments);
|
||||
Object.assign(originalService, {
|
||||
_beep() {}, // Do nothing
|
||||
_poll() {}, // Do nothing
|
||||
_registerWindowUnload() {}, // Do nothing
|
||||
isOdooFocused() {
|
||||
return true;
|
||||
},
|
||||
updateOption() {},
|
||||
});
|
||||
return originalService;
|
||||
},
|
||||
};
|
||||
|
||||
services = {
|
||||
bus_service: customBusService,
|
||||
messaging: messagingService,
|
||||
messagingValues,
|
||||
systrayService,
|
||||
...services,
|
||||
};
|
||||
// during tests, we need to wait for the legacyBusService to be added before adding the messaging
|
||||
// service to the registry or it will result in an error during the webClient creation.
|
||||
const addMessagingToRegistryFn = ev => {
|
||||
const { key, operation } = ev.detail;
|
||||
if (key === 'legacy_bus_service' && operation === 'add') {
|
||||
serviceRegistry.add('messaging', messagingService);
|
||||
serviceRegistry.add('systray_service', systrayService);
|
||||
serviceRegistry.add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv(owl.Component.env));
|
||||
serviceRegistry.removeEventListener('UPDATE', addMessagingToRegistryFn);
|
||||
}
|
||||
};
|
||||
serviceRegistry.addEventListener('UPDATE', addMessagingToRegistryFn);
|
||||
|
||||
Object.entries(services).forEach(([serviceName, service]) => {
|
||||
serviceRegistry.add(serviceName, service);
|
||||
});
|
||||
Object.entries(legacyServices).forEach(([serviceName, service]) => {
|
||||
legacyServiceRegistry.add(serviceName, service);
|
||||
});
|
||||
|
||||
return legacyServiceRegistry;
|
||||
registry.category('wowlToLegacyServiceMappers').add('bus_service_to_legacy_env', makeBusServiceToLegacyEnv);
|
||||
registry.category('wowlToLegacyServiceMappers').add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,8 +119,6 @@ function setupMessagingServiceRegistries({
|
||||
* @param {Object} param0
|
||||
* @param {Object} [param0.serverData]
|
||||
* @param {Object} [param0.services]
|
||||
* @param {Object} [param0.legacyServices]
|
||||
* @param {Object} [param0.legacyParams={}]
|
||||
* @param {Object} [param0.loadingBaseDelayDuration]
|
||||
* @param {Object} [param0.messagingBeforeCreationDeferred]
|
||||
* @param {Promise} [param0.testSetupDoneDeferred] The Promise to whose revolution has to be
|
||||
@@ -154,7 +136,7 @@ function setupMessagingServiceRegistries({
|
||||
servicesParameters[parameterName] = value;
|
||||
}
|
||||
}
|
||||
const legacyServiceRegistry = setupMessagingServiceRegistries(servicesParameters);
|
||||
setupMessagingServiceRegistries(servicesParameters);
|
||||
|
||||
const webClientParameters = {};
|
||||
for (const [parameterName, value] of param0Entries) {
|
||||
@@ -162,15 +144,7 @@ function setupMessagingServiceRegistries({
|
||||
webClientParameters[parameterName] = value;
|
||||
}
|
||||
}
|
||||
webClientParameters.legacyParams = {
|
||||
legacyServicesToDeployInWowlEnv: {
|
||||
legacy_bus_service: legacyBusService,
|
||||
},
|
||||
serviceRegistry: legacyServiceRegistry,
|
||||
...webClientParameters.legacyParams,
|
||||
};
|
||||
const webClient = await createWebClient(webClientParameters);
|
||||
return webClient;
|
||||
return createWebClient(webClientParameters);
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import BusService from 'bus.BusService';
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
isScrolledToBottom,
|
||||
@@ -14,6 +12,7 @@ import { makeFakeNotificationService } from "@web/../tests/helpers/mock_services
|
||||
import { destroy } from '@web/../tests/helpers/utils';
|
||||
|
||||
import { makeTestPromise, file } from 'web.test_utils';
|
||||
import { busService } from '@bus/services/bus_service';
|
||||
|
||||
const { createFile, inputFiles } = file;
|
||||
|
||||
@@ -3561,15 +3560,23 @@ QUnit.test('receive new chat message: out of odoo focus (notification, channel)'
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({ channel_type: 'chat' });
|
||||
const { env, openDiscuss } = await start({
|
||||
legacyServices: {
|
||||
bus_service: BusService.extend({
|
||||
const customBusService = {
|
||||
...busService,
|
||||
start() {
|
||||
const originalBusService = busService.start(...arguments);
|
||||
Object.assign(originalBusService, {
|
||||
_beep() {}, // Do nothing
|
||||
_poll() {}, // Do nothing
|
||||
_registerWindowUnload() {}, // Do nothing
|
||||
isOdooFocused: () => false,
|
||||
updateOption() {},
|
||||
}),
|
||||
});
|
||||
return originalBusService;
|
||||
},
|
||||
};
|
||||
const { env, openDiscuss } = await start({
|
||||
services: {
|
||||
bus_service: customBusService,
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
@@ -3599,15 +3606,23 @@ QUnit.test('receive new chat message: out of odoo focus (notification, chat)', a
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({ channel_type: "chat" });
|
||||
const { env, openDiscuss } = await start({
|
||||
legacyServices: {
|
||||
bus_service: BusService.extend({
|
||||
const customBusService = {
|
||||
...busService,
|
||||
start() {
|
||||
const originalBusService = busService.start(...arguments);
|
||||
Object.assign(originalBusService, {
|
||||
_beep() {}, // Do nothing
|
||||
_poll() {}, // Do nothing
|
||||
_registerWindowUnload() {}, // Do nothing
|
||||
isOdooFocused: () => false,
|
||||
updateOption() {},
|
||||
}),
|
||||
});
|
||||
return originalBusService;
|
||||
},
|
||||
};
|
||||
const { env, openDiscuss } = await start({
|
||||
services: {
|
||||
bus_service: customBusService,
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
@@ -3641,15 +3656,23 @@ QUnit.test('receive new chat messages: out of odoo focus (tab title)', async fun
|
||||
{ channel_type: 'chat', public: 'private' },
|
||||
{ channel_type: 'chat', public: 'private' },
|
||||
]);
|
||||
const { env, openDiscuss } = await start({
|
||||
legacyServices: {
|
||||
bus_service: BusService.extend({
|
||||
const customBusService = {
|
||||
...busService,
|
||||
start() {
|
||||
const originalBusService = busService.start(...arguments);
|
||||
Object.assign(originalBusService, {
|
||||
_beep() {}, // Do nothing
|
||||
_poll() {}, // Do nothing
|
||||
_registerWindowUnload() {}, // Do nothing
|
||||
isOdooFocused: () => false,
|
||||
updateOption() {},
|
||||
}),
|
||||
});
|
||||
return originalBusService;
|
||||
},
|
||||
};
|
||||
const { env, openDiscuss } = await start({
|
||||
services: {
|
||||
bus_service: customBusService,
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
('include', 'web.assets_backend'),
|
||||
|
||||
# Remove Longpolling bus and packages needed this bus
|
||||
('remove', 'bus/static/src/js/services/assets_watchdog_service.js'),
|
||||
('remove', 'bus/static/src/services/assets_watchdog_service.js'),
|
||||
('remove', 'mail/static/src/services/messaging_service.js'),
|
||||
|
||||
('remove', 'mail/static/src/components/dialog_manager/dialog_manager.js'),
|
||||
|
||||
@@ -73,6 +73,9 @@ export function makeRAMLocalStorage() {
|
||||
return {
|
||||
setItem(key, value) {
|
||||
store[key] = value;
|
||||
window.dispatchEvent(
|
||||
new StorageEvent('storage', { key, newValue: value })
|
||||
);
|
||||
},
|
||||
getItem(key) {
|
||||
return store[key];
|
||||
@@ -82,6 +85,9 @@ export function makeRAMLocalStorage() {
|
||||
},
|
||||
removeItem(key) {
|
||||
delete store[key];
|
||||
window.dispatchEvent(
|
||||
new StorageEvent('storage', { key, newValue: null })
|
||||
);
|
||||
},
|
||||
get length() {
|
||||
return Object.keys(store).length;
|
||||
|
||||
@@ -34,15 +34,13 @@ function _genericJsonRpc (fct_name, params, settings, fct) {
|
||||
var result = xhr.then(function(result) {
|
||||
core.bus.trigger('rpc:result', data, result);
|
||||
if (result.error !== undefined) {
|
||||
if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") {
|
||||
console.debug(
|
||||
"Server application error\n",
|
||||
"Error code:", result.error.code, "\n",
|
||||
"Error message:", result.error.message, "\n",
|
||||
"Error data message:\n", result.error.data.message, "\n",
|
||||
"Error data debug:\n", result.error.data.debug
|
||||
);
|
||||
}
|
||||
console.debug(
|
||||
"Server application error\n",
|
||||
"Error code:", result.error.code, "\n",
|
||||
"Error message:", result.error.message, "\n",
|
||||
"Error data message:\n", result.error.data.message, "\n",
|
||||
"Error data debug:\n", result.error.data.debug
|
||||
);
|
||||
return Promise.reject({type: "server", error: result.error});
|
||||
} else {
|
||||
return result.result;
|
||||
|
||||
@@ -377,6 +377,10 @@ export async function createPublicRoot(RootWidget) {
|
||||
serviceRegistry.add("legacy_notification", makeLegacyNotificationService(legacyEnv));
|
||||
serviceRegistry.add("legacy_dialog_mapping", makeLegacyDialogMappingService(legacyEnv));
|
||||
serviceRegistry.add("legacy_rainbowman_service", makeLegacyRainbowManService(legacyEnv));
|
||||
const wowlToLegacyServiceMappers = registry.category('wowlToLegacyServiceMappers').getEntries();
|
||||
for (const [legacyServiceName, wowlToLegacyServiceMapper] of wowlToLegacyServiceMappers) {
|
||||
serviceRegistry.add(legacyServiceName, wowlToLegacyServiceMapper(legacyEnv));
|
||||
}
|
||||
await Promise.all([whenReady(), session.is_bound]);
|
||||
|
||||
// Patch browser.fetch and the rpc service to use the correct base url when
|
||||
|
||||
@@ -46,6 +46,10 @@ export const legacySetupProm = new Promise((resolve) => {
|
||||
const legacyCommandService = makeLegacyCommandService(legacyEnv);
|
||||
serviceRegistry.add("legacy_command", legacyCommandService);
|
||||
serviceRegistry.add("legacy_dropdown", makeLegacyDropdownService(legacyEnv));
|
||||
const wowlToLegacyServiceMappers = registry.category('wowlToLegacyServiceMappers').getEntries();
|
||||
for (const [legacyServiceName, wowlToLegacyServiceMapper] of wowlToLegacyServiceMappers) {
|
||||
serviceRegistry.add(legacyServiceName, wowlToLegacyServiceMapper(legacyEnv));
|
||||
}
|
||||
await Promise.all([whenReady(), session.is_bound]);
|
||||
legacyEnv.templates = session.owlTemplates;
|
||||
legacySetupResolver(legacyEnv);
|
||||
|
||||
@@ -53,6 +53,7 @@ function prepareRegistriesWithCleanup() {
|
||||
cloneRegistryWithCleanup(registry.category("command_setup"));
|
||||
cloneRegistryWithCleanup(registry.category("view_widgets"));
|
||||
cloneRegistryWithCleanup(registry.category("fields"));
|
||||
cloneRegistryWithCleanup(registry.category('wowlToLegacyServiceMappers'));
|
||||
|
||||
cloneRegistryWithCleanup(registry.category("main_components"));
|
||||
cloneRegistryWithCleanup(registry.category("fields"));
|
||||
@@ -63,6 +64,7 @@ function prepareRegistriesWithCleanup() {
|
||||
clearRegistryWithCleanup(registry.category("error_dialogs"));
|
||||
clearRegistryWithCleanup(registry.category("favoriteMenu"));
|
||||
clearRegistryWithCleanup(registry.category("ir.actions.report handlers"));
|
||||
clearRegistryWithCleanup(registry.category('wowlToLegacyServiceMappers'));
|
||||
|
||||
clearRegistryWithCleanup(registry.category("services"));
|
||||
clearServicesMetadataWithCleanup();
|
||||
|
||||
@@ -12,6 +12,7 @@ import { registerCleanup } from "./cleanup";
|
||||
import { patchWithCleanup } from "./utils";
|
||||
import { companyService } from "@web/webclient/company_service";
|
||||
import { uiService } from "@web/core/ui/ui_service";
|
||||
import { ConnectionAbortedError } from "../../src/core/network/rpc_service";
|
||||
|
||||
const { Component, status } = owl;
|
||||
|
||||
@@ -60,7 +61,16 @@ export function makeFakeRPCService(mockRPC) {
|
||||
return {
|
||||
name: "rpc",
|
||||
start() {
|
||||
return buildMockRPC(mockRPC);
|
||||
const rpcService = buildMockRPC(mockRPC);
|
||||
return function () {
|
||||
let rejectFn;
|
||||
const rpcProm = new Promise((resolve, reject) => {
|
||||
rejectFn = reject;
|
||||
resolve(rpcService(...arguments));
|
||||
});
|
||||
rpcProm.abort = () => rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort"));
|
||||
return rpcProm;
|
||||
};
|
||||
},
|
||||
specializeForComponent: rpcService.specializeForComponent,
|
||||
};
|
||||
|
||||
@@ -117,6 +117,8 @@ function makeMockLocation(hasListeners = () => true) {
|
||||
function patchBrowserWithCleanup() {
|
||||
const originalAddEventListener = browser.addEventListener;
|
||||
const originalRemoveEventListener = browser.removeEventListener;
|
||||
const originalSetTimeout = browser.setTimeout;
|
||||
const originalClearTimeout = browser.clearTimeout;
|
||||
|
||||
let hasHashChangeListeners = false;
|
||||
const mockLocation = makeMockLocation(() => hasHashChangeListeners);
|
||||
@@ -134,6 +136,15 @@ function patchBrowserWithCleanup() {
|
||||
originalRemoveEventListener(...arguments);
|
||||
});
|
||||
},
|
||||
// patch setTimeout to automatically remove timeouts bound (via
|
||||
// browser.setTimeout) during a test (e.g. during the deployment of a service)
|
||||
setTimeout() {
|
||||
const timeout = originalSetTimeout(...arguments);
|
||||
registerCleanup(() => {
|
||||
originalClearTimeout(timeout);
|
||||
});
|
||||
return timeout;
|
||||
},
|
||||
navigator: {
|
||||
userAgent: browser.navigator.userAgent.replace(/\([^)]*\)/, "(X11; Linux x86_64)"),
|
||||
},
|
||||
|
||||
@@ -194,10 +194,10 @@ export async function addLegacyMockEnvironment(env, legacyParams = {}) {
|
||||
const legacyActionManagerService = makeLegacyActionManagerService(legacyEnv);
|
||||
serviceRegistry.add("legacy_action_manager", legacyActionManagerService);
|
||||
serviceRegistry.add("legacy_notification", makeLegacyNotificationService(legacyEnv));
|
||||
// deploy the legacyServices given in parameter in WowlEnv
|
||||
const { legacyServicesToDeployInWowlEnv } = legacyParams;
|
||||
for (const legacyServiceName in legacyServicesToDeployInWowlEnv) {
|
||||
serviceRegistry.add(legacyServiceName, legacyServicesToDeployInWowlEnv[legacyServiceName]);
|
||||
// deploy wowl services into the legacy env.
|
||||
const wowlToLegacyServiceMappers = registry.category('wowlToLegacyServiceMappers').getEntries();
|
||||
for (const [legacyServiceName, wowlToLegacyServiceMapper] of wowlToLegacyServiceMappers) {
|
||||
serviceRegistry.add(legacyServiceName, wowlToLegacyServiceMapper(legacyEnv));
|
||||
}
|
||||
// patch DebouncedField delay
|
||||
const debouncedField = basicFields.DebouncedField;
|
||||
|
||||
Reference in New Issue
Block a user