[FIX] bus: co-existing instances of bus.bus
Before this rev., it was not possible to have co-existing instances of bus.bus, each one polling its own server, for several reasons. First, the Bus class couldn't be overridden or parameterized to specify which server and route to call to perform the longpolling. Second, when several instances of bus.bus coexisted, their use of the localStorage (for the CrosstabBus) conflicted, and as a result, only one of them actually polled. This rev. makes it possible, and it was necessary for the new addon 'im_support'. Task 26762
This commit is contained in:
@@ -14,9 +14,17 @@ var MASTER_TAB_HEARTBEAT_PERIOD = 1500; // 1.5 second
|
||||
bus.ERROR_DELAY = 10000;
|
||||
|
||||
bus.Bus = Widget.extend({
|
||||
init: function(){
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} params
|
||||
* @param {Object} params.session the session to use to perform the poll RPC
|
||||
* @param {string} params.pollRoute the longpolling route to use
|
||||
*/
|
||||
init: function (params) {
|
||||
var self = this;
|
||||
this._super();
|
||||
this.pollRoute = params.pollRoute;
|
||||
this.session = params.session;
|
||||
this.options = {};
|
||||
this.activated = false;
|
||||
this.bus_id = _.uniqueId('bus');
|
||||
@@ -77,7 +85,7 @@ bus.Bus = Widget.extend({
|
||||
}
|
||||
var data = {channels: self.channels, last: self.last, options: options};
|
||||
// The backend has a maximum cycle time of 50 seconds so give +10 seconds
|
||||
session.rpc('/longpolling/poll', data, {shadow : true, timeout: 60000}).then(function(result) {
|
||||
this.session.rpc(this.pollRoute, data, {shadow : true, timeout: 60000}).then(function(result) {
|
||||
self.on_notification(result);
|
||||
if(!self.stop){
|
||||
self.poll();
|
||||
@@ -145,13 +153,17 @@ bus.Bus = Widget.extend({
|
||||
* - bus.tab_list : list of opened tab ids
|
||||
* - bus.tab_master : generated id of the master tab
|
||||
*/
|
||||
var CrossTabBus = bus.Bus.extend({
|
||||
bus.CrossTabBus = bus.Bus.extend({
|
||||
init: function(){
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
// used to prefix localStorage keys
|
||||
this.sanitizedOrigin = this.session.origin.replace(/:\/{0,2}/g, '_');
|
||||
|
||||
this.is_master = false;
|
||||
this.is_registered = false;
|
||||
if (parseInt(getItem('bus.last_ts', 0)) + 50000 < new Date().getTime()) {
|
||||
setItem('bus.last', -1);
|
||||
if (parseInt(this._getItem('last_ts', 0)) + 50000 < new Date().getTime()) {
|
||||
this._setItem('last', -1);
|
||||
}
|
||||
|
||||
on("storage", this.on_storage.bind(this));
|
||||
@@ -170,15 +182,15 @@ var CrossTabBus = bus.Bus.extend({
|
||||
// Write last_presence in local storage if it has been updated since last heartbeat
|
||||
var hb_period = this.is_master ? MASTER_TAB_HEARTBEAT_PERIOD : TAB_HEARTBEAT_PERIOD;
|
||||
if (self.last_presence + hb_period > new Date().getTime()) {
|
||||
setItem('bus.last_presence', self.last_presence);
|
||||
self._setItem('last_presence', self.last_presence);
|
||||
}
|
||||
});
|
||||
}, this._generateKey.bind(this));
|
||||
if (this.is_master) {
|
||||
setItem('bus.channels', this.channels);
|
||||
setItem('bus.options', this.options);
|
||||
this._setItem('channels', this.channels);
|
||||
this._setItem('options', this.options);
|
||||
} else {
|
||||
this.channels = getItem('bus.channels', this.channels);
|
||||
this.options = getItem('bus.options', this.options);
|
||||
this.channels = this._getItem('channels', this.channels);
|
||||
this.options = this._getItem('options', this.options);
|
||||
}
|
||||
return; // start_polling will be called again on tab registration
|
||||
}
|
||||
@@ -189,7 +201,7 @@ var CrossTabBus = bus.Bus.extend({
|
||||
},
|
||||
on_notification: function(notifications){
|
||||
if(this.is_master) { // broadcast to other tabs
|
||||
var last = getItem('bus.last', -1);
|
||||
var last = this._getItem('last', -1);
|
||||
var max_id = Math.max(last, 0);
|
||||
var new_notifications = _.filter(notifications, function (notif) {
|
||||
max_id = Math.max(max_id, notif.id);
|
||||
@@ -197,9 +209,9 @@ var CrossTabBus = bus.Bus.extend({
|
||||
});
|
||||
this.last = max_id;
|
||||
if (new_notifications.length) {
|
||||
setItem('bus.last', max_id);
|
||||
setItem('bus.last_ts', new Date().getTime());
|
||||
setItem('bus.notification', new_notifications);
|
||||
this._setItem('last', max_id);
|
||||
this._setItem('last_ts', new Date().getTime());
|
||||
this._setItem('notification', new_notifications);
|
||||
this._super(new_notifications);
|
||||
}
|
||||
} else {
|
||||
@@ -211,45 +223,78 @@ var CrossTabBus = bus.Bus.extend({
|
||||
// localStorage (avoid race condition)
|
||||
var value = e.newValue;
|
||||
// notifications changed
|
||||
if(e.key === 'bus.notification'){
|
||||
if (e.key === this._generateKey('notification')) {
|
||||
var notifs = JSON.parse(value);
|
||||
this.on_notification(notifs);
|
||||
}
|
||||
// update channels
|
||||
if(e.key === 'bus.channels'){
|
||||
if (e.key === this._generateKey('channels')) {
|
||||
this.channels = JSON.parse(value);
|
||||
}
|
||||
// update options
|
||||
if(e.key === 'bus.options'){
|
||||
if (e.key === this._generateKey('options')) {
|
||||
this.options = JSON.parse(value);
|
||||
}
|
||||
// update focus
|
||||
if(e.key === 'bus.focus'){
|
||||
if (e.key === this._generateKey('focus')) {
|
||||
this.set('window_focus', JSON.parse(value));
|
||||
}
|
||||
},
|
||||
add_channel: function(){
|
||||
this._super.apply(this, arguments);
|
||||
setItem('bus.channels', this.channels);
|
||||
this._setItem('channels', this.channels);
|
||||
},
|
||||
delete_channel: function(){
|
||||
this._super.apply(this, arguments);
|
||||
setItem('bus.channels', this.channels);
|
||||
this._setItem('channels', this.channels);
|
||||
},
|
||||
get_last_presence: function () {
|
||||
return getItem('bus.last_presence') || new Date().getTime();
|
||||
return this._getItem('last_presence') || new Date().getTime();
|
||||
},
|
||||
update_option: function(){
|
||||
this._super.apply(this, arguments);
|
||||
setItem('bus.options', this.options);
|
||||
this._setItem('options', this.options);
|
||||
},
|
||||
delete_option: function(){
|
||||
this._super.apply(this, arguments);
|
||||
setItem('bus.options', this.options);
|
||||
this._setItem('options', this.options);
|
||||
},
|
||||
focus_change: function(focus) {
|
||||
this._super.apply(this, arguments);
|
||||
setItem('bus.focus', focus);
|
||||
this._setItem('focus', focus);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Generates localStorage keys prefixed by bus. (the name of this addon),
|
||||
* and the sanitized origin, to prevent keys from conflicting when several
|
||||
* bus instances (polling different origins) co-exist.
|
||||
*
|
||||
* @private
|
||||
* @param {string} key
|
||||
* @returns key prefixed with the origin
|
||||
*/
|
||||
_generateKey: function (key) {
|
||||
return 'bus.' + this.sanitizedOrigin + '.' + key;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} key
|
||||
* @param {*} defaultValue
|
||||
*/
|
||||
_getItem: function (key, defaultValue) {
|
||||
return getItem(this._generateKey(key), defaultValue);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} key
|
||||
* @param {*} value
|
||||
*/
|
||||
_setItem: function (key, value) {
|
||||
setItem(this._generateKey(key), value);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -272,14 +317,16 @@ function setItem(key, value) {
|
||||
local_storage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
|
||||
var tab_manager = {
|
||||
peersKey: 'bus.peers',
|
||||
masterKey: 'bus.master',
|
||||
heartbeatKey: 'bus.heartbeat',
|
||||
isMaster: false,
|
||||
id: new Date().getTime() + ':' + (Math.random() * 1000000000 | 0),
|
||||
|
||||
register_tab: function (is_master_callback, is_no_longer_master, on_heartbeat_callback) {
|
||||
register_tab: function (is_master_callback, is_no_longer_master, on_heartbeat_callback, generateKey) {
|
||||
this.heartbeatKey = generateKey('heartbeat');
|
||||
this.masterKey = generateKey('master');
|
||||
this.peersKey = generateKey('peers');
|
||||
|
||||
this.is_master_callback = is_master_callback;
|
||||
this.is_no_longer_master = is_no_longer_master || function () {};
|
||||
this.on_heartbeat_callback = on_heartbeat_callback || function () {};
|
||||
@@ -398,10 +445,14 @@ var tab_manager = {
|
||||
|
||||
// bus singleton, depending of the browser :
|
||||
// if supporting LocalStorage, there will be only one tab polling
|
||||
var params = {
|
||||
pollRoute: '/longpolling/poll',
|
||||
session: session,
|
||||
};
|
||||
if(typeof Storage !== "undefined"){
|
||||
bus.bus = new CrossTabBus();
|
||||
bus.bus = new bus.CrossTabBus(params);
|
||||
} else {
|
||||
bus.bus = new bus.Bus();
|
||||
bus.bus = new bus.Bus(params);
|
||||
}
|
||||
|
||||
return bus;
|
||||
|
||||
Reference in New Issue
Block a user