| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 'use strict'; | |
| 6 | |
| 7 /** @suppress {duplicate} */ | |
| 8 var remoting = remoting || {}; | |
| 9 | |
| 10 /** | |
| 11 * A connection to an XMPP server. | |
| 12 * | |
| 13 * @constructor | |
| 14 * @implements {remoting.SignalStrategy} | |
| 15 */ | |
| 16 remoting.XmppConnection = function() { | |
| 17 /** @private */ | |
| 18 this.server_ = ''; | |
| 19 /** @private */ | |
| 20 this.port_ = 0; | |
| 21 /** @private {?function(remoting.SignalStrategy.State):void} */ | |
| 22 this.onStateChangedCallback_ = null; | |
| 23 /** @private {?function(Element):void} */ | |
| 24 this.onIncomingStanzaCallback_ = null; | |
| 25 /** @type {?remoting.TcpSocket} @private */ | |
| 26 this.socket_ = null; | |
| 27 /** @private */ | |
| 28 this.state_ = remoting.SignalStrategy.State.NOT_CONNECTED; | |
| 29 /** @private */ | |
| 30 this.sendPending_ = false; | |
| 31 /** @private */ | |
| 32 this.startTlsPending_ = false; | |
| 33 /** @private {Array<ArrayBuffer>} */ | |
| 34 this.sendQueue_ = []; | |
| 35 /** @private {remoting.XmppLoginHandler} */ | |
| 36 this.loginHandler_ = null; | |
| 37 /** @private {remoting.XmppStreamParser} */ | |
| 38 this.streamParser_ = null; | |
| 39 /** @private */ | |
| 40 this.jid_ = ''; | |
| 41 /** @private */ | |
| 42 this.error_ = remoting.Error.none(); | |
| 43 }; | |
| 44 | |
| 45 /** | |
| 46 * @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback | |
| 47 */ | |
| 48 remoting.XmppConnection.prototype.setStateChangedCallback = function( | |
| 49 onStateChangedCallback) { | |
| 50 this.onStateChangedCallback_ = onStateChangedCallback; | |
| 51 }; | |
| 52 | |
| 53 remoting.XmppConnection.prototype.setSocketForTests = function( | |
| 54 /** remoting.TcpSocket */ socket) { | |
| 55 this.socket_ = socket; | |
| 56 }; | |
| 57 | |
| 58 /** | |
| 59 * @param {?function(Element):void} onIncomingStanzaCallback Callback to call on | |
| 60 * incoming messages. | |
| 61 */ | |
| 62 remoting.XmppConnection.prototype.setIncomingStanzaCallback = | |
| 63 function(onIncomingStanzaCallback) { | |
| 64 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; | |
| 65 }; | |
| 66 | |
| 67 /** | |
| 68 * @param {string} server | |
| 69 * @param {string} username | |
| 70 * @param {string} authToken | |
| 71 */ | |
| 72 remoting.XmppConnection.prototype.connect = | |
| 73 function(server, username, authToken) { | |
| 74 base.debug.assert(this.state_ == remoting.SignalStrategy.State.NOT_CONNECTED); | |
| 75 base.debug.assert(this.onStateChangedCallback_ != null); | |
| 76 | |
| 77 this.error_ = remoting.Error.none(); | |
| 78 var hostnameAndPort = server.split(':', 2); | |
| 79 this.server_ = hostnameAndPort[0]; | |
| 80 this.port_ = | |
| 81 (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222; | |
| 82 | |
| 83 // The server name is passed as to attribute in the <stream>. When connecting | |
| 84 // to talk.google.com it affects the certificate the server will use for TLS: | |
| 85 // talk.google.com uses gmail certificate when specified server is gmail.com | |
| 86 // or googlemail.com and google.com cert otherwise. In the same time it | |
| 87 // doesn't accept talk.google.com as target server. Here we use google.com | |
| 88 // server name when authenticating to talk.google.com. This ensures that the | |
| 89 // server will use google.com cert which will be accepted by the TLS | |
| 90 // implementation in Chrome (TLS API doesn't allow specifying domain other | |
| 91 // than the one that was passed to connect()). | |
| 92 var xmppServer = this.server_; | |
| 93 if (xmppServer == 'talk.google.com') | |
| 94 xmppServer = 'google.com'; | |
| 95 | |
| 96 // <starttls> handshake before starting TLS is not needed when connecting on | |
| 97 // the HTTPS port. | |
| 98 var needHandshakeBeforeTls = this.port_ != 443; | |
| 99 | |
| 100 /** @type {remoting.XmppLoginHandler} */ | |
| 101 this.loginHandler_ = new remoting.XmppLoginHandler( | |
| 102 xmppServer, username, authToken, needHandshakeBeforeTls, | |
| 103 this.sendString_.bind(this), this.startTls_.bind(this), | |
| 104 this.onHandshakeDone_.bind(this), this.onError_.bind(this)); | |
| 105 this.setState_(remoting.SignalStrategy.State.CONNECTING); | |
| 106 | |
| 107 if (!this.socket_) { | |
| 108 this.socket_ = new remoting.TcpSocket(); | |
| 109 } | |
| 110 var that = this; | |
| 111 this.socket_.connect(this.server_, this.port_) | |
| 112 .then(this.onSocketConnected_.bind(this)) | |
| 113 .catch(function(error) { | |
| 114 that.onError_(new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE), | |
| 115 'Failed to connect to ' + that.server_ + ': ' + error); | |
| 116 }); | |
| 117 }; | |
| 118 | |
| 119 /** @param {string} message */ | |
| 120 remoting.XmppConnection.prototype.sendMessage = function(message) { | |
| 121 base.debug.assert(this.state_ == remoting.SignalStrategy.State.CONNECTED); | |
| 122 this.sendString_(message); | |
| 123 }; | |
| 124 | |
| 125 /** | |
| 126 * @param {remoting.LogToServer} logToServer The LogToServer instance for the | |
| 127 * connection. | |
| 128 */ | |
| 129 remoting.XmppConnection.prototype.sendConnectionSetupResults = | |
| 130 function(logToServer) { | |
| 131 }; | |
| 132 | |
| 133 /** @return {remoting.SignalStrategy.State} Current state */ | |
| 134 remoting.XmppConnection.prototype.getState = function() { | |
| 135 return this.state_; | |
| 136 }; | |
| 137 | |
| 138 /** @return {!remoting.Error} Error when in FAILED state. */ | |
| 139 remoting.XmppConnection.prototype.getError = function() { | |
| 140 return this.error_; | |
| 141 }; | |
| 142 | |
| 143 /** @return {string} Current JID when in CONNECTED state. */ | |
| 144 remoting.XmppConnection.prototype.getJid = function() { | |
| 145 return this.jid_; | |
| 146 }; | |
| 147 | |
| 148 /** @return {remoting.SignalStrategy.Type} The signal strategy type. */ | |
| 149 remoting.XmppConnection.prototype.getType = function() { | |
| 150 return remoting.SignalStrategy.Type.XMPP; | |
| 151 }; | |
| 152 | |
| 153 remoting.XmppConnection.prototype.dispose = function() { | |
| 154 base.dispose(this.socket_); | |
| 155 this.socket_ = null; | |
| 156 this.setState_(remoting.SignalStrategy.State.CLOSED); | |
| 157 }; | |
| 158 | |
| 159 /** @private */ | |
| 160 remoting.XmppConnection.prototype.onSocketConnected_ = function() { | |
| 161 // Check if connection was destroyed. | |
| 162 if (this.state_ != remoting.SignalStrategy.State.CONNECTING) { | |
| 163 return; | |
| 164 } | |
| 165 | |
| 166 this.setState_(remoting.SignalStrategy.State.HANDSHAKE); | |
| 167 this.loginHandler_.start(); | |
| 168 | |
| 169 if (!this.startTlsPending_) { | |
| 170 this.socket_.startReceiving(this.onReceive_.bind(this), | |
| 171 this.onReceiveError_.bind(this)); | |
| 172 } | |
| 173 }; | |
| 174 | |
| 175 /** | |
| 176 * @param {ArrayBuffer} data | |
| 177 * @private | |
| 178 */ | |
| 179 remoting.XmppConnection.prototype.onReceive_ = function(data) { | |
| 180 base.debug.assert(this.state_ == remoting.SignalStrategy.State.HANDSHAKE || | |
| 181 this.state_ == remoting.SignalStrategy.State.CONNECTED); | |
| 182 | |
| 183 if (this.state_ == remoting.SignalStrategy.State.HANDSHAKE) { | |
| 184 this.loginHandler_.onDataReceived(data); | |
| 185 } else if (this.state_ == remoting.SignalStrategy.State.CONNECTED) { | |
| 186 this.streamParser_.appendData(data); | |
| 187 } | |
| 188 }; | |
| 189 | |
| 190 /** | |
| 191 * @param {number} errorCode | |
| 192 * @private | |
| 193 */ | |
| 194 remoting.XmppConnection.prototype.onReceiveError_ = function(errorCode) { | |
| 195 this.onError_(new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE), | |
| 196 'Failed to receive from XMPP socket: ' + errorCode); | |
| 197 }; | |
| 198 | |
| 199 /** | |
| 200 * @param {string} text | |
| 201 * @private | |
| 202 */ | |
| 203 remoting.XmppConnection.prototype.sendString_ = function(text) { | |
| 204 this.sendBuffer_(base.encodeUtf8(text)); | |
| 205 }; | |
| 206 | |
| 207 /** | |
| 208 * @param {ArrayBuffer} data | |
| 209 * @private | |
| 210 */ | |
| 211 remoting.XmppConnection.prototype.sendBuffer_ = function(data) { | |
| 212 this.sendQueue_.push(data); | |
| 213 this.flushSendQueue_(); | |
| 214 }; | |
| 215 | |
| 216 /** | |
| 217 * @private | |
| 218 */ | |
| 219 remoting.XmppConnection.prototype.flushSendQueue_ = function() { | |
| 220 if (this.sendPending_ || this.sendQueue_.length == 0) { | |
| 221 return; | |
| 222 } | |
| 223 | |
| 224 var that = this; | |
| 225 | |
| 226 this.sendPending_ = true; | |
| 227 this.socket_.send(this.sendQueue_[0]) | |
| 228 .then(function(/** number */ bytesSent) { | |
| 229 that.sendPending_ = false; | |
| 230 that.onSent_(bytesSent); | |
| 231 }) | |
| 232 .catch(function(/** number */ error) { | |
| 233 that.sendPending_ = false; | |
| 234 that.onError_(new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE), | |
| 235 'TCP write failed with error ' + error); | |
| 236 }); | |
| 237 }; | |
| 238 | |
| 239 /** | |
| 240 * @param {number} bytesSent | |
| 241 * @private | |
| 242 */ | |
| 243 remoting.XmppConnection.prototype.onSent_ = function(bytesSent) { | |
| 244 // Ignore send() result if the socket was closed. | |
| 245 if (this.state_ != remoting.SignalStrategy.State.HANDSHAKE && | |
| 246 this.state_ != remoting.SignalStrategy.State.CONNECTED) { | |
| 247 return; | |
| 248 } | |
| 249 | |
| 250 base.debug.assert(this.sendQueue_.length > 0); | |
| 251 | |
| 252 var data = this.sendQueue_[0]; | |
| 253 base.debug.assert(bytesSent <= data.byteLength); | |
| 254 if (bytesSent == data.byteLength) { | |
| 255 this.sendQueue_.shift(); | |
| 256 } else { | |
| 257 this.sendQueue_[0] = data.slice(data.byteLength - bytesSent); | |
| 258 } | |
| 259 | |
| 260 this.flushSendQueue_(); | |
| 261 }; | |
| 262 | |
| 263 /** | |
| 264 * @private | |
| 265 */ | |
| 266 remoting.XmppConnection.prototype.startTls_ = function() { | |
| 267 base.debug.assert(!this.startTlsPending_); | |
| 268 | |
| 269 var that = this; | |
| 270 | |
| 271 this.startTlsPending_ = true; | |
| 272 this.socket_.startTls() | |
| 273 .then(function() { | |
| 274 that.startTlsPending_ = false; | |
| 275 that.socket_.startReceiving(that.onReceive_.bind(that), | |
| 276 that.onReceiveError_.bind(that)); | |
| 277 | |
| 278 that.loginHandler_.onTlsStarted(); | |
| 279 }) | |
| 280 .catch(function(/** number */ error) { | |
| 281 that.startTlsPending_ = false; | |
| 282 that.onError_(new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE), | |
| 283 'Failed to start TLS: ' + error); | |
| 284 }); | |
| 285 } | |
| 286 | |
| 287 /** | |
| 288 * @param {string} jid | |
| 289 * @param {remoting.XmppStreamParser} streamParser | |
| 290 * @private | |
| 291 */ | |
| 292 remoting.XmppConnection.prototype.onHandshakeDone_ = | |
| 293 function(jid, streamParser) { | |
| 294 this.jid_ = jid; | |
| 295 this.streamParser_ = streamParser; | |
| 296 this.streamParser_.setCallbacks(this.onIncomingStanza_.bind(this), | |
| 297 this.onParserError_.bind(this)); | |
| 298 this.setState_(remoting.SignalStrategy.State.CONNECTED); | |
| 299 }; | |
| 300 | |
| 301 /** | |
| 302 * @param {Element} stanza | |
| 303 * @private | |
| 304 */ | |
| 305 remoting.XmppConnection.prototype.onIncomingStanza_ = function(stanza) { | |
| 306 if (this.onIncomingStanzaCallback_) { | |
| 307 this.onIncomingStanzaCallback_(stanza); | |
| 308 } | |
| 309 }; | |
| 310 | |
| 311 /** | |
| 312 * @param {string} text | |
| 313 * @private | |
| 314 */ | |
| 315 remoting.XmppConnection.prototype.onParserError_ = function(text) { | |
| 316 this.onError_(remoting.Error.unexpected(), text); | |
| 317 }; | |
| 318 | |
| 319 /** | |
| 320 * @param {!remoting.Error} error | |
| 321 * @param {string} text | |
| 322 * @private | |
| 323 */ | |
| 324 remoting.XmppConnection.prototype.onError_ = function(error, text) { | |
| 325 console.error(text); | |
| 326 this.error_ = error; | |
| 327 base.dispose(this.socket_); | |
| 328 this.socket_ = null; | |
| 329 this.setState_(remoting.SignalStrategy.State.FAILED); | |
| 330 }; | |
| 331 | |
| 332 /** | |
| 333 * @param {remoting.SignalStrategy.State} newState | |
| 334 * @private | |
| 335 */ | |
| 336 remoting.XmppConnection.prototype.setState_ = function(newState) { | |
| 337 if (this.state_ != newState) { | |
| 338 this.state_ = newState; | |
| 339 this.onStateChangedCallback_(this.state_); | |
| 340 } | |
| 341 }; | |
| OLD | NEW |