diff --git a/src/scripts/strophe/plugins/caps.js b/src/scripts/strophe/plugins/caps.js index 02e9cc0a..31952b6e 100755 --- a/src/scripts/strophe/plugins/caps.js +++ b/src/scripts/strophe/plugins/caps.js @@ -1,240 +1,103 @@ -/** - * Entity Capabilities (XEP-0115) - * - * Depends on disco plugin. - * - * See: http://xmpp.org/extensions/xep-0115.html - * - * Authors: - * - Michael Weibel - * - * Copyright: - * - Michael Weibel - */ +/* +# This plugin is distributed under the terms of the MIT licence. +# Please see the LICENCE file for details. +# +# Copyright (c) +# Markus Kohlhase, 2011 +# Adán Sánchez de Pedro Crespo, 2014 - Strophe.addConnectionPlugin('caps', { - /** Constant: HASH - * Hash used - * - * Currently only sha-1 is supported. - */ - HASH: 'sha-1', - /** Variable: node - * Client which is being used. - * - * Can be overwritten as soon as Strophe has been initialized. - */ - node: 'http://strophe.im/strophejs/', - /** PrivateVariable: _ver - * Own generated version string - */ - _ver: '', - /** PrivateVariable: _connection - * Strophe connection - */ - _connection: null, - /** PrivateVariable: _knownCapabilities - * A hashtable containing version-strings and their capabilities, serialized - * as string. - * - * TODO: Maybe those caps shouldn't be serialized. - */ - _knownCapabilities: {}, - /** PrivateVariable: _jidVerIndex - * A hashtable containing jids and their versions for better lookup of capabilities. - */ - _jidVerIndex: {}, +# File: strophe.caps.js +# A Strophe plugin for ( http://xmpp.org/extensions/xep-0115.html ) - /** Function: init - * Initialize plugin: - * - Add caps namespace - * - Add caps feature to disco plugin - * - Add handler for caps stanzas - * - * Parameters: - * (Strophe.Connection) conn - Strophe connection - */ - init: function(conn) { - this._connection = conn; +# NOTE: This plugin has following dependencies: +# +# - strophe.disco.js (by François de Metz) +# - sha1.js +*/ - Strophe.addNamespace('CAPS', 'http://jabber.org/protocol/caps'); - if (!this._connection.disco) { - throw "Caps plugin requires the disco plugin to be installed."; - } +Strophe.addConnectionPlugin('caps', +{ + _connection: null, + + init: function (conn) { + this._conn = conn; + Strophe.addNamespace('CAPS', 'http://jabber.org/protocol/caps'); + if (this._conn.disco === void 0) throw new Error('disco plugin required!'); + if (b64_sha1 === void 0) throw new Error('SHA-1 library required!'); + this._conn.disco.addFeature(Strophe.NS.CAPS); + this._conn.disco.addFeature(Strophe.NS.DISCO_INFO); + this._conn.disco.addFeature(Strophe.NS.DISCO_ITEMS); + if (this._conn.disco._identities.length === 0) { + return this._conn.disco.addIdentity('client', Lungo.Core.environment().isMobile ? 'phone' : 'pc', 'http://loqui.im', ''); + } + }, + + addFeature: function(feature) { + return this._conn.disco.addFeature(feature); + }, + + removeFeature: function(feature) { + return this._conn.disco.removeFeature(feature); + }, + + sendPres: function() { + return this._conn.send($pres().cnode(this.createCapsNode().tree())); + }, + + createCapsNode: function() { + var node; + if (this._conn.disco._identities.length > 0) { + node = this._conn.disco._identities[0].name || ""; + } else { + node = dummyId.name; + } + return $build("c", { + xmlns: Strophe.NS.CAPS, + hash: "sha-1", + node: node, + ver: this.generateVerificationString() + }); + }, + + propertySort: function(array, property) { + return array.sort(function(a, b) { + if (a[property] > b[property]) { + return -1; + } else { + return 1; + } + }); + }, + + generateVerificationString: function() { + var S, features, i, id, ids, k, key, ns, _i, _j, _k, _len, _len2, _len3, _ref, _ref2; + ids = []; + _ref = this._conn.disco._identities; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + i = _ref[_i]; + ids.push(i); + } + features = []; + _ref2 = this._conn.disco._features; + for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { + k = _ref2[_j]; + features.push(k); + } + S = ""; + this.propertySort(ids, "category"); + this.propertySort(ids, "type"); + this.propertySort(ids, "lang"); + for (key in ids) { + id = ids[key]; + S += "" + id.category + "/" + id.type + "/" + id.lang + "/" + id.name + "<"; + } + features.sort(); + for (_k = 0, _len3 = features.length; _k < _len3; _k++) { + ns = features[_k]; + S += "" + ns + "<"; + } + return "" + (b64_sha1(S)) + "="; + } - this._connection.disco.addFeature(Strophe.NS.CAPS); - this._connection.addHandler(this._delegateCapabilities.bind(this), Strophe.NS.CAPS); - }, - - /** Function: generateCapsAttrs - * Returns the attributes for generating the "c"-stanza containing the own version - * - * Returns: - * (Object) - attributes - */ - generateCapsAttrs: function() { - return { - 'xmlns': Strophe.NS.CAPS, - 'hash': this.HASH, - 'node': this.node, - 'ver': this.generateVer() - }; - }, - - /** Function: generateVer - * Returns the base64 encoded version string (encoded itself with sha1) - * - * Returns: - * (String) - version - */ - generateVer: function() { - if (this._ver !== "") { - return this._ver; - } - - var ver = "", - identities = this._connection.disco._identities.sort(this._sortIdentities), - identitiesLen = identities.length, - features = this._connection.disco._features.sort(), - featuresLen = features.length; - for(var i = 0; i < identitiesLen; i++) { - var curIdent = identities[i]; - ver += curIdent.category + "/" + curIdent.type + "/" + curIdent.lang + "/" + curIdent.name + "<"; - } - for(var i = 0; i < featuresLen; i++) { - ver += features[i] + '<'; - } - - this._ver = b64_sha1(ver); - return this._ver; - }, - - /** Function: getCapabilitiesByJid - * Returns serialized capabilities of a jid (if available). - * Otherwise null. - * - * Parameters: - * (String) jid - Jabber id - * - * Returns: - * (String|null) - capabilities, serialized; or null when not available. - */ - getCapabilitiesByJid: function(jid) { - if (this._jidVerIndex[jid]) { - return this._knownCapabilities[this._jidVerIndex[jid]]; - } - return null; - }, - - /** PrivateFunction: _delegateCapabilities - * Checks if the version has already been saved. - * If yes: do nothing. - * If no: Request capabilities - * - * Parameters: - * (Strophe.Builder) stanza - Stanza - * - * Returns: - * (Boolean) - */ - _delegateCapabilities: function(stanza) { - var from = stanza.getAttribute('from'), - c = stanza.querySelector('c'), - ver = c.getAttribute('ver'), - node = c.getAttribute('node'); - if (!this._knownCapabilities[ver]) { - return this._requestCapabilities(from, node, ver); - } else { - this._jidVerIndex[from] = ver; - } - if (!this._jidVerIndex[from] || !this._jidVerIndex[from] !== ver) { - this._jidVerIndex[from] = ver; - } - return true; - }, - - /** PrivateFunction: _requestCapabilities - * Requests capabilities from the one which sent the caps-info stanza. - * This is done using disco info. - * - * Additionally, it registers a handler for handling the reply. - * - * Parameters: - * (String) to - Destination jid - * (String) node - Node attribute of the caps-stanza - * (String) ver - Version of the caps-stanza - * - * Returns: - * (Boolean) - true - */ - _requestCapabilities: function(to, node, ver) { - if (to !== this._connection.jid) { - var id = this._connection.disco.info(to, node + '#' + ver); - this._connection.addHandler(this._handleDiscoInfoReply.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'result', id, to); - } - return true; - }, - - /** PrivateFunction: _handleDiscoInfoReply - * Parses the disco info reply and adds the version & it's capabilities to the _knownCapabilities variable. - * Additionally, it adds the jid & the version to the _jidVerIndex variable for a better lookup. - * - * Parameters: - * (Strophe.Builder) stanza - Disco info stanza - * - * Returns: - * (Boolean) - false, to automatically remove the handler. - */ - _handleDiscoInfoReply: function(stanza) { - var query = stanza.querySelector('query'), - node = query.getAttribute('node').split('#'), - ver = node[1], - from = stanza.getAttribute('from'); - if (!this._knownCapabilities[ver]) { - var childNodes = query.childNodes, - childNodesLen = childNodes.length; - this._knownCapabilities[ver] = []; - for(var i = 0; i < childNodesLen; i++) { - var node =childNodes[i]; - this._knownCapabilities[ver].push({name: node.nodeName, attributes: node.attributes}); - } - this._jidVerIndex[from] = ver; - } else if (!this._jidVerIndex[from] || !this._jidVerIndex[from] !== ver) { - this._jidVerIndex[from] = ver; - } - return false; - }, - - /** PrivateFunction: _sortIdentities - * Sorts two identities according the sorting requirements in XEP-0115. - * - * Parameters: - * (Object) a - Identity a - * (Object) b - Identity b - * - * Returns: - * (Integer) - 1, 0 or -1; according to which one's greater. - */ - _sortIdentities: function(a, b) { - if (a.category > b.category) { - return 1; - } - if (a.category < b.category) { - return -1; - } - if (a.type > b.type) { - return 1; - } - if (a.type < b.type) { - return -1; - } - if (a.lang > b.lang) { - return 1; - } - if (a.lang < b.lang) { - return -1; - } - return 0; - } - }); +}); diff --git a/src/scripts/strophe/plugins/disco.js b/src/scripts/strophe/plugins/disco.js index e568be04..cb67a6cc 100755 --- a/src/scripts/strophe/plugins/disco.js +++ b/src/scripts/strophe/plugins/disco.js @@ -7,34 +7,6 @@ * Implement http://xmpp.org/extensions/xep-0030.html * TODO: manage node hierarchies, and node on info request */ - -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define("strophe.disco", [ - "strophe" - ], function (Strophe) { - factory( - Strophe.Strophe, - Strophe.$build, - Strophe.$iq , - Strophe.$msg, - Strophe.$pres - ); - return Strophe; - }); - } else { - // Browser globals - factory( - root.Strophe, - root.$build, - root.$iq , - root.$msg, - root.$pres - ); - } -}(this, function (Strophe, $build, $iq, $msg, $pres) { - Strophe.addConnectionPlugin('disco', { _connection: null, @@ -49,14 +21,19 @@ Strophe.addConnectionPlugin('disco', */ init: function(conn) { - this._connection = conn; + this._connection = conn; this._identities = []; this._features = []; this._items = []; - // disco info - conn.addHandler(this._onDiscoInfo.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'get', null, null); - // disco items - conn.addHandler(this._onDiscoItems.bind(this), Strophe.NS.DISCO_ITEMS, 'iq', 'get', null, null); + }, + handlify: function(handler) + { + return [ + this._connection.addHandler(this._onDiscoInfo.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'get', null, null), + this._connection.addHandler(this._onDiscoItems.bind(this), Strophe.NS.DISCO_ITEMS, 'iq', 'get', null, null), + this._connection.addHandler(handler, Strophe.NS.DISCO_INFO, 'iq', 'result', null, null), + this._connection.addHandler(handler, Strophe.NS.DISCO_ITEMS, 'iq', 'result', null, null) + ]; }, /** Function: addIdentity * See http://xmpp.org/registrar/disco-categories.html @@ -115,7 +92,7 @@ Strophe.addConnectionPlugin('disco', for (var i=0; i + Nathan Zorn *Complete CoffeeScript rewrite: - Andreas Guth + Andreas Guth */ -var Occupant, RoomConfig, XmppRoom, - hasProp = {}.hasOwnProperty, - bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - -Strophe.addConnectionPlugin('muc', { - _connection: null, - rooms: {}, - roomNames: [], - - /*Function - Initialize the MUC plugin. Sets the correct connection object and - extends the namesace. - */ - init: function(conn) { - this._connection = conn; - this._muc_handler = null; - Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner"); - Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin"); - Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user"); - Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig"); - return Strophe.addNamespace('MUC_REGISTER', "jabber:iq:register"); - }, - - /*Function - Join a multi-user chat room - Parameters: - (String) room - The multi-user chat room to join. - (String) nick - The nickname to use in the chat room. Optional - (Function) msg_handler_cb - The function call to handle messages from the - specified chat room. - (Function) pres_handler_cb - The function call back to handle presence - in the chat room. - (Function) roster_cb - The function call to handle roster info in the chat room - (String) password - The optional password to use. (password protected - rooms only) - (Object) history_attrs - Optional attributes for retrieving history - (XML DOM Element) extended_presence - Optional XML for extending presence - */ - join: function(room, nick, msg_handler_cb, pres_handler_cb, roster_cb, password, history_attrs, extended_presence) { - var msg, room_nick; - room_nick = this.test_append_nick(room, nick); - msg = $pres({ - from: this._connection.jid, - to: room_nick - }).c("x", { - xmlns: Strophe.NS.MUC - }); - if (history_attrs != null) { - msg = msg.c("history", history_attrs).up(); - } - if (password != null) { - msg.cnode(Strophe.xmlElement("password", [], password)); - } - if (extended_presence != null) { - msg.up().cnode(extended_presence); - } - if (this._muc_handler == null) { - this._muc_handler = this._connection.addHandler((function(_this) { - return function(stanza) { - var from, handler, handlers, i, id, len, roomname, x, xmlns, xquery; - from = stanza.getAttribute('from'); - if (!from) { - return true; - } - roomname = from.split("/")[0]; - if (!_this.rooms[roomname]) { - return true; - } - room = _this.rooms[roomname]; - handlers = {}; - if (stanza.nodeName === "message") { - handlers = room._message_handlers; - } else if (stanza.nodeName === "presence") { - xquery = stanza.getElementsByTagName("x"); - if (xquery.length > 0) { - for (i = 0, len = xquery.length; i < len; i++) { - x = xquery[i]; - xmlns = x.getAttribute("xmlns"); - if (xmlns && xmlns.match(Strophe.NS.MUC)) { - handlers = room._presence_handlers; - break; + +(function() { + var Occupant, RoomConfig, XmppRoom, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + Strophe.addConnectionPlugin('muc', { + _connection: null, + rooms: {}, + roomNames: [], + + /*Function + Initialize the MUC plugin. Sets the correct connection object and + extends the namesace. + */ + init: function(conn) { + this._connection = conn; + this._muc_handler = null; + Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner"); + Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin"); + Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user"); + Strophe.addNamespace('XEP0249', "jabber:x:conference"); + return Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig"); + }, + + /*Function + Join a multi-user chat room + Parameters: + (String) room - The multi-user chat room to join. + (String) nick - The nickname to use in the chat room. Optional + (Function) msg_handler_cb - The function call to handle messages from the + specified chat room. + (Function) pres_handler_cb - The function call back to handle presence + in the chat room. + (Function) roster_cb - The function call to handle roster info in the chat room + (String) password - The optional password to use. (password protected + rooms only) + (Object) history_attrs - Optional attributes for retrieving history + (XML DOM Element) extended_presence - Optional XML for extending presence + */ + join: function(room, nick, msg_handler_cb, pres_handler_cb, roster_cb, password, history_attrs) { + var msg, room_nick; + room_nick = this.test_append_nick(room, nick); + msg = $pres({ + from: this._connection.jid, + to: room_nick + }).c("x", { + xmlns: Strophe.NS.MUC + }); + if (history_attrs != null) { + msg = msg.c("history", history_attrs).up(); + } + if (password != null) { + msg.cnode(Strophe.xmlElement("password", [], password)); + } + if (typeof extended_presence !== "undefined" && extended_presence !== null) { + msg.up.cnode(extended_presence); + } + if (this._muc_handler == null) { + this._muc_handler = this._connection.addHandler((function(_this) { + return function(stanza) { + var from, handler, handlers, id, roomname, x, xmlns, xquery, _i, _len; + from = stanza.getAttribute('from'); + if (!from) { + return true; + } + roomname = from.split("/")[0]; + if (!_this.rooms[roomname]) { + return true; + } + room = _this.rooms[roomname]; + handlers = {}; + if (stanza.nodeName === "message") { + handlers = room._message_handlers; + } else if (stanza.nodeName === "presence") { + xquery = stanza.getElementsByTagName("x"); + if (xquery.length > 0) { + for (_i = 0, _len = xquery.length; _i < _len; _i++) { + x = xquery[_i]; + xmlns = x.getAttribute("xmlns"); + if (xmlns && xmlns.match(Strophe.NS.MUC)) { + handlers = room._presence_handlers; + break; + } } } } - } - for (id in handlers) { - handler = handlers[id]; - if (!handler(stanza, room)) { - delete handlers[id]; + for (id in handlers) { + handler = handlers[id]; + if (!handler(stanza, room)) { + delete handlers[id]; + } } - } - return true; - }; - })(this)); - } - if (!this.rooms.hasOwnProperty(room)) { - this.rooms[room] = new XmppRoom(this, room, nick, password); + return true; + }; + })(this)); + } + if (!this.rooms.hasOwnProperty(room)) { + this.rooms[room] = new XmppRoom(this, room, nick, password); + this.roomNames.push(room); + } if (pres_handler_cb) { this.rooms[room].addHandler('presence', pres_handler_cb); } @@ -112,1037 +117,895 @@ Strophe.addConnectionPlugin('muc', { if (roster_cb) { this.rooms[room].addHandler('roster', roster_cb); } - this.roomNames.push(room); - } - return this._connection.send(msg); - }, - - /*Function - Leave a multi-user chat room - Parameters: - (String) room - The multi-user chat room to leave. - (String) nick - The nick name used in the room. - (Function) handler_cb - Optional function to handle the successful leave. - (String) exit_msg - optional exit message. - Returns: - iqid - The unique id for the room leave. - */ - leave: function(room, nick, handler_cb, exit_msg) { - var id, presence, presenceid, room_nick; - id = this.roomNames.indexOf(room); - delete this.rooms[room]; - if (id >= 0) { - this.roomNames.splice(id, 1); - if (this.roomNames.length === 0) { - this._connection.deleteHandler(this._muc_handler); - this._muc_handler = null; + return this._connection.send(msg); + }, + + /*Function + Leave a multi-user chat room + Parameters: + (String) room - The multi-user chat room to leave. + (String) nick - The nick name used in the room. + (Function) handler_cb - Optional function to handle the successful leave. + (String) exit_msg - optional exit message. + Returns: + iqid - The unique id for the room leave. + */ + leave: function(room, nick, handler_cb, exit_msg) { + var id, presence, presenceid, room_nick; + id = this.roomNames.indexOf(room); + delete this.rooms[room]; + if (id >= 0) { + this.roomNames.splice(id, 1); + if (this.roomNames.length === 0) { + this._connection.deleteHandler(this._muc_handler); + this._muc_handler = null; + } } - } - room_nick = this.test_append_nick(room, nick); - presenceid = this._connection.getUniqueId(); - presence = $pres({ - type: "unavailable", - id: presenceid, - from: this._connection.jid, - to: room_nick - }); - if (exit_msg != null) { - presence.c("status", exit_msg); - } - if (handler_cb != null) { - this._connection.addHandler(handler_cb, null, "presence", null, presenceid); - } - this._connection.send(presence); - return presenceid; - }, - - /*Function - Parameters: - (String) room - The multi-user chat room name. - (String) nick - The nick name used in the chat room. - (String) message - The plaintext message to send to the room. - (String) html_message - The message to send to the room with html markup. - (String) type - "groupchat" for group chat messages o - "chat" for private chat messages - Returns: - msgiq - the unique id used to send the message - */ - message: function(room, nick, message, html_message, type, msgid) { - var msg, parent, room_nick; - room_nick = this.test_append_nick(room, nick); - type = type || (nick != null ? "chat" : "groupchat"); - msgid = msgid || this._connection.getUniqueId(); - msg = $msg({ - to: room_nick, - from: this._connection.jid, - type: type, - id: msgid - }).c("body").t(message); - msg.up(); - if (html_message != null) { - msg.c("html", { - xmlns: Strophe.NS.XHTML_IM - }).c("body", { - xmlns: Strophe.NS.XHTML - }).h(html_message); - if (msg.node.childNodes.length === 0) { - parent = msg.node.parentNode; - msg.up().up(); - msg.node.removeChild(parent); - } else { - msg.up().up(); + room_nick = this.test_append_nick(room, nick); + presenceid = this._connection.getUniqueId(); + presence = $pres({ + type: "unavailable", + id: presenceid, + from: this._connection.jid, + to: room_nick + }); + if (exit_msg != null) { + presence.c("status", exit_msg); } - } - msg.c("x", { - xmlns: "jabber:x:event" - }).c("composing"); - this._connection.send(msg); - return msgid; - }, - - /*Function - Convenience Function to send a Message to all Occupants - Parameters: - (String) room - The multi-user chat room name. - (String) message - The plaintext message to send to the room. - (String) html_message - The message to send to the room with html markup. - (String) msgid - Optional unique ID which will be set as the 'id' attribute of the stanza - Returns: - msgiq - the unique id used to send the message - */ - groupchat: function(room, message, html_message, msgid) { - return this.message(room, null, message, html_message, void 0, msgid); - }, - - /*Function - Send a mediated invitation. - Parameters: - (String) room - The multi-user chat room name. - (String) receiver - The invitation's receiver. - (String) reason - Optional reason for joining the room. - Returns: - msgiq - the unique id used to send the invitation - */ - invite: function(room, receiver, reason) { - var invitation, msgid; - msgid = this._connection.getUniqueId(); - invitation = $msg({ - from: this._connection.jid, - to: room, - id: msgid - }).c('x', { - xmlns: Strophe.NS.MUC_USER - }).c('invite', { - to: receiver - }); - if (reason != null) { - invitation.c('reason', reason); - } - this._connection.send(invitation); - return msgid; - }, - - /*Function - Send a mediated multiple invitation. - Parameters: - (String) room - The multi-user chat room name. - (Array) receivers - The invitation's receivers. - (String) reason - Optional reason for joining the room. - Returns: - msgiq - the unique id used to send the invitation - */ - multipleInvites: function(room, receivers, reason) { - var i, invitation, len, msgid, receiver; - msgid = this._connection.getUniqueId(); - invitation = $msg({ - from: this._connection.jid, - to: room, - id: msgid - }).c('x', { - xmlns: Strophe.NS.MUC_USER - }); - for (i = 0, len = receivers.length; i < len; i++) { - receiver = receivers[i]; - invitation.c('invite', { + if (handler_cb != null) { + this._connection.addHandler(handler_cb, null, "presence", null, presenceid); + } + this._connection.send(presence); + return presenceid; + }, + + /*Function + Parameters: + (String) room - The multi-user chat room name. + (String) nick - The nick name used in the chat room. + (String) message - The plaintext message to send to the room. + (String) html_message - The message to send to the room with html markup. + (String) type - "groupchat" for group chat messages o + "chat" for private chat messages + Returns: + msgiq - the unique id used to send the message + */ + message: function(room, nick, message, msgId, html_message, type) { + var msg, parent, room_nick; + room_nick = this.test_append_nick(room, nick); + type = type || (nick != null ? "chat" : "groupchat"); + msg = $msg({ + to: room, + from: this._connection.jid, + type: type, + id: msgId + }).c("body", { + /*xmlns: Strophe.NS.CLIENT*/ + }).t(message); + /*msg.up(); + if (html_message != null) { + msg.c("html", { + xmlns: Strophe.NS.XHTML_IM + }).c("body", { + xmlns: Strophe.NS.XHTML + }).h(html_message); + if (msg.node.childNodes.length === 0) { + parent = msg.node.parentNode; + msg.up().up(); + msg.node.removeChild(parent); + } else { + msg.up().up(); + } + }*/ + /*msg.c("x", { + xmlns: "jabber:x:event" + }).c("composing");*/ + this._connection.send(msg); + return msg.tree(); + }, + + /*Function + Convenience Function to send a Message to all Occupants + Parameters: + (String) room - The multi-user chat room name. + (String) message - The plaintext message to send to the room. + (String) html_message - The message to send to the room with html markup. + Returns: + msgiq - the unique id used to send the message + */ + groupchat: function(room, message, html_message) { + return this.message(room, null, message, html_message); + }, + + /*Function + Send a mediated invitation. + Parameters: + (String) room - The multi-user chat room name. + (String) receiver - The invitation's receiver. + (String) reason - Optional reason for joining the room. + Returns: + msgiq - the unique id used to send the invitation + */ + invite: function(room, receiver, reason) { + var invitation, msgid; + msgid = this._connection.getUniqueId(); + invitation = $msg({ + from: this._connection.jid, + to: room, + id: msgid + }).c('x', { + xmlns: Strophe.NS.MUC_USER + }).c('invite', { to: receiver }); if (reason != null) { invitation.c('reason', reason); - invitation.up(); } - invitation.up(); - } - this._connection.send(invitation); - return msgid; - }, - - /*Function - Send a direct invitation. - Parameters: - (String) room - The multi-user chat room name. - (String) receiver - The invitation's receiver. - (String) reason - Optional reason for joining the room. - (String) password - Optional password for the room. - Returns: - msgiq - the unique id used to send the invitation - */ - directInvite: function(room, receiver, reason, password) { - var attrs, invitation, msgid; - msgid = this._connection.getUniqueId(); - attrs = { - xmlns: 'jabber:x:conference', - jid: room - }; - if (reason != null) { - attrs.reason = reason; - } - if (password != null) { - attrs.password = password; - } - invitation = $msg({ - from: this._connection.jid, - to: receiver, - id: msgid - }).c('x', attrs); - this._connection.send(invitation); - return msgid; - }, - - /*Function - Queries a room for a list of occupants - (String) room - The multi-user chat room name. - (Function) success_cb - Optional function to handle the info. - (Function) error_cb - Optional function to handle an error. - Returns: - id - the unique id used to send the info request - */ - queryOccupants: function(room, success_cb, error_cb) { - var attrs, info; - attrs = { - xmlns: Strophe.NS.DISCO_ITEMS - }; - info = $iq({ - from: this._connection.jid, - to: room, - type: 'get' - }).c('query', attrs); - return this._connection.sendIQ(info, success_cb, error_cb); - }, - - /*Function - Start a room configuration. - Parameters: - (String) room - The multi-user chat room name. - (Function) handler_cb - Optional function to handle the config form. - Returns: - id - the unique id used to send the configuration request - */ - configure: function(room, handler_cb, error_cb) { - var config, stanza; - config = $iq({ - to: room, - type: "get" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }); - stanza = config.tree(); - return this._connection.sendIQ(stanza, handler_cb, error_cb); - }, - - /*Function - Cancel the room configuration - Parameters: - (String) room - The multi-user chat room name. - Returns: - id - the unique id used to cancel the configuration. - */ - cancelConfigure: function(room) { - var config, stanza; - config = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }).c("x", { - xmlns: "jabber:x:data", - type: "cancel" - }); - stanza = config.tree(); - return this._connection.sendIQ(stanza); - }, - - /*Function - Save a room configuration. - Parameters: - (String) room - The multi-user chat room name. - (Array) config- Form Object or an array of form elements used to configure the room. - Returns: - id - the unique id used to save the configuration. - */ - saveConfiguration: function(room, config, success_cb, error_cb) { - var conf, i, iq, len, stanza; - iq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }); - if (typeof Strophe.x !== "undefined" && typeof Strophe.x.Form !== "undefined" && config instanceof Strophe.x.Form) { - config.type = "submit"; - iq.cnode(config.toXML()); - } else { - iq.c("x", { + this._connection.send(invitation); + return msgid; + }, + + /*Function + Send a direct invitation. + Parameters: + (String) room - The multi-user chat room name. + (String) receiver - The invitation's receiver. + (String) reason - Optional reason for joining the room. + (String) password - Optional password for the room. + Returns: + msgiq - the unique id used to send the invitation + */ + directInvite: function(room, receiver, reason, password) { + var attrs, invitation, msgid; + msgid = this._connection.getUniqueId(); + attrs = { + xmlns: 'jabber:x:conference', + jid: room + }; + if (reason != null) { + attrs.reason = reason; + } + if (password != null) { + attrs.password = password; + } + invitation = $msg({ + from: this._connection.jid, + to: receiver, + id: msgid + }).c('x', attrs); + this._connection.send(invitation); + return msgid; + }, + + /*Function + Queries a room for a list of occupants + (String) room - The multi-user chat room name. + (Function) success_cb - Optional function to handle the info. + (Function) error_cb - Optional function to handle an error. + Returns: + id - the unique id used to send the info request + */ + queryOccupants: function(room, success_cb, error_cb) { + var attrs, info; + attrs = { + xmlns: Strophe.NS.DISCO_ITEMS + }; + info = $iq({ + from: this._connection.jid, + to: room, + type: 'get' + }).c('query', attrs); + return this._connection.sendIQ(info, success_cb, error_cb); + }, + + /*Function + Start a room configuration. + Parameters: + (String) room - The multi-user chat room name. + (Function) handler_cb - Optional function to handle the config form. + Returns: + id - the unique id used to send the configuration request + */ + configure: function(room, handler_cb, error_cb) { + var config, stanza; + config = $iq({ + to: room, + type: "get" + }).c("query", { + xmlns: Strophe.NS.MUC_OWNER + }); + stanza = config.tree(); + return this._connection.sendIQ(stanza, handler_cb, error_cb); + }, + + /*Function + Cancel the room configuration + Parameters: + (String) room - The multi-user chat room name. + Returns: + id - the unique id used to cancel the configuration. + */ + cancelConfigure: function(room) { + var config, stanza; + config = $iq({ + to: room, + type: "set" + }).c("query", { + xmlns: Strophe.NS.MUC_OWNER + }).c("x", { + xmlns: "jabber:x:data", + type: "cancel" + }); + stanza = config.tree(); + return this._connection.sendIQ(stanza); + }, + + /*Function + Save a room configuration. + Parameters: + (String) room - The multi-user chat room name. + (Array) config- Form Object or an array of form elements used to configure the room. + Returns: + id - the unique id used to save the configuration. + */ + saveConfiguration: function(room, config, success_cb, error_cb) { + var conf, iq, stanza, _i, _len; + iq = $iq({ + to: room, + type: "set" + }).c("query", { + xmlns: Strophe.NS.MUC_OWNER + }); + if (typeof Form !== "undefined" && config instanceof Form) { + config.type = "submit"; + iq.cnode(config.toXML()); + } else { + iq.c("x", { + xmlns: "jabber:x:data", + type: "submit" + }); + for (_i = 0, _len = config.length; _i < _len; _i++) { + conf = config[_i]; + iq.cnode(conf).up(); + } + } + stanza = iq.tree(); + return this._connection.sendIQ(stanza, success_cb, error_cb); + }, + + /*Function + Parameters: + (String) room - The multi-user chat room name. + Returns: + id - the unique id used to create the chat room. + */ + createInstantRoom: function(room, success_cb, error_cb) { + var roomiq; + roomiq = $iq({ + to: room, + type: "set" + }).c("query", { + xmlns: Strophe.NS.MUC_OWNER + }).c("x", { xmlns: "jabber:x:data", type: "submit" }); - for (i = 0, len = config.length; i < len; i++) { - conf = config[i]; - iq.cnode(conf).up(); + return this._connection.sendIQ(roomiq.tree(), success_cb, error_cb); + }, + + /*Function + Set the topic of the chat room. + Parameters: + (String) room - The multi-user chat room name. + (String) topic - Topic message. + */ + setTopic: function(room, topic) { + var msg; + msg = $msg({ + to: room, + from: this._connection.jid, + type: "groupchat" + }).c("subject", { + xmlns: "jabber:client" + }).t(topic); + return this._connection.send(msg.tree()); + }, + + /*Function + Internal Function that Changes the role or affiliation of a member + of a MUC room. This function is used by modifyRole and modifyAffiliation. + The modification can only be done by a room moderator. An error will be + returned if the user doesn't have permission. + Parameters: + (String) room - The multi-user chat room name. + (Object) item - Object with nick and role or jid and affiliation attribute + (String) reason - Optional reason for the change. + (Function) handler_cb - Optional callback for success + (Function) error_cb - Optional callback for error + Returns: + iq - the id of the mode change request. + */ + _modifyPrivilege: function(room, item, reason, handler_cb, error_cb) { + var iq; + iq = $iq({ + to: room, + type: "set" + }).c("query", { + xmlns: Strophe.NS.MUC_ADMIN + }).cnode(item.node); + if (reason != null) { + iq.c("reason", reason); } - } - stanza = iq.tree(); - return this._connection.sendIQ(stanza, success_cb, error_cb); - }, - - /*Function - Parameters: - (String) room - The multi-user chat room name. - Returns: - id - the unique id used to create the chat room. - */ - createInstantRoom: function(room, success_cb, error_cb) { - var roomiq; - roomiq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }).c("x", { - xmlns: "jabber:x:data", - type: "submit" - }); - return this._connection.sendIQ(roomiq.tree(), success_cb, error_cb); - }, - - /*Function - Parameters: - (String) room - The multi-user chat room name. - (Object) config - the configuration. ex: {"muc#roomconfig_publicroom": "0", "muc#roomconfig_persistentroom": "1"} - Returns: - id - the unique id used to create the chat room. - */ - createConfiguredRoom: function(room, config, success_cb, error_cb) { - var k, roomiq, v; - roomiq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_OWNER - }).c("x", { - xmlns: "jabber:x:data", - type: "submit" - }); - roomiq.c('field', { - 'var': 'FORM_TYPE' - }).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up(); - for (k in config) { - if (!hasProp.call(config, k)) continue; - v = config[k]; - roomiq.c('field', { - 'var': k - }).c('value').t(v).up().up(); - } - return this._connection.sendIQ(roomiq.tree(), success_cb, error_cb); - }, - - /*Function - Set the topic of the chat room. - Parameters: - (String) room - The multi-user chat room name. - (String) topic - Topic message. - */ - setTopic: function(room, topic) { - var msg; - msg = $msg({ - to: room, - from: this._connection.jid, - type: "groupchat" - }).c("subject", { - xmlns: "jabber:client" - }).t(topic); - return this._connection.send(msg.tree()); - }, - - /*Function - Internal Function that Changes the role or affiliation of a member - of a MUC room. This function is used by modifyRole and modifyAffiliation. - The modification can only be done by a room moderator. An error will be - returned if the user doesn't have permission. - Parameters: - (String) room - The multi-user chat room name. - (Object) item - Object with nick and role or jid and affiliation attribute - (String) reason - Optional reason for the change. - (Function) handler_cb - Optional callback for success - (Function) error_cb - Optional callback for error - Returns: - iq - the id of the mode change request. - */ - _modifyPrivilege: function(room, item, reason, handler_cb, error_cb) { - var iq; - iq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_ADMIN - }).cnode(item.node); - if (reason != null) { - iq.c("reason", reason); - } - return this._connection.sendIQ(iq.tree(), handler_cb, error_cb); - }, - - /*Function - Changes the role of a member of a MUC room. - The modification can only be done by a room moderator. An error will be - returned if the user doesn't have permission. - Parameters: - (String) room - The multi-user chat room name. - (String) nick - The nick name of the user to modify. - (String) role - The new role of the user. - (String) affiliation - The new affiliation of the user. - (String) reason - Optional reason for the change. - (Function) handler_cb - Optional callback for success - (Function) error_cb - Optional callback for error - Returns: - iq - the id of the mode change request. - */ - modifyRole: function(room, nick, role, reason, handler_cb, error_cb) { - var item; - item = $build("item", { - nick: nick, - role: role - }); - return this._modifyPrivilege(room, item, reason, handler_cb, error_cb); - }, - kick: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'none', reason, handler_cb, error_cb); - }, - voice: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb); - }, - mute: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'visitor', reason, handler_cb, error_cb); - }, - op: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'moderator', reason, handler_cb, error_cb); - }, - deop: function(room, nick, reason, handler_cb, error_cb) { - return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb); - }, - - /*Function - Changes the affiliation of a member of a MUC room. - The modification can only be done by a room moderator. An error will be - returned if the user doesn't have permission. - Parameters: - (String) room - The multi-user chat room name. - (String) jid - The jid of the user to modify. - (String) affiliation - The new affiliation of the user. - (String) reason - Optional reason for the change. - (Function) handler_cb - Optional callback for success - (Function) error_cb - Optional callback for error - Returns: - iq - the id of the mode change request. - */ - modifyAffiliation: function(room, jid, affiliation, reason, handler_cb, error_cb) { - var item; - item = $build("item", { - jid: jid, - affiliation: affiliation - }); - return this._modifyPrivilege(room, item, reason, handler_cb, error_cb); - }, - ban: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'outcast', reason, handler_cb, error_cb); - }, - member: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'member', reason, handler_cb, error_cb); - }, - revoke: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'none', reason, handler_cb, error_cb); - }, - owner: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'owner', reason, handler_cb, error_cb); - }, - admin: function(room, jid, reason, handler_cb, error_cb) { - return this.modifyAffiliation(room, jid, 'admin', reason, handler_cb, error_cb); - }, - - /*Function - Change the current users nick name. - Parameters: - (String) room - The multi-user chat room name. - (String) user - The new nick name. - */ - changeNick: function(room, user) { - var presence, room_nick; - room_nick = this.test_append_nick(room, user); - presence = $pres({ - from: this._connection.jid, - to: room_nick, - id: this._connection.getUniqueId() - }); - return this._connection.send(presence.tree()); - }, - - /*Function - Change the current users status. - Parameters: - (String) room - The multi-user chat room name. - (String) user - The current nick. - (String) show - The new show-text. - (String) status - The new status-text. - */ - setStatus: function(room, user, show, status) { - var presence, room_nick; - room_nick = this.test_append_nick(room, user); - presence = $pres({ - from: this._connection.jid, - to: room_nick - }); - if (show != null) { - presence.c('show', show).up(); - } - if (status != null) { - presence.c('status', status); - } - return this._connection.send(presence.tree()); - }, - - /*Function - Registering with a room. - @see http://xmpp.org/extensions/xep-0045.html#register - Parameters: - (String) room - The multi-user chat room name. - (Function) handle_cb - Function to call for room list return. - (Function) error_cb - Function to call on error. - */ - registrationRequest: function(room, handle_cb, error_cb) { - var iq; - iq = $iq({ - to: room, - from: this._connection.jid, - type: "get" - }).c("query", { - xmlns: Strophe.NS.MUC_REGISTER - }); - return this._connection.sendIQ(iq, function(stanza) { - var $field, $fields, field, fields, i, len, length; - $fields = stanza.getElementsByTagName('field'); - length = $fields.length; - fields = { - required: [], - optional: [] - }; - for (i = 0, len = $fields.length; i < len; i++) { - $field = $fields[i]; - field = { - "var": $field.getAttribute('var'), - label: $field.getAttribute('label'), - type: $field.getAttribute('type') - }; - if ($field.getElementsByTagName('required').length > 0) { - fields.required.push(field); - } else { - fields.optional.push(field); - } + return this._connection.sendIQ(iq.tree(), handler_cb, error_cb); + }, + + /*Function + Changes the role of a member of a MUC room. + The modification can only be done by a room moderator. An error will be + returned if the user doesn't have permission. + Parameters: + (String) room - The multi-user chat room name. + (String) nick - The nick name of the user to modify. + (String) role - The new role of the user. + (String) affiliation - The new affiliation of the user. + (String) reason - Optional reason for the change. + (Function) handler_cb - Optional callback for success + (Function) error_cb - Optional callback for error + Returns: + iq - the id of the mode change request. + */ + modifyRole: function(room, nick, role, reason, handler_cb, error_cb) { + var item; + item = $build("item", { + nick: nick, + role: role + }); + return this._modifyPrivilege(room, item, reason, handler_cb, error_cb); + }, + kick: function(room, nick, reason, handler_cb, error_cb) { + return this.modifyRole(room, nick, 'none', reason, handler_cb, error_cb); + }, + voice: function(room, nick, reason, handler_cb, error_cb) { + return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb); + }, + mute: function(room, nick, reason, handler_cb, error_cb) { + return this.modifyRole(room, nick, 'visitor', reason, handler_cb, error_cb); + }, + op: function(room, nick, reason, handler_cb, error_cb) { + return this.modifyRole(room, nick, 'moderator', reason, handler_cb, error_cb); + }, + deop: function(room, nick, reason, handler_cb, error_cb) { + return this.modifyRole(room, nick, 'participant', reason, handler_cb, error_cb); + }, + + /*Function + Changes the affiliation of a member of a MUC room. + The modification can only be done by a room moderator. An error will be + returned if the user doesn't have permission. + Parameters: + (String) room - The multi-user chat room name. + (String) jid - The jid of the user to modify. + (String) affiliation - The new affiliation of the user. + (String) reason - Optional reason for the change. + (Function) handler_cb - Optional callback for success + (Function) error_cb - Optional callback for error + Returns: + iq - the id of the mode change request. + */ + modifyAffiliation: function(room, jid, affiliation, reason, handler_cb, error_cb) { + var item; + item = $build("item", { + jid: jid, + affiliation: affiliation + }); + return this._modifyPrivilege(room, item, reason, handler_cb, error_cb); + }, + ban: function(room, jid, reason, handler_cb, error_cb) { + return this.modifyAffiliation(room, jid, 'outcast', reason, handler_cb, error_cb); + }, + member: function(room, jid, reason, handler_cb, error_cb) { + return this.modifyAffiliation(room, jid, 'member', reason, handler_cb, error_cb); + }, + revoke: function(room, jid, reason, handler_cb, error_cb) { + return this.modifyAffiliation(room, jid, 'none', reason, handler_cb, error_cb); + }, + owner: function(room, jid, reason, handler_cb, error_cb) { + return this.modifyAffiliation(room, jid, 'owner', reason, handler_cb, error_cb); + }, + admin: function(room, jid, reason, handler_cb, error_cb) { + return this.modifyAffiliation(room, jid, 'admin', reason, handler_cb, error_cb); + }, + + /*Function + Change the current users nick name. + Parameters: + (String) room - The multi-user chat room name. + (String) user - The new nick name. + */ + changeNick: function(room, user) { + var presence, room_nick; + room_nick = this.test_append_nick(room, user); + presence = $pres({ + from: this._connection.jid, + to: room_nick, + id: this._connection.getUniqueId() + }); + return this._connection.send(presence.tree()); + }, + + /*Function + Change the current users status. + Parameters: + (String) room - The multi-user chat room name. + (String) user - The current nick. + (String) show - The new show-text. + (String) status - The new status-text. + */ + setStatus: function(room, user, show, status) { + var presence, room_nick; + room_nick = this.test_append_nick(room, user); + presence = $pres({ + from: this._connection.jid, + to: room_nick + }); + if (show != null) { + presence.c('show', show).up(); + } + if (status != null) { + presence.c('status', status); } - return handle_cb(fields); - }, error_cb); - }, - - /*Function - Submits registration form. - Parameters: - (String) room - The multi-user chat room name. - (Function) handle_cb - Function to call for room list return. - (Function) error_cb - Function to call on error. - */ - submitRegistrationForm: function(room, fields, handle_cb, error_cb) { - var iq, key, val; - iq = $iq({ - to: room, - type: "set" - }).c("query", { - xmlns: Strophe.NS.MUC_REGISTER - }); - iq.c("x", { - xmlns: "jabber:x:data", - type: "submit" - }); - iq.c('field', { - 'var': 'FORM_TYPE' - }).c('value').t('http://jabber.org/protocol/muc#register').up().up(); - for (key in fields) { - val = fields[key]; - iq.c('field', { - 'var': key - }).c('value').t(val).up().up(); + return this._connection.send(presence.tree()); + }, + + /*Function + List all chat room available on a server. + Parameters: + (String) server - name of chat server. + (String) handle_cb - Function to call for room list return. + (String) error_cb - Function to call on error. + */ + listRooms: function(server, handle_cb, error_cb) { + var iq; + iq = $iq({ + to: server, + from: this._connection.jid, + type: "get" + }).c("query", { + xmlns: Strophe.NS.DISCO_ITEMS + }); + return this._connection.sendIQ(iq, handle_cb, error_cb); + }, + test_append_nick: function(room, nick) { + var domain, node; + node = Strophe.escapeNode(Strophe.getNodeFromJid(room)); + domain = Strophe.getDomainFromJid(room); + return node + "@" + domain + (nick != null ? "/" + nick : ""); } - return this._connection.sendIQ(iq, handle_cb, error_cb); - }, - - /*Function - List all chat room available on a server. - Parameters: - (String) server - name of chat server. - (String) handle_cb - Function to call for room list return. - (String) error_cb - Function to call on error. - */ - listRooms: function(server, handle_cb, error_cb) { - var iq; - iq = $iq({ - to: server, - from: this._connection.jid, - type: "get" - }).c("query", { - xmlns: Strophe.NS.DISCO_ITEMS - }); - return this._connection.sendIQ(iq, handle_cb, error_cb); - }, - test_append_nick: function(room, nick) { - var domain, node; - node = Strophe.escapeNode(Strophe.getNodeFromJid(room)); - domain = Strophe.getDomainFromJid(room); - return node + "@" + domain + (nick != null ? "/" + nick : ""); - } -}); - -XmppRoom = (function() { - function XmppRoom(client, name1, nick1, password1) { - this.client = client; - this.name = name1; - this.nick = nick1; - this.password = password1; - this._roomRosterHandler = bind(this._roomRosterHandler, this); - this._addOccupant = bind(this._addOccupant, this); - this.roster = {}; - this._message_handlers = {}; - this._presence_handlers = {}; - this._roster_handlers = {}; - this._handler_ids = 0; - if (this.client.muc) { - this.client = this.client.muc; + }); + + XmppRoom = (function() { + function XmppRoom(client, name, nick, password) { + this.client = client; + this.name = name; + this.nick = nick; + this.password = password; + this._roomRosterHandler = __bind(this._roomRosterHandler, this); + this._addOccupant = __bind(this._addOccupant, this); + this.roster = {}; + this._message_handlers = {}; + this._presence_handlers = {}; + this._roster_handlers = {}; + this._handler_ids = 0; + if (client.muc) { + this.client = client.muc; + } + this.name = Strophe.getBareJidFromJid(name); + this.addHandler('presence', this._roomRosterHandler); } - this.name = Strophe.getBareJidFromJid(this.name); - this.addHandler('presence', this._roomRosterHandler); - } - - XmppRoom.prototype.join = function(msg_handler_cb, pres_handler_cb, roster_cb) { - return this.client.join(this.name, this.nick, msg_handler_cb, pres_handler_cb, roster_cb, this.password); - }; - - XmppRoom.prototype.leave = function(handler_cb, message) { - this.client.leave(this.name, this.nick, handler_cb, message); - return delete this.client.rooms[this.name]; - }; - XmppRoom.prototype.message = function(nick, message, html_message, type) { - return this.client.message(this.name, nick, message, html_message, type); - }; - - XmppRoom.prototype.groupchat = function(message, html_message) { - return this.client.groupchat(this.name, message, html_message); - }; - - XmppRoom.prototype.invite = function(receiver, reason) { - return this.client.invite(this.name, receiver, reason); - }; - - XmppRoom.prototype.multipleInvites = function(receivers, reason) { - return this.client.invite(this.name, receivers, reason); - }; - - XmppRoom.prototype.directInvite = function(receiver, reason) { - return this.client.directInvite(this.name, receiver, reason, this.password); - }; - - XmppRoom.prototype.configure = function(handler_cb) { - return this.client.configure(this.name, handler_cb); - }; - - XmppRoom.prototype.cancelConfigure = function() { - return this.client.cancelConfigure(this.name); - }; - - XmppRoom.prototype.saveConfiguration = function(config) { - return this.client.saveConfiguration(this.name, config); - }; - - XmppRoom.prototype.queryOccupants = function(success_cb, error_cb) { - return this.client.queryOccupants(this.name, success_cb, error_cb); - }; - - XmppRoom.prototype.setTopic = function(topic) { - return this.client.setTopic(this.name, topic); - }; - - XmppRoom.prototype.modifyRole = function(nick, role, reason, success_cb, error_cb) { - return this.client.modifyRole(this.name, nick, role, reason, success_cb, error_cb); - }; - - XmppRoom.prototype.kick = function(nick, reason, handler_cb, error_cb) { - return this.client.kick(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.voice = function(nick, reason, handler_cb, error_cb) { - return this.client.voice(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.mute = function(nick, reason, handler_cb, error_cb) { - return this.client.mute(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.op = function(nick, reason, handler_cb, error_cb) { - return this.client.op(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.deop = function(nick, reason, handler_cb, error_cb) { - return this.client.deop(this.name, nick, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.modifyAffiliation = function(jid, affiliation, reason, success_cb, error_cb) { - return this.client.modifyAffiliation(this.name, jid, affiliation, reason, success_cb, error_cb); - }; - - XmppRoom.prototype.ban = function(jid, reason, handler_cb, error_cb) { - return this.client.ban(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.member = function(jid, reason, handler_cb, error_cb) { - return this.client.member(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.revoke = function(jid, reason, handler_cb, error_cb) { - return this.client.revoke(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.owner = function(jid, reason, handler_cb, error_cb) { - return this.client.owner(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.admin = function(jid, reason, handler_cb, error_cb) { - return this.client.admin(this.name, jid, reason, handler_cb, error_cb); - }; - - XmppRoom.prototype.changeNick = function(nick1) { - this.nick = nick1; - return this.client.changeNick(this.name, nick); - }; - - XmppRoom.prototype.setStatus = function(show, status) { - return this.client.setStatus(this.name, this.nick, show, status); - }; - - - /*Function - Adds a handler to the MUC room. - Parameters: - (String) handler_type - 'message', 'presence' or 'roster'. - (Function) handler - The handler function. - Returns: - id - the id of handler. - */ - - XmppRoom.prototype.addHandler = function(handler_type, handler) { - var id; - id = this._handler_ids++; - switch (handler_type) { - case 'presence': - this._presence_handlers[id] = handler; - break; - case 'message': - this._message_handlers[id] = handler; - break; - case 'roster': - this._roster_handlers[id] = handler; - break; - default: - this._handler_ids--; - return null; - } - return id; - }; - - - /*Function - Removes a handler from the MUC room. - This function takes ONLY ids returned by the addHandler function - of this room. passing handler ids returned by connection.addHandler - may brake things! - Parameters: - (number) id - the id of the handler - */ - - XmppRoom.prototype.removeHandler = function(id) { - delete this._presence_handlers[id]; - delete this._message_handlers[id]; - return delete this._roster_handlers[id]; - }; - - - /*Function - Creates and adds an Occupant to the Room Roster. - Parameters: - (Object) data - the data the Occupant is filled with - Returns: - occ - the created Occupant. - */ - - XmppRoom.prototype._addOccupant = function(data) { - var occ; - occ = new Occupant(data, this); - this.roster[occ.nick] = occ; - return occ; - }; - - - /*Function - The standard handler that managed the Room Roster. - Parameters: - (Object) pres - the presence stanza containing user information - */ - - XmppRoom.prototype._roomRosterHandler = function(pres) { - var data, handler, id, newnick, nick, ref; - data = XmppRoom._parsePresence(pres); - nick = data.nick; - newnick = data.newnick || null; - switch (data.type) { - case 'error': - return true; - case 'unavailable': - if (newnick) { - data.nick = newnick; - if (this.roster[nick] && this.roster[newnick]) { - this.roster[nick].update(this.roster[newnick]); - this.roster[newnick] = this.roster[nick]; + XmppRoom.prototype.join = function(msg_handler_cb, pres_handler_cb, roster_cb) { + return this.client.join(this.name, this.nick, msg_handler_cb, pres_handler_cb, roster_cb, this.password); + }; + + XmppRoom.prototype.leave = function(handler_cb, message) { + this.client.leave(this.name, this.nick, handler_cb, message); + return delete this.client.rooms[this.name]; + }; + + XmppRoom.prototype.message = function(nick, message, html_message, type) { + return this.client.message(this.name, nick, message, html_message, type); + }; + + XmppRoom.prototype.groupchat = function(message, html_message) { + return this.client.groupchat(this.name, message, html_message); + }; + + XmppRoom.prototype.invite = function(receiver, reason) { + return this.client.invite(this.name, receiver, reason); + }; + + XmppRoom.prototype.directInvite = function(receiver, reason) { + return this.client.directInvite(this.name, receiver, reason, this.password); + }; + + XmppRoom.prototype.configure = function(handler_cb) { + return this.client.configure(this.name, handler_cb); + }; + + XmppRoom.prototype.cancelConfigure = function() { + return this.client.cancelConfigure(this.name); + }; + + XmppRoom.prototype.saveConfiguration = function(config) { + return this.client.saveConfiguration(this.name, config); + }; + + XmppRoom.prototype.queryOccupants = function(success_cb, error_cb) { + return this.client.queryOccupants(this.name, success_cb, error_cb); + }; + + XmppRoom.prototype.setTopic = function(topic) { + return this.client.setTopic(this.name, topic); + }; + + XmppRoom.prototype.modifyRole = function(nick, role, reason, success_cb, error_cb) { + return this.client.modifyRole(this.name, nick, role, reason, success_cb, error_cb); + }; + + XmppRoom.prototype.kick = function(nick, reason, handler_cb, error_cb) { + return this.client.kick(this.name, nick, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.voice = function(nick, reason, handler_cb, error_cb) { + return this.client.voice(this.name, nick, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.mute = function(nick, reason, handler_cb, error_cb) { + return this.client.mute(this.name, nick, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.op = function(nick, reason, handler_cb, error_cb) { + return this.client.op(this.name, nick, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.deop = function(nick, reason, handler_cb, error_cb) { + return this.client.deop(this.name, nick, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.modifyAffiliation = function(jid, affiliation, reason, success_cb, error_cb) { + return this.client.modifyAffiliation(this.name, jid, affiliation, reason, success_cb, error_cb); + }; + + XmppRoom.prototype.ban = function(jid, reason, handler_cb, error_cb) { + return this.client.ban(this.name, jid, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.member = function(jid, reason, handler_cb, error_cb) { + return this.client.member(this.name, jid, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.revoke = function(jid, reason, handler_cb, error_cb) { + return this.client.revoke(this.name, jid, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.owner = function(jid, reason, handler_cb, error_cb) { + return this.client.owner(this.name, jid, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.admin = function(jid, reason, handler_cb, error_cb) { + return this.client.admin(this.name, jid, reason, handler_cb, error_cb); + }; + + XmppRoom.prototype.changeNick = function(nick) { + this.nick = nick; + return this.client.changeNick(this.name, nick); + }; + + XmppRoom.prototype.setStatus = function(show, status) { + return this.client.setStatus(this.name, this.nick, show, status); + }; + + + /*Function + Adds a handler to the MUC room. + Parameters: + (String) handler_type - 'message', 'presence' or 'roster'. + (Function) handler - The handler function. + Returns: + id - the id of handler. + */ + + XmppRoom.prototype.addHandler = function(handler_type, handler) { + var id; + id = this._handler_ids++; + switch (handler_type) { + case 'presence': + this._presence_handlers[id] = handler; + break; + case 'message': + this._message_handlers[id] = handler; + break; + case 'roster': + this._roster_handlers[id] = handler; + break; + default: + this._handler_ids--; + return null; + } + return id; + }; + + + /*Function + Removes a handler from the MUC room. + This function takes ONLY ids returned by the addHandler function + of this room. passing handler ids returned by connection.addHandler + may brake things! + Parameters: + (number) id - the id of the handler + */ + + XmppRoom.prototype.removeHandler = function(id) { + delete this._presence_handlers[id]; + delete this._message_handlers[id]; + return delete this._roster_handlers[id]; + }; + + + /*Function + Creates and adds an Occupant to the Room Roster. + Parameters: + (Object) data - the data the Occupant is filled with + Returns: + occ - the created Occupant. + */ + + XmppRoom.prototype._addOccupant = function(data) { + var occ; + occ = new Occupant(data, this); + this.roster[occ.nick] = occ; + return occ; + }; + + + /*Function + The standard handler that managed the Room Roster. + Parameters: + (Object) pres - the presence stanza containing user information + */ + + XmppRoom.prototype._roomRosterHandler = function(pres) { + var data, handler, id, newnick, nick, _ref; + data = XmppRoom._parsePresence(pres); + nick = data.nick; + newnick = data.newnick || null; + switch (data.type) { + case 'error': + return; + case 'unavailable': + if (newnick) { + data.nick = newnick; + if (this.roster[nick] && this.roster[newnick]) { + this.roster[nick].update(this.roster[newnick]); + this.roster[newnick] = this.roster[nick]; + } + if (this.roster[nick] && !this.roster[newnick]) { + this.roster[newnick] = this.roster[nick].update(data); + } } - if (this.roster[nick] && !this.roster[newnick]) { - this.roster[newnick] = this.roster[nick].update(data); + delete this.roster[nick]; + break; + default: + if (this.roster[nick]) { + this.roster[nick].update(data); + } else { + this._addOccupant(data); } + } + _ref = this._roster_handlers; + for (id in _ref) { + handler = _ref[id]; + if (!handler(this.roster, this)) { + delete this._roster_handlers[id]; } - delete this.roster[nick]; - break; - default: - if (this.roster[nick]) { - this.roster[nick].update(data); - } else { - this._addOccupant(data); - } - } - ref = this._roster_handlers; - for (id in ref) { - handler = ref[id]; - if (!handler(this.roster, this)) { - delete this._roster_handlers[id]; } - } - return true; - }; - - - /*Function - Parses a presence stanza - Parameters: - (Object) data - the data extracted from the presence stanza - */ - - XmppRoom._parsePresence = function(pres) { - var c, c2, data, i, j, len, len1, ref, ref1; - data = {}; - data.nick = Strophe.getResourceFromJid(pres.getAttribute("from")); - data.type = pres.getAttribute("type"); - data.states = []; - ref = pres.childNodes; - for (i = 0, len = ref.length; i < len; i++) { - c = ref[i]; - switch (c.nodeName) { - case "error": - data.errorcode = c.getAttribute("code"); - data.error = (ref1 = c.childNodes[0]) != null ? ref1.nodeName : undefined; - break; - case "status": - data.status = c.textContent || null; - break; - case "show": - data.show = c.textContent || null; - break; - case "x": - if (c.getAttribute("xmlns") === Strophe.NS.MUC_USER) { - ref1 = c.childNodes; - for (j = 0, len1 = ref1.length; j < len1; j++) { - c2 = ref1[j]; - switch (c2.nodeName) { - case "item": - data.affiliation = c2.getAttribute("affiliation"); - data.role = c2.getAttribute("role"); - data.jid = c2.getAttribute("jid"); - data.newnick = c2.getAttribute("nick"); - break; - case "status": - if (c2.getAttribute("code")) { - data.states.push(c2.getAttribute("code")); - } + return true; + }; + + + /*Function + Parses a presence stanza + Parameters: + (Object) data - the data extracted from the presence stanza + */ + + XmppRoom._parsePresence = function(pres) { + var a, c, c2, data, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7; + data = {}; + a = pres.attributes; + data.nick = Strophe.getResourceFromJid(a.from.textContent); + data.type = ((_ref = a.type) != null ? _ref.textContent : void 0) || null; + data.states = []; + _ref1 = pres.childNodes; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + c = _ref1[_i]; + switch (c.nodeName) { + case "status": + data.status = c.textContent || null; + break; + case "show": + data.show = c.textContent || null; + break; + case "x": + a = c.attributes; + if (((_ref2 = a.xmlns) != null ? _ref2.textContent : void 0) === Strophe.NS.MUC_USER) { + _ref3 = c.childNodes; + for (_j = 0, _len1 = _ref3.length; _j < _len1; _j++) { + c2 = _ref3[_j]; + switch (c2.nodeName) { + case "item": + a = c2.attributes; + data.affiliation = ((_ref4 = a.affiliation) != null ? _ref4.textContent : void 0) || null; + data.role = ((_ref5 = a.role) != null ? _ref5.textContent : void 0) || null; + data.jid = ((_ref6 = a.jid) != null ? _ref6.textContent : void 0) || null; + data.newnick = ((_ref7 = a.nick) != null ? _ref7.textContent : void 0) || null; + break; + case "status": + if (c2.attributes.code) { + data.states.push(c2.attributes.code.textContent); + } + } } } - } + } } - } - return data; - }; + return data; + }; - return XmppRoom; + return XmppRoom; -})(); + })(); -RoomConfig = (function() { - function RoomConfig(info) { - this.parse = bind(this.parse, this); - if (info != null) { - this.parse(info); + RoomConfig = (function() { + function RoomConfig(info) { + this.parse = __bind(this.parse, this); + if (info != null) { + this.parse(info); + } } - } - - RoomConfig.prototype.parse = function(result) { - var attr, attrs, child, field, i, identity, j, l, len, len1, len2, query, ref; - query = result.getElementsByTagName("query")[0].childNodes; - this.identities = []; - this.features = []; - this.x = []; - for (i = 0, len = query.length; i < len; i++) { - child = query[i]; - attrs = child.attributes; - switch (child.nodeName) { - case "identity": - identity = {}; - for (j = 0, len1 = attrs.length; j < len1; j++) { - attr = attrs[j]; - identity[attr.name] = attr.textContent; - } - this.identities.push(identity); - break; - case "feature": - this.features.push(child.getAttribute("var")); - break; - case "x": - if ((!child.childNodes[0].getAttribute("var") === 'FORM_TYPE') || (!child.childNodes[0].getAttribute("type") === 'hidden')) { + + RoomConfig.prototype.parse = function(result) { + var attr, attrs, child, field, identity, query, _i, _j, _k, _len, _len1, _len2, _ref; + query = result.getElementsByTagName("query")[0].childNodes; + this.identities = []; + this.features = []; + this.x = []; + for (_i = 0, _len = query.length; _i < _len; _i++) { + child = query[_i]; + attrs = child.attributes; + switch (child.nodeName) { + case "identity": + identity = {}; + for (_j = 0, _len1 = attrs.length; _j < _len1; _j++) { + attr = attrs[_j]; + identity[attr.name] = attr.textContent; + } + this.identities.push(identity); break; - } - ref = child.childNodes; - for (l = 0, len2 = ref.length; l < len2; l++) { - field = ref[l]; - if (!field.attributes.type) { + case "feature": + this.features.push(attrs["var"].textContent); + break; + case "x": + attrs = child.childNodes[0].attributes; + if ((!attrs["var"].textContent === 'FORM_TYPE') || (!attrs.type.textContent === 'hidden')) { + break; + } + _ref = child.childNodes; + for (_k = 0, _len2 = _ref.length; _k < _len2; _k++) { + field = _ref[_k]; + if (!(!field.attributes.type)) { + continue; + } + attrs = field.attributes; this.x.push({ - "var": field.getAttribute("var"), - label: field.getAttribute("label") || "", + "var": attrs["var"].textContent, + label: attrs.label.textContent || "", value: field.firstChild.textContent || "" }); } - } + } } + return { + "identities": this.identities, + "features": this.features, + "x": this.x + }; + }; + + return RoomConfig; + + })(); + + Occupant = (function() { + function Occupant(data, room) { + this.room = room; + this.update = __bind(this.update, this); + this.admin = __bind(this.admin, this); + this.owner = __bind(this.owner, this); + this.revoke = __bind(this.revoke, this); + this.member = __bind(this.member, this); + this.ban = __bind(this.ban, this); + this.modifyAffiliation = __bind(this.modifyAffiliation, this); + this.deop = __bind(this.deop, this); + this.op = __bind(this.op, this); + this.mute = __bind(this.mute, this); + this.voice = __bind(this.voice, this); + this.kick = __bind(this.kick, this); + this.modifyRole = __bind(this.modifyRole, this); + this.update(data); } - return { - "identities": this.identities, - "features": this.features, - "x": this.x + + Occupant.prototype.modifyRole = function(role, reason, success_cb, error_cb) { + return this.room.modifyRole(this.nick, role, reason, success_cb, error_cb); + }; + + Occupant.prototype.kick = function(reason, handler_cb, error_cb) { + return this.room.kick(this.nick, reason, handler_cb, error_cb); + }; + + Occupant.prototype.voice = function(reason, handler_cb, error_cb) { + return this.room.voice(this.nick, reason, handler_cb, error_cb); + }; + + Occupant.prototype.mute = function(reason, handler_cb, error_cb) { + return this.room.mute(this.nick, reason, handler_cb, error_cb); + }; + + Occupant.prototype.op = function(reason, handler_cb, error_cb) { + return this.room.op(this.nick, reason, handler_cb, error_cb); + }; + + Occupant.prototype.deop = function(reason, handler_cb, error_cb) { + return this.room.deop(this.nick, reason, handler_cb, error_cb); + }; + + Occupant.prototype.modifyAffiliation = function(affiliation, reason, success_cb, error_cb) { + return this.room.modifyAffiliation(this.jid, affiliation, reason, success_cb, error_cb); + }; + + Occupant.prototype.ban = function(reason, handler_cb, error_cb) { + return this.room.ban(this.jid, reason, handler_cb, error_cb); + }; + + Occupant.prototype.member = function(reason, handler_cb, error_cb) { + return this.room.member(this.jid, reason, handler_cb, error_cb); }; - }; - - return RoomConfig; - -})(); - -Occupant = (function() { - function Occupant(data, room1) { - this.room = room1; - this.update = bind(this.update, this); - this.admin = bind(this.admin, this); - this.owner = bind(this.owner, this); - this.revoke = bind(this.revoke, this); - this.member = bind(this.member, this); - this.ban = bind(this.ban, this); - this.modifyAffiliation = bind(this.modifyAffiliation, this); - this.deop = bind(this.deop, this); - this.op = bind(this.op, this); - this.mute = bind(this.mute, this); - this.voice = bind(this.voice, this); - this.kick = bind(this.kick, this); - this.modifyRole = bind(this.modifyRole, this); - this.update(data); - } - - Occupant.prototype.modifyRole = function(role, reason, success_cb, error_cb) { - return this.room.modifyRole(this.nick, role, reason, success_cb, error_cb); - }; - - Occupant.prototype.kick = function(reason, handler_cb, error_cb) { - return this.room.kick(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.voice = function(reason, handler_cb, error_cb) { - return this.room.voice(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.mute = function(reason, handler_cb, error_cb) { - return this.room.mute(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.op = function(reason, handler_cb, error_cb) { - return this.room.op(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.deop = function(reason, handler_cb, error_cb) { - return this.room.deop(this.nick, reason, handler_cb, error_cb); - }; - - Occupant.prototype.modifyAffiliation = function(affiliation, reason, success_cb, error_cb) { - return this.room.modifyAffiliation(this.jid, affiliation, reason, success_cb, error_cb); - }; - - Occupant.prototype.ban = function(reason, handler_cb, error_cb) { - return this.room.ban(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.member = function(reason, handler_cb, error_cb) { - return this.room.member(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.revoke = function(reason, handler_cb, error_cb) { - return this.room.revoke(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.owner = function(reason, handler_cb, error_cb) { - return this.room.owner(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.admin = function(reason, handler_cb, error_cb) { - return this.room.admin(this.jid, reason, handler_cb, error_cb); - }; - - Occupant.prototype.update = function(data) { - this.nick = data.nick || null; - this.affiliation = data.affiliation || null; - this.role = data.role || null; - this.jid = data.jid || null; - this.status = data.status || null; - this.show = data.show || null; - return this; - }; - - return Occupant; - -})(); - -// --- -// generated by coffee-script 1.9.2 \ No newline at end of file + + Occupant.prototype.revoke = function(reason, handler_cb, error_cb) { + return this.room.revoke(this.jid, reason, handler_cb, error_cb); + }; + + Occupant.prototype.owner = function(reason, handler_cb, error_cb) { + return this.room.owner(this.jid, reason, handler_cb, error_cb); + }; + + Occupant.prototype.admin = function(reason, handler_cb, error_cb) { + return this.room.admin(this.jid, reason, handler_cb, error_cb); + }; + + Occupant.prototype.update = function(data) { + this.nick = data.nick || null; + this.affiliation = data.affiliation || null; + this.role = data.role || null; + this.jid = data.jid || null; + this.status = data.status || null; + this.show = data.show || null; + return this; + }; + + return Occupant; + + })(); + +}).call(this); + diff --git a/src/scripts/strophe/plugins/roster.js b/src/scripts/strophe/plugins/roster.js index 9f63ed11..f1c7e8f5 100755 --- a/src/scripts/strophe/plugins/roster.js +++ b/src/scripts/strophe/plugins/roster.js @@ -15,6 +15,34 @@ */ Strophe.addConnectionPlugin('roster', { + _connection: null, + + _callbacks : [], + /** Property: items + * Roster items + * [ + * { + * name : "", + * jid : "", + * subscription : "", + * ask : "", + * groups : ["", ""], + * resources : { + * myresource : { + * show : "", + * status : "", + * priority : 0 + * } + * } + * } + * ] + */ + items : [], + /** Property: ver + * current roster revision + * always null if server doesn't support xep 237 + */ + ver : null, /** Function: init * Plugin init * @@ -23,37 +51,8 @@ Strophe.addConnectionPlugin('roster', */ init: function(conn) { - this._connection = conn; - this._callbacks = []; - this._callbacks_request = []; - - /** Property: items - * Roster items - * [ - * { - * name : "", - * jid : "", - * subscription : "", - * ask : "", - * groups : ["", ""], - * resources : { - * myresource : { - * show : "", - * status : "", - * priority : "" - * } - * } - * } - * ] - */ - var items = []; - - /** Property: ver - * current roster revision - * always null if server doesn't support xep 237 - */ - var ver = null; - + this._connection = conn; + this.items = []; // 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; @@ -72,35 +71,33 @@ Strophe.addConnectionPlugin('roster', Strophe.error(e); } } - if (typeof oldCallback === "function") { + if (oldCallback !== null) oldCallback.apply(this, arguments); - } }; - conn.connect = function(jid, pass, callback, wait, hold, route, authcid) + 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, route, authcid]); + if (typeof arguments[0] == "undefined") + arguments[0] = null; + if (typeof arguments[1] == "undefined") + arguments[1] = null; + arguments[2] = newCallback; + _connect.apply(conn, arguments); }; 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]); + if (typeof arguments[0] == "undefined") + arguments[0] = null; + if (typeof arguments[1] == "undefined") + arguments[1] = null; + if (typeof arguments[2] == "undefined") + arguments[2] = null; + arguments[3] = newCallback; + _attach.apply(conn, arguments); }; 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 @@ -146,12 +143,13 @@ Strophe.addConnectionPlugin('roster', { this._callbacks.push(call_back); }, - - registerRequestCallback: function (call_back) + /** Function: clearCallbacks + * clear all callbacks on roster + */ + clearCallbacks: function() { - this._callbacks_request.push(call_back); + this._callbacks.length = 0; }, - /** Function: findItem * Find item by JID * @@ -160,16 +158,13 @@ Strophe.addConnectionPlugin('roster', */ findItem : function(jid) { - if(this.items) - { - for (var i = 0; i < this.items.length; i++) - { - if (this.items[i] && this.items[i].jid == jid) - { - return this.items[i]; - } - } - } + for (var i = 0; i < this.items.length; i++) + { + if (this.items[i] && this.items[i].jid == jid) + { + return this.items[i]; + } + } return false; }, /** Function: removeItem @@ -195,17 +190,13 @@ Strophe.addConnectionPlugin('roster', * * Parameters: * (String) jid - * (String) message (optional) - * (String) nick (optional) + * (String) message */ - subscribe: function(jid, message, nick) { + subscribe: function(jid, message) + { 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(); - } + if (message && message != "") + pres.c("status").t(message); this._connection.send(pres); }, /** Function: unsubscribe @@ -218,7 +209,7 @@ Strophe.addConnectionPlugin('roster', unsubscribe: function(jid, message) { var pres = $pres({to: jid, type: "unsubscribe"}); - if (message && message !== "") + if (message && message != "") pres.c("status").t(message); this._connection.send(pres); }, @@ -232,7 +223,7 @@ Strophe.addConnectionPlugin('roster', authorize: function(jid, message) { var pres = $pres({to: jid, type: "subscribed"}); - if (message && message !== "") + if (message && message != "") pres.c("status").t(message); this._connection.send(pres); }, @@ -246,7 +237,7 @@ Strophe.addConnectionPlugin('roster', unauthorize: function(jid, message) { var pres = $pres({to: jid, type: "unsubscribed"}); - if (message && message !== "") + if (message && message != "") pres.c("status").t(message); this._connection.send(pres); }, @@ -319,10 +310,7 @@ Strophe.addConnectionPlugin('roster', _onReceiveRosterSuccess: function(userCallback, stanza) { this._updateItems(stanza); - this._call_backs(this.items); - if (typeof userCallback === "function") { - userCallback(this.items); - } + userCallback(this.items); }, /** PrivateFunction: _onReceiveRosterError * @@ -340,16 +328,13 @@ Strophe.addConnectionPlugin('roster', var jid = presence.getAttribute('from'); var from = Strophe.getBareJidFromJid(jid); var item = this.findItem(from); - var type = presence.getAttribute('type'); + var to = Strophe.getBareJidFromJid(presence.getAttribute('to')); // not in roster if (!item) { - // if 'friend request' presence - if (type === 'subscribe') { - this._call_backs_request(from); - } return true; } + var type = presence.getAttribute('type'); if (type == 'unavailable') { delete item.resources[Strophe.getResourceFromJid(jid)]; @@ -358,9 +343,13 @@ Strophe.addConnectionPlugin('roster', { // 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]) : "" + 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) ? Number(Strophe.getText(presence.getElementsByTagName('priority')[0])) : 0, + photo : (presence.getElementsByTagName('x').length != 0) ? ( + (presence.getElementsByTagName('x')[0].getElementsByTagName('photo').length != 0) ? Strophe.getText(presence.getElementsByTagName('x')[0].getElementsByTagName('photo')[0]) : "" + ) : "", + caps : $(presence).find('c').length && $(presence).find('c').attr('node') + '#' + $(presence).find('c').attr('ver') }; } else @@ -368,31 +357,17 @@ Strophe.addConnectionPlugin('roster', // Stanza is not a presence notification. (It's probably a subscription type stanza.) return true; } - this._call_backs(this.items, item); + this._call_backs(this.items, item, to); return true; }, - /** PrivateFunction: _call_backs_request - * call all the callbacks waiting for 'friend request' presences - */ - _call_backs_request : function(from) - { - for (var i = 0; i < this._callbacks_request.length; i++) // [].forEach my love ... - { - this._callbacks_request[i](from); - } - }, - /** PrivateFunction: _call_backs - * first parameter is the full roster - * second is optional, newly added or updated item - * third is otional, in case of update, send the previous state of the - * update item + * */ - _call_backs : function(items, item, previousItem) + _call_backs : function(items, item, to) { - for (var i = 0; i < this._callbacks.length; i++) // [].forEach my love ... + for (var i in this._callbacks) { - this._callbacks[i](items, item, previousItem); + this._callbacks[i](items, item, to); } }, /** PrivateFunction: _onReceiveIQ @@ -403,7 +378,7 @@ Strophe.addConnectionPlugin('roster', 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)) + 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); @@ -416,7 +391,8 @@ Strophe.addConnectionPlugin('roster', _updateItems : function(iq) { var query = iq.getElementsByTagName('query'); - if (query.length !== 0) + var to = Strophe.getBareJidFromJid($(iq).attr('to')); + if (query.length != 0) { this.ver = query.item(0).getAttribute('ver'); var self = this; @@ -427,66 +403,49 @@ Strophe.addConnectionPlugin('roster', } ); } + this._call_backs(this.items); }, /** PrivateFunction: _updateItem * Update internal representation of roster item */ - _updateItem : function(itemTag) + _updateItem : function(item) { - var jid = itemTag.getAttribute("jid"); - var name = itemTag.getAttribute("name"); - var subscription = itemTag.getAttribute("subscription"); - var ask = itemTag.getAttribute("ask"); + var jid = item.getAttribute("jid"); + var name = item.getAttribute("name"); + var subscription = item.getAttribute("subscription"); + var ask = item.getAttribute("ask"); var groups = []; - - Strophe.forEachChild(itemTag, 'group', + Strophe.forEachChild(item, 'group', function(group) { groups.push(Strophe.getText(group)); } ); - var item; - var previousItem; - if (subscription == "remove") { - var hashBeenRemoved = this.removeItem(jid); - if (hashBeenRemoved) { - this._call_backs( - this.items, - {jid: jid, subscription: 'remove'} - ); - } + this.removeItem(jid); return; } - item = this.findItem(jid); + var item = this.findItem(jid); if (!item) { - item = { + this.items.push({ name : name, jid : jid, subscription : subscription, ask : ask, groups : groups, resources : {} - }; - this.items.push(item); + }); } else { - previousItem = { - name: item.name, - subscription: item.subscription, - ask: item.ask, - groups: item.groups - }; item.name = name; item.subscription = subscription; item.ask = ask; item.groups = groups; } - this._call_backs(this.items, item, previousItem); } }); diff --git a/src/scripts/strophe/plugins/vcard.js b/src/scripts/strophe/plugins/vcard.js index 0a7418a5..436086db 100755 --- a/src/scripts/strophe/plugins/vcard.js +++ b/src/scripts/strophe/plugins/vcard.js @@ -1,70 +1,66 @@ -/* Plugin to implement the vCard extension. - * http://xmpp.org/extensions/xep-0054.html - * - * Author: Nathan Zorn (nathan.zorn@gmail.com) - * AMD support by JC Brand - */ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define([ - "strophe" - ], function (Strophe) { - factory( - Strophe.Strophe, - Strophe.$build, - Strophe.$iq , - Strophe.$msg, - Strophe.$pres - ); - return Strophe; - }); - } else { - // Browser globals - factory( - root.Strophe, - root.$build, - root.$iq , - root.$msg, - root.$pres - ); - } -}(this, function (Strophe, $build, $iq, $msg, $pres) { - - var buildIq = function(type, jid, vCardEl) { - var iq = $iq(jid ? {type: type, to: jid} : {type: type}); - iq.c("vCard", {xmlns: Strophe.NS.VCARD}); - if (vCardEl) { - iq.cnode(vCardEl); - } - return iq; - }; +/* +Plugin to implement the vCard extension. +http://xmpp.org/extensions/xep-0054.html - Strophe.addConnectionPlugin('vcard', { - _connection: null, - init: function(conn) { - this._connection = conn; - return Strophe.addNamespace('VCARD', 'vcard-temp'); - }, +Authors: +Nathan Zorn (nathan.zorn@gmail.com) +Adán Sánchez de Pedro Crespo (adansdpc@waalt.com) - /* Function - * Retrieve a vCard for a JID/Entity - * Parameters: - * (Function) handler_cb - The callback function used to handle the request. - * (String) jid - optional - The name of the entity to request the vCard - * If no jid is given, this function retrieves the current user's vcard. - * */ - get: function(handler_cb, jid, error_cb) { - var iq = buildIq("get", jid); - return this._connection.sendIQ(iq, handler_cb, error_cb); - }, - - /* Function - * Set an entity's vCard. - */ - set: function(handler_cb, vCardEl, jid, error_cb) { - var iq = buildIq("set", jid, vCardEl); - return this._connection.sendIQ(iq, handler_cb, error_cb); - } - }); -})); +*/ +/* jslint configuration: */ +/* global document, window, setTimeout, clearTimeout, console, + XMLHttpRequest, ActiveXObject, + Base64, MD5, + Strophe, $build, $msg, $iq, $pres +*/ +var buildIq = function(type, from, jid, vCardEl) { + var iq; + if (!jid) + { + //retrieve current jid's vCard + iq = $iq({type:type, from:from}); + } + else + { + iq = $iq({type:type, to:jid, from:from}); + } + var ret = iq.c("vCard", {xmlns:Strophe.NS.VCARD}); + if (vCardEl) + { + ret = ret.cnode(vCardEl); + } + return ret; +}; +Strophe.addConnectionPlugin('vcard', { + _connection: null, + // Called by Strophe.Connection constructor + init: function(conn) { + this._connection = conn; + Strophe.addNamespace('VCARD', 'vcard-temp'); + }, + /****Function + Retrieve a vCard for a JID/Entity + Parameters: + (Function) handler_cb - The callback function used to handle the request. + (String) jid - optional - The name of the entity to request the vCard + If no jid is given, this function retrieves the current user's vcard. + */ + get: function(handler_cb, jid) { + var iq = buildIq("get", this._connection.jid, jid); + this._connection.sendIQ(iq.tree(), handler_cb, null); + }, + /*** Function + Set an entity's vCard. + */ + set: function(handler_cb, vCardEl, jid) { + var iq = buildIq("set", this._connection.jid, jid, vCardEl); + console.log(Strophe.serialize(iq)); + this._connection.sendIQ(iq.tree(), handler_cb, null); + }, + + createUpdateNode: function(vCardEl) { + var xNode = $build('x', {xmlns: 'vcard-temp:x:update'}); + xNode.cnode(vCardEl); + return xNode; + } +}); diff --git a/src/scripts/strophe/strophe.js b/src/scripts/strophe/strophe.js index b601f7e2..4bc2f92e 100755 --- a/src/scripts/strophe/strophe.js +++ b/src/scripts/strophe/strophe.js @@ -1412,7 +1412,8 @@ Strophe = { DISCONNECTING: 7, ATTACHED: 8, REDIRECT: 9, - CONNTIMEOUT: 10 + CONNTIMEOUT: 10, + STARTTLS: 11 }, /** Constants: Log Level Constants @@ -2766,6 +2767,9 @@ Strophe.Connection = function (service, options) { if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 || proto.indexOf("ws") === 0) { this._proto = new Strophe.Websocket(this); + } else if (service.indexOf("tcp:") === 0 || + proto.indexOf("tcp") === 0) { + this._proto = new Strophe.Tcpsocket(this); } else { this._proto = new Strophe.Bosh(this); } @@ -3861,6 +3865,12 @@ Strophe.Connection.prototype = { return; } + // In this case, _connect_cb will be called again once encryption is active. + if (conncheck === Strophe.Status.STARTTLS) { + return; + } + + // Check for the stream:features tag var hasFeatures; if (bodyWrap.getElementsByTagNameNS) { @@ -4941,7 +4951,7 @@ Strophe.Request.prototype = { _newXHR: function () { var xhr = null; if (window.XMLHttpRequest) { - xhr = new XMLHttpRequest(); + xhr = new XMLHttpRequest({ mozSystem: true }); if (xhr.overrideMimeType) { xhr.overrideMimeType("text/xml; charset=utf-8"); } @@ -6307,6 +6317,678 @@ Strophe.Websocket.prototype = { this._conn.flush(); }, + /** PrivateFunction: _sendRestart + * + * Send an xmpp:restart stanza. + */ + _sendRestart: function () + { + clearTimeout(this._conn._idleTimeout); + this._conn._onIdle.bind(this._conn)(); + } +}; +return Strophe; +})); + +/* + This program is distributed under the terms of the MIT license. + Please see the LICENSE file for details. +*/ + +/* jshint undef: true, unused: true:, noarg: true, latedef: true */ +/* global define, window, clearTimeout, WebSocket, DOMParser, Strophe, $build */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define('strophe-tcpsocket', ['strophe-core'], function (core) { + return factory( + core.Strophe, + core.$build + ); + }); + } else { + // Browser globals + return factory(Strophe, $build); + } +}(this, function (Strophe, $build) { + +/** Class: Strophe.TcpSocket + * _Private_ helper class that handles raw TCP Connections + * + * The Strophe.TcpSocket class is used internally by Strophe.Connection + * to encapsulate TcpSocket sessions. It is not meant to be used from user's code. + */ + +/** File: tcpsocket.js + * A JavaScript library to enable XMPP over raw TCP sockets in Strophejs. + */ + +/** PrivateConstructor: Strophe.Tcpsocket + * Create and initialize a Strophe.Tcpsocket object. + * Currently only sets the connection Object. + * + * Parameters: + * (Strophe.Connection) connection - The Strophe.Connection that will use Tcpsockets. + * + * Returns: + * A new Strophe.Tcpsocket object. + */ +Strophe.Tcpsocket = function(connection) { + this._conn = connection; + this.strip = "wrapper"; + + var service = connection.service; +}; + +Strophe.Tcpsocket.prototype = { + /** PrivateFunction: _buildStream + * _Private_ helper function to generate the start tag for Tcpsocket + * + * Returns: + * A Strophe.Builder with a element. + */ + _buildStream: function () + { + return $build("stream:stream", { + "to": this._conn.domain, + "xmlns": Strophe.NS.CLIENT, + "xmlns:stream": Strophe.NS.STREAM, + "version": '1.0' + }); + }, + + /** PrivateFunction: _check_streamerror + * _Private_ checks a message for stream:error + * + * Parameters: + * (Strophe.Request) bodyWrap - The received stanza. + * connectstatus - The ConnectStatus that will be set on error. + * Returns: + * true if there was a streamerror, false otherwise. + */ + _check_streamerror: function (bodyWrap, connectstatus) { + var errors; + if (bodyWrap.getElementsByTagNameNS) { + errors = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, "error"); + } else { + errors = bodyWrap.getElementsByTagName("stream:error"); + } + if (errors.length === 0) { + return false; + } + var error = errors[0]; + + var condition = ""; + var text = ""; + + var ns = "urn:ietf:params:xml:ns:xmpp-streams"; + for (var i = 0; i < error.childNodes.length; i++) { + var e = error.childNodes[i]; + if (e.getAttribute("xmlns") !== ns) { + break; + } if (e.nodeName === "text") { + text = e.textContent; + } else { + condition = e.nodeName; + } + } + + var errorString = "TCPsocket stream error: "; + + if (condition) { + errorString += condition; + } else { + errorString += "unknown"; + } + + if (text) { + errorString += " - " + condition; + } + + Strophe.error(errorString); + + // close the connection on stream_error + this._conn._changeConnectStatus(connectstatus, condition); + this._conn._doDisconnect(); + return true; + }, + + /** PrivateFunction: _reset + * Reset the connection. + * + * This function is called by the reset function of the Strophe Connection. + * Is not needed by Tcpsocket. + */ + _reset: function () + { + return; + }, + + /** PrivateFunction: _connect + * _Private_ function called by Strophe.Connection.connect + * + * Creates a TCP socket for a connection and assigns Callbacks to it. + * Does nothing if there already is a TCP socket. + */ + _connect: function () { + // Ensure that there is no open TCP socket from a previous Connection. + this._closeSocket(); + + this._wrote_starttls = false; + this._finished_starttls = false; + this._started_starttls = false; + + // Create the new TCP socket + var host = this._conn.service.substr(4).split(':'); + this.socket = navigator.mozTCPSocket.open(host[0], host[1] || 5222); + this._receiveFunction = this._connect_cb_wrapper; + this.socket.onopen = this._onOpen.bind(this); + this.socket.onerror = this._onError.bind(this); + this.socket.onclose = this._onClose.bind(this); + this.socket.ondata = this._onReceive.bind(this); + }, + + _receiveBuffer: false, + + // returns the length of the valid xml in the string. + // returns false if the string is not (yet) valid xml. + _stringIsValidXml: function(string) { + var startIndex = 0; + var endOfStartTag; + var startTag; + while (true) { + startIndex = string.indexOf('<', startIndex); + if(startIndex < 0) { + console.log("no startIndex"); + return false; + } + // skip XML declarations. + if(string.charAt(startIndex+1) == '?') { + startIndex = startIndex+1; + } else { + endOfStartTag = string.indexOf('>', startIndex); + if(endOfStartTag <= 0) { + console.log("no end of start tag"); + return false; + } + if(string.charAt(endOfStartTag-1) == '/') { + // empty tag + console.log("empty tag"); + return endOfStartTag+1; + } + var spaceIndex = string.indexOf(' ', startIndex); + if(spaceIndex > 0 && spaceIndex < endOfStartTag) { + endOfStartTag = spaceIndex; + } + startTag = string.substring(startIndex+1, endOfStartTag); + if(startTag == 'stream:stream') { + startIndex = endOfStartTag; + continue; + } + break; + } + } + var endTag = ''; + var endIndex = string.indexOf(endTag); + if(endIndex > 0) { + return endIndex + endTag.length; + } else { + console.log("not valid xml; looking for \""+endTag+"\""); + return false; + } + }, + + _wrote_starttls: false, + _finished_starttls: false, + _started_starttls: false, + + _doProceed: function() { + console.log("doProceed"); + + this._started_starttls = true; + this.socket.upgradeToSecure(); + this.socket.send(""); + this._receiveFunction = this._connect_cb_wrapper; + + this._finished_starttls = true; + }, + + _onReceive: function(message) { + var string = decodeURIComponent(escape(message.data)); + console.log("_onReceive: "+string); + + // Make sure we send only complete XML documents. + // Save the string of incomplete documents. + // Abort app on error. + if (!this._receiveBuffer) { + this._receiveBuffer = string; + } else { + this._receiveBuffer += string; + } + while (this._receiveBuffer) { + string = this._receiveBuffer; + var length = this._stringIsValidXml(string); + if(length) { + if(string.length == length) { + this._receiveBuffer = false; + } else { + // If the xml ended before the string did, + // assume the rest is a new xml document. Save it for later. + this._receiveBuffer = string.substring(length); + string = string.substr(0, length); + } + console.log("_receiveFunction: "+string); + this._receiveFunction({ data:string }); + } else { + break; + } + } + }, + + /** PrivateFunction: _connect_cb + * _Private_ function called by Strophe.Connection._connect_cb + * + * checks for stream:error + * + * Parameters: + * (Strophe.Request) bodyWrap - The received stanza. + */ + _connect_cb: function(bodyWrap) { + var error = this._check_streamerror(bodyWrap, Strophe.Status.CONNFAIL); + if (error) { + return Strophe.Status.CONNFAIL; + } + + if (this._finished_starttls) { + return; + } + + // Check for the starttls tag + var hasStarttls = bodyWrap.getElementsByTagName("starttls").length > 0; + if (hasStarttls && !this._wrote_starttls) { + this.socket.send(""); + this._wrote_starttls = true; + this._starttls_body = bodyWrap; + return Strophe.Status.STARTTLS; + } + }, + + /** PrivateFunction: _handleStreamStart + * _Private_ function that checks the opening stream:stream tag for errors. + * + * Disconnects if there is an error and returns false, true otherwise. + * + * Parameters: + * (Node) message - Stanza containing the stream:stream. + */ + _handleStreamStart: function(message) { + var error = false; + // Check for errors in the stream:stream tag + var ns = message.getAttribute("xmlns"); + if (typeof ns !== "string") { + error = "Missing xmlns in stream:stream"; + } else if (ns !== Strophe.NS.CLIENT) { + error = "Wrong xmlns in stream:stream: " + ns; + } + + var ns_stream = message.namespaceURI; + if (typeof ns_stream !== "string") { + error = "Missing xmlns:stream in stream:stream"; + } else if (ns_stream !== Strophe.NS.STREAM) { + error = "Wrong xmlns:stream in stream:stream: " + ns_stream; + } + + var ver = message.getAttribute("version"); + if (typeof ver !== "string") { + error = "Missing version in stream:stream"; + } else if (ver !== "1.0") { + error = "Wrong version in stream:stream: " + ver; + } + + if (error) { + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, error); + this._conn._doDisconnect(); + return false; + } + + return true; + }, + + /** PrivateFunction: _connect_cb_wrapper + * _Private_ function that handles the first connection messages. + * + * On receiving an opening stream tag this callback replaces itself with the real + * message handler. On receiving a stream error the connection is terminated. + */ + _connect_cb_wrapper: function(message) { + if (message.data.indexOf(".*/, ""); + + var streamStart = new DOMParser().parseFromString(data, "text/xml").documentElement; + this._conn.xmlInput(streamStart); + this._conn.rawInput(message.data); + + //_handleStreamSteart will check for XML errors and disconnect on error + if (this._handleStreamStart(streamStart)) { + this._connect_cb(streamStart); + + // ensure received stream:stream is NOT selfclosing and save it for following messages + this.streamStart = message.data.replace(/^$/, ""); + + //handle any data following the stream:stream tag. + data = message.data.replace(/.*(.*)/, "$1"); + if (data.length > 0) { + console.log('data following stream:stream tag present. calling _connect_cb_wrapper.') + this._connect_cb_wrapper({data: data}); + } + } + } else if (message.data === "") { + this._conn.rawInput(message.data); + this._conn.xmlInput(document.createElement("stream:stream")); + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Received closing stream"); + this._conn._doDisconnect(); + return; + } else { + var string = this._streamWrap(message.data); + var elem = new DOMParser().parseFromString(string, "text/xml").documentElement; + this._receiveFunction = this._onMessage.bind(this); + this._conn._connect_cb(elem, null, message.data); + } + }, + + /** PrivateFunction: _disconnect + * _Private_ function called by Strophe.Connection.disconnect + * + * Disconnects and sends a last stanza if one is given + * + * Parameters: + * (Request) pres - This stanza will be sent before disconnecting. + */ + _disconnect: function (pres) + { + if (this.socket && this.socket.readyState !== 'closed') { + if (pres) { + this._conn.send(pres); + } + var closeString = ''; + this._conn.rawOutput(closeString); + try { + this.socket.send(closeString); + } catch (e) { + Strophe.info("Couldn't send closing stream tag."); + } + } + + this._conn._doDisconnect(); + }, + + /** PrivateFunction: _doDisconnect + * _Private_ function to disconnect. + * + * Just closes the Socket for Tcpsocket + */ + _doDisconnect: function () + { + Strophe.info("Tcpsocket _doDisconnect was called"); + this._closeSocket(); + }, + + /** PrivateFunction _streamWrap + * _Private_ helper function to wrap a stanza in a tag. + * This is used so Strophe can process stanzas from Tcpsocket like BOSH + */ + _streamWrap: function (stanza) + { + return "" + stanza + ''; + }, + + + /** PrivateFunction: _closeSocket + * _Private_ function to close the Tcpsocket. + * + * Closes the socket if it is still open and deletes it + */ + _closeSocket: function () + { + if (this.socket) { try { + this.socket.close(); + } catch (e) {} } + this.socket = null; + }, + + /** PrivateFunction: _emptyQueue + * _Private_ function to check if the message queue is empty. + * + * Returns: + * True, because Tcpsocket messages are send immediately after queueing. + */ + _emptyQueue: function () + { + return true; + }, + + /** PrivateFunction: _onClose + * _Private_ function to handle Tcpsocket closing. + * + * Nothing to do here for Tcpsocket + */ + _onClose: function() { + console.log("onClose"); + if(this._conn.connected && !this._conn.disconnecting) { + Strophe.error("Tcpsocket closed unexcectedly"); + this._conn._doDisconnect(); + } else { + Strophe.info("Tcpsocket closed"); + } + }, + + /** PrivateFunction: _no_auth_received + * + * Called on stream start/restart when no stream:features + * has been received. + */ + _no_auth_received: function (_callback) + { + Strophe.error("Server did not send any auth methods"); + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "Server did not send any auth methods"); + if (_callback) { + _callback = _callback.bind(this._conn); + _callback(); + } + this._conn._doDisconnect(); + }, + + /** PrivateFunction: _onDisconnectTimeout + * _Private_ timeout handler for handling non-graceful disconnection. + * + * This does nothing for Tcpsocket + */ + _onDisconnectTimeout: function () {}, + + /** PrivateFunction: _abortAllRequests + * _Private_ helper function that makes sure all pending requests are aborted. + */ + _abortAllRequests: function () {}, + + /** PrivateFunction: _onError + * _Private_ function to handle tcpsocket errors. + * + * Parameters: + * (Object) error - The tcpsocket error. + */ + _onError: function(error) { + console.log("onError " + error); + console.log(error); + Strophe.error("Tcpsocket error " + error); + this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, "The Tcpsocket connection could not be established was disconnected."); + this._disconnect(); + }, + + /** PrivateFunction: _onIdle + * _Private_ function called by Strophe.Connection._onIdle + * + * sends all queued stanzas + */ + _onIdle: function () { + var data = this._conn._data; + if (data.length > 0 && !this._conn.paused) { + for (var i = 0; i < data.length; i++) { + if (data[i] !== null) { + var stanza, rawStanza; + if (data[i] === "restart") { + stanza = this._buildStream().tree(); + rawStanza = this._removeClosingTag(stanza); + } else { + stanza = data[i]; + rawStanza = Strophe.serialize(stanza); + } + this._conn.xmlOutput(stanza); + this._conn.rawOutput(rawStanza); + this.socket.send(unescape(encodeURIComponent(rawStanza))); + } + } + this._conn._data = []; + } + }, + + /** PrivateFunction: _onMessage + * _Private_ function to handle socket messages. + * + * This function parses each of the messages as if they are full documents. [TODO : We may actually want to use a SAX Push parser]. + * + * Since all XMPP traffic starts with "" + * The first stanza will always fail to be parsed... + * Addtionnaly, the seconds stanza will always be a with the stream NS defined in the previous stanza... so we need to 'force' the inclusion of the NS in this stanza! + * + * Parameters: + * (string) message - The socket message. + */ + _onMessage: function(message) { + var elem, data, extraData; + if (this._started_starttls && !this._finished_starttls) { + console.log("ignore message..."); + return; + } + + // check for closing stream + if (message.data === "") { + var close = ""; + this._conn.rawInput(close); + this._conn.xmlInput(document.createElement("stream:stream")); + if (!this._conn.disconnecting) { + this._conn._doDisconnect(); + } + return; + } else if (message.data.indexOf("= 0) { + this._doProceed(); + return; + } else if (message.data.search(".*/, ""); + elem = new DOMParser().parseFromString(data, "text/xml").documentElement; + + if (!this._handleStreamStart(elem)) { + return; + } + + //handle any data following the stream:stream tag. + data = message.data.replace(/.*(.*)/, "$1"); + if (data.length > 0) { + elem = new DOMParser().parseFromString(data, "text/xml").documentElement; + extraData = data; + } + + // ensure received stream:stream is NOT selfclosing and save it for following messages + this.streamStart = message.data.replace(/^$/, ""); + } else { + data = this._streamWrap(message.data); + elem = new DOMParser().parseFromString(data, "text/xml").documentElement; + } + + if (this._check_streamerror(elem, Strophe.Status.ERROR)) { + return; + } + + //handle unavailable presence stanza before disconnecting + if (this._conn.disconnecting && + elem.firstChild.nodeName === "presence" && + elem.firstChild.getAttribute("type") === "unavailable") { + this._conn.xmlInput(elem); + this._conn.rawInput(Strophe.serialize(elem)); + // if we are already disconnecting we will ignore the unavailable stanza and + // wait for the tag before we close the connection + return; + } + this._conn._dataRecv(elem, message.data); + + if (extraData != null) { + this._onMessage({data: extraData}); + } + }, + + /** PrivateFunction: _onOpen + * _Private_ function to handle tcpsocket connection setup. + * + * The opening stream tag is sent here. + */ + _onOpen: function() { + Strophe.info("Tcpsocket open"); + var start = this._buildStream(); + this._conn.xmlOutput(start.tree()); + + var startString = this._removeClosingTag(start); + this._conn.rawOutput(startString); + this.socket.send(unescape(encodeURIComponent(startString))); + }, + + /** PrivateFunction: _removeClosingTag + * _Private_ function to Make the first non-selfclosing + * + * Parameters: + * (Object) elem - The tag. + * + * Returns: + * The stream:stream tag as String + */ + _removeClosingTag: function(elem) { + var string = Strophe.serialize(elem); + string = string.replace(/<(stream:stream .*[^\/])\/>$/, "<$1>"); + return string; + }, + + /** PrivateFunction: _reqToData + * _Private_ function to get a stanza out of a request. + * + * Tcpsocket don't use requests, so the passed argument is just returned. + * + * Parameters: + * (Object) stanza - The stanza. + * + * Returns: + * The stanza that was passed. + */ + _reqToData: function (stanza) + { + return stanza; + }, + + /** PrivateFunction: _send + * _Private_ part of the Connection.send function for Tcpsocket + */ + _send: function () { + this._conn.flush(); + }, + /** PrivateFunction: _sendRestart * * Send an xmpp:restart stanza.