diff options
Diffstat (limited to 'js/dojo/dojox/xmpp')
| -rw-r--r-- | js/dojo/dojox/xmpp/ChatService.js | 146 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/PresenceService.js | 312 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/README | 55 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/RosterService.js | 273 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/TransportSession.js | 512 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/UserService.js | 98 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/bosh.js | 243 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/sasl.js | 187 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/util.js | 151 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/widget/ChatSession.js | 46 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/widget/templates/ChatSession.html | 5 | ||||
| -rw-r--r-- | js/dojo/dojox/xmpp/xmppSession.js | 854 |
12 files changed, 2882 insertions, 0 deletions
diff --git a/js/dojo/dojox/xmpp/ChatService.js b/js/dojo/dojox/xmpp/ChatService.js new file mode 100644 index 0000000..c070337 --- /dev/null +++ b/js/dojo/dojox/xmpp/ChatService.js @@ -0,0 +1,146 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/ChatService", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.ChatService"); + +dojox.xmpp.chat = { + CHAT_STATE_NS: 'http://jabber.org/protocol/chatstates', + + ACTIVE_STATE: 'active', + COMPOSING_STATE: 'composing', + INACTIVE_STATE: 'inactive', + PAUSED_STATE: 'paused', + GONE_STATE: 'gone' +} + +dojo.declare("dojox.xmpp.ChatService", null, { + state: "", + + constructor: function(){ + this.state=""; + this.chatid = Math.round(Math.random() * 1000000000000000); + }, + + recieveMessage: function(msg,initial){ + if (msg&&!initial){ + this.onNewMessage(msg); + } + }, + + setSession: function(session){ + this.session = session; + }, + + setState: function(state){ + if (this.state != state){ + this.state = state; + } + }, + + invite: function(contact){ + if (this.uid){return;} + + + if(!contact || contact==''){ + throw new Error("ChatService::invite() contact is NULL"); + } + + this.uid = contact; + + var req = { + xmlns: "jabber:client", + to: this.uid, + from: this.session.jid + "/" + this.session.resource, + type: "chat" + } + var request = new dojox.string.Builder(dojox.xmpp.util.createElement("message", req, false)); + request.append(dojox.xmpp.util.createElement("thread",{},false)); + request.append(this.chatid); + request.append("</thread>"); + request.append(dojox.xmpp.util.createElement("active",{xmlns: dojox.xmpp.chat.CHAT_STATE_NS},true)); + request.append("</message>"); + this.session.dispatchPacket(request.toString()); + + this.onInvite(contact); + this.setState(dojox.xmpp.chat.CHAT_STATE_NS); + }, + + + sendMessage: function(msg){ + if (!this.uid){ + //console.log("ChatService::sendMessage() - Contact Id is null, need to invite to chat"); + return; + } + + if ((!msg.body || msg.body=="") && !msg.xhtml){return;} + + var req = { + xmlns: "jabber:client", + to: this.uid, + from: this.session.jid + "/" + this.session.resource, + type: "chat" + } + + var message = new dojox.string.Builder(dojox.xmpp.util.createElement("message",req,false)); + var html = dojox.xmpp.util.createElement("html", { "xmlns":dojox.xmpp.xmpp.XHTML_IM_NS},false) + + var bodyTag = dojox.xmpp.util.createElement("body", {"xml:lang":this.session.lang, "xmlns":dojox.xmpp.xmpp.XHTML_BODY_NS}, false) + msg.body + "</body>"; + var bodyPlainTag = dojox.xmpp.util.createElement("body", {}, false) + dojox.xmpp.util.stripHtml(msg.body) + "</body>"; +/* + if (msg.xhtml){ + if (msg.xhtml.getAttribute('xmlns') != dojox.xmpp.xmpp.XHTML_IM_NS){ + //console.log("ChatService::sendMessage() - Cannot use this xhtml without the propper xmlns"); + }else{ + //FIXME do this in some portable way + //console.log("ChatService::sendMessage() - FIXME Serialize XHTML to string: ", msg.xhtml.toString()); + } + } +*/ + if (message.subject && message.subject != ""){ + message.append(dojox.xmpp.util.createElement("subject",{},false)); + message.append(message.subject); + message.append("</subject>"); + } + message.append(bodyPlainTag); + message.append(html); + message.append(bodyTag); + message.append("</html>"); + message.append(dojox.xmpp.util.createElement("thread", {}, false)); + message.append(this.chatid); + message.append("</thread>"); + + if (this.useChatStates){ + message.append(dojox.xmpp.util.createElement("active",{xmlns: dojox.xmpp.chat.CHAT_STATE_NS},true)); + } + message.append("</message>"); + + this.session.dispatchPacket(message.toString()); + }, + + sendChatState: function(state){ + if (!this.useChatState || this.firstMessage){return;} + if (state==this._currentState){return;} + + var req={ + xmlns: "jabber:client", + to: this.uid, + from: this.session.jid + "/" + this.session.resource, + type: "chat" + } + + var request = new dojox.string.Builder(dojox.xmpp.util.createElement("message",req,false)); + request.append(dojox.xmpp.util.createElement(state, {xmlns: dojox.xmpp.chat.CHAT_STATE_NS},true)); + this._currentState = state; + request.append("<thread>"); + request.append(this.chatid); + request.append("</thread></message>"); + + this.session.dispatchPacket(request.toString()); + }, + + //EVENTS + onNewMessage: function(msg){}, + onInvite: function(contact){} +}); + +}); diff --git a/js/dojo/dojox/xmpp/PresenceService.js b/js/dojo/dojox/xmpp/PresenceService.js new file mode 100644 index 0000000..f2184b5 --- /dev/null +++ b/js/dojo/dojox/xmpp/PresenceService.js @@ -0,0 +1,312 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/PresenceService", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.PresenceService"); + +dojox.xmpp.presence = { + UPDATE: 201, + SUBSCRIPTION_REQUEST: 202, +// SUBSCRIPTION_REQUEST_PENDING: 203, + /* used when 'ask' attribute is absent on a roster item */ + SUBSCRIPTION_SUBSTATUS_NONE: 204, + + SUBSCRIPTION_NONE: 'none', + SUBSCRIPTION_FROM: 'from', + SUBSCRIPTION_TO: 'to', + SUBSCRIPTION_BOTH: 'both', + SUBSCRIPTION_REQUEST_PENDING: 'pending', + + STATUS_ONLINE: 'online', + STATUS_AWAY: 'away', + STATUS_CHAT: 'chat', + STATUS_DND: 'dnd', + STATUS_EXTENDED_AWAY: 'xa', + STATUS_OFFLINE: 'offline', + + STATUS_INVISIBLE: 'invisible' +} + +dojo.declare("dojox.xmpp.PresenceService", null, { + constructor: function(xmppService){ + this.session= xmppService; + this.isInvisible = false; + this.avatarHash = null; + this.presence = null; + this.restrictedContactjids = {}; + }, + + publish: function(presence){ + ////console.log("Presence::publish() ", presence); + this.presence = presence; + this._setPresence(); + }, + + /** + <presence from='juliet@capulet.com/balcony'> + <x xmlns='vcard-temp:x:update'> + <photo>sha1-hash-of-image</photo> + </x> + </presence> + + + <presence> + <x xmlns='vcard-temp:x:update'> + <photo/> + </x> + </presence> + + */ + + sendAvatarHash: function(avatarHash) { + this.avatarHash = avatarHash; + this._setPresence(); + }, + + + _setPresence: function() { + var presence = this.presence; + var p = {xmlns: 'jabber:client'}; + + if (presence && presence.to){ + p.to = presence.to; + } + + if (presence.show && presence.show==dojox.xmpp.presence.STATUS_OFFLINE){ + p.type = 'unavailable'; + } + + if (presence.show && presence.show==dojox.xmpp.presence.STATUS_INVISIBLE) { + this._setInvisible(); + this.isInvisible = true; + return; + }; + + if(this.isInvisible) { + //console.log("was invisible, making visible"); + this._setVisible(); + } + + var req = new dojox.string.Builder(dojox.xmpp.util.createElement("presence",p, false)); + + if (presence.show && presence.show!=dojox.xmpp.presence.STATUS_OFFLINE ) { + req.append(dojox.xmpp.util.createElement("show",{},false)); + req.append(presence.show); + req.append("</show>"); + } + + if(presence.status) { + req.append(dojox.xmpp.util.createElement("status",{},false)); + req.append(presence.status); + req.append("</status>"); + } + + if(this.avatarHash) { + req.append(dojox.xmpp.util.createElement("x",{xmlns: 'vcard-temp:x:update'},false)); + req.append(dojox.xmpp.util.createElement("photo",{},false)); + req.append(this.avatarHash); + req.append("</photo>"); + req.append("</x>"); + } + + + if (presence.priority && presence.show!=dojox.xmpp.presence.STATUS_OFFLINE){ + if(presence.priority > 127 || presence.priority < -128){ + presence.priority = 5; + } + req.append(dojox.xmpp.util.createElement("priority",{},false)); + req.append(presence.priority); + req.append("</priority>"); + } + + req.append("</presence>"); + this.session.dispatchPacket(req.toString()); + }, + + /* + + <iq from='bilbo@tolkien.lit/shire' type='set' id='inv1'> + <query xmlns='jabber:iq:privacy'> + <list name='invisible'> + <item action='deny' order='1'> + <presence-out/> + </item> + </list> + </query> + </iq> + + <iq from='bilbo@tolkien.lit/shire' type='set' id='active1'> + <query xmlns='jabber:iq:privacy'> + <active name='invisible'/> + </query> + </iq> + + Make visible: + <iq from='bilbo@tolkien.lit/shire' type='set' id='active6'> + <query xmlns='jabber:iq:privacy'> + <active/> + </query> + </iq> + + */ + + toggleBlockContact: function(jid) { + if(!this.restrictedContactjids[jid]) { + this.restrictedContactjids[jid] = this._createRestrictedJid(); + } + + this.restrictedContactjids[jid].blocked = !this.restrictedContactjids[jid].blocked; + //console.log("setting outbound block for ", jid, this.restrictedContactjids[jid]); + this._updateRestricted(); + return this.restrictedContactjids; + }, + + + toggleContactInvisiblity: function(jid) { + if(!this.restrictedContactjids[jid]) { + this.restrictedContactjids[jid] = this._createRestrictedJid(); + } + + this.restrictedContactjids[jid].invisible = !this.restrictedContactjids[jid].invisible; + //console.log("setting outbound presence for ", jid, this.restrictedContactjids[jid]); + this._updateRestricted(); + return this.restrictedContactjids; + }, + + _createRestrictedJid: function() { + return {invisible: false, blocked:false}; + }, + + _updateRestricted: function() { + + var props={ + id: this.session.getNextIqId(), + from: this.session.jid + "/" + this.session.resource, + type: "set" + }; + + var req = new dojox.string.Builder(dojox.xmpp.util.createElement("iq",props,false)); + req.append(dojox.xmpp.util.createElement("query",{xmlns: "jabber:iq:privacy"},false)); + req.append(dojox.xmpp.util.createElement("list",{name: "iwcRestrictedContacts"},false)) + var count = 1; + for(var jid in this.restrictedContactjids) { + var item = this.restrictedContactjids[jid]; + //console.log("restricted ", jid, item); + if(item.blocked || item.invisible) { + req.append(dojox.xmpp.util.createElement("item",{value: dojox.xmpp.util.encodeJid(jid), action: "deny", order: count++},false)); + if(item.blocked) { + req.append(dojox.xmpp.util.createElement("message",{},true)); + } + if(item.invisible) { + req.append(dojox.xmpp.util.createElement("presence-out",{},true)); + } + req.append("</item>"); + } else { + delete this.restrictedContactjids[jid]; + } + + + + } + req.append("</list>"); + req.append("</query>"); + req.append("</iq>"); + //console.log("Restricted list: ", req.toString()); + + var req2 = new dojox.string.Builder(dojox.xmpp.util.createElement("iq",props,false)); + req2.append(dojox.xmpp.util.createElement("query",{xmlns: "jabber:iq:privacy"},false)); + req2.append(dojox.xmpp.util.createElement("active",{name:"iwcRestrictedContacts"},true)); + req2.append("</query>"); + req2.append("</iq>"); + + //console.log("Activate list: ", req2.toString()); + + + this.session.dispatchPacket(req.toString()); + this.session.dispatchPacket(req2.toString()); + }, + + _setVisible: function() { + var props={ + id: this.session.getNextIqId(), + from: this.session.jid + "/" + this.session.resource, + type: "set" + }; + var req = new dojox.string.Builder(dojox.xmpp.util.createElement("iq",props,false)); + req.append(dojox.xmpp.util.createElement("query",{xmlns: "jabber:iq:privacy"},false)); + req.append(dojox.xmpp.util.createElement("active",{},true)); + req.append("</query>"); + req.append("</iq>"); + //console.log(req.toString()); + this.session.dispatchPacket(req.toString()); + }, + + _setInvisible: function() { + //console.log("Setting user as invisible"); + var props={ + id: this.session.getNextIqId(), + from: this.session.jid + "/" + this.session.resource, + type: "set" + }; + var req = new dojox.string.Builder(dojox.xmpp.util.createElement("iq",props,false)); + req.append(dojox.xmpp.util.createElement("query",{xmlns: "jabber:iq:privacy"},false)); + req.append(dojox.xmpp.util.createElement("list",{name: "invisible"},false)) + req.append(dojox.xmpp.util.createElement("item",{action: "deny", order: "1"},false)) + req.append(dojox.xmpp.util.createElement("presence-out",{},true)); + req.append("</item>"); + req.append("</list>"); + req.append("</query>"); + req.append("</iq>"); + + + props={ + id: this.session.getNextIqId(), + from: this.session.jid + "/" + this.session.resource, + type: "set" + }; + + var req2 = new dojox.string.Builder(dojox.xmpp.util.createElement("iq",props,false)); + req2.append(dojox.xmpp.util.createElement("query",{xmlns: "jabber:iq:privacy"},false)); + req2.append(dojox.xmpp.util.createElement("active",{name:"invisible"},true)); + req2.append("</query>"); + req2.append("</iq>"); + //console.log(req.toString()); + //console.log(req2.toString()); + this.session.dispatchPacket(req.toString()); + this.session.dispatchPacket(req2.toString()); + }, + + _manageSubscriptions: function(contact, type){ + if (!contact){return;} + + if (contact.indexOf('@')==-1){ + contact += '@' + this.session.domain; + } + + var req = dojox.xmpp.util.createElement("presence",{to:contact,type:type},true); + this.session.dispatchPacket(req); + + }, + + subscribe: function(contact){ + this._manageSubscriptions(contact, "subscribe"); + }, + + approveSubscription: function(contact){ + this._manageSubscriptions(contact, "subscribed"); + }, + + unsubscribe: function(contact){ + this._manageSubscriptions(contact, "unsubscribe"); + }, + + declineSubscription: function(contact){ + this._manageSubscriptions(contact, "unsubscribed"); + }, + + cancelSubscription: function(contact){ + this._manageSubscriptions(contact, "unsubscribed"); + } + +}); + +}); diff --git a/js/dojo/dojox/xmpp/README b/js/dojo/dojox/xmpp/README new file mode 100644 index 0000000..f4e8856 --- /dev/null +++ b/js/dojo/dojox/xmpp/README @@ -0,0 +1,55 @@ +------------------------------------------------------------------------------- +DojoX XMPP (Jabber Client) +------------------------------------------------------------------------------- +Version .9 +Release date: 07/05/2008 +------------------------------------------------------------------------------- +Project state: experimental +------------------------------------------------------------------------------- +[ ] l18n support? +[ ] a11y support? +------------------------------------------------------------------------------- +Credits + Dustin Machi + Jason Cline + Revin Guillen + Mike Wilcox - updates +------------------------------------------------------------------------------- +Project description + +XMPP Service implementation in pure javascript. Uses BOSH and works cross +domain. +------------------------------------------------------------------------------- +Dependencies: + +Dojo Core +------------------------------------------------------------------------------- +Documentation + +FIXME +------------------------------------------------------------------------------- +Installation instructions + +To use the XMPP test, you should have the appropriate server installed on your +machine. We reccomend Openfire, a real time collaboration server licensed under +the Open Source GPL.: +http://www.igniterealtime.org/projects/openfire/index.jsp + +It's very easy to install. Download the version for your machine and launch the installer. +After installation is complete, server settings are made at: +http://127.0.0.1:9090/index.jsp + +The settings for the most part are those that suggest the easiest for setup. The main setting +you need to notice is HTTP Binding. This needs to be enabled at port 7070. Also enable +Script Syntax for BOSH clients. + +Next go to the top tabs to Users/Groups and create a user or two. It gives you the option to make +a user the Admin - this will overwrite the current Admin. + +Now you can launch test_xmppService.html. In the login, use the user ID and password from one of +the users you just created. I used my computer name for Domain, but I'm not sure what this does. +Finally, in HTTP-Bind URL, use the address for Openfire, with the HTTP Bind port of 7070: +http://127.0.0.1:7070/http-bind/ + +You can open another tab in Firefox and log in as a second user. + diff --git a/js/dojo/dojox/xmpp/RosterService.js b/js/dojo/dojox/xmpp/RosterService.js new file mode 100644 index 0000000..5264323 --- /dev/null +++ b/js/dojo/dojox/xmpp/RosterService.js @@ -0,0 +1,273 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/RosterService", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.RosterService"); + +dojox.xmpp.roster = { + ADDED: 101, + CHANGED: 102, + REMOVED: 103 +} + +dojo.declare("dojox.xmpp.RosterService", null, { + constructor: function(xmppSession){ + this.session = xmppSession; + }, + + addRosterItem: function(jid, name, groups){ + if(!jid){ + throw new Error ("Roster::addRosterItem() - User ID is null"); + } + var iqId = this.session.getNextIqId(); + var req = { + id: iqId, + from: this.session.jid + "/" + this.session.resource, + type: "set" + } + + var request = new dojox.string.Builder(dojox.xmpp.util.createElement("iq", req, false)); + request.append(dojox.xmpp.util.createElement("query",{xmlns: 'jabber:iq:roster'},false)); + jid = dojox.xmpp.util.encodeJid(jid); + if (jid.indexOf('@')== -1){ + jid = jid + '@' + this.session.domain; + } + + + request.append(dojox.xmpp.util.createElement("item",{jid:jid,name:dojox.xmpp.util.xmlEncode(name)},false)); + + if (groups){ + for (var i=0; i<groups.length; i++){ + request.append("<group>"); + request.append(groups[i]); + request.append("</group>"); + } + } + + request.append("</item></query></iq>"); + //console.log(request.toString()); + + var def = this.session.dispatchPacket(request.toString(),"iq",req.id); + def.addCallback(this, "verifyRoster"); + return def; + }, + + updateRosterItem: function(jid, name, groups){ + if (jid.indexOf('@') == -1){ + jid += jid + '@' + this.session.domain; + } + + var req = { + id: this.session.getNextIqId(), + from: this.session.jid + "/" + this.session.resource, + type: "set" + } + + var request = new dojox.string.Builder(dojox.xmpp.util.createElement("iq", req, false)); + request.append(dojox.xmpp.util.createElement("query",{xmlns: 'jabber:iq:roster'},false)); + + var i = this.session.getRosterIndex(jid); + + //item not found + if (i==-1){return;} + var item = { + jid:jid + }; + if(name){ + item.name = name; + } else if(this.session.roster[i].name){ + item.name = this.session.roster[i].name; + } + if(item.name) { + item.name = dojox.xmpp.util.xmlEncode(item.name); + } + request.append(dojox.xmpp.util.createElement("item",item,false)); + + var newGroups = groups ? groups : this.session.roster[i].groups; + + if (newGroups){ + for (var x=0;x<newGroups.length;x++){ + request.append("<group>"); + request.append(newGroups[x]); + request.append("</group>"); + } + } + + request.append("</item></query></iq>"); + + var def = this.session.dispatchPacket(request.toString(),"iq",req.id); + def.addCallback(this, "verifyRoster"); + return def; + }, + + verifyRoster: function(res){ + if (res.getAttribute('type')=='result'){ + //this.onAddRosterItem(res.getAttribute('id')); + }else{ + var err=this.session.processXmppError(res); + this.onAddRosterItemFailed(err); + } + return res; + }, + + addRosterItemToGroup: function(jid, group){ + if (!jid) throw new Error("Roster::addRosterItemToGroup() JID is null or undefined"); + if (!group) throw new Error("Roster::addRosterItemToGroup() group is null or undefined"); + + var index = this.session.getRosterIndex(jid); + if (index==-1){return;} + + var item = this.session.roster[index]; + var tgroups = []; + + var found = false; + + for (var i=0; ((item<item.groups.length) && (!found)); i++){ + if (item.groups[i]!=group){continue;} + found=true; + } + + if(!found){ + return this.updateRosterItem(jid, item.name, item.groups.concat(group),index); + } + + return dojox.xmpp.xmpp.INVALID_ID; + }, + + removeRosterGroup: function(group) { + var roster = this.session.roster; + for(var i=0;i<roster.length;i++){ + var item = roster[i]; + if(item.groups.length > 0) { + //var found = false; + for(var j = 0;j < item.groups.length; j++) { + if (item.groups[j]==group){ + item.groups.splice(j,1); + this.updateRosterItem(item.jid, item.name, item.groups); + //found=true; + } + } + } + } + }, + + renameRosterGroup: function(group, newGroup) { + var roster = this.session.roster; + for(var i=0;i<roster.length;i++){ + var item = roster[i]; + if(item.groups.length > 0) { + //var found = false; + for(var j = 0;j < item.groups.length; j++) { + if (item.groups[j]==group){ + item.groups[j] = newGroup; + this.updateRosterItem(item.jid, item.name, item.groups); + // found=true; + } + } + } + } + }, + + removeRosterItemFromGroup: function(jid, group){ + if (!jid) throw new Error("Roster::addRosterItemToGroup() JID is null or undefined"); + if (!group) throw new Error("Roster::addRosterItemToGroup() group is null or undefined"); + + var index = this.session.getRosterIndex(jid); + if (index==-1){return;} + + var item = this.session.roster[index]; + var found = false; + + for (var i=0; ((i<item.groups.length) && (!found)); i++){ + if (item.groups[i]!=group){continue;} + found=true; + index = i; + } + + if(found==true){ + item.groups.splice(index,1); + return this.updateRosterItem(jid, item.name, item.groups); + } + + return dojox.xmpp.xmpp.INVALID_ID; + }, + + rosterItemRenameGroup: function(jid, oldGroup, newGroup){ + if (!jid) throw new Error("Roster::rosterItemRenameGroup() JID is null or undefined"); + if (!newGroup) throw new Error("Roster::rosterItemRenameGroup() group is null or undefined"); + + var index = this.session.getRosterIndex(jid); + if (index==-1){return;} + + var item = this.session.roster[index]; + var found = false; + + for (var i=0; ((i<item.groups.length) && (!found)); i++){ + if (item.groups[i]==oldGroup){ + item.groups[i] = newGroup; + found=true; + } + } + + if(found==true){ + return this.updateRosterItem(jid, item.name, item.groups); + } + + return dojox.xmpp.xmpp.INVALID_ID; + }, + + renameRosterItem: function(jid,newName){ + if (!jid) throw new Error("Roster::addRosterItemToGroup() JID is null or undefined"); + if (!newName) throw new Error("Roster::addRosterItemToGroup() New Name is null or undefined"); + + var index = this.session.getRosterIndex(jid); + if (index==-1){return;} + + return this.updateRosterItem(jid, newName, this.session.roster.groups,index); + }, + + removeRosterItem: function(jid){ + if (!jid) throw new Error("Roster::addRosterItemToGroup() JID is null or undefined"); + + var req={ + id: this.session.getNextIqId(), + from: this.session.jid + "/" + this.session.resource, + type: 'set' + }; + var request = new dojox.string.Builder(dojox.xmpp.util.createElement("iq", req, false)); + + request.append(dojox.xmpp.util.createElement("query",{xmlns: "jabber:iq:roster"},false)); + + if (jid.indexOf('@')== -1){ + jid += jid + '@' + this.session.domain; + } + + request.append(dojox.xmpp.util.createElement('item',{jid:jid,subscription:"remove"},true)); + + request.append("</query></iq>"); + + var def = this.session.dispatchPacket(request.toString(),"iq",req.id); + def.addCallback(this, "verifyRoster"); + return def; + }, + + //Avatar functions...I removed this stuff for now..can we even do anything useful + //with this data even if we have it? + getAvatar: function(jid){ + }, + + publishAvatar: function(type,binval){ + + }, + + //EVENTS + + onVerifyRoster: function(id){ + //console.log("Roster::onVerifyRoster() - ", id); + }, + + onVerifyRosterFailed: function(err){ + //console.log("onVerifyRosterFailed: ", err); + } +}); + +}); diff --git a/js/dojo/dojox/xmpp/TransportSession.js b/js/dojo/dojox/xmpp/TransportSession.js new file mode 100644 index 0000000..aa4d032 --- /dev/null +++ b/js/dojo/dojox/xmpp/TransportSession.js @@ -0,0 +1,512 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/TransportSession", ["dijit","dojo","dojox","dojo/require!dojox/xmpp/bosh,dojox/xmpp/util,dojox/data/dom"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.TransportSession"); +dojo.require("dojox.xmpp.bosh"); +dojo.require("dojox.xmpp.util"); +dojo.require("dojox.data.dom"); + +dojox.xmpp.TransportSession = function(props) { + // we have to set this here because "this" doesn't work + // in the dojo.extend call. + this.sendTimeout = (this.wait+20)*1000; + + //mixin any options that we want to provide to this service + if (props && dojo.isObject(props)) { + dojo.mixin(this, props); + if(this.useScriptSrcTransport){ + this.transportIframes = []; + } + } + +}; + +dojo.extend(dojox.xmpp.TransportSession, { + + /* options/defaults */ + rid: 0, + hold: 1, + polling:1000, + secure: false, + wait: 60, + lang: 'en', + submitContentType: 'text/xml; charset=utf=8', + serviceUrl: '/httpbind', + defaultResource: "dojoIm", + domain: 'imserver.com', + sendTimeout: 0, //(this.wait+20)*1000 + + useScriptSrcTransport:false, + + + keepAliveTimer:null, + + //status + state: "NotReady", + transmitState: "Idle", + + protocolPacketQueue: [], + outboundQueue: [], + outboundRequests: {}, + inboundQueue: [], + deferredRequests: {}, + matchTypeIdAttribute: {}, + + open: function() { + this.status = "notReady"; + this.rid = Math.round(Math.random() * 1000000000); + this.protocolPacketQueue = []; + this.outboundQueue = []; + this.outboundRequests = {}; + this.inboundQueue = []; + this.deferredRequests = {}; + this.matchTypeIdAttribute = {}; + + + this.keepAliveTimer = setTimeout(dojo.hitch(this, "_keepAlive"), 10000); + + if(this.useScriptSrcTransport){ + dojox.xmpp.bosh.initialize({ + iframes: this.hold+1, + load: dojo.hitch(this, function(){ + this._sendLogin(); + }) + }); + } else { + this._sendLogin(); + } + }, + + _sendLogin: function() { + var rid = this.rid++; + var req = { + content: this.submitContentType, + hold: this.hold, + rid: rid, + to: this.domain, + secure: this.secure, + wait: this.wait, + "xml:lang": this.lang, + "xmpp:version": "1.0", + xmlns: dojox.xmpp.xmpp.BODY_NS, + "xmlns:xmpp": "urn:xmpp:xbosh" + }; + + var msg = dojox.xmpp.util.createElement("body", req, true); + this.addToOutboundQueue(msg, rid); + }, + + _sendRestart: function(){ + var rid = this.rid++; + var req = { + rid: rid, + sid: this.sid, + to: this.domain, + "xmpp:restart": "true", + "xml:lang": this.lang, + xmlns: dojox.xmpp.xmpp.BODY_NS, + "xmlns:xmpp": "urn:xmpp:xbosh" + }; + + var msg = dojox.xmpp.util.createElement("body", req, true); + this.addToOutboundQueue(msg, rid); + }, + + processScriptSrc: function(msg, rid) { + //console.log("processScriptSrc::", rid, msg); + // var msgDom = dojox.xml.DomParser.parse(msg); + var msgDom = dojox.xml.parser.parse(msg, "text/xml"); + //console.log("parsed mgs", msgDom); + //console.log("Queue", this.outboundQueue); + if(msgDom) { + this.processDocument(msgDom, rid); + } else { + //console.log("Recived bad document from server",msg); + } + }, + + _keepAlive: function(){ + if (this.state=="wait" || this.isTerminated()) { + return; + } + this._dispatchPacket(); + this.keepAliveTimer = setTimeout(dojo.hitch(this, "_keepAlive"), 10000); + }, + + + close: function(protocolMsg){ + + + var rid = this.rid++; + var req = { + + sid: this.sid, + rid: rid, + type: "terminate" + }; + var envelope = null; + + if (protocolMsg) { + envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, false)); + envelope.append(protocolMsg); + envelope.append("</body>"); + } else { + envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, false)); + } + + // this.sendXml(envelope,rid); + this.addToOutboundQueue(envelope.toString(), rid); + this.state=="Terminate"; + }, + + dispatchPacket: function(msg, protocolMatchType, matchId, matchProperty){ + // summary + // Main Packet dispatcher, most calls should be made with this other + // than a few setup calls which use add items to the queue directly + //protocolMatchType, matchId, and matchProperty are optional params + //that allow a deferred to be tied to a protocol response instad of the whole + //rid + + // //console.log("In dispatchPacket ", msg, protocolMatchType, matchId, matchProperty); + if (msg){ + this.protocolPacketQueue.push(msg); + } + + var def = new dojo.Deferred(); + //def.rid = req.rid; + + if (protocolMatchType && matchId){ + def.protocolMatchType = protocolMatchType; + def.matchId = matchId; + def.matchProperty = matchProperty || "id"; + if(def.matchProperty != "id") { + this.matchTypeIdAttribute[protocolMatchType] = def.matchProperty; + } + } + + this.deferredRequests[def.protocolMatchType + "-" +def.matchId]=def; + if(!this.dispatchTimer) { + this.dispatchTimer = setTimeout(dojo.hitch(this, "_dispatchPacket"), 600); + } + return def; + }, + + _dispatchPacket: function(){ + + clearTimeout(this.dispatchTimer); + delete this.dispatchTimer; + + if (!this.sid){ + console.debug("TransportSession::dispatchPacket() No SID, packet dropped.") + return; + } + + if (!this.authId){ + //FIXME according to original nodes, this should wait a little while and try + // again up to three times to see if we get this data. + console.debug("TransportSession::dispatchPacket() No authId, packet dropped [FIXME]") + return; + } + + + + //if there is a pending request with the server, don't poll + if (this.transmitState != "error" && (this.protocolPacketQueue.length == 0) && (this.outboundQueue.length > 0)) { + return; + } + + if (this.state=="wait" || this.isTerminated()) { + return; + } + + var req = { + sid: this.sid, + xmlns: dojox.xmpp.xmpp.BODY_NS + } + + var envelope + if (this.protocolPacketQueue.length > 0){ + req.rid= this.rid++; + envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, false)); + envelope.append(this.processProtocolPacketQueue()); + envelope.append("</body>"); + delete this.lastPollTime; + } else { + //console.log("Nothing to send, I'm just polling."); + if(this.lastPollTime) { + var now = new Date().getTime(); + if(now - this.lastPollTime < this.polling) { + //console.log("Waiting to poll ", this.polling - (now - this.lastPollTime)+10); + this.dispatchTimer = setTimeout(dojo.hitch(this, "_dispatchPacket"), this.polling - (now - this.lastPollTime)+10); + return; + } + + } + req.rid= this.rid++; + this.lastPollTime = new Date().getTime(); + envelope = new dojox.string.Builder(dojox.xmpp.util.createElement("body", req, true)); + + } + + + this.addToOutboundQueue(envelope.toString(),req.rid); + + }, + + redispatchPacket: function(rid){ + var env = this.outboundRequests[rid]; + this.sendXml(env, rid); + }, + + addToOutboundQueue: function(msg, rid){ + this.outboundQueue.push({msg: msg,rid: rid}); + this.outboundRequests[rid]=msg; + this.sendXml(msg, rid); + }, + + removeFromOutboundQueue: function(rid){ + for(var i=0; i<this.outboundQueue.length;i++){ + if (rid == this.outboundQueue[i]["rid"]){ + this.outboundQueue.splice(i, 1); + break; + } + } + delete this.outboundRequests[rid]; + }, + + processProtocolPacketQueue: function(){ + var packets = new dojox.string.Builder(); + for(var i=0; i<this.protocolPacketQueue.length;i++){ + packets.append(this.protocolPacketQueue[i]); + } + this.protocolPacketQueue=[]; + return packets.toString(); + }, + + sendXml: function(message, rid){ + if(this.isTerminated()) { + return false; + } + //console.log("TransportSession::sendXml()"+ new Date().getTime() + " RID: ", rid, " MSG: ", message); + this.transmitState = "transmitting"; + var def = null; + if(this.useScriptSrcTransport) { + //console.log("using script src to transmit"); + def = dojox.xmpp.bosh.get({ + rid: rid, + url: this.serviceUrl+'?'+encodeURIComponent(message), + error: dojo.hitch(this, function(res, io){ + this.setState("Terminate", "error"); + return false; + }), + timeout: this.sendTimeout + }); + } else { + def = dojo.rawXhrPost({ + contentType: "text/xml", + url: this.serviceUrl, + postData: message, + handleAs: "xml", + error: dojo.hitch(this, function(res, io) { + ////console.log("foo", res, io.xhr.responseXML, io.xhr.status); + return this.processError(io.xhr.responseXML, io.xhr.status , rid); + }), + timeout: this.sendTimeout + }); + } + //process the result document + def.addCallback(this, function(res){ + return this.processDocument(res, rid); + }); + return def; + }, + + processDocument: function(doc, rid){ + if(this.isTerminated() || !doc.firstChild) { + return false; + } + //console.log("TransportSession:processDocument() ", doc, rid); + this.transmitState = "idle"; + + var body = doc.firstChild; + if (body.nodeName != 'body'){ + //console.log("TransportSession::processDocument() firstChild is not <body> element ", doc, " RID: ", rid); + } + + if (this.outboundQueue.length<1){return false;} + + var expectedId = this.outboundQueue[0]["rid"]; + //console.log("expectedId", expectedId); + if (rid==expectedId){ + this.removeFromOutboundQueue(rid); + this.processResponse(body, rid); + this.processInboundQueue(); + }else{ + //console.log("TransportSession::processDocument() rid: ", rid, " expected: ", expectedId); + var gap = rid-expectedId; + + if (gap < this.hold + 2){ + this.addToInboundQueue(doc,rid); + }else{ + //console.log("TransportSession::processDocument() RID is outside of the expected response window"); + } + } + return doc; + }, + + processInboundQueue: function(){ + while (this.inboundQueue.length > 0) { + var item = this.inboundQueue.shift(); + this.processDocument(item["doc"], item["rid"]); + } + }, + + addToInboundQueue: function(doc,rid){ + for (var i=0; i<this.inboundQueue.length;i++){ + if (rid < this.inboundQueue[i]["rid"]){continue;} + this.inboundQueue.splice(i,0,{doc: doc, rid: rid}); + } + }, + + processResponse: function(body,rid){ + ////console.log("TransportSession:processResponse() ", body, " RID: ", rid); + + if (body.getAttribute("type")=='terminate'){ + var reasonNode = body.firstChild.firstChild; + var errorMessage = ""; + if(reasonNode.nodeName == "conflict") { + errorMessage = "conflict" + } + this.setState("Terminate", errorMessage); + + return; + } + + if ((this.state != 'Ready')&&(this.state != 'Terminate')) { + var sid=body.getAttribute("sid"); + if (sid){ + this.sid=sid; + } else { + throw new Error("No sid returned during xmpp session startup"); + } + + this.authId = body.getAttribute("authid"); + if (this.authId == "") { + if (this.authRetries-- < 1) { + console.error("Unable to obtain Authorization ID"); + this.terminateSession(); + } + } + this.wait= body.getAttribute("wait"); + if( body.getAttribute("polling")){ + this.polling= parseInt(body.getAttribute("polling"))*1000; + } + + //console.log("Polling value ", this.polling); + this.inactivity = body.getAttribute("inactivity"); + this.setState("Ready"); + } + + dojo.forEach(body.childNodes, function(node){ + this.processProtocolResponse(node, rid); + }, this); + + //need to make sure, since if you use sendXml directly instead of using + //dispatch packets, there wont' be a call back function here + //normally the deferred will get fired by a child message at the protocol level + //but if it hasn't fired by now, go ahead and fire it with the full body + /*if (this.deferredRequests[rid] && this.deferredRequests[rid].fired==-1){ + this.deferredRequests[rid].callback(body); + }*/ + + //delete from the list of outstanding requests + //delete this.deferredRequests[rid]; + + if (this.transmitState == "idle"){ + this.dispatchPacket(); + } + }, + + + processProtocolResponse: function(msg, rid){ + //summary + //process the individual protocol messages and if there + //is a matching set of protocolMatchType, matchId, and matchPropery + //fire off the deferred + + this.onProcessProtocolResponse(msg); + var key = msg.nodeName + "-" +msg.getAttribute("id"); + var def = this.deferredRequests[key]; + if (def){ + def.callback(msg); + delete this.deferredRequests[key]; + } + }, + + setState: function(state, message){ + if (this.state != state) { + if (this["on"+state]){ + this["on"+state](state, this.state, message); + } + this.state=state; + } + }, + + isTerminated: function() { + + return this.state=="Terminate"; + }, + + processError: function(err, httpStatusCode,rid){ + //console.log("Processing server error ", err, httpStatusCode,rid); + if(this.isTerminated()) { + return false; + } + + + if(httpStatusCode != 200) { + if(httpStatusCode >= 400 && httpStatusCode < 500){ + /* Any status code between 400 and 500 should terminate + * the connection */ + this.setState("Terminate", errorMessage); + return false; + }else{ + this.removeFromOutboundQueue(rid); + setTimeout(dojo.hitch(this, function(){ this.dispatchPacket(); }), 200); + return true; + } + return false; + } + + if (err && err.dojoType && err.dojoType=="timeout"){ + //console.log("Wait timeout"); + } + + this.removeFromOutboundQueue(rid); + //FIXME conditional processing if request will be needed based on type of error. + if(err && err.firstChild) { + //console.log("Error ", err.firstChild.getAttribute("type") + " status code " + httpStatusCode); + + if (err.firstChild.getAttribute("type")=='terminate'){ + var reasonNode = err.firstChild.firstChild; + var errorMessage = ""; + if(reasonNode && reasonNode.nodeName == "conflict") { + errorMessage = "conflict" + } + this.setState("Terminate", errorMessage); + return false; + } + } + this.transmitState = "error"; + setTimeout(dojo.hitch(this, function(){ this.dispatchPacket(); }), 200); + //console.log("Error: ", arguments); + return true; + }, + + //events + onTerminate: function(newState, oldState, message){ }, + onProcessProtocolResponse: function(msg){}, + onReady: function(newState, oldState){} +}); + +}); diff --git a/js/dojo/dojox/xmpp/UserService.js b/js/dojo/dojox/xmpp/UserService.js new file mode 100644 index 0000000..1c9fe69 --- /dev/null +++ b/js/dojo/dojox/xmpp/UserService.js @@ -0,0 +1,98 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/UserService", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.UserService"); + +dojo.declare("dojox.xmpp.UserService", null, { + constructor: function(xmppService){ + this.session= xmppService; + }, + + getPersonalProfile: function(){ + var req={ + id: this.session.getNextIqId(), + type: 'get' + } + var request = new dojox.string.Builder(dojox.xmpp.util.createElement("iq",req,false)); + request.append(dojox.xmpp.util.createElement("query",{xmlns:"jabber:iq:private"},false)); + request.append(dojox.xmpp.util.createElement("sunmsgr",{xmlsns:'sun:xmpp:properties'},true)); + request.append("</query></iq>"); + + var def = this.session.dispatchPacket(request.toString(),"iq",req.id); + def.addCallback(this, "_onGetPersonalProfile"); + }, + + setPersonalProfile: function(props){ + var req={ + id: this.session.getNextIqId(), + type: 'set' + } + + var request = new dojox.string.Builder(dojox.xmpp.util.createElement("iq",req,false)); + request.append(dojox.xmpp.util.createElement("query",{xmlns:"jabber:iq:private"},false)); + request.append(dojox.xmpp.util.createElement("sunmsgr",{xmlsns:'sun:xmpp:properties'},false)); + + for (var key in props){ + request.append(dojox.xmpp.util.createElement("property",{name: key},false)); + request.append(dojox.xmpp.util.createElement("value",{},false)); + request.append(props[key]); + request.append("</value></props>"); + } + + request.append("</sunmsgr></query></iq>"); + + var def = this.session.dispatchPacket(request.toString(), "iq", req.id); + def.addCallback(this, "_onSetPersonalProfile"); + }, + + _onSetPersonalProfile: function(response){ + if(response.getAttribute('type')=='result'){ + this.onSetPersonalProfile(response.getAttribute('id')); + }else if(response.getAttribute('type')=='error'){ + var err = this.session.processXmppError(response); + this.onSetPersonalProfileFailure(err); + } + }, + + onSetPersonalProfile: function(id){}, + onSetPersonalProfileFailure: function(err){}, + + _onGetPersonalProfile: function(profile){ + if (profile.getAttribute('type')=='result'){ + var props = {}; + + if (profile.hasChildNodes()){ + var queryNode = profile.firstChild; + if ((queryNode.nodeName=="query")&&(queryNode.getAttribute('xmlns')=='jabber:iq:private')){ + var sunNode = queryNode.firstChild; + if ((sunNode.nodeName=='query')&&(sunNode.getAttributes('xmlns')=='sun:xmpp:properties')){ + for (var i=0; i<sunNode.childNodes.length;i++){ + var n = sunNode.childNodes[i]; + if(n.nodeName == 'property'){ + var name = n.getAttribute('name'); + var val = n.firstChild || ""; + props[name]=val; + } + } + } + } + this.onGetPersonalProfile(props); + } + }else if (profile.getAttribute('type')=='error'){ + var err = this.session.processXmppError(profile); + this.onGetPersonalProfileFailure(err); + } + + return profile; + }, + + onGetPersonalProfile: function(profile){ + //console.log("UserService::onGetPersonalProfile() ", profile); + }, + + onGetPersonalProfileFailure: function(err){ + //console.log("UserService::onGetPersonalProfileFailure() ", err); + } +}); + +}); diff --git a/js/dojo/dojox/xmpp/bosh.js b/js/dojo/dojox/xmpp/bosh.js new file mode 100644 index 0000000..680aa15 --- /dev/null +++ b/js/dojo/dojox/xmpp/bosh.js @@ -0,0 +1,243 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/bosh", ["dijit","dojo","dojox","dojo/require!dojo/io/script,dojo/io/iframe,dojox/xml/parser"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.bosh"); + +dojo.require("dojo.io.script"); +dojo.require("dojo.io.iframe"); +dojo.require("dojox.xml.parser"); + +/*===== +dojo.declare("dojox.xmpp.bosh.__initArgs", null, { + constructor: function(){ + // summary: + // The arguments passed to dojox.xmpp.bosh.initialize + // iframes: + // The number of iframes to use for transmission + // load: + // The function called when the first iframe is + // loaded. Generally used to signal when to send + // login information + this.iframes = iframes; + this.load = load; + } +}); +dojo.declare("dojox.xmpp.bosh.__ioArgs", dojo.__IoArgs, { + constructor: function(){ + // summary: + // All the properties described in the dojo.__ioArgs type, apply to this + // type as well, EXCEPT "handleAs". It is not applicable to + // dojox.xmpp.bosh.get() calls, since it is implied that the + // return will be a string of XML. + // rid: + // The rid of the message being sent. + this.rid = rid; + } +}); +=====*/ + +dojox.xmpp.bosh = { + transportIframes: [], + initialize: function(/*dojox.xmpp.bosh.__initArgs*/ args){ + this.transportIframes = []; + + var scopedObj = dojox._scopeName + '.xmpp.bosh'; + + var c = dojo.connect(dojo.getObject(scopedObj), '_iframeOnload', this, function(index){ + if(index==0){ + args.load(); + dojo.disconnect(c); + } + }); + + for(var i = 0; i < args.iframes; i++){ + var fname = 'xmpp-transport-'+i; + var iframe = dojo.byId('xmpp-transport-'+i); + if(iframe){ + // we have to clean up the dojo.io.iframe references + if(window[fname]){ window[fname] = null; } + if(window.frames[fname]){ window.frames[fname] = null; } + dojo.destroy(iframe); + } + iframe = dojo.io.iframe.create("xmpp-transport-" + i, scopedObj + "._iframeOnload("+i+");" ); + this.transportIframes.push(iframe); + } + }, + + _iframeOnload: function(index){ + var doc = dojo.io.iframe.doc(dojo.byId("xmpp-transport-" + index)); + doc.write("<script>var isLoaded=true; var rid=0; var transmiting=false; function _BOSH_(msg) { transmiting=false; parent.dojox.xmpp.bosh.handle(msg, rid); } </script>"); + }, + + findOpenIframe: function() { + for(var i = 0; i < this.transportIframes.length; i++) { + var iframe = this.transportIframes[i]; + var win = iframe.contentWindow; + //console.log("Open transport?", win, win.isLoaded, win.transmiting); + + if(win.isLoaded && !win.transmiting) { + return iframe; + } + } + return false; + }, + + handle: function(msg, rid){ + var dfd = this['rid'+rid]; + + var xmlMsg = dojox.xml.parser.parse(msg, 'text/xml'); + + if(xmlMsg){ + dfd.ioArgs.xmppMessage = xmlMsg; + }else{ + dfd.errback(new Error("Recieved bad document from server: " + msg)); + } + }, + + get: function(/*dojox.xmpp.bosh.__ioArgs*/args){ + // summary: + // sends a get request using a dynamically created script tag. + var iframe = this.findOpenIframe(); + var iframeDoc = dojo.io.iframe.doc(iframe); + + args.frameDoc = iframeDoc; + + var dfd = this._makeScriptDeferred(args); + var ioArgs = dfd.ioArgs; + + iframe.contentWindow.rid=ioArgs.rid; + iframe.contentWindow.transmiting=true; + + dojo._ioAddQueryToUrl(ioArgs); + dojo._ioNotifyStart(dfd); + + dojo.io.script.attach(ioArgs.id, ioArgs.url, iframeDoc); + + dojo._ioWatch(dfd, this._validCheck, this._ioCheck, this._resHandle); + return dfd; + }, + + remove: function(/*String*/id, /*Document?*/frameDocument){ + //summary: removes the script element with the given id, from the given frameDocument. + //If no frameDocument is passed, the current document is used. + dojo.destroy(dojo.byId(id, frameDocument)); + + //Remove the BOSH callback on dojox.xmpp.bosh, if it exists. + if(this[id]){ + delete this[id]; + } + }, + + _makeScriptDeferred: function(/*Object*/args){ + //summary: + // sets up a Deferred object for an IO request. + var dfd = dojo._ioSetArgs(args, this._deferredCancel, this._deferredOk, this._deferredError); + + var ioArgs = dfd.ioArgs; + + ioArgs.id = 'rid' + args.rid; + ioArgs.rid = args.rid; + ioArgs.canDelete = true; + ioArgs.frameDoc = args.frameDoc; + + this[ioArgs.id] = dfd; + + return dfd; // dojo.Deferred + }, + + _deferredCancel: function(/*Deferred*/dfd){ + //summary: canceller function for dojo._ioSetArgs call. + + //DO NOT use "this" and expect it to be dojox.xmpp.bosh. + dfd.canceled = true; + if(dfd.ioArgs.canDelete){ + dojox.xmpp.bosh._addDeadScript(dfd.ioArgs); + } + }, + + _deferredOk: function(/*Deferred*/dfd){ + //summary: okHandler function for dojo._ioSetArgs call. + + //DO NOT use "this" and expect it to be dojo.xmpp.bosh. + var ioArgs = dfd.ioArgs; + + //Add script to list of things that can be removed. + if(ioArgs.canDelete){ + dojox.xmpp.bosh._addDeadScript(ioArgs); + } + + //Favor JSONP responses, script load events then lastly ioArgs. + //The ioArgs are goofy, but cannot return the dfd since that stops + //the callback chain in Deferred. The return value is not that important + //in that case, probably a checkString case. + return ioArgs.xmppMessage || ioArgs; + }, + + _deferredError: function(/*Error*/error, /*Deferred*/dfd){ + //summary: errHandler function for dojo._ioSetArgs call. + + if(dfd.ioArgs.canDelete){ + //DO NOT use "this" and expect it to be dojox.xmpp.bosh + if(error.dojoType == "timeout"){ + //For timeouts, remove the script element immediately to + //avoid a response from it coming back later and causing trouble. + dojox.xmpp.bosh.remove(dfd.ioArgs.id, dfd.ioArgs.frameDoc); + }else{ + dojox.xmpp.bosh._addDeadScript(dfd.ioArgs); + } + } + return error; + }, + + _deadScripts: [], + _addDeadScript: function(/*Object*/ioArgs){ + //summary: sets up an entry in the deadScripts array. + dojox.xmpp.bosh._deadScripts.push({id: ioArgs.id, frameDoc: ioArgs.frameDoc}); + //Being extra paranoid about leaks: + ioArgs.frameDoc = null; + }, + + _validCheck: function(/*Deferred*/dfd){ + //summary: inflight check function to see if dfd is still valid. + + //Do script cleanup here. We wait for one inflight pass + //to make sure we don't get any weird things by trying to remove a script + //tag that is part of the call chain (IE 6 has been known to + //crash in that case). + var _self = dojox.xmpp.bosh; + var deadScripts = _self._deadScripts; + if(deadScripts && deadScripts.length > 0){ + for(var i = 0; i < deadScripts.length; i++){ + //Remove the script tag + _self.remove(deadScripts[i].id, deadScripts[i].frameDoc); + deadScripts[i].frameDoc = null; + } + dojox.xmpp.bosh._deadScripts = []; + } + + return true; + }, + + _ioCheck: function(/*Deferred*/dfd){ + //summary: inflight check function to see if IO finished. + var ioArgs = dfd.ioArgs; + //Check for returned message + if(ioArgs.xmppMessage){ + return true; + } + return false; + }, + + _resHandle: function(/*Deferred*/dfd){ + //summary: inflight function to handle a completed response. + if(dojox.xmpp.bosh._ioCheck(dfd)){ + dfd.callback(dfd); + }else{ + //This path should never happen since the only way we can get + //to _resHandle is if _ioCheck is true. + dfd.errback(new Error("inconceivable dojox.xmpp.bosh._resHandle error")); + } + } +}; + +}); diff --git a/js/dojo/dojox/xmpp/sasl.js b/js/dojo/dojox/xmpp/sasl.js new file mode 100644 index 0000000..150b37b --- /dev/null +++ b/js/dojo/dojox/xmpp/sasl.js @@ -0,0 +1,187 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/sasl", ["dijit","dojo","dojox","dojo/require!dojox/xmpp/util,dojo/AdapterRegistry,dojox/encoding/digests/MD5"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.sasl"); +dojo.require("dojox.xmpp.util"); + +dojo.require("dojo.AdapterRegistry"); +dojo.require("dojox.encoding.digests.MD5"); + +dojox.xmpp.sasl.saslNS = "urn:ietf:params:xml:ns:xmpp-sasl"; + +dojo.declare("dojox.xmpp.sasl._Base", null, { + mechanism: null, + closeAuthTag: true, + + constructor: function(session){ + this.session = session; + + this.startAuth(); + }, + startAuth: function(){ + var auth = new dojox.string.Builder(dojox.xmpp.util.createElement("auth", { + xmlns: dojox.xmpp.sasl.saslNS, + mechanism: this.mechanism + }, this.closeAuthTag)); + this.appendToAuth(auth); + this.session.dispatchPacket(auth.toString()); + }, + appendToAuth: function(auth){}, + onChallenge: function(msg){ + if(!this.first_challenge){ + this.first_challenge = true; + this.onFirstChallenge(msg); + }else{ + this.onSecondChallenge(msg); + } + }, + onFirstChallenge: function(){}, + onSecondChallenge: function(){}, + onSuccess: function(){ + this.session.sendRestart(); + } +}); + +dojo.declare("dojox.xmpp.sasl.SunWebClientAuth", dojox.xmpp.sasl._Base, { + mechanism: "SUN-COMMS-CLIENT-PROXY-AUTH" +}); + +dojo.declare("dojox.xmpp.sasl.Plain", dojox.xmpp.sasl._Base, { + mechanism: "PLAIN", + closeAuthTag: false, + + appendToAuth: function(auth){ + var id = this.session.jid; + var index = this.session.jid.indexOf('@'); + if (index != -1){ + id = this.session.jid.substring(0, index); + } + var token = this.session.jid + '\u0000' + id + '\u0000' + this.session.password; + token = dojox.xmpp.util.Base64.encode(token); + + auth.append(token); + auth.append("</auth>"); + + delete this.session.password; + } +}); + +dojo.declare("dojox.xmpp.sasl.DigestMD5", dojox.xmpp.sasl._Base, { + mechanism: "DIGEST-MD5", + + onFirstChallenge: function(msg){ + var dxed = dojox.encoding.digests; + var dxedo = dojox.encoding.digests.outputTypes; + var HEX = function(n){ + return dxed.MD5(n, dxedo.Hex); + }; + var H = function(s){ + return dxed.MD5(s, dxedo.String); + }; + + var ch_str = dojox.xmpp.util.Base64.decode(msg.firstChild.nodeValue); + var ch = { + realm: "", + nonce: "", + qop: "auth", + maxbuf: 65536 + }; + ch_str.replace(/([a-z]+)=([^,]+)/g, function(t,k,v){ + v = v.replace(/^"(.+)"$/, "$1"); + ch[k] = v; + }); + + var A2_append = ''; + switch(ch.qop){ + case 'auth-int': + case 'auth-conf': + A2_append = ':00000000000000000000000000000000'; + case 'auth': + break; + default: + return false; + } + var cnonce = dxed.MD5(Math.random() * 1234567890, dxedo.Hex); + var digest_uri = 'xmpp/' + this.session.domain; + + var username = this.session.jid; + var index = this.session.jid.indexOf('@'); + if (index != -1){ + username = this.session.jid.substring(0, index); + } + username = dojox.xmpp.util.encodeJid(username); + + var A1 = new dojox.string.Builder(); + A1.append(H(username + ':' + ch.realm + ':' + this.session.password), + ':', ch.nonce + ':' + cnonce); + delete this.session.password; + var A2_rspauth = ':' + digest_uri + A2_append; + var A2 = 'AUTHENTICATE' + A2_rspauth; + + var response_value = new dojox.string.Builder(); + response_value.append(HEX(A1.toString()), ':', ch.nonce, ':00000001:', cnonce, ':', + ch.qop, ':') + + var ret = new dojox.string.Builder(); + ret.append('username="', username, '",', + 'realm="', ch.realm, '",', + 'nonce=', ch.nonce, ',', + 'cnonce="', cnonce, '",', + 'nc="00000001",qop="', ch.qop, '",digest-uri="', digest_uri, '",', + 'response="', HEX(response_value.toString() + HEX(A2)), '",charset="utf-8"'); + + var response = new dojox.string.Builder(dojox.xmpp.util.createElement("response", { + xmlns: dojox.xmpp.xmpp.SASL_NS + }, false)); + response.append(dojox.xmpp.util.Base64.encode(ret.toString())); + response.append('</response>'); + + this.rspauth = HEX(response_value.toString() + HEX(A2_rspauth)); + + this.session.dispatchPacket(response.toString()); + }, + + onSecondChallenge: function(msg){ + var ch_str = dojox.xmpp.util.Base64.decode(msg.firstChild.nodeValue); + + if(this.rspauth == ch_str.substring(8)){ + var response = new dojox.string.Builder(dojox.xmpp.util.createElement("response", { + xmlns: dojox.xmpp.xmpp.SASL_NS + }, true)); + this.session.dispatchPacket(response.toString()); + }else{ + //FIXME + } + } +}); + +dojox.xmpp.sasl.registry = new dojo.AdapterRegistry(); +dojox.xmpp.sasl.registry.register( + 'SUN-COMMS-CLIENT-PROXY-AUTH', + function(mechanism){ + return mechanism == 'SUN-COMMS-CLIENT-PROXY-AUTH'; + }, + function(mechanism, session){ + return new dojox.xmpp.sasl.SunWebClientAuth(session); + } +); +dojox.xmpp.sasl.registry.register( + 'DIGEST-MD5', + function(mechanism){ + return mechanism == 'DIGEST-MD5'; + }, + function(mechanism, session){ + return new dojox.xmpp.sasl.DigestMD5(session); + } +); +dojox.xmpp.sasl.registry.register( + 'PLAIN', + function(mechanism){ + return mechanism == 'PLAIN'; + }, + function(mechanism, session){ + return new dojox.xmpp.sasl.Plain(session); + } +); + +}); diff --git a/js/dojo/dojox/xmpp/util.js b/js/dojo/dojox/xmpp/util.js new file mode 100644 index 0000000..4582c3c --- /dev/null +++ b/js/dojo/dojox/xmpp/util.js @@ -0,0 +1,151 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/util", ["dijit","dojo","dojox","dojo/require!dojox/string/Builder,dojox/encoding/base64"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.util"); +dojo.require("dojox.string.Builder"); +dojo.require("dojox.encoding.base64"); + +dojox.xmpp.util.xmlEncode = function(str) { + if(str) { + str = str.replace("&", "&").replace(">", ">").replace("<", "<").replace("'", "'").replace('"', """); + } + return str; +} + +dojox.xmpp.util.encodeJid = function(jid) { + var buffer = new dojox.string.Builder(); + for(var i =0; i < jid.length; i++) { + var ch = jid.charAt(i); + var rep = ch; + switch(ch){ + case ' ' : + rep = "\\20"; + break; + case '"' : + rep = "\\22"; + break; + case '#' : + rep = "\\23"; + break; + case '&' : + rep = "\\26"; + break; + case "'" : + rep = "\\27"; + break; + case '/' : + rep = "\\2f"; + break; + case ':' : + rep = "\\3a"; + break; + case '<' : + rep = "\\3c"; + break; + case '>' : + rep = "\\3e"; + break; + } + buffer.append(rep); + } + return buffer.toString(); + } + +dojox.xmpp.util.decodeJid = function(jid) { + + jid = jid.replace(/\\([23][02367acef])/g, function(match) { + switch(match){ + case "\\20" : + return ' '; + case "\\22" : + return '"'; + case "\\23" : + return '#' ; + case "\\26" : + return '&'; + case "\\27" : + return "'"; + case "\\2f" : + return '/'; + case "\\3a" : + return ':' ; + case "\\3c" : + return '<'; + case "\\3e" : + return '>'; + } + return "ARG"; + }); + + return jid; +} + + +dojox.xmpp.util.createElement = function(tag, attributes, terminal){ + var elem = new dojox.string.Builder("<"); + elem.append(tag + " "); + + for (var attr in attributes){ + elem.append(attr + '="'); + elem.append(attributes[attr]); + elem.append('" '); + } + + if (terminal){ + elem.append("/>"); + }else{ + elem.append(">"); + } + + return elem.toString(); +} + +dojox.xmpp.util.stripHtml = function(str){ + // summary + // Strips all HTML, including attributes and brackets + // | <div onmouse="doBadThing()">Click <b>Me</b></div> + // | becomes: Click Me + var re=/<[^>]*?>/gi; + for (var i=0; i<arguments.length; i++) {} + return str.replace(re, ""); +} + +dojox.xmpp.util.decodeHtmlEntities = function(str){ + // Summary: decodes HTML entities to js characters so the string can be + // fed to a textarea.value + var ta = dojo.doc.createElement("textarea"); + ta.innerHTML = str.replace(/</g,"<").replace(/>/g,">"); + return ta.value; +} + +dojox.xmpp.util.htmlToPlain = function(str){ + str = dojox.xmpp.util.decodeHtmlEntities(str); + str = str.replace(/<br\s*[i\/]{0,1}>/gi,"\n"); + str = dojox.xmpp.util.stripHtml(str); + return str; +} + +dojox.xmpp.util.Base64 = {}; + +dojox.xmpp.util.Base64.encode = function(input){ + var s2b = function(s){ + var b = []; + for(var i = 0; i < s.length; ++i){ + b.push(s.charCodeAt(i)); + } + return b; + }; + return dojox.encoding.base64.encode(s2b(input)); +} + + +dojox.xmpp.util.Base64.decode = function(input){ + var b2s = function(b){ + var s = []; + dojo.forEach(b, function(c){ s.push(String.fromCharCode(c)); }); + return s.join(""); + }; + return b2s(dojox.encoding.base64.decode(input)); +} + +}); diff --git a/js/dojo/dojox/xmpp/widget/ChatSession.js b/js/dojo/dojox/xmpp/widget/ChatSession.js new file mode 100644 index 0000000..21424a6 --- /dev/null +++ b/js/dojo/dojox/xmpp/widget/ChatSession.js @@ -0,0 +1,46 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/widget/ChatSession", ["dijit","dojo","dojox","dojo/require!dijit/layout/LayoutContainer,dijit/_Templated"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.widget.ChatSession"); + +dojo.require("dijit.layout.LayoutContainer"); +dojo.require("dijit._Templated"); + +dojo.declare("dojox.xmpp.widget.ChatSession", + [dijit.layout.LayoutContainer, dijit._Templated], + { + templateString: dojo.cache("dojox.xmpp.widget", "templates/ChatSession.html", "<div>\n<div dojoAttachPoint=\"messages\" dojoType=\"dijit.layout.ContentPane\" layoutAlign=\"client\" style=\"overflow:auto\">\n</div>\n<div dojoType=\"dijit.layout.ContentPane\" layoutAlign=\"bottom\" style=\"border-top: 2px solid #333333; height: 35px;\"><input dojoAttachPoint=\"chatInput\" dojoAttachEvent=\"onkeypress: onKeyPress\" style=\"width: 100%;height: 35px;\" /></div>\n</div>"), + enableSubWidgets: true, + widgetsInTemplate: true, + + widgetType: "ChatSession", + chatWith: null, + instance: null, + postCreate: function(){ + //console.log("Neato!"); + }, + + displayMessage: function(message, type) { + //console.log("displayMessage", this, message); + if(message) { + var name = message.from ? this.chatWith : "me"; + this.messages.domNode.innerHTML += "<b>" + name + ":</b> " + message.body + "<br/>"; + this.goToLastMessage(); + } + + }, + + goToLastMessage: function() { + this.messages.domNode.scrollTop = this.messages.domNode.scrollHeight; + }, + + onKeyPress: function(e){ + var key = e.keyCode || e.charCode; + if ((key == dojo.keys.ENTER) && (this.chatInput.value != "")){ + this.instance.sendMessage({body: this.chatInput.value}); + this.displayMessage( {body: this.chatInput.value}, "out"); + this.chatInput.value = ""; + } + } +}); +}); diff --git a/js/dojo/dojox/xmpp/widget/templates/ChatSession.html b/js/dojo/dojox/xmpp/widget/templates/ChatSession.html new file mode 100644 index 0000000..0a94d8c --- /dev/null +++ b/js/dojo/dojox/xmpp/widget/templates/ChatSession.html @@ -0,0 +1,5 @@ +<div> +<div dojoAttachPoint="messages" dojoType="dijit.layout.ContentPane" layoutAlign="client" style="overflow:auto"> +</div> +<div dojoType="dijit.layout.ContentPane" layoutAlign="bottom" style="border-top: 2px solid #333333; height: 35px;"><input dojoAttachPoint="chatInput" dojoAttachEvent="onkeypress: onKeyPress" style="width: 100%;height: 35px;" /></div> +</div>
\ No newline at end of file diff --git a/js/dojo/dojox/xmpp/xmppSession.js b/js/dojo/dojox/xmpp/xmppSession.js new file mode 100644 index 0000000..5209b36 --- /dev/null +++ b/js/dojo/dojox/xmpp/xmppSession.js @@ -0,0 +1,854 @@ +//>>built +// wrapped by build app +define("dojox/xmpp/xmppSession", ["dijit","dojo","dojox","dojo/require!dojox/xmpp/TransportSession,dojox/xmpp/RosterService,dojox/xmpp/PresenceService,dojox/xmpp/UserService,dojox/xmpp/ChatService,dojox/xmpp/sasl"], function(dijit,dojo,dojox){ +dojo.provide("dojox.xmpp.xmppSession"); + +dojo.require("dojox.xmpp.TransportSession"); +dojo.require("dojox.xmpp.RosterService"); +dojo.require("dojox.xmpp.PresenceService"); +dojo.require("dojox.xmpp.UserService"); +dojo.require("dojox.xmpp.ChatService"); +dojo.require("dojox.xmpp.sasl"); + +dojox.xmpp.xmpp = { + STREAM_NS: 'http://etherx.jabber.org/streams', + CLIENT_NS: 'jabber:client', + STANZA_NS: 'urn:ietf:params:xml:ns:xmpp-stanzas', + SASL_NS: 'urn:ietf:params:xml:ns:xmpp-sasl', + BIND_NS: 'urn:ietf:params:xml:ns:xmpp-bind', + SESSION_NS: 'urn:ietf:params:xml:ns:xmpp-session', + BODY_NS: "http://jabber.org/protocol/httpbind", + + XHTML_BODY_NS: "http://www.w3.org/1999/xhtml", + XHTML_IM_NS: "http://jabber.org/protocol/xhtml-im", + + INACTIVE: "Inactive", + CONNECTED: "Connected", + ACTIVE: "Active", + TERMINATE: "Terminate", + LOGIN_FAILURE: "LoginFailure", + + INVALID_ID: -1, + NO_ID: 0, + + error:{ + BAD_REQUEST: 'bad-request', + CONFLICT: 'conflict', + FEATURE_NOT_IMPLEMENTED: 'feature-not-implemented', + FORBIDDEN: 'forbidden', + GONE: 'gone', + INTERNAL_SERVER_ERROR: 'internal-server-error', + ITEM_NOT_FOUND: 'item-not-found', + ID_MALFORMED: 'jid-malformed', + NOT_ACCEPTABLE: 'not-acceptable', + NOT_ALLOWED: 'not-allowed', + NOT_AUTHORIZED: 'not-authorized', + SERVICE_UNAVAILABLE: 'service-unavailable', + SUBSCRIPTION_REQUIRED: 'subscription-required', + UNEXPECTED_REQUEST: 'unexpected-request' + } +}; + +dojox.xmpp.xmppSession = function(props){ + this.roster = []; + this.chatRegister = []; + this._iqId = Math.round(Math.random() * 1000000000); + + //mixin any options that we want to provide to this service + if (props && dojo.isObject(props)) { + dojo.mixin(this, props); + } + + this.session = new dojox.xmpp.TransportSession(props); + dojo.connect(this.session, "onReady", this, "onTransportReady"); + dojo.connect(this.session, "onTerminate", this, "onTransportTerminate"); + dojo.connect(this.session, "onProcessProtocolResponse", this, "processProtocolResponse"); +}; + + +dojo.extend(dojox.xmpp.xmppSession, { + + roster: [], + chatRegister: [], + _iqId: 0, + + open: function(user, password, resource){ + + if (!user) { + throw new Error("User id cannot be null"); + } else { + this.jid = user; + if(user.indexOf('@') == -1) { + this.jid = this.jid + '@' + this.domain; + } + } + + //allow null password here as its not needed in the SSO case + if (password) { + this.password = password; + } + + //normally you should NOT supply a resource and let the server send you one + //as part of your jid...see onBindResource() + if (resource) { + this.resource = resource; + } + + this.session.open(); + }, + + close: function(){ + this.state = dojox.xmpp.xmpp.TERMINATE; + this.session.close(dojox.xmpp.util.createElement("presence",{type:"unavailable",xmlns:dojox.xmpp.xmpp.CLIENT_NS},true)); + }, + + processProtocolResponse: function(msg){ + //console.log("xmppSession::processProtocolResponse() ", msg, msg.nodeName); + var type = msg.nodeName; + var nsIndex =type.indexOf(":"); + if(nsIndex > 0) { + type = type.substring(nsIndex+1); + } + switch(type){ + case "iq": + case "presence": + case "message": + case "features": + this[type + "Handler"](msg); + break; + default: + //console.log("default action?", msg.getAttribute('xmlns')); + if(msg.getAttribute('xmlns')==dojox.xmpp.xmpp.SASL_NS){ + this.saslHandler(msg); + } + } + }, + + //HANDLERS + + messageHandler: function(msg){ + //console.log("xmppSession::messageHandler() ",msg); + switch(msg.getAttribute('type')){ + case "chat": + this.chatHandler(msg); + break; + case "normal": + default: + this.simpleMessageHandler(msg); + } + + }, + + iqHandler: function(msg){ + //console.log("xmppSession::iqHandler()", msg); + if (msg.getAttribute('type')=="set"){ + this.iqSetHandler(msg); + return; + } else if (msg.getAttribute('type')=='get'){ + // this.sendStanzaError('iq', this.domain, msg.getAttribute('from'), 'cancel', 'service-unavailable', 'service not implemented'); + return; + } + }, + + presenceHandler: function(msg){ + //console.log("xmppSession::presenceHandler()"); + switch(msg.getAttribute('type')){ + case 'subscribe': + //console.log("PresenceHandler: ", msg.getAttribute('from')); + this.presenceSubscriptionRequest(msg.getAttribute('from')); + break; + case 'subscribed': + case 'unsubscribed': + break; + case 'error': + this.processXmppError(msg); + //console.log("xmppService::presenceHandler() Error"); + break; + default: + this.presenceUpdate(msg); + break; + } + }, + + featuresHandler: function(msg){ + //console.log("xmppSession::featuresHandler() ",msg); + var authMechanisms = []; + var hasBindFeature = false; + var hasSessionFeature = false; + + if(msg.hasChildNodes()){ + for(var i=0; i<msg.childNodes.length;i++){ + var n = msg.childNodes[i]; + //console.log("featuresHandler::node", n); + switch(n.nodeName){ + case 'mechanisms': + for (var x=0; x<n.childNodes.length; x++){ + //console.log("featuresHandler::node::mechanisms", n.childNodes[x].firstChild.nodeValue); + authMechanisms.push(n.childNodes[x].firstChild.nodeValue); + } + break; + case 'bind': + //if (n.getAttribute('xmlns')==dojox.xmpp.xmpp.BIND_NS) { + hasBindFeature = true; + // } + break; + case 'session': + hasSessionFeature = true; + } + } + } + //console.log("Has connected/bind?", this.state, hasBindFeature, authMechanisms); + if(this.state == dojox.xmpp.xmpp.CONNECTED){ + if(!this.auth){ + // start the login + for(var i=0; i<authMechanisms.length; i++){ + try{ + this.auth = dojox.xmpp.sasl.registry.match(authMechanisms[i], this); + break; + }catch(e){ + console.warn("No suitable auth mechanism found for: ", authMechanisms[i]); + } + } + }else if(hasBindFeature){ + this.bindResource(hasSessionFeature); + } + } + }, + + saslHandler: function(msg){ + //console.log("xmppSession::saslHandler() ", msg); + if(msg.nodeName=="success"){ + this.auth.onSuccess(); + return; + } + + if(msg.nodeName=="challenge"){ + this.auth.onChallenge(msg); + return; + } + + if(msg.hasChildNodes()){ + this.onLoginFailure(msg.firstChild.nodeName); + this.session.setState('Terminate', msg.firstChild.nodeName); + } + }, + + sendRestart: function(){ + this.session._sendRestart(); + }, + + + //SUB HANDLERS + + chatHandler: function(msg){ + //console.log("xmppSession::chatHandler() ", msg); + var message = { + from: msg.getAttribute('from'), + to: msg.getAttribute('to') + } + + var chatState = null; + //console.log("chat child node ", msg.childNodes, msg.childNodes.length); + for (var i=0; i<msg.childNodes.length; i++){ + var n = msg.childNodes[i]; + if (n.hasChildNodes()){ + //console.log("chat child node ", n); + switch(n.nodeName){ + case 'thread': + message.chatid = n.firstChild.nodeValue; + break; + case 'body': + if (!n.getAttribute('xmlns') || (n.getAttribute('xmlns')=="")){ + message.body = n.firstChild.nodeValue; + } + break; + case 'subject': + message.subject = n.firstChild.nodeValue; + case 'html': + if (n.getAttribute('xmlns')==dojox.xmpp.xmpp.XHTML_IM_NS){ + message.xhtml = n.getElementsByTagName("body")[0]; + } + break; + case 'x': + break; + default: + //console.log("xmppSession::chatHandler() Unknown node type: ",n.nodeName); + } + } + /*//console.log("Foo", n, n.nodeName); + if(n.getAttribute('xmlns')==dojox.xmpp.chat.CHAT_STATE_NS){ + chatState = n.nodeName; + }*/ + } + + var found = -1; + if (message.chatid){ + for (var i=0; i< this.chatRegister.length; i++){ + var ci = this.chatRegister[i]; + ////console.log("ci.chatid: ", ci.chatid, message.chatid); + if (ci && ci.chatid == message.chatid) { + found = i; + break; + } + } + } else { + for (var i=0; i< this.chatRegister.length; i++){ + var ci = this.chatRegister[i]; + if(ci){ + if (ci.uid==this.getBareJid(message.from)){ + found = i; + } + } + } + } + + if (found>-1 && chatState){ + var chat = this.chatRegister[found]; + chat.setState(chatState); + + if (chat.firstMessage){ + if (chatState == dojox.xmpp.chat.ACTIVE_STATE) { + chat.useChatState = (chatState != null) ? true : false; + chat.firstMessage = false; + } + } + } + + if ((!message.body || message.body=="") && !message.xhtml) {return;} + + if (found>-1){ + var chat = this.chatRegister[found]; + chat.recieveMessage(message); + }else{ + var chatInstance = new dojox.xmpp.ChatService(); + chatInstance.uid = this.getBareJid(message.from); + chatInstance.chatid = message.chatid; + chatInstance.firstMessage = true; + if(!chatState || chatState != dojox.xmpp.chat.ACTIVE_STATE){ + this.useChatState = false; + } + this.registerChatInstance(chatInstance, message); + } + }, + + simpleMessageHandler: function(msg){ + //console.log("xmppSession::simpleMessageHandler() ", msg); + }, + + registerChatInstance: function(chatInstance, message){ + chatInstance.setSession(this); + this.chatRegister.push(chatInstance); + this.onRegisterChatInstance(chatInstance, message); + chatInstance.recieveMessage(message,true); + }, + + iqSetHandler: function(msg){ + if (msg.hasChildNodes()){ + var fn = msg.firstChild; + switch(fn.nodeName){ + case 'query': + if(fn.getAttribute('xmlns') == "jabber:iq:roster"){ + this.rosterSetHandler(fn); + this.sendIqResult(msg.getAttribute('id'), msg.getAttribute('from')); + } + break; + default: + // this.sendStanzaError('iq', this.domain, msg.getAttribute('id'), 'cancel', 'service-unavailable', 'service not implemented'); + break; + } + } + }, + + sendIqResult: function(iqId, to){ + var req = { + id: iqId, + to: to || this.domain, + type: 'result', + from: this.jid + "/" + this.resource + } + this.dispatchPacket(dojox.xmpp.util.createElement("iq",req,true)); + }, + + rosterSetHandler: function(elem){ + //console.log("xmppSession::rosterSetHandler()", arguments); + for (var i=0; i<elem.childNodes.length;i++){ + var n = elem.childNodes[i]; + + if (n.nodeName=="item"){ + var found = false; + var state = -1; + var rosterItem = null; + var previousCopy = null; + for(var x=0; x<this.roster.length;x++){ + var r = this.roster[x]; + if(n.getAttribute('jid')==r.jid){ + found = true; + if(n.getAttribute('subscription')=='remove'){ + //remove the item + rosterItem = { + id: r.jid, + name: r.name, + groups:[] + } + + for (var y=0;y<r.groups.length;y++){ + rosterItem.groups.push(r.groups[y]); + } + + this.roster.splice(x,1); + state = dojox.xmpp.roster.REMOVED; + + } else { //update + previousCopy = dojo.clone(r); + var itemName = n.getAttribute('name'); + if (itemName){ + this.roster[x].name = itemName; + } + + r.groups = []; + + if (n.getAttribute('subscription')){ + r.status = n.getAttribute('subscription'); + } + + r.substatus = dojox.xmpp.presence.SUBSCRIPTION_SUBSTATUS_NONE; + if(n.getAttribute('ask')=='subscribe'){ + r.substatus = dojox.xmpp.presence.SUBSCRIPTION_REQUEST_PENDING; + } + + for(var y=0;y<n.childNodes.length;y++){ + var groupNode = n.childNodes[y]; + if ((groupNode.nodeName=='group')&&(groupNode.hasChildNodes())){ + var gname = groupNode.firstChild.nodeValue; + r.groups.push(gname); + } + } + rosterItem = r; + state = dojox.xmpp.roster.CHANGED; + } + break; + } + } + if(!found && (n.getAttribute('subscription')!='remove')){ + r = this.createRosterEntry(n); + rosterItem = r; + state = dojox.xmpp.roster.ADDED; + } + + switch(state){ + case dojox.xmpp.roster.ADDED: + this.onRosterAdded(rosterItem); + break; + case dojox.xmpp.roster.REMOVED: + this.onRosterRemoved(rosterItem); + break; + case dojox.xmpp.roster.CHANGED: + this.onRosterChanged(rosterItem, previousCopy); + break; + } + } + } + }, + + presenceUpdate: function(msg){ + if(msg.getAttribute('to')){ + var jid = this.getBareJid(msg.getAttribute('to')); + if(jid != this.jid) { + //console.log("xmppService::presenceUpdate Update Recieved with wrong address - ",jid); + return; + } + } + + var fromRes = this.getResourceFromJid(msg.getAttribute('from')); + + var p = { + from: this.getBareJid(msg.getAttribute('from')), + resource: fromRes, + show: dojox.xmpp.presence.STATUS_ONLINE, + priority: 5, + hasAvatar: false + } + + if(msg.getAttribute('type')=='unavailable'){ + p.show=dojox.xmpp.presence.STATUS_OFFLINE + } + + for (var i=0; i<msg.childNodes.length;i++){ + var n=msg.childNodes[i]; + if (n.hasChildNodes()){ + switch(n.nodeName){ + case 'status': + case 'show': + p[n.nodeName]=n.firstChild.nodeValue; + break; + case 'status': + p.priority=parseInt(n.firstChild.nodeValue); + break; + case 'x': + if(n.firstChild && n.firstChild.firstChild && n.firstChild.firstChild.nodeValue != "") { + p.avatarHash= n.firstChild.firstChild.nodeValue; + p.hasAvatar = true; + } + break; + } + } + } + + this.onPresenceUpdate(p); + }, + + retrieveRoster: function(){ + ////console.log("xmppService::retrieveRoster()"); + var props={ + id: this.getNextIqId(), + from: this.jid + "/" + this.resource, + type: "get" + } + var req = new dojox.string.Builder(dojox.xmpp.util.createElement("iq",props,false)); + req.append(dojox.xmpp.util.createElement("query",{xmlns: "jabber:iq:roster"},true)); + req.append("</iq>"); + + var def = this.dispatchPacket(req,"iq", props.id); + def.addCallback(this, "onRetrieveRoster"); + + }, + + getRosterIndex: function(jid){ + if(jid.indexOf('@')==-1){ + jid += '@' + this.domain; + } + for (var i=0; i<this.roster.length;i++){ + if(jid == this.roster[i].jid) { return i; } + } + return -1; + }, + + createRosterEntry: function(elem){ + ////console.log("xmppService::createRosterEntry()"); + var re = { + name: elem.getAttribute('name'), + jid: elem.getAttribute('jid'), + groups: [], + status: dojox.xmpp.presence.SUBSCRIPTION_NONE, + substatus: dojox.xmpp.presence.SUBSCRIPTION_SUBSTATUS_NONE + // displayToUser: false + } + + if (!re.name){ + re.name = re.id; + } + + + + for(var i=0; i<elem.childNodes.length;i++){ + var n = elem.childNodes[i]; + if (n.nodeName=='group' && n.hasChildNodes()){ + re.groups.push(n.firstChild.nodeValue); + } + } + + if (elem.getAttribute('subscription')){ + re.status = elem.getAttribute('subscription'); + } + + if (elem.getAttribute('ask')=='subscribe'){ + re.substatus = dojox.xmpp.presence.SUBSCRIPTION_REQUEST_PENDING; + } + //Display contact rules from http://www.xmpp.org/extensions/xep-0162.html#contacts + /* if(re.status == dojox.xmpp.presence.SUBSCRIPTION_REQUEST_PENDING || + re.status == dojox.xmpp.presence.SUBSCRIPTION_TO || + re.status == dojox.xmpp.presence.SUBSCRIPTION_BOTH || + re.groups.length > 0 || + re.name + ) { + re.displayToUser = true; + } +*/ + return re; + }, + + bindResource: function(hasSession){ + var props = { + id: this.getNextIqId(), + type: "set" + } + var bindReq = new dojox.string.Builder(dojox.xmpp.util.createElement("iq", props, false)); + bindReq.append(dojox.xmpp.util.createElement("bind", {xmlns: dojox.xmpp.xmpp.BIND_NS}, false)); + + if (this.resource){ + bindReq.append(dojox.xmpp.util.createElement("resource")); + bindReq.append(this.resource); + bindReq.append("</resource>"); + } + + bindReq.append("</bind></iq>"); + + var def = this.dispatchPacket(bindReq, "iq", props.id); + def.addCallback(this, function(msg){ + this.onBindResource(msg, hasSession); + return msg; + }); + }, + + getNextIqId: function(){ + return "im_" + this._iqId++; + }, + + presenceSubscriptionRequest: function(msg) { + this.onSubscriptionRequest(msg); + /* + this.onSubscriptionRequest({ + from: msg, + resource:"", + show:"", + status:"", + priority: 5 + }); + */ + }, + + dispatchPacket: function(msg, type, matchId){ + if (this.state != "Terminate") { + return this.session.dispatchPacket(msg,type,matchId); + }else{ + //console.log("xmppSession::dispatchPacket - Session in Terminate state, dropping packet"); + } + }, + + setState: function(state, message){ + if (this.state != state){ + if (this["on"+state]){ + this["on"+state](state, this.state, message); + } + this.state=state; + } + }, + + search: function(searchString, service, searchAttribute){ + var req={ + id: this.getNextIqId(), + "xml:lang": this.lang, + type: 'set', + from: this.jid + '/' + this.resource, + to: service + } + var request = new dojox.string.Builder(dojox.xmpp.util.createElement("iq",req,false)); + request.append(dojox.xmpp.util.createElement('query',{xmlns:'jabber:iq:search'},false)); + request.append(dojox.xmpp.util.createElement(searchAttribute,{},false)); + request.append(searchString); + request.append("</").append(searchAttribute).append(">"); + request.append("</query></iq>"); + + var def = this.dispatchPacket(request.toString,"iq",req.id); + def.addCallback(this, "_onSearchResults"); + }, + + _onSearchResults: function(msg){ + if ((msg.getAttribute('type')=='result')&&(msg.hasChildNodes())){ + //console.log("xmppSession::_onSearchResults(): ", msg.firstChild); + + //call the search results event with an array of results + this.onSearchResults([]); + } + }, + + // EVENTS + + onLogin: function(){ + ////console.log("xmppSession::onLogin()"); + this.retrieveRoster(); + }, + + onLoginFailure: function(msg){ + //console.log("xmppSession::onLoginFailure ", msg); + }, + + onBindResource: function(msg, hasSession){ + //console.log("xmppSession::onBindResource() ", msg); + + if (msg.getAttribute('type')=='result'){ + //console.log("xmppSession::onBindResource() Got Result Message"); + if ((msg.hasChildNodes()) && (msg.firstChild.nodeName=="bind")){ + var bindTag = msg.firstChild; + if ((bindTag.hasChildNodes()) && (bindTag.firstChild.nodeName=="jid")){ + if (bindTag.firstChild.hasChildNodes()){ + var fulljid = bindTag.firstChild.firstChild.nodeValue; + this.jid = this.getBareJid(fulljid); + this.resource = this.getResourceFromJid(fulljid); + } + } + if(hasSession){ + var props = { + id: this.getNextIqId(), + type: "set" + } + var bindReq = new dojox.string.Builder(dojox.xmpp.util.createElement("iq", props, false)); + bindReq.append(dojox.xmpp.util.createElement("session", {xmlns: dojox.xmpp.xmpp.SESSION_NS}, true)); + bindReq.append("</iq>"); + + var def = this.dispatchPacket(bindReq, "iq", props.id); + def.addCallback(this, "onBindSession"); + return; + } + }else{ + //console.log("xmppService::onBindResource() No Bind Element Found"); + } + + this.onLogin(); + + }else if(msg.getAttribute('type')=='error'){ + //console.log("xmppSession::onBindResource() Bind Error ", msg); + var err = this.processXmppError(msg); + this.onLoginFailure(err); + } + }, + + onBindSession: function(msg){ + if(msg.getAttribute('type')=='error'){ + //console.log("xmppSession::onBindSession() Bind Error ", msg); + var err = this.processXmppError(msg); + this.onLoginFailure(err); + }else{ + this.onLogin(); + } + }, + + onSearchResults: function(results){ + //console.log("xmppSession::onSearchResult() ", results); + }, + + onRetrieveRoster: function(msg){ + ////console.log("xmppService::onRetrieveRoster() ", arguments); + + if ((msg.getAttribute('type')=='result') && msg.hasChildNodes()){ + var query = msg.getElementsByTagName('query')[0]; + if (query.getAttribute('xmlns')=="jabber:iq:roster"){ + for (var i=0;i<query.childNodes.length;i++){ + if (query.childNodes[i].nodeName=="item"){ + this.roster[i] = this.createRosterEntry(query.childNodes[i]); + } + } + } + }else if(msg.getAttribute('type')=="error"){ + //console.log("xmppService::storeRoster() Error recieved on roster get"); + } + + ////console.log("Roster: ", this.roster); + this.setState(dojox.xmpp.xmpp.ACTIVE); + this.onRosterUpdated(); + + return msg; + }, + + onRosterUpdated: function() {}, + + onSubscriptionRequest: function(req){}, + + onPresenceUpdate: function(p){}, + + onTransportReady: function(){ + this.setState(dojox.xmpp.xmpp.CONNECTED); + this.rosterService = new dojox.xmpp.RosterService(this); + this.presenceService= new dojox.xmpp.PresenceService(this); + this.userService = new dojox.xmpp.UserService(this); + + ////console.log("xmppSession::onTransportReady()"); + }, + + onTransportTerminate: function(newState, oldState, message){ + this.setState(dojox.xmpp.xmpp.TERMINATE, message); + }, + + onConnected: function(){ + ////console.log("xmppSession::onConnected()"); + }, + + onTerminate: function(newState, oldState, message){ + //console.log("xmppSession::onTerminate()", newState, oldState, message); + }, + + onActive: function(){ + ////console.log("xmppSession::onActive()"); + //this.presenceService.publish({show: dojox.xmpp.presence.STATUS_ONLINE}); + }, + + onRegisterChatInstance: function(chatInstance, message){ + ////console.log("xmppSession::onRegisterChatInstance()"); + }, + + onRosterAdded: function(ri){}, + onRosterRemoved: function(ri){}, + onRosterChanged: function(ri, previousCopy){}, + + //Utilities + + processXmppError: function(msg){ + ////console.log("xmppSession::processXmppError() ", msg); + var err = { + stanzaType: msg.nodeName, + id: msg.getAttribute('id') + } + + for (var i=0; i<msg.childNodes.length; i++){ + var n = msg.childNodes[i]; + switch(n.nodeName){ + case 'error': + err.errorType = n.getAttribute('type'); + for (var x=0; x< n.childNodes.length; x++){ + var cn = n.childNodes[x]; + if ((cn.nodeName=="text") && (cn.getAttribute('xmlns') == dojox.xmpp.xmpp.STANZA_NS) && cn.hasChildNodes()) { + err.message = cn.firstChild.nodeValue; + } else if ((cn.getAttribute('xmlns') == dojox.xmpp.xmpp.STANZA_NS) &&(!cn.hasChildNodes())){ + err.condition = cn.nodeName; + } + } + break; + default: + break; + } + } + return err; + }, + + sendStanzaError: function(stanzaType,to,id,errorType,condition,text){ + ////console.log("xmppSession: sendStanzaError() ", arguments); + var req = {type:'error'}; + if (to) { req.to=to; } + if (id) { req.id=id; } + + var request = new dojox.string.Builder(dojox.xmpp.util.createElement(stanzaType,req,false)); + request.append(dojox.xmpp.util.createElement('error',{type:errorType},false)); + request.append(dojox.xmpp.util.createElement('condition',{xmlns:dojox.xmpp.xmpp.STANZA_NS},true)); + + if(text){ + var textAttr={ + xmlns: dojox.xmpp.xmpp.STANZA_NS, + "xml:lang":this.lang + } + request.append(dojox.xmpp.util.createElement('text',textAttr,false)); + request.append(text).append("</text>"); + } + request.append("</error></").append(stanzaType).append(">"); + + this.dispatchPacket(request.toString()); + }, + + getBareJid: function(jid){ + var i = jid.indexOf('/'); + if (i != -1){ + return jid.substring(0, i); + } + return jid; + }, + + getResourceFromJid: function(jid){ + var i = jid.indexOf('/'); + if (i != -1){ + return jid.substring((i + 1), jid.length); + } + return ""; + } + +}); + +}); |
