Mercurial > eldonilo > lightstring
comparison lightstring.js @ 12:9fbd0e3678b5
add comments, jsdoc syntax + move the parser and the serializer to the Lightstring namespace so they don't get recreated at every new Lightstring.Connection
author | Sonny Piers <sonny.piers@gmail.com> |
---|---|
date | Sun, 15 Jan 2012 03:47:28 +0100 |
parents | 24bc40461b2c |
children | 9aeb0750b9d1 |
comparison
equal
deleted
inserted
replaced
11:efe98c3634e4 | 12:9fbd0e3678b5 |
---|---|
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | 14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | 15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 */ | 17 */ |
18 | 18 |
19 | |
20 /** | |
21 * @namespace No code from lightstring should be callable outside this namespace/scope. | |
22 */ | |
19 var Lightstring = { | 23 var Lightstring = { |
24 /** | |
25 * @namespace Holds XMPP namespaces. | |
26 * | |
27 */ | |
20 NS: {}, | 28 NS: {}, |
29 /** | |
30 * @namespace Holds XMPP stanza builders. | |
31 * | |
32 */ | |
21 stanza: {}, | 33 stanza: {}, |
34 /** | |
35 * @private | |
36 */ | |
37 parser: new DOMParser(), | |
38 /** | |
39 * @private | |
40 */ | |
41 serializer: new XMLSerializer(), | |
42 /** | |
43 * @function Transforms a XML string to a DOM object. | |
44 * @param {String} aString XML string | |
45 * @returns {Object} Domified XML. | |
46 */ | |
47 xml2dom: function(aString) { | |
48 return this.parser.parseFromString(aString, 'text/xml').documentElement; | |
49 }, | |
50 /** | |
51 * @function Transforms a DOM object to a XML string. | |
52 * @param {Object} aString DOM object | |
53 * @returns {String} Stringified DOM. | |
54 */ | |
55 dom2xml: function(aElement) { | |
56 return this.serializer.serializeToString(aElement); | |
57 }, | |
22 }; | 58 }; |
23 | 59 |
60 /** | |
61 * @constructor Creates a new Lightstring connection | |
62 * @param {String} [aService] The Websocket service URL. | |
63 * @memberOf Lightstring | |
64 */ | |
24 Lightstring.Connection = function (aService) { | 65 Lightstring.Connection = function (aService) { |
25 var parser = new DOMParser(); | 66 if(aService) |
26 var serializer = new XMLSerializer(); | 67 this.service = aService; |
27 this.service = aService; | |
28 this.handlers = {}; | 68 this.handlers = {}; |
29 this.iqid = 1024; | 69 this.iqid = 1024; |
30 this.getNewId = function() { | 70 this.getNewId = function() { |
31 this.iqid++; | 71 this.iqid++; |
32 return 'sendiq:'+this.iqid; | 72 return 'sendiq:'+this.iqid; |
33 }; | 73 }; |
34 this.parse = function(str) { | 74 /** |
35 return parser.parseFromString(str, 'text/xml').documentElement; | 75 * @function Create and open a websocket then go though the XMPP authentification process. |
36 }; | 76 * @param {String} [aJid] The JID (Jabber id) to use. |
37 this.serialize = function(elm) { | 77 * @param {String} [aPassword] The associated password. |
38 return serializer.serializeToString(elm); | 78 */ |
39 }; | |
40 this.connect = function(aJid, aPassword) { | 79 this.connect = function(aJid, aPassword) { |
41 this.emit('connecting'); | 80 this.emit('connecting'); |
42 if(aJid) | 81 if(aJid) |
43 this.jid = aJid; | 82 this.jid = aJid; |
44 if(this.jid) { | 83 if(this.jid) { |
66 | 105 |
67 var that = this; | 106 var that = this; |
68 this.socket.addEventListener('open', function() { | 107 this.socket.addEventListener('open', function() { |
69 if(this.protocol !== 'xmpp') | 108 if(this.protocol !== 'xmpp') |
70 throw "Lightstring: The server located at "+that.service+" is not XMPP aware."; | 109 throw "Lightstring: The server located at "+that.service+" is not XMPP aware."; |
110 //FIXME no ending "/" - node-xmpp-bosh bug | |
71 var stream = | 111 var stream = |
72 "<stream:stream to='"+that.domain+"'\ | 112 "<stream:stream to='"+that.domain+"'\ |
73 xmlns='jabber:client'\ | 113 xmlns='jabber:client'\ |
74 xmlns:stream='http://etherx.jabber.org/streams'\ | 114 xmlns:stream='http://etherx.jabber.org/streams'\ |
75 version='1.0'/>"; | 115 version='1.0'/>"; |
76 //FIXME should be this but doesn't works with node-xmpp-bosh | 116 |
77 //~ var stream = | |
78 //~ "<stream:stream to='"+that.domain+"'\ | |
79 xmlns='jabber:client'\ | |
80 xmlns:stream='http://etherx.jabber.org/streams'\ | |
81 version='1.0'/>"; | |
82 that.socket.send(stream) | 117 that.socket.send(stream) |
83 that.emit('XMLOutput', stream); | 118 that.emit('XMLOutput', stream); |
84 }); | 119 }); |
85 this.socket.addEventListener('error', function(e) { | 120 this.socket.addEventListener('error', function(e) { |
86 that.emit('error', e.data); | 121 that.emit('error', e.data); |
88 this.socket.addEventListener('close', function(e) { | 123 this.socket.addEventListener('close', function(e) { |
89 that.emit('disconnected', e.data); | 124 that.emit('disconnected', e.data); |
90 }); | 125 }); |
91 this.socket.addEventListener('message', function(e) { | 126 this.socket.addEventListener('message', function(e) { |
92 that.emit('XMLInput', e.data); | 127 that.emit('XMLInput', e.data); |
93 var elm = that.parse(e.data); | 128 var elm = Lightstring.xml2dom(e.data); |
94 that.emit('DOMInput', elm); | 129 that.emit('DOMInput', elm); |
95 that.emit(elm.tagName, elm); | 130 that.emit(elm.tagName, elm); |
96 | 131 |
97 if(elm.tagName === 'iq') | 132 if(elm.tagName === 'iq') |
98 that.emit(elm.getAttribute('id'), elm); | 133 that.emit(elm.getAttribute('id'), elm); |
99 }); | 134 }); |
100 }; | 135 }; |
136 /** | |
137 * @function Send a message. | |
138 * @param {String|Object} aStanza The message to send. | |
139 * @param {Function} [aCallback] A callback that will be called when the answer will answer. | |
140 */ | |
101 this.send = function(aStanza, aCallback) { | 141 this.send = function(aStanza, aCallback) { |
102 if(typeof aStanza === 'string') { | 142 if(typeof aStanza === 'string') { |
103 var str = aStanza; | 143 var str = aStanza; |
104 var elm = this.parse(str); | 144 var elm = Lightstring.xml2dom(str); |
105 } | 145 } |
106 else if(aStanza instanceof Element) { | 146 else if(aStanza instanceof Element) { |
107 var elm = aStanza; | 147 var elm = aStanza; |
108 var str = this.serialize(elm); | 148 var str = this.dom2xml(elm); |
109 } | 149 } |
110 else { | 150 else { |
111 that.emit('error', 'Unsupported data type.'); | 151 that.emit('error', 'Unsupported data type.'); |
112 } | 152 } |
113 | 153 |
114 | 154 |
115 if(elm.tagName === 'iq') { | 155 if(elm.tagName === 'iq') { |
116 var id = elm.getAttribute('id'); | 156 var id = elm.getAttribute('id'); |
117 if(!id) { | 157 if(!id) { |
118 elm.setAttribute('id', this.getNewId()) | 158 elm.setAttribute('id', this.getNewId()) |
119 str = this.serialize(elm) | 159 str = Lightstring.dom2xml(elm) |
120 } | 160 } |
121 if(aCallback) | 161 if(aCallback) |
122 this.on(elm.getAttribute('id'), aCallback); | 162 this.on(elm.getAttribute('id'), aCallback); |
123 } | 163 } |
124 else if(aCallback) { | 164 else if(aCallback) { |
128 | 168 |
129 this.socket.send(str); | 169 this.socket.send(str); |
130 this.emit('XMLOutput', str); | 170 this.emit('XMLOutput', str); |
131 this.emit('DOMOutput', elm); | 171 this.emit('DOMOutput', elm); |
132 }; | 172 }; |
173 /** | |
174 * @function Close the XMPP stream and the socket. | |
175 */ | |
133 this.disconnect = function() { | 176 this.disconnect = function() { |
134 this.emit('disconnecting'); | 177 this.emit('disconnecting'); |
135 this.send('</stream:stream>'); | 178 this.send('</stream:stream>'); |
136 this.socket.close(); | 179 this.socket.close(); |
137 }; | 180 }; |
138 this.emit = function(name, data) { | 181 /** |
139 var handlers = this.handlers[name]; | 182 * @function Emit an event. |
183 * @param {String} aName The event name. | |
184 * @param {Function|Array|Object} [aData] Data about the event. | |
185 */ | |
186 this.emit = function(aName, aData) { | |
187 var handlers = this.handlers[aName]; | |
140 if(!handlers) | 188 if(!handlers) |
141 return; | 189 return; |
142 | 190 |
143 //FIXME Better idea than passing the context as argument? | 191 //FIXME Better idea than passing the context as argument? |
144 for(var i=0; i<handlers.length; i++) | 192 for(var i=0; i<handlers.length; i++) |
145 handlers[i](data, this); | 193 handlers[i](aData, this); |
146 | 194 |
147 if(name.match('sendiq:')) | 195 if(aName.match('sendiq:')) |
148 delete this.handlers[name]; | 196 delete this.handlers[aName]; |
149 }; | 197 }; |
150 this.on = function(name, callback) { | 198 /** |
151 if(!this.handlers[name]) | 199 * @function Register an event handler. |
152 this.handlers[name] = []; | 200 * @param {String} aName The event name. |
153 this.handlers[name].push(callback); | 201 * @param {Function} aCallback The callback to call when the event is emitted. |
202 */ | |
203 this.on = function(aName, callback) { | |
204 if(!this.handlers[aName]) | |
205 this.handlers[aName] = []; | |
206 this.handlers[aName].push(callback); | |
154 }; | 207 }; |
155 //FIXME do this! | 208 //FIXME do this! |
156 this.once = function(name, callback) { | 209 //~ this.once = function(name, callback) { |
157 if(!this.handlers[name]) | 210 //~ if(!this.handlers[name]) |
158 this.handlers[name] = []; | 211 //~ this.handlers[name] = []; |
159 this.handlers[name].push(callback); | 212 //~ this.handlers[name].push(callback); |
160 }; | 213 //~ }; |
161 //Internal | |
162 this.on('stream:features', function(stanza, that) { | 214 this.on('stream:features', function(stanza, that) { |
163 var nodes = stanza.querySelectorAll('mechanism'); | 215 var nodes = stanza.querySelectorAll('mechanism'); |
164 //SASL/Auth features | 216 //SASL/Auth features |
165 if(nodes.length > 0) { | 217 if(nodes.length > 0) { |
166 that.emit('mechanisms', stanza); | 218 that.emit('mechanisms', stanza); |
208 } | 260 } |
209 ); | 261 ); |
210 }); | 262 }); |
211 } | 263 } |
212 }); | 264 }); |
213 //Internal | |
214 this.on('success', function(stanza, that) { | 265 this.on('success', function(stanza, that) { |
215 that.send( | 266 that.send( |
216 "<stream:stream to='"+that.domain+"'\ | 267 "<stream:stream to='"+that.domain+"'\ |
217 xmlns='jabber:client'\ | 268 xmlns='jabber:client'\ |
218 xmlns:stream='http://etherx.jabber.org/streams'\ | 269 xmlns:stream='http://etherx.jabber.org/streams'\ |
219 version='1.0' />" | 270 version='1.0' />" |
220 ); | 271 ); |
221 }); | 272 }); |
222 //Internal | |
223 this.on('failure', function(stanza, that) { | 273 this.on('failure', function(stanza, that) { |
224 that.emit('conn-error', stanza.firstChild.tagName); | 274 that.emit('conn-error', stanza.firstChild.tagName); |
225 }); | 275 }); |
226 //Internal | |
227 this.on('challenge', function(stanza, that) { | 276 this.on('challenge', function(stanza, that) { |
228 //FIXME this is mostly Strophe code | 277 //FIXME this is mostly Strophe code |
229 | 278 |
230 function _quote(str) { | 279 function _quote(str) { |
231 return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; | 280 return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; |