[IMP] bus: allow tabs to have differents channels

This commit prepares the ground for the collaborative feature.

Before this commit, it was impossible to have different channels for
each tabs. In collaboration, multiples tabs could listen to differents set
of channels.

Example:
One tab X could be subscribed to channel [a, b] and tab Y on channel
[a, b] and a tab Z on [a, c].

Before this commit, only the channels of the master tab were
listened. So in this case it was either [a, b] or [a, c] depending on
which tab is the master.

So either channel b or channel c were not listened depending on which tab
is the master.

Now, each time a tab listens or stops listens to a channel, the
master tab listen all channels for all tabs.

task-2497931
odoo pr: 75768

Part-of: odoo/odoo#75768
This commit is contained in:
Nicolas Bayet
2021-09-06 19:42:56 +00:00
parent 33788fd2de
commit b743fda90d
2 changed files with 65 additions and 12 deletions
+52 -7
View File
@@ -46,6 +46,7 @@ var CrossTabBus = Longpolling.extend({
var now = new Date().getTime();
// used to prefix localStorage keys
this._sanitizedOrigin = session.origin.replace(/:\/{0,2}/g, '_');
this._currentTabChannels = new Set();
// prevents collisions between different tabs and in tests
this._id = _.uniqueId(this.LOCAL_STORAGE_PREFIX) + ':' + now;
if (this._callLocalStorage('getItem', 'last_ts', 0) + 50000 < now) {
@@ -66,18 +67,20 @@ var CrossTabBus = Longpolling.extend({
*
* @override
*/
addChannel: function () {
addChannel: function (channel) {
this._currentTabChannels.add(channel);
this._super.apply(this, arguments);
this._callLocalStorage('setItem', 'channels', this._channels);
this._updateChannels();
},
/**
* Share the bus channels with the others tab by the local storage
*
* @override
*/
deleteChannel: function () {
deleteChannel: function (channel) {
this._currentTabChannels.delete(channel);
this._super.apply(this, arguments);
this._callLocalStorage('setItem', 'channels', this._channels);
this._updateChannels();
},
/**
* @return {string}
@@ -118,12 +121,11 @@ var CrossTabBus = Longpolling.extend({
this._heartbeat();
if (this._isMasterTab) {
this._callLocalStorage('setItem', 'channels', this._channels);
this._callLocalStorage('setItem', 'options', this._options);
} else {
this._channels = this._callLocalStorage('getItem', 'channels', this._channels);
this._options = this._callLocalStorage('getItem', 'options', this._options);
}
this._updateChannels();
return; // startPolling will be called again on tab registration
}
@@ -274,6 +276,45 @@ var CrossTabBus = Longpolling.extend({
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 () {
const currentPeerIds = new Set(Object.keys(this._callLocalStorage('getItem', 'peers') || {}));
const peerChannels = this._callLocalStorage('getItem', 'channels') || {};
const peerChannelsBefore = JSON.stringify(peerChannels);
peerChannels[this._id] = Array.from(this._currentTabChannels);
// Clean outdated channels.
for (const channelPeerId of Object.keys(peerChannels)) {
if (!currentPeerIds.has(channelPeerId)) {
delete peerChannels[channelPeerId];
}
}
const peerChannelsAfter = JSON.stringify(peerChannels);
if (peerChannelsBefore === peerChannelsAfter) {
return false;
}
this._callLocalStorage('setItem', 'channels', peerChannels);
const allChannels = new Set();
for (const channels of Object.values(peerChannels)) {
for (const channel of channels) {
allChannels.add(channel);
}
}
// Insure the current tab channels are always in the aggregated channels
// in case this tab is not in the currentPeerIds nor peerChannels.
for (const channel of this._currentTabChannels) {
allChannels.add(channel);
}
this._channels = Array.from(allChannels);
return true;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
@@ -327,7 +368,9 @@ var CrossTabBus = Longpolling.extend({
}
// update channels
else if (key === this._generateKey('channels')) {
this._channels = value;
if (this._updateChannels()) {
this._restartPolling();
};
}
// update options
else if (key === this._generateKey('options')) {
@@ -349,6 +392,8 @@ var CrossTabBus = Longpolling.extend({
var peers = this._callLocalStorage('getItem', 'peers') || {};
delete peers[this._id];
this._callLocalStorage('setItem', 'peers', peers);
this._currentTabChannels.clear();
this._updateChannels();
// unload master
if (this._isMasterTab) {
+13 -5
View File
@@ -75,11 +75,7 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
addChannel: function (channel) {
if (this._channels.indexOf(channel) === -1) {
this._channels.push(channel);
if (this._pollRpc) {
this._pollRpc.abort();
} else {
this.startPolling();
}
this._restartPolling();
}
},
/**
@@ -255,6 +251,18 @@ var LongpollingBus = Bus.extend(ServicesMixin, {
_onPresence: function () {
this._lastPresenceTime = new Date().getTime();
},
/**
* Restart polling.
*
* @private
*/
_restartPolling() {
if (this._pollRpc) {
this._pollRpc.abort();
} else {
this.startPolling();
}
},
});
return LongpollingBus;