Browse Source

Keep a local customized copy of strophe.roster.js

The new changes made to strophe.roster.js are incompatible with the way
converse.js works.

Will likely replace strophe.roster.js completely.
JC Brand 10 years ago
parent
commit
7e4c1d6d8d
3 changed files with 449 additions and 3 deletions
  1. 1 2
      bower.json
  2. 1 1
      main.js
  3. 447 0
      src/strophe.roster.js

+ 1 - 2
bower.json

@@ -16,7 +16,6 @@
     "backbone.browserStorage": "*",
     "backbone.overview": "*",
     "strophe": "~1.1.3",
-    "strophe.roster": "https://raw.github.com/strophe/strophejs-plugins/b1f364eb6e854ffe844c57add38e885cfeb9b498/roster/strophe.roster.js",
     "strophe.muc": "https://raw.githubusercontent.com/strophe/strophejs-plugins/master/muc/strophe.muc.js",
     "otr": "0.2.12",
     "crypto-js-evanvosberg": "~3.1.2",
@@ -30,7 +29,7 @@
     "bootstrapJS": "https://raw.githubusercontent.com/jcbrand/bootstrap/7d96a5f60d26c67b5348b270a775518b96a702c8/dist/js/bootstrap.js",
     "fontawesome": "~4.1.0",
     "typeahead.js": "https://raw.githubusercontent.com/jcbrand/typeahead.js/eedfb10505dd3a20123d1fafc07c1352d83f0ab3/dist/typeahead.jquery.js",
-    "strophejs-plugins": "~0.0.4"
+    "strophejs-plugins": "git@github.com:strophe/strophejs-plugins.git#a56421ff4ecf0807113ab48c46728715597df599"
   },
   "exportsOverride": {}
 }

+ 1 - 1
main.js

@@ -17,7 +17,7 @@ config = {
         "strophe":                  "components/strophe/strophe",
         "strophe.disco":            "components/strophejs-plugins/disco/strophe.disco",
         "strophe.muc":              "components/strophe.muc/index",
-        "strophe.roster":           "components/strophe.roster/index",
+        "strophe.roster":           "src/strophe.roster",
         "strophe.vcard":            "components/strophejs-plugins/vcard/strophe.vcard",
         "text":                     'components/requirejs-text/text',
         "tpl":                      'components/requirejs-tpl-jcbrand/tpl',

+ 447 - 0
src/strophe.roster.js

@@ -0,0 +1,447 @@
+/*
+  Copyright 2010, François de Metz <francois@2metz.fr>
+*/
+/**
+ * Roster Plugin
+ * Allow easily roster management
+ *
+ *  Features
+ *  * Get roster from server
+ *  * handle presence
+ *  * handle roster iq
+ *  * subscribe/unsubscribe
+ *  * authorize/unauthorize
+ *  * roster versioning (xep 237)
+ */
+Strophe.addConnectionPlugin('roster',
+{
+    /** Function: init
+     * Plugin init
+     *
+     * Parameters:
+     *   (Strophe.Connection) conn - Strophe connection
+     */
+    init: function(conn)
+    {
+        this._connection = conn;
+        this._callbacks = [];
+        /** Property: items
+         *  Roster items
+         *  [
+         *    {
+         *        name         : "",
+         *        jid          : "",
+         *        subscription : "",
+         *        ask          : "",
+         *        groups       : ["", ""],
+         *        resources    : {
+         *            myresource : {
+         *                show   : "",
+         *                status : "",
+         *                priority : ""
+         *            }
+         *        }
+         *    }
+         * ]
+         */
+        this.items = [];
+        /** Property: ver
+        * current roster revision
+        * always null if server doesn't support xep 237
+        */
+        this.ver = null;
+        // Override the connect and attach methods to always add presence and roster handlers.
+        // They are removed when the connection disconnects, so must be added on connection.
+        var oldCallback, roster = this, _connect = conn.connect, _attach = conn.attach;
+        var newCallback = function(status)
+        {
+            if (status == Strophe.Status.ATTACHED || status == Strophe.Status.CONNECTED)
+            {
+                try
+                {
+                    // Presence subscription
+                    conn.addHandler(roster._onReceivePresence.bind(roster), null, 'presence', null, null, null);
+                    conn.addHandler(roster._onReceiveIQ.bind(roster), Strophe.NS.ROSTER, 'iq', "set", null, null);
+                }
+                catch (e)
+                {
+                    Strophe.error(e);
+                }
+            }
+            if (typeof oldCallback === "function") {
+                oldCallback.apply(this, arguments);
+            }
+        };
+        conn.connect = function(jid, pass, callback, wait, hold)
+        {
+            oldCallback = callback;
+            if (typeof jid  == "undefined")
+                jid  = null;
+            if (typeof pass == "undefined")
+                pass = null;
+            callback = newCallback;
+            _connect.apply(conn, [jid, pass, callback, wait, hold]);
+        };
+        conn.attach = function(jid, sid, rid, callback, wait, hold, wind)
+        {
+            oldCallback = callback;
+            if (typeof jid == "undefined")
+                jid = null;
+            if (typeof sid == "undefined")
+                sid = null;
+            if (typeof rid == "undefined")
+                rid = null;
+            callback = newCallback;
+            _attach.apply(conn, [jid, sid, rid, callback, wait, hold, wind]);
+        };
+
+        Strophe.addNamespace('ROSTER_VER', 'urn:xmpp:features:rosterver');
+        Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick');
+    },
+    /** Function: supportVersioning
+     * return true if roster versioning is enabled on server
+     */
+    supportVersioning: function()
+    {
+        return (this._connection.features && this._connection.features.getElementsByTagName('ver').length > 0);
+    },
+    /** Function: get
+     * Get Roster on server
+     *
+     * Parameters:
+     *   (Function) userCallback - callback on roster result
+     *   (String) ver - current rev of roster
+     *      (only used if roster versioning is enabled)
+     *   (Array) items - initial items of ver
+     *      (only used if roster versioning is enabled)
+     *     In browser context you can use sessionStorage
+     *     to store your roster in json (JSON.stringify())
+     */
+    get: function(userCallback, ver, items)
+    {
+        var attrs = {xmlns: Strophe.NS.ROSTER};
+        if (this.supportVersioning())
+        {
+            // empty rev because i want an rev attribute in the result
+            attrs.ver = ver || '';
+            this.items = items || [];
+        }
+        var iq = $iq({type: 'get',  'id' : this._connection.getUniqueId('roster')}).c('query', attrs);
+        return this._connection.sendIQ(iq,
+                                this._onReceiveRosterSuccess.bind(this, userCallback),
+                                this._onReceiveRosterError.bind(this, userCallback));
+    },
+    /** Function: registerCallback
+     * register callback on roster (presence and iq)
+     *
+     * Parameters:
+     *   (Function) call_back
+     */
+    registerCallback: function(call_back)
+    {
+        this._callbacks.push(call_back);
+    },
+    /** Function: findItem
+     * Find item by JID
+     *
+     * Parameters:
+     *     (String) jid
+     */
+    findItem : function(jid)
+    {
+        try {
+            for (var i = 0; i < this.items.length; i++)
+            {
+                if (this.items[i] && this.items[i].jid == jid)
+                {
+                    return this.items[i];
+                }
+            }
+        } catch (e)
+        {
+            Strophe.error(e);
+        }
+        return false;
+    },
+    /** Function: removeItem
+     * Remove item by JID
+     *
+     * Parameters:
+     *     (String) jid
+     */
+    removeItem : function(jid)
+    {
+        for (var i = 0; i < this.items.length; i++)
+        {
+            if (this.items[i] && this.items[i].jid == jid)
+            {
+                this.items.splice(i, 1);
+                return true;
+            }
+        }
+        return false;
+    },
+    /** Function: subscribe
+     * Subscribe presence
+     *
+     * Parameters:
+     *     (String) jid
+     *     (String) message (optional)
+     *     (String) nick  (optional)
+     */
+    subscribe: function(jid, message, nick) {
+        var pres = $pres({to: jid, type: "subscribe"});
+        if (message && message !== "") {
+            pres.c("status").t(message).up();
+        }
+        if (nick && nick !== "") {
+            pres.c('nick', {'xmlns': Strophe.NS.NICK}).t(nick).up();
+        }
+        this._connection.send(pres);
+    },
+    /** Function: unsubscribe
+     * Unsubscribe presence
+     *
+     * Parameters:
+     *     (String) jid
+     *     (String) message
+     */
+    unsubscribe: function(jid, message)
+    {
+        var pres = $pres({to: jid, type: "unsubscribe"});
+        if (message && message !== "")
+            pres.c("status").t(message);
+        this._connection.send(pres);
+    },
+    /** Function: authorize
+     * Authorize presence subscription
+     *
+     * Parameters:
+     *     (String) jid
+     *     (String) message
+     */
+    authorize: function(jid, message)
+    {
+        var pres = $pres({to: jid, type: "subscribed"});
+        if (message && message !== "")
+            pres.c("status").t(message);
+        this._connection.send(pres);
+    },
+    /** Function: unauthorize
+     * Unauthorize presence subscription
+     *
+     * Parameters:
+     *     (String) jid
+     *     (String) message
+     */
+    unauthorize: function(jid, message)
+    {
+        var pres = $pres({to: jid, type: "unsubscribed"});
+        if (message && message !== "")
+            pres.c("status").t(message);
+        this._connection.send(pres);
+    },
+    /** Function: add
+     * Add roster item
+     *
+     * Parameters:
+     *   (String) jid - item jid
+     *   (String) name - name
+     *   (Array) groups
+     *   (Function) call_back
+     */
+    add: function(jid, name, groups, call_back)
+    {
+        var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: jid,
+                                                                                      name: name});
+        for (var i = 0; i < groups.length; i++)
+        {
+            iq.c('group').t(groups[i]).up();
+        }
+        this._connection.sendIQ(iq, call_back, call_back);
+    },
+    /** Function: update
+     * Update roster item
+     *
+     * Parameters:
+     *   (String) jid - item jid
+     *   (String) name - name
+     *   (Array) groups
+     *   (Function) call_back
+     */
+    update: function(jid, name, groups, call_back)
+    {
+        var item = this.findItem(jid);
+        if (!item)
+        {
+            throw "item not found";
+        }
+        var newName = name || item.name;
+        var newGroups = groups || item.groups;
+        var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid,
+                                                                                      name: newName});
+        for (var i = 0; i < newGroups.length; i++)
+        {
+            iq.c('group').t(newGroups[i]).up();
+        }
+        return this._connection.sendIQ(iq, call_back, call_back);
+    },
+    /** Function: remove
+     * Remove roster item
+     *
+     * Parameters:
+     *   (String) jid - item jid
+     *   (Function) call_back
+     */
+    remove: function(jid, call_back)
+    {
+        var item = this.findItem(jid);
+        if (!item)
+        {
+            throw "item not found";
+        }
+        var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid,
+                                                                                      subscription: "remove"});
+        this._connection.sendIQ(iq, call_back, call_back);
+    },
+    /** PrivateFunction: _onReceiveRosterSuccess
+     *
+     */
+    _onReceiveRosterSuccess: function(userCallback, stanza)
+    {
+        this._updateItems(stanza);
+        if (typeof userCallback === "function") {
+            userCallback(this.items);
+        }
+    },
+    /** PrivateFunction: _onReceiveRosterError
+     *
+     */
+    _onReceiveRosterError: function(userCallback, stanza)
+    {
+        userCallback(this.items);
+    },
+    /** PrivateFunction: _onReceivePresence
+     * Handle presence
+     */
+    _onReceivePresence : function(presence)
+    {
+        // TODO: from is optional
+        var jid = presence.getAttribute('from');
+        var from = Strophe.getBareJidFromJid(jid);
+        var item = this.findItem(from);
+        // not in roster
+        if (!item)
+        {
+            return true;
+        }
+        var type = presence.getAttribute('type');
+        if (type == 'unavailable')
+        {
+            delete item.resources[Strophe.getResourceFromJid(jid)];
+        }
+        else if (!type)
+        {
+            // TODO: add timestamp
+            item.resources[Strophe.getResourceFromJid(jid)] = {
+                show     : (presence.getElementsByTagName('show').length !== 0) ? Strophe.getText(presence.getElementsByTagName('show')[0]) : "",
+                status   : (presence.getElementsByTagName('status').length !== 0) ? Strophe.getText(presence.getElementsByTagName('status')[0]) : "",
+                priority : (presence.getElementsByTagName('priority').length !== 0) ? Strophe.getText(presence.getElementsByTagName('priority')[0]) : ""
+            };
+        }
+        else
+        {
+            // Stanza is not a presence notification. (It's probably a subscription type stanza.)
+            return true;
+        }
+        this._call_backs(this.items, item);
+        return true;
+    },
+    /** PrivateFunction: _call_backs
+     *
+     */
+    _call_backs : function(items, item)
+    {
+        for (var i = 0; i < this._callbacks.length; i++) // [].forEach my love ...
+        {
+            this._callbacks[i](items, item);
+        }
+    },
+    /** PrivateFunction: _onReceiveIQ
+     * Handle roster push.
+     */
+    _onReceiveIQ : function(iq)
+    {
+        var id = iq.getAttribute('id');
+        var from = iq.getAttribute('from');
+        // Receiving client MUST ignore stanza unless it has no from or from = user's JID.
+        if (from && from !== "" && from != this._connection.jid && from != Strophe.getBareJidFromJid(this._connection.jid))
+            return true;
+        var iqresult = $iq({type: 'result', id: id, from: this._connection.jid});
+        this._connection.send(iqresult);
+        this._updateItems(iq);
+        return true;
+    },
+    /** PrivateFunction: _updateItems
+     * Update items from iq
+     */
+    _updateItems : function(iq)
+    {
+        var query = iq.getElementsByTagName('query');
+        if (query.length !== 0)
+        {
+            this.ver = query.item(0).getAttribute('ver');
+            var self = this;
+            Strophe.forEachChild(query.item(0), 'item',
+                function (item)
+                {
+                    self._updateItem(item);
+                }
+           );
+        }
+        this._call_backs(this.items);
+    },
+    /** PrivateFunction: _updateItem
+     * Update internal representation of roster item
+     */
+    _updateItem : function(item)
+    {
+        var jid           = item.getAttribute("jid");
+        var name          = item.getAttribute("name");
+        var subscription  = item.getAttribute("subscription");
+        var ask           = item.getAttribute("ask");
+        var groups        = [];
+        Strophe.forEachChild(item, 'group',
+            function(group)
+            {
+                groups.push(Strophe.getText(group));
+            }
+        );
+
+        if (subscription == "remove")
+        {
+            this.removeItem(jid);
+            return;
+        }
+
+        item = this.findItem(jid);
+        if (!item)
+        {
+            this.items.push({
+                name         : name,
+                jid          : jid,
+                subscription : subscription,
+                ask          : ask,
+                groups       : groups,
+                resources    : {}
+            });
+        }
+        else
+        {
+            item.name = name;
+            item.subscription = subscription;
+            item.ask = ask;
+            item.groups = groups;
+        }
+    }
+});