浏览代码

Decouple automatic away and XEP-0352 support.

- Add new config option csi_waiting_time for CSI support.
- The auto_away and auto_xa options won't send out CSI stanzas if csi_waiting_time is 0
- Update docs and add tests for both features.
JC Brand 10 年之前
父节点
当前提交
868435173f
共有 4 个文件被更改,包括 149 次插入59 次删除
  1. 53 52
      converse.js
  2. 1 0
      docs/CHANGES.rst
  3. 22 7
      docs/source/configuration.rst
  4. 73 0
      spec/converse.js

+ 53 - 52
converse.js

@@ -287,8 +287,8 @@
             allow_logout: true,
             allow_logout: true,
             allow_muc: true,
             allow_muc: true,
             allow_otr: true,
             allow_otr: true,
-            auto_away: 0, //in seconds
-            auto_xa: 0, //in seconds
+            auto_away: 0, // Seconds after which user status is set to 'away'
+            auto_xa: 0, // Seconds after which user status is set to 'xa'
             allow_registration: true,
             allow_registration: true,
             animate: true,
             animate: true,
             auto_list_rooms: false,
             auto_list_rooms: false,
@@ -297,6 +297,7 @@
             auto_subscribe: false,
             auto_subscribe: false,
             bosh_service_url: undefined, // The BOSH connection manager URL.
             bosh_service_url: undefined, // The BOSH connection manager URL.
             cache_otr_key: false,
             cache_otr_key: false,
+            csi_waiting_time: 0, // Support for XEP-0352. Seconds before client is considered idle and CSI is sent out.
             debug: false,
             debug: false,
             domain_placeholder: __(" e.g. conversejs.org"),  // Placeholder text shown in the domain input on the registration form
             domain_placeholder: __(" e.g. conversejs.org"),  // Placeholder text shown in the domain input on the registration form
             default_box_height: 400, // The default height, in pixels, for the control box, chat boxes and chatrooms.
             default_box_height: 400, // The default height, in pixels, for the control box, chat boxes and chatrooms.
@@ -306,7 +307,7 @@
             hide_offline_users: false,
             hide_offline_users: false,
             jid: undefined,
             jid: undefined,
             keepalive: false,
             keepalive: false,
-            message_carbons: false,
+            message_carbons: false, // Support for XEP-280
             no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width)
             no_trimming: false, // Set to true for phantomjs tests (where browser apparently has no width)
             ping_interval: 180, //in seconds
             ping_interval: 180, //in seconds
             play_sounds: false,
             play_sounds: false,
@@ -337,6 +338,8 @@
             xhr_user_search: false,
             xhr_user_search: false,
             xhr_user_search_url: ''
             xhr_user_search_url: ''
         };
         };
+
+
         _.extend(this, this.default_settings);
         _.extend(this, this.default_settings);
         // Allow only whitelisted configuration attributes to be overwritten
         // Allow only whitelisted configuration attributes to be overwritten
         _.extend(this, _.pick(settings, Object.keys(this.default_settings)));
         _.extend(this, _.pick(settings, Object.keys(this.default_settings)));
@@ -415,54 +418,56 @@
         // ----------------------
         // ----------------------
 
 
         this.sendCSI = function (stat) {
         this.sendCSI = function (stat) {
-            if (converse.features[Strophe.NS.CSI]) {
+            /* Send out a Chat Status Notification (XEP-0352) */
+            if (converse.features[Strophe.NS.CSI] || true) {
                 converse.connection.send($build(stat, {xmlns: Strophe.NS.CSI}));
                 converse.connection.send($build(stat, {xmlns: Strophe.NS.CSI}));
+                this.inactive = (stat === INACTIVE) ? true : false;
             }
             }
         };
         };
-        this.autoAwayReset = function () {
-            if (converse._idleCounter > 0) {
-                converse._idleCounter = 0;
-                if (converse._autoAway > 0) {
-                    converse._autoAway = 0;
-                    converse.sendCSI(ACTIVE);
-                    converse.xmppstatus.setStatus('online');
-                }
+
+        this.onUserActivity = function () {
+            /* Reset counters and flags relating to user activity. */
+            if (this.idle_seconds > 0) {
+                this.idle_seconds = 0;
+            }
+            if (this.inactive) {
+                this.sendCSI(ACTIVE);
+            }
+            if (this.auto_changed_status === true) {
+                this.auto_changed_status = false;
+                this.xmppstatus.setStatus('online');
             }
             }
         };
         };
-        this.registerAutoAwayHandler = function () {
-            // TODO: we should probably come up with a way to decouple CSI and auto-away
-            if (this.auto_away > 0 || this.auto_xa > 0) {
-                if (this.auto_xa > 0 && this.auto_xa < this.auto_away) {
-                    this.auto_xa = this.auto_away;
-                }
-                this._idleCounter = 0;
-                this._autoAway = 0;
-                $(window).on('click' , function () { converse.autoAwayReset(); });
-                $(window).on('mousemove' , function () { converse.autoAwayReset(); });
-                $(window).on('keypress' , function () { converse.autoAwayReset(); });
-                $(window).on('focus' , function () { converse.autoAwayReset(); });
-                $(window).on(unloadevent , function () { converse.autoAwayReset(); });
-
-                window.setInterval(function () {
-                    if ((this._idleCounter <= this.auto_away || (this.auto_xa > 0 && this._idleCounter <= this.auto_xa)) &&
-                        (this.xmppstatus.get('status') == 'online' && this._autoAway === 0) || (this.xmppstatus.get('status') == 'away' && this._autoAway == 1) ){
-                        this._idleCounter++;
-                    }
-                    if (this.auto_away > 0 && this._autoAway != 1 && this._idleCounter > this.auto_away && this._idleCounter <= this.auto_xa){
-                        this.sendCSI(INACTIVE);
-                        this._autoAway = 1;
-                        this.xmppstatus.setStatus('away');
-                    }
-                    else if (this.auto_xa > 0 && this._autoAway != 2 && this._idleCounter > this.auto_xa){
-                        this.sendCSI(INACTIVE);
-                        this._autoAway = 2;
-                        this.xmppstatus.setStatus('xa');
-                    }
-                }.bind(this), 1000); //every seconds
-                return true;
+
+        this.onEverySecond = function () {
+            /* An interval handler running every second */
+            var stat = this.xmppstatus.getStatus();
+            this.idle_seconds++;
+            if (this.idle_seconds > this.csi_waiting_time && !this.inactive) {
+                this.sendCSI(INACTIVE);
+            }
+            if (this.auto_away > 0 && this.idle_seconds > this.auto_away && stat !== 'away' && stat !== 'xa') {
+                this.auto_changed_status = true;
+                this.xmppstatus.setStatus('away');
+            } else if (this.auto_xa > 0 && this.idle_seconds > this.auto_xa && stat !== 'xa') {
+                this.auto_changed_status = true;
+                this.xmppstatus.setStatus('xa');
             }
             }
         };
         };
 
 
+        this.registerIntervalHandler = function () {
+            /* Set an interval of one second and register a handler for it.
+             * Required for the auto_away, auto_xa and csi_waiting_time features.
+             */
+            if (this.auto_away < 1 && this.auto_xa < 1 && this.csi_waiting_time < 1) {
+                // Waiting time of less then one second means features aren't used.
+                return;
+            }
+            this.idle_seconds = 0;
+            this.auto_changed_status = false; // Was the user's status changed by converse.js?
+            $(window).on('click mousemove keypress focus'+unloadevent , this.onUserActivity.bind(this));
+            window.setInterval(this.onEverySecond.bind(this), 1000);
+        };
 		
 		
         this.playNotification = function () {
         this.playNotification = function () {
             var audio;
             var audio;
@@ -842,7 +847,7 @@
             this.enableCarbons();
             this.enableCarbons();
             this.initStatus($.proxy(function () {
             this.initStatus($.proxy(function () {
                 this.registerPingHandler();
                 this.registerPingHandler();
-                this.registerAutoAwayHandler();				
+                this.registerIntervalHandler();				
                 this.chatboxes.onConnected();
                 this.chatboxes.onConnected();
                 this.giveFeedback(__('Contacts'));
                 this.giveFeedback(__('Contacts'));
                 if (this.callback) {
                 if (this.callback) {
@@ -1206,9 +1211,7 @@
                     );
                     );
                 this.renderToolbar().renderAvatar();
                 this.renderToolbar().renderAvatar();
                 converse.emit('chatBoxOpened', this);
                 converse.emit('chatBoxOpened', this);
-                setTimeout(function () {
-                    converse.refreshWebkit();
-                }, 50);
+                setTimeout(converse.refreshWebkit, 50);
                 return this.showStatusMessage();
                 return this.showStatusMessage();
             },
             },
 
 
@@ -2543,9 +2546,7 @@
                 this.$el.attr('id', this.model.get('box_id'))
                 this.$el.attr('id', this.model.get('box_id'))
                         .html(converse.templates.chatroom(this.model.toJSON()));
                         .html(converse.templates.chatroom(this.model.toJSON()));
                 this.renderChatArea();
                 this.renderChatArea();
-                setTimeout(function () {
-                    converse.refreshWebkit();
-                }, 50);
+                setTimeout(converse.refreshWebkit, 50);
                 return this;
                 return this;
             },
             },
 
 
@@ -5720,7 +5721,7 @@
              */
              */
             if (this.keepalive) {
             if (this.keepalive) {
                 try {
                 try {
-                    return this.connection.restore(null, this.onConnectStatusChanged);
+                    return this.connection.restore(undefined, this.onConnectStatusChanged);
                 } catch (e) {
                 } catch (e) {
                     converse.log("Could not restore sessions. Error message: "+e.message);
                     converse.log("Could not restore sessions. Error message: "+e.message);
                 }
                 }
@@ -5749,7 +5750,7 @@
                     throw new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both.");
                     throw new Error("initConnection: you must supply a value for either the bosh_service_url or websocket_url or both.");
                 }
                 }
                 if (('WebSocket' in window || 'MozWebSocket' in window) && this.websocket_url) {
                 if (('WebSocket' in window || 'MozWebSocket' in window) && this.websocket_url) {
-                    this.connection = new Strophe.Connection(this.websocket_url, {'keepalive': this.keepalive});
+                    this.connection = new Strophe.Connection(this.websocket_url);
                 } else if (this.bosh_service_url) {
                 } else if (this.bosh_service_url) {
                     this.connection = new Strophe.Connection(this.bosh_service_url, {'keepalive': this.keepalive});
                     this.connection = new Strophe.Connection(this.bosh_service_url, {'keepalive': this.keepalive});
                 } else {
                 } else {

+ 1 - 0
docs/CHANGES.rst

@@ -23,6 +23,7 @@ Changelog
 * Refactored in order to remove the strophe.roster.js dependency. [jcbrand]
 * Refactored in order to remove the strophe.roster.js dependency. [jcbrand]
 * Refactored the plugin architecture. Add `overrides` convention for
 * Refactored the plugin architecture. Add `overrides` convention for
   automatically overriding converse.js's methods and Backbone views and models. [jcbrand]
   automatically overriding converse.js's methods and Backbone views and models. [jcbrand]
+* Decouple automatic away and XEP-0352 support. [jcbrand]
 
 
 0.9.3 (2015-05-01)
 0.9.3 (2015-05-01)
 ------------------
 ------------------

+ 22 - 7
docs/source/configuration.rst

@@ -180,21 +180,22 @@ auto_away
 
 
 * Default:  ``0``
 * Default:  ``0``
 
 
-This option can be used to let converse.js automatically change user presence
+The amount of seconds after which the user's presence status should
+automatically become ``away``.
 
 
-This set the number a seconds before user presence become ``away``
-If the value if negative or ``0``, the function is disabled.
+If the user's status is ``extended away``, it won't be changed to ``away``.
+
+If the given value is negative or ``0``, this option is disabled.
 
 
 auto_xa
 auto_xa
 -------
 -------
 
 
 * Default:  ``0``
 * Default:  ``0``
 
 
-This option can be used to let converse.js automatically change user presence
+The amount of seconds after which the user's presence status should
+automatically become ``extended away``.
 
 
-This set the number a seconds before user presence become ``xa`` (eXtended Away)
-The value must be greater than ``auto_away``
-If the value if negative or ``0``, the function is disabled.
+If the value is negative or ``0``, the function is disabled.
 
 
 auto_reconnect
 auto_reconnect
 --------------
 --------------
@@ -253,6 +254,20 @@ This setting can only be used together with ``allow_otr = true``.
     to retrieve your private key and read your all the chat messages in your
     to retrieve your private key and read your all the chat messages in your
     current session. Previous sessions however cannot be decrypted.
     current session. Previous sessions however cannot be decrypted.
 
 
+csi_waiting_time
+----------------
+
+* Default: ``0``
+
+This option adds support for **XEP-0085 Chat State Indication**.
+
+If converse.js is idle for the configured amount of seconds, a chat state
+indication of ``inactive`` will be sent out to the XMPP server (if the server
+supports CSI).
+
+Afterwards, ss soon as there is any activity (for example, the mouse moves),
+a chat state indication of ``active`` will be sent out.
+
 debug
 debug
 -----
 -----
 
 

+ 73 - 0
spec/converse.js

@@ -57,6 +57,79 @@
             });
             });
         });
         });
 
 
+        describe("A chat state indication", function () {
+
+            it("are sent out when the client becomes or stops being idle", function () {
+                spyOn(converse, 'sendCSI').andCallThrough();
+                var sent_stanza;
+                spyOn(converse.connection, 'send').andCallFake(function (stanza) {
+                    sent_stanza = stanza;
+                });
+                var i = 0;
+                converse.idle_seconds = 0; // Usually initialized by registerIntervalHandler
+                converse.features['urn:xmpp:csi:0'] = true; // Mock that the server supports CSI
+
+                converse.csi_waiting_time = 3; // The relevant config option
+                while (i <= converse.csi_waiting_time) {
+                    expect(converse.sendCSI).not.toHaveBeenCalled();
+                    converse.onEverySecond();
+                    i++;
+                }
+                expect(converse.sendCSI).toHaveBeenCalledWith('inactive');
+                expect(sent_stanza.toLocaleString()).toBe(
+                    "<inactive xmlns='urn:xmpp:csi:0'/>"
+                );
+                converse.onUserActivity();
+                expect(converse.sendCSI).toHaveBeenCalledWith('active');
+                expect(sent_stanza.toLocaleString()).toBe(
+                    "<active xmlns='urn:xmpp:csi:0'/>"
+                );
+
+                // Reset values
+                converse.csi_waiting_time = 0;
+                converse.features['urn:xmpp:csi:0'] = false;
+            });
+        });
+
+        describe("Automatic status change", function () {
+
+            it("happens when the client is idle for long enough", function () {
+                var i = 0;
+                // Usually initialized by registerIntervalHandler
+                converse.idle_seconds = 0; 
+                converse.auto_changed_status = false;
+
+                // The relevant config options
+                converse.auto_away = 3;
+                converse.auto_xa = 6;
+
+                expect(converse.xmppstatus.getStatus()).toBe('online');
+
+                while (i <= converse.auto_away) {
+                    converse.onEverySecond();
+                    i++;
+                }
+                expect(converse.auto_changed_status).toBe(true);
+
+                while (i <= converse.auto_xa) {
+                    expect(converse.xmppstatus.getStatus()).toBe('away');
+                    converse.onEverySecond();
+                    i++;
+                }
+                expect(converse.xmppstatus.getStatus()).toBe('xa');
+                expect(converse.auto_changed_status).toBe(true);
+
+                converse.onUserActivity();
+                expect(converse.xmppstatus.getStatus()).toBe('online');
+                expect(converse.auto_changed_status).toBe(false);
+
+                // Reset values
+                converse.auto_away = 0;
+                converse.auto_xa = 0;
+                converse.auto_changed_status = false;
+            });
+        });
+
         describe("The \"tokens\" API", $.proxy(function () {
         describe("The \"tokens\" API", $.proxy(function () {
             beforeEach(function () {
             beforeEach(function () {
                 test_utils.closeAllChatBoxes();
                 test_utils.closeAllChatBoxes();