summaryrefslogtreecommitdiff
path: root/addons/bus/static/src/js/longpolling_bus.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/bus/static/src/js/longpolling_bus.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/bus/static/src/js/longpolling_bus.js')
-rw-r--r--addons/bus/static/src/js/longpolling_bus.js262
1 files changed, 262 insertions, 0 deletions
diff --git a/addons/bus/static/src/js/longpolling_bus.js b/addons/bus/static/src/js/longpolling_bus.js
new file mode 100644
index 00000000..9c8df877
--- /dev/null
+++ b/addons/bus/static/src/js/longpolling_bus.js
@@ -0,0 +1,262 @@
+odoo.define('bus.Longpolling', function (require) {
+"use strict";
+
+var Bus = require('web.Bus');
+var ServicesMixin = require('web.ServicesMixin');
+
+
+/**
+ * Event Longpolling bus used to bind events on the server long polling return
+ *
+ * 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',
+
+ // properties
+ _isActive: null,
+ _lastNotificationID: 0,
+ _isOdooFocused: true,
+ _pollRetryTimeout: null,
+
+ /**
+ * @override
+ */
+ init: function (parent, params) {
+ this._super.apply(this, arguments);
+ this._id = _.uniqueId('bus');
+
+ // the _id is modified by crosstab_bus, so we can't use it to unbind the events in the destroy.
+ this._longPollingBusId = this._id;
+ this._options = {};
+ this._channels = [];
+
+ // 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();
+ },
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+ /**
+ * Register a new channel to listen on the longpoll (ignore if already
+ * listening on this channel).
+ * Aborts a pending longpoll, in order to re-start another longpoll, so
+ * that we can immediately get notifications on newly registered channel.
+ *
+ * @param {string} channel
+ */
+ addChannel: function (channel) {
+ if (this._channels.indexOf(channel) === -1) {
+ this._channels.push(channel);
+ if (this._pollRpc) {
+ this._pollRpc.abort();
+ } else {
+ this.startPolling();
+ }
+ }
+ },
+ /**
+ * Unregister a channel from listening on the longpoll.
+ *
+ * Aborts a pending longpoll, in order to re-start another longpoll, so
+ * that we immediately remove ourselves from listening on notifications
+ * on this channel.
+ *
+ * @param {string} channel
+ */
+ deleteChannel: function (channel) {
+ var index = this._channels.indexOf(channel);
+ if (index !== -1) {
+ this._channels.splice(index, 1);
+ if (this._pollRpc) {
+ this._pollRpc.abort();
+ }
+ }
+ },
+ /**
+ * Tell whether odoo is focused or not
+ *
+ * @returns {boolean}
+ */
+ isOdooFocused: function () {
+ 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 () {
+ if (this._isActive === null) {
+ this._poll = this._poll.bind(this);
+ }
+ if (!this._isActive) {
+ 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 () {
+ 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.
+ *
+ * @param {string} key
+ * @param {any} value
+ */
+ updateOption: function (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 () {
+ return this._lastPresenceTime;
+ },
+ /**
+ * Continually start a poll:
+ *
+ * A poll is a connection that is kept open for a relatively long period
+ * (up to 1 minute). Local bus data are sent to the server each time a poll
+ * is initiated, and the server may return some "real-time" notifications
+ * about registered channels.
+ *
+ * A poll ends on timeout, on abort, on receiving some notifications, or on
+ * receiving an error. Another poll usually starts afterward, except if the
+ * poll is aborted or stopped (@see stopPolling).
+ *
+ * @private
+ */
+ _poll: function () {
+ var self = this;
+ if (!this._isActive) {
+ return;
+ }
+ var now = new Date().getTime();
+ var options = _.extend({}, this._options, {
+ bus_inactivity: now - this._getLastPresence(),
+ });
+ var data = {channels: this._channels, last: this._lastNotificationID, options: options};
+ // The backend has a maximum cycle time of 50 seconds so give +10 seconds
+ this._pollRpc = this._makePoll(data);
+ this._pollRpc.then(function (result) {
+ self._pollRpc = false;
+ self._onPoll(result);
+ self._poll();
+ }).guardedCatch(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 {
+ // random delay to avoid massive longpolling
+ 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});
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+ /**
+ * Handler when the focus of the window change.
+ * Trigger the 'window_focus' event.
+ *
+ * @private
+ * @param {Object} params
+ * @param {Boolean} params.focus
+ */
+ _onFocusChange: function (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.
+ * Triggered the 'notification' event with a list [channel, message] from notifications.
+ *
+ * @private
+ * @param {Object[]} notifications, Input notifications have an id, channel, message
+ * @returns {Array[]} Output arrays have notification's channel and message
+ */
+ _onPoll: function (notifications) {
+ var self = this;
+ var notifs = _.map(notifications, function (notif) {
+ if (notif.id > self._lastNotificationID) {
+ self._lastNotificationID = notif.id;
+ }
+ return [notif.channel, notif.message];
+ });
+ 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 () {
+ this._lastPresenceTime = new Date().getTime();
+ },
+});
+
+return LongpollingBus;
+
+});