1 Xmpp4Js.Lang.namespace( "Xmpp4Js.Chat" ); 2 3 /** 4 * Automatically registers events on connection. 5 * @class Manages chat conversations for a given connection. 6 * @constructor 7 * @param {Xmpp4Js.JabberConnection} con The connection object to associate with. 8 */ 9 Xmpp4Js.Chat.ChatManager = function(con) { 10 /** @private @type Xmpp4Js.JabberConnection */ 11 this.con = con; 12 /** @private @type Map */ 13 this.threads = {}; 14 /** An array of Chat objects. @private @type Xmpp4Js.Chat.Chat */ 15 this.chats = []; 16 17 this.addEvents({ 18 /** 19 * Fires when a chat has been created. Does not include the message, 20 * but gives GUI managers an opportunity to setup a listener before 21 * the first messageReceived is called on the chat object. 22 * 23 * @event chatStarted 24 * @param chat {Xmpp4Js.Chat.Chat} The chat that was created 25 */ 26 "chatStarted" : true, 27 28 29 /** 30 * Fires when a chat message was received (including first). 31 * It is safe to assume that chatStarted will be invoked before this. 32 * 33 * @event messageReceived 34 * @param {Xmpp4Js.Chat.Chat} chat the chat window 35 * @param {Xmpp4Js.Packet.Message} message The incoming message 36 */ 37 "messageReceived" : true, 38 39 /** 40 * Fires when a chat is closed. At this point it only happens locally, with 41 * a call to close(). If a new chat from the same user or with the same threadId 42 * comes, it will be considered a new conversation and the chatStarted event 43 * will be fired. 44 */ 45 "chatClosed" : true 46 }); 47 48 this._registerEvents(); 49 } 50 51 /** 52 * Options able to be set. 53 */ 54 Xmpp4Js.Chat.ChatManager.prototype = { 55 options : { 56 /** 57 * only applies to finding chats. internally they're still stored (option may change) 58 * @type boolean 59 */ 60 ignoreResource: false, 61 /** 62 * only applies to finding chats. internally they're still stored (option may change) 63 * @type boolean 64 */ 65 ignoreThread: false 66 }, 67 68 /** 69 * Register a packet listener on the connection associated with this ChatManager. 70 * @private 71 */ 72 _registerEvents : function() { 73 this.con.addPacketListener( function(stanza) { 74 this._handleMessageReceived( stanza ); 75 }.bind(this), new Xmpp4Js.PacketFilter.PacketClassFilter( Xmpp4Js.Packet.Message )); 76 }, 77 78 /** 79 * Handles raising events including chatStarted and messageReceived. 80 * Only watches for normal and chat message types. 81 * @param {Xmpp4Js.Packet.Message} messagePacket The message packet 82 * @private 83 */ 84 _handleMessageReceived : function( messagePacket ) { 85 86 if( messagePacket.getType() != "" && messagePacket.getType() != "normal" && messagePacket.getType() != "chat" ) { 87 return; 88 } 89 90 var chat = null; 91 var jid = messagePacket.getFromJid(); 92 var thread = messagePacket.getThread(); 93 94 try { 95 chat = this.findBestChat( jid, thread ); 96 } catch(e) { 97 // couldn't find chat exception... 98 // TODO check 99 chat = this.createChat( jid, thread ); 100 } 101 102 this.fireEvent( "messageReceived", chat, messagePacket ); 103 chat.fireEvent( "messageReceived", chat, messagePacket ); 104 }, 105 106 /** 107 * Creates a new chat and returns it, and fires the chatStarted event. 108 * 109 * @param {Jid} jid 110 * @param {Function} listener 111 * @param (String) thread 112 * @return Xmpp4Js.Chat.Chat 113 */ 114 createChat : function (jid, thread, listener) { 115 var chat = new Xmpp4Js.Chat.Chat( this, jid, thread ); 116 this.threads[ chat.getThread() ] = chat; 117 this.chats.push( chat ); 118 119 this.fireEvent("chatStarted", chat); 120 121 if( listener instanceof Function ) { 122 chat.on( "messageReceived", listener ); 123 } 124 125 return chat; 126 }, 127 128 /** 129 * Closes a given chat, and sends events. The chat will no longer be 130 * available from getUserChat or getThreadChat. If a new chat from the 131 * same user or with the same threadId comes, it will be considered a 132 * new conversation and the chatStarted event will be fired. 133 * 134 * @param {Xmpp4Js.Chat.Chat} chat The chat object to close 135 */ 136 closeChat : function(chat) { 137 // assumes that the chat only exists once 138 for( var i = 0; i < this.chats.length; i++ ) { 139 if( chat == this.chats[i] ) { 140 this.chats = this.chats.slice( i, i ); 141 break; 142 } 143 } 144 145 delete this.threads[ chat.getThread() ]; 146 147 chat.fireEvent( "close", chat ); 148 this.fireEvent( "chatClosed", chat); 149 }, 150 151 /** 152 * Returns a chat by thread ID 153 * @param {String} thread 154 * @return Xmpp4Js.Chat.Chat 155 */ 156 getThreadChat : function (thread) { 157 return this.threads[ thread ]; 158 }, 159 160 /** 161 * Gets a chat by Jid 162 * @param {Xmpp4Js.Jid} jid Jid object or string. 163 * @return Xmpp4Js.Chat.Chat 164 */ 165 getUserChat : function (jid) { 166 for( var i = 0; i < this.chats.length; i++ ) { 167 var chat = this.chats[i]; 168 if( chat.getParticipant().equals( jid, this.options.ignoreResource ) ) { 169 return chat; 170 } 171 } 172 }, 173 174 /** 175 * Get or return a Chat object using a given message. 176 * This implementation checks thread -> jid (no resource) -> create. 177 * @todo write test 178 * @param msg {Xmpp4Js.Packet.Message} A message stanza to determine. 179 * @param isOutgoing {Boolean} If true use toJid, else fromJid 180 * @param ignoreResource {Boolean} Resort to ignoring the resource if needed (last attempt). 181 * @return {Xmpp4Js.Chat.Chat} The end result of the chat finding. 182 */ 183 findBestChat : function (jid, thread) { 184 var chat = null; 185 var forJid = new Xmpp4Js.Jid(jid); 186 187 if( thread && !this.options.ignoreThread ) { 188 chat = this.getThreadChat( thread ); 189 } else { 190 chat = this.getUserChat( forJid ); 191 } 192 193 if( chat == null ) { 194 195 throw new Error( "Could not find best chat for user/thread combination." ); 196 } 197 198 return chat; 199 }, 200 201 /** 202 * Set options. 203 * FIXME Currently overwrites all existing options 204 * @param {Map} options 205 * @see #options 206 */ 207 setOptions : function(options) { 208 this.options = options; 209 }, 210 211 /** 212 * Get options associated with this ChatManager. 213 * @return Map 214 * @see #options 215 */ 216 getOptions : function() { 217 return this.options; 218 }, 219 220 /** 221 * The connection that this ChatManager is associated with. 222 * @return Xmpp4Js.JabberConnection 223 */ 224 getConnection : function() { 225 return this.con; 226 } 227 }; 228 229 Xmpp4Js.Chat.ChatManager.instances = {}; 230 Xmpp4Js.Chat.ChatManager.getInstanceFor = function(con) { 231 var instances = Xmpp4Js.Chat.ChatManager.instances; 232 233 if( instances[con.id] === undefined ) { 234 instances[con.id] = new Xmpp4Js.Chat.ChatManager( con ); 235 } 236 237 return instances[con.id]; 238 }; 239 240 241 Xmpp4Js.Lang.extend(Xmpp4Js.Chat.ChatManager, Xmpp4Js.Event.EventProvider, Xmpp4Js.Chat.ChatManager.prototype); 242