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, '\\"') + '"';