OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 'use strict'; | 5 'use strict'; |
6 | 6 |
7 /** @suppress {duplicate} */ | 7 /** @suppress {duplicate} */ |
8 var remoting = remoting || {}; | 8 var remoting = remoting || {}; |
9 | 9 |
10 /** | 10 /** |
11 * A connection to an XMPP server. | 11 * A connection to an XMPP server. |
12 * | 12 * |
13 * TODO(sergeyu): Chrome provides two APIs for TCP sockets: chrome.socket and | 13 * TODO(sergeyu): Chrome provides two APIs for TCP sockets: chrome.socket and |
14 * chrome.sockets.tcp . chrome.socket is deprecated but it's still used here | 14 * chrome.sockets.tcp . chrome.socket is deprecated but it's still used here |
15 * because TLS support in chrome.sockets.tcp is currently broken, see | 15 * because TLS support in chrome.sockets.tcp is currently broken, see |
16 * crbug.com/403076 . | 16 * crbug.com/403076 . |
17 * | 17 * |
18 * @param {function(remoting.XmppConnection.State):void} onStateChangedCallback | 18 * @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback |
19 * Callback to call on state change. | 19 * Callback to call on state change. |
20 * @param {function(Element):void} onIncomingStanzaCallback Callback to call to | |
21 * handle incoming messages. | |
22 * @constructor | 20 * @constructor |
23 * @implements {base.Disposable} | 21 * @implements {remoting.SignalStrategy} |
24 */ | 22 */ |
25 remoting.XmppConnection = | 23 remoting.XmppConnection = function(onStateChangedCallback) { |
26 function(onStateChangedCallback, onIncomingStanzaCallback) { | |
27 /** @private */ | 24 /** @private */ |
28 this.server_ = ''; | 25 this.server_ = ''; |
29 /** @private */ | 26 /** @private */ |
30 this.port_ = 0; | 27 this.port_ = 0; |
31 /** @private */ | 28 /** @private */ |
32 this.onStateChangedCallback_ = onStateChangedCallback; | 29 this.onStateChangedCallback_ = onStateChangedCallback; |
33 /** @private */ | 30 /** @type {?function(Element):void} @private */ |
34 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; | 31 this.onIncomingStanzaCallback_ = null; |
35 /** @private */ | 32 /** @private */ |
36 this.socketId_ = -1; | 33 this.socketId_ = -1; |
37 /** @private */ | 34 /** @private */ |
38 this.state_ = remoting.XmppConnection.State.NOT_CONNECTED; | 35 this.state_ = remoting.SignalStrategy.State.NOT_CONNECTED; |
39 /** @private */ | 36 /** @private */ |
40 this.readPending_ = false; | 37 this.readPending_ = false; |
41 /** @private */ | 38 /** @private */ |
42 this.sendPending_ = false; | 39 this.sendPending_ = false; |
43 /** @private */ | 40 /** @private */ |
44 this.startTlsPending_ = false; | 41 this.startTlsPending_ = false; |
45 /** @type {Array.<ArrayBuffer>} @private */ | 42 /** @type {Array.<ArrayBuffer>} @private */ |
46 this.sendQueue_ = []; | 43 this.sendQueue_ = []; |
47 /** @type {remoting.XmppLoginHandler} @private*/ | 44 /** @type {remoting.XmppLoginHandler} @private*/ |
48 this.loginHandler_ = null; | 45 this.loginHandler_ = null; |
49 /** @type {remoting.XmppStreamParser} @private*/ | 46 /** @type {remoting.XmppStreamParser} @private*/ |
50 this.streamParser_ = null; | 47 this.streamParser_ = null; |
51 /** @private */ | 48 /** @private */ |
52 this.jid_ = ''; | 49 this.jid_ = ''; |
53 /** @private */ | 50 /** @private */ |
54 this.error_ = remoting.Error.NONE; | 51 this.error_ = remoting.Error.NONE; |
55 }; | 52 }; |
56 | 53 |
57 /** | 54 /** |
58 * @enum {number} XmppConnection states. Possible state transitions: | 55 * @param {?function(Element):void} onIncomingStanzaCallback Callback to call on |
59 * NOT_CONNECTED -> CONNECTING (connect() called). | 56 * incoming messages. |
60 * CONNECTING -> HANDSHAKE (connected successfully). | |
61 * HANDSHAKE -> CONNECTED (authenticated successfully). | |
62 * CONNECTING -> FAILED (connection failed). | |
63 * HANDSHAKE -> FAILED (authentication failed). | |
64 * * -> CLOSED (dispose() called). | |
65 */ | 57 */ |
66 remoting.XmppConnection.State = { | 58 remoting.XmppConnection.prototype.setIncomingStanzaCallback = |
67 NOT_CONNECTED: 0, | 59 function(onIncomingStanzaCallback) { |
68 CONNECTING: 1, | 60 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; |
69 HANDSHAKE: 2, | |
70 CONNECTED: 3, | |
71 FAILED: 4, | |
72 CLOSED: 5 | |
73 }; | 61 }; |
74 | 62 |
75 /** | 63 /** |
76 * @param {string} server | 64 * @param {string} server |
77 * @param {string} username | 65 * @param {string} username |
78 * @param {string} authToken | 66 * @param {string} authToken |
79 */ | 67 */ |
80 remoting.XmppConnection.prototype.connect = | 68 remoting.XmppConnection.prototype.connect = |
81 function(server, username, authToken) { | 69 function(server, username, authToken) { |
82 base.debug.assert(this.state_ == remoting.XmppConnection.State.NOT_CONNECTED); | 70 base.debug.assert(this.state_ == remoting.SignalStrategy.State.NOT_CONNECTED); |
83 | 71 |
84 this.error_ = remoting.Error.NONE; | 72 this.error_ = remoting.Error.NONE; |
85 var hostnameAndPort = server.split(':', 2); | 73 var hostnameAndPort = server.split(':', 2); |
86 this.server_ = hostnameAndPort[0]; | 74 this.server_ = hostnameAndPort[0]; |
87 this.port_ = | 75 this.port_ = |
88 (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222; | 76 (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222; |
89 | 77 |
90 // The server name is passed as to attribute in the <stream>. When connecting | 78 // The server name is passed as to attribute in the <stream>. When connecting |
91 // to talk.google.com it affects the certificate the server will use for TLS: | 79 // to talk.google.com it affects the certificate the server will use for TLS: |
92 // talk.google.com uses gmail certificate when specified server is gmail.com | 80 // talk.google.com uses gmail certificate when specified server is gmail.com |
93 // or googlemail.com and google.com cert otherwise. In the same time it | 81 // or googlemail.com and google.com cert otherwise. In the same time it |
94 // doesn't accept talk.google.com as target server. Here we use google.com | 82 // doesn't accept talk.google.com as target server. Here we use google.com |
95 // server name when authenticating to talk.google.com. This ensures that the | 83 // server name when authenticating to talk.google.com. This ensures that the |
96 // server will use google.com cert which will be accepted by the TLS | 84 // server will use google.com cert which will be accepted by the TLS |
97 // implementation in Chrome (TLS API doesn't allow specifying domain other | 85 // implementation in Chrome (TLS API doesn't allow specifying domain other |
98 // than the one that was passed to connect()). | 86 // than the one that was passed to connect()). |
99 var xmppServer = this.server_; | 87 var xmppServer = this.server_; |
100 if (xmppServer == 'talk.google.com') | 88 if (xmppServer == 'talk.google.com') |
101 xmppServer = 'google.com'; | 89 xmppServer = 'google.com'; |
102 | 90 |
103 /** @type {remoting.XmppLoginHandler} */ | 91 /** @type {remoting.XmppLoginHandler} */ |
104 this.loginHandler_ = | 92 this.loginHandler_ = |
105 new remoting.XmppLoginHandler(xmppServer, username, authToken, | 93 new remoting.XmppLoginHandler(xmppServer, username, authToken, |
106 this.sendInternal_.bind(this), | 94 this.sendInternal_.bind(this), |
107 this.startTls_.bind(this), | 95 this.startTls_.bind(this), |
108 this.onHandshakeDone_.bind(this), | 96 this.onHandshakeDone_.bind(this), |
109 this.onError_.bind(this)); | 97 this.onError_.bind(this)); |
110 chrome.socket.create("tcp", {}, this.onSocketCreated_.bind(this)); | 98 chrome.socket.create("tcp", {}, this.onSocketCreated_.bind(this)); |
111 this.setState_(remoting.XmppConnection.State.CONNECTING); | 99 this.setState_(remoting.SignalStrategy.State.CONNECTING); |
112 }; | 100 }; |
113 | 101 |
114 /** @param {string} message */ | 102 /** @param {string} message */ |
115 remoting.XmppConnection.prototype.sendMessage = function(message) { | 103 remoting.XmppConnection.prototype.sendMessage = function(message) { |
116 base.debug.assert(this.state_ == remoting.XmppConnection.State.CONNECTED); | 104 base.debug.assert(this.state_ == remoting.SignalStrategy.State.CONNECTED); |
117 this.sendInternal_(message); | 105 this.sendInternal_(message); |
118 }; | 106 }; |
119 | 107 |
120 /** @return {remoting.XmppConnection.State} Current state */ | 108 /** @return {remoting.SignalStrategy.State} Current state */ |
121 remoting.XmppConnection.prototype.getState = function() { | 109 remoting.XmppConnection.prototype.getState = function() { |
122 return this.state_; | 110 return this.state_; |
123 }; | 111 }; |
124 | 112 |
125 /** @return {remoting.Error} Error when in FAILED state. */ | 113 /** @return {remoting.Error} Error when in FAILED state. */ |
126 remoting.XmppConnection.prototype.getError = function() { | 114 remoting.XmppConnection.prototype.getError = function() { |
127 return this.error_; | 115 return this.error_; |
128 }; | 116 }; |
129 | 117 |
130 /** @return {string} Current JID when in CONNECTED state. */ | 118 /** @return {string} Current JID when in CONNECTED state. */ |
131 remoting.XmppConnection.prototype.getJid = function() { | 119 remoting.XmppConnection.prototype.getJid = function() { |
132 return this.jid_; | 120 return this.jid_; |
133 }; | 121 }; |
134 | 122 |
135 remoting.XmppConnection.prototype.dispose = function() { | 123 remoting.XmppConnection.prototype.dispose = function() { |
136 this.closeSocket_(); | 124 this.closeSocket_(); |
137 this.setState_(remoting.XmppConnection.State.CLOSED); | 125 this.setState_(remoting.SignalStrategy.State.CLOSED); |
138 }; | 126 }; |
139 | 127 |
140 /** | 128 /** |
141 * @param {chrome.socket.CreateInfo} createInfo | 129 * @param {chrome.socket.CreateInfo} createInfo |
142 * @private | 130 * @private |
143 */ | 131 */ |
144 remoting.XmppConnection.prototype.onSocketCreated_ = function(createInfo) { | 132 remoting.XmppConnection.prototype.onSocketCreated_ = function(createInfo) { |
145 // Check if connection was destroyed. | 133 // Check if connection was destroyed. |
146 if (this.state_ != remoting.XmppConnection.State.CONNECTING) { | 134 if (this.state_ != remoting.SignalStrategy.State.CONNECTING) { |
147 chrome.socket.destroy(createInfo.socketId); | 135 chrome.socket.destroy(createInfo.socketId); |
148 return; | 136 return; |
149 } | 137 } |
150 | 138 |
151 this.socketId_ = createInfo.socketId; | 139 this.socketId_ = createInfo.socketId; |
152 | 140 |
153 chrome.socket.connect(this.socketId_, | 141 chrome.socket.connect(this.socketId_, |
154 this.server_, | 142 this.server_, |
155 this.port_, | 143 this.port_, |
156 this.onSocketConnected_.bind(this)); | 144 this.onSocketConnected_.bind(this)); |
157 }; | 145 }; |
158 | 146 |
159 /** | 147 /** |
160 * @param {number} result | 148 * @param {number} result |
161 * @private | 149 * @private |
162 */ | 150 */ |
163 remoting.XmppConnection.prototype.onSocketConnected_ = function(result) { | 151 remoting.XmppConnection.prototype.onSocketConnected_ = function(result) { |
164 // Check if connection was destroyed. | 152 // Check if connection was destroyed. |
165 if (this.state_ != remoting.XmppConnection.State.CONNECTING) { | 153 if (this.state_ != remoting.SignalStrategy.State.CONNECTING) { |
166 return; | 154 return; |
167 } | 155 } |
168 | 156 |
169 if (result != 0) { | 157 if (result != 0) { |
170 this.onError_(remoting.Error.NETWORK_FAILURE, | 158 this.onError_(remoting.Error.NETWORK_FAILURE, |
171 'Failed to connect to ' + this.server_ + ': ' + result); | 159 'Failed to connect to ' + this.server_ + ': ' + result); |
172 return; | 160 return; |
173 } | 161 } |
174 | 162 |
175 this.setState_(remoting.XmppConnection.State.HANDSHAKE); | 163 this.setState_(remoting.SignalStrategy.State.HANDSHAKE); |
176 | 164 |
177 this.tryRead_(); | 165 this.tryRead_(); |
178 this.loginHandler_.start(); | 166 this.loginHandler_.start(); |
179 }; | 167 }; |
180 | 168 |
181 /** | 169 /** |
182 * @private | 170 * @private |
183 */ | 171 */ |
184 remoting.XmppConnection.prototype.tryRead_ = function() { | 172 remoting.XmppConnection.prototype.tryRead_ = function() { |
185 base.debug.assert(!this.readPending_); | 173 base.debug.assert(!this.readPending_); |
186 base.debug.assert(this.state_ == remoting.XmppConnection.State.HANDSHAKE || | 174 base.debug.assert(this.state_ == remoting.SignalStrategy.State.HANDSHAKE || |
187 this.state_ == remoting.XmppConnection.State.CONNECTED); | 175 this.state_ == remoting.SignalStrategy.State.CONNECTED); |
188 base.debug.assert(!this.startTlsPending_); | 176 base.debug.assert(!this.startTlsPending_); |
189 | 177 |
190 this.readPending_ = true; | 178 this.readPending_ = true; |
191 chrome.socket.read(this.socketId_, this.onRead_.bind(this)); | 179 chrome.socket.read(this.socketId_, this.onRead_.bind(this)); |
192 }; | 180 }; |
193 | 181 |
194 /** | 182 /** |
195 * @param {chrome.socket.ReadInfo} readInfo | 183 * @param {chrome.socket.ReadInfo} readInfo |
196 * @private | 184 * @private |
197 */ | 185 */ |
198 remoting.XmppConnection.prototype.onRead_ = function(readInfo) { | 186 remoting.XmppConnection.prototype.onRead_ = function(readInfo) { |
199 base.debug.assert(this.readPending_); | 187 base.debug.assert(this.readPending_); |
200 this.readPending_ = false; | 188 this.readPending_ = false; |
201 | 189 |
202 // Check if the socket was closed while reading. | 190 // Check if the socket was closed while reading. |
203 if (this.state_ != remoting.XmppConnection.State.HANDSHAKE && | 191 if (this.state_ != remoting.SignalStrategy.State.HANDSHAKE && |
204 this.state_ != remoting.XmppConnection.State.CONNECTED) { | 192 this.state_ != remoting.SignalStrategy.State.CONNECTED) { |
205 return; | 193 return; |
206 } | 194 } |
207 | 195 |
| 196 |
208 if (readInfo.resultCode < 0) { | 197 if (readInfo.resultCode < 0) { |
209 this.onError_(remoting.Error.NETWORK_FAILURE, | 198 this.onError_(remoting.Error.NETWORK_FAILURE, |
210 'Failed to receive from XMPP socket: ' + readInfo.resultCode); | 199 'Failed to receive from XMPP socket: ' + readInfo.resultCode); |
211 return; | 200 return; |
212 } | 201 } |
213 | 202 |
214 if (this.state_ == remoting.XmppConnection.State.HANDSHAKE) { | 203 if (this.state_ == remoting.SignalStrategy.State.HANDSHAKE) { |
215 this.loginHandler_.onDataReceived(readInfo.data); | 204 this.loginHandler_.onDataReceived(readInfo.data); |
216 } else if (this.state_ == remoting.XmppConnection.State.CONNECTED) { | 205 } else if (this.state_ == remoting.SignalStrategy.State.CONNECTED) { |
217 this.streamParser_.appendData(readInfo.data); | 206 this.streamParser_.appendData(readInfo.data); |
218 } | 207 } |
219 | 208 |
220 if (!this.startTlsPending_) { | 209 if (!this.startTlsPending_) { |
221 this.tryRead_(); | 210 this.tryRead_(); |
222 } | 211 } |
223 }; | 212 }; |
224 | 213 |
225 /** | 214 /** |
226 * @param {string} text | 215 * @param {string} text |
(...skipping 19 matching lines...) Expand all Loading... |
246 | 235 |
247 /** | 236 /** |
248 * @param {chrome.socket.WriteInfo} writeInfo | 237 * @param {chrome.socket.WriteInfo} writeInfo |
249 * @private | 238 * @private |
250 */ | 239 */ |
251 remoting.XmppConnection.prototype.onWrite_ = function(writeInfo) { | 240 remoting.XmppConnection.prototype.onWrite_ = function(writeInfo) { |
252 base.debug.assert(this.sendPending_); | 241 base.debug.assert(this.sendPending_); |
253 this.sendPending_ = false; | 242 this.sendPending_ = false; |
254 | 243 |
255 // Ignore write() result if the socket was closed. | 244 // Ignore write() result if the socket was closed. |
256 if (this.state_ != remoting.XmppConnection.State.HANDSHAKE && | 245 if (this.state_ != remoting.SignalStrategy.State.HANDSHAKE && |
257 this.state_ != remoting.XmppConnection.State.CONNECTED) { | 246 this.state_ != remoting.SignalStrategy.State.CONNECTED) { |
258 return; | 247 return; |
259 } | 248 } |
260 | 249 |
261 if (writeInfo.bytesWritten < 0) { | 250 if (writeInfo.bytesWritten < 0) { |
262 this.onError_(remoting.Error.NETWORK_FAILURE, | 251 this.onError_(remoting.Error.NETWORK_FAILURE, |
263 'TCP write failed with error ' + writeInfo.bytesWritten); | 252 'TCP write failed with error ' + writeInfo.bytesWritten); |
264 return; | 253 return; |
265 } | 254 } |
266 | 255 |
267 base.debug.assert(this.sendQueue_.length > 0); | 256 base.debug.assert(this.sendQueue_.length > 0); |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
309 | 298 |
310 /** | 299 /** |
311 * @param {string} jid | 300 * @param {string} jid |
312 * @param {remoting.XmppStreamParser} streamParser | 301 * @param {remoting.XmppStreamParser} streamParser |
313 * @private | 302 * @private |
314 */ | 303 */ |
315 remoting.XmppConnection.prototype.onHandshakeDone_ = | 304 remoting.XmppConnection.prototype.onHandshakeDone_ = |
316 function(jid, streamParser) { | 305 function(jid, streamParser) { |
317 this.jid_ = jid; | 306 this.jid_ = jid; |
318 this.streamParser_ = streamParser; | 307 this.streamParser_ = streamParser; |
319 this.streamParser_.setCallbacks(this.onIncomingStanzaCallback_, | 308 this.streamParser_.setCallbacks(this.onIncomingStanza_.bind(this), |
320 this.onParserError_.bind(this)); | 309 this.onParserError_.bind(this)); |
321 this.setState_(remoting.XmppConnection.State.CONNECTED); | 310 this.setState_(remoting.SignalStrategy.State.CONNECTED); |
322 }; | 311 }; |
323 | 312 |
324 /** | 313 /** |
| 314 * @param {Element} stanza |
| 315 * @private |
| 316 */ |
| 317 remoting.XmppConnection.prototype.onIncomingStanza_ = function(stanza) { |
| 318 if (this.onIncomingStanzaCallback_) { |
| 319 this.onIncomingStanzaCallback_(stanza); |
| 320 } |
| 321 }; |
| 322 |
| 323 /** |
325 * @param {string} text | 324 * @param {string} text |
326 * @private | 325 * @private |
327 */ | 326 */ |
328 remoting.XmppConnection.prototype.onParserError_ = function(text) { | 327 remoting.XmppConnection.prototype.onParserError_ = function(text) { |
329 this.onError_(remoting.Error.UNEXPECTED, text); | 328 this.onError_(remoting.Error.UNEXPECTED, text); |
330 } | 329 } |
331 | 330 |
332 /** | 331 /** |
333 * @param {remoting.Error} error | 332 * @param {remoting.Error} error |
334 * @param {string} text | 333 * @param {string} text |
335 * @private | 334 * @private |
336 */ | 335 */ |
337 remoting.XmppConnection.prototype.onError_ = function(error, text) { | 336 remoting.XmppConnection.prototype.onError_ = function(error, text) { |
338 console.error(text); | 337 console.error(text); |
339 this.error_ = error; | 338 this.error_ = error; |
340 this.closeSocket_(); | 339 this.closeSocket_(); |
341 this.setState_(remoting.XmppConnection.State.FAILED); | 340 this.setState_(remoting.SignalStrategy.State.FAILED); |
342 }; | 341 }; |
343 | 342 |
344 /** | 343 /** |
345 * @private | 344 * @private |
346 */ | 345 */ |
347 remoting.XmppConnection.prototype.closeSocket_ = function() { | 346 remoting.XmppConnection.prototype.closeSocket_ = function() { |
348 if (this.socketId_ != -1) { | 347 if (this.socketId_ != -1) { |
349 chrome.socket.destroy(this.socketId_); | 348 chrome.socket.destroy(this.socketId_); |
350 this.socketId_ = -1; | 349 this.socketId_ = -1; |
351 } | 350 } |
352 }; | 351 }; |
353 | 352 |
354 /** | 353 /** |
355 * @param {remoting.XmppConnection.State} newState | 354 * @param {remoting.SignalStrategy.State} newState |
356 * @private | 355 * @private |
357 */ | 356 */ |
358 remoting.XmppConnection.prototype.setState_ = function(newState) { | 357 remoting.XmppConnection.prototype.setState_ = function(newState) { |
359 if (this.state_ != newState) { | 358 if (this.state_ != newState) { |
360 this.state_ = newState; | 359 this.state_ = newState; |
361 this.onStateChangedCallback_(this.state_); | 360 this.onStateChangedCallback_(this.state_); |
362 } | 361 } |
363 }; | 362 }; |
OLD | NEW |