Chromium Code Reviews| 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 * Object used to connect to XMPP server. | |
| 12 * | |
| 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 | |
| 15 * because TLS support in chrome.sockets.tcp is currently broken, see | |
| 16 * crbug.com/403076 . | |
| 17 * | |
| 18 * @param {function(remoting.XmppConnection.State):void} onStateChangedCallback | |
| 19 * Callback to call on state change. | |
| 20 * @param {function(Element):void} onIncomingStanzaCallback Callback to call to | |
| 21 * handle incoming messages. | |
| 22 * @constructor | |
| 23 */ | |
| 24 remoting.XmppConnection = | |
| 25 function(onStateChangedCallback, onIncomingStanzaCallback) { | |
| 26 this.server_ = ''; | |
|
kelvinp
2014/08/29 01:37:09
@private. Same for other private members and func
Sergey Ulanov
2014/08/29 23:40:30
Done.
| |
| 27 this.port_ = 0; | |
| 28 this.onStateChangedCallback_ = onStateChangedCallback; | |
| 29 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; | |
| 30 this.socketId_ = -1; | |
| 31 this.state_ = remoting.XmppConnection.State.NOT_CONNECTED; | |
| 32 this.readPending_ = false; | |
| 33 this.sendPending_ = false; | |
| 34 this.starttlsPending_ = false; | |
| 35 /** @type {Array.<ArrayBuffer>} */ | |
| 36 this.sendQueue_ = []; | |
| 37 this.jid_ = ''; | |
| 38 this.error_ = remoting.Error.NONE; | |
| 39 } | |
| 40 | |
| 41 /** | |
| 42 * @enum {number} XmppConnection states. Possible state transitions: | |
| 43 * NOT_CONNECTED -> CONNECTING (connect() called). | |
| 44 * CONNECTING -> HANDSHAKE (connected successfully). | |
| 45 * HANDSHAKE -> CONNECTED (connected successfully). | |
|
kelvinp
2014/08/29 01:37:09
Nit: HANDSHAKE -> CONNECTED (Authenticated success
Sergey Ulanov
2014/08/29 23:40:29
Done.
| |
| 46 * CONNECTING -> FAILED (connection failed). | |
| 47 * HANDSHAKE -> FAILED (authentication failed). | |
| 48 * * -> CLOSED (dispose() called). | |
| 49 */ | |
| 50 remoting.XmppConnection.State = { | |
| 51 NOT_CONNECTED: 0, | |
| 52 CONNECTING: 1, | |
| 53 HANDSHAKE: 2, | |
| 54 CONNECTED: 3, | |
| 55 FAILED: 4, | |
| 56 CLOSED: 5 | |
| 57 }; | |
| 58 | |
| 59 /** | |
| 60 * @param {string} server | |
| 61 * @param {string} username | |
| 62 * @param {string} authToken | |
| 63 */ | |
| 64 remoting.XmppConnection.prototype.connect = | |
| 65 function(server, username, authToken) { | |
| 66 if (this.state_ != remoting.XmppConnection.State.NOT_CONNECTED) { | |
| 67 console.error( | |
| 68 'remoting.XmppConnection.prototype.connect() can only be called once.'); | |
| 69 return; | |
| 70 } | |
| 71 | |
| 72 this.error_ = remoting.Error.NONE; | |
| 73 var hostnameAndPort = server.split(':', 2); | |
| 74 this.server_ = hostnameAndPort[0]; | |
| 75 this.port_ = | |
| 76 (hostnameAndPort.length == 2) ? parseInt(hostnameAndPort[1], 10) : 5222; | |
| 77 | |
| 78 // The server name is passed as to attribute in the <stream>. When connecting | |
| 79 // to talk.google.com it affects the certificate the server will use for TLS: | |
| 80 // talk.google.com uses gmail certificate when specified server is gmail.com | |
| 81 // or googlemail.com and google.com cert otherwise. In the same time it | |
| 82 // doesn't accept talk.google.com as target server. Here we use google.com | |
| 83 // server name when authenticating to talk.google.com. This ensures that the | |
| 84 // server will use google.com cert which will be accepted by the TLS | |
| 85 // implementation in Chrome (TLS API doesn't allow specifying domain other | |
| 86 // than the one that was passed to connect()). | |
| 87 var xmppServer = this.server_; | |
| 88 if (xmppServer == 'talk.google.com') | |
| 89 xmppServer = 'google.com'; | |
| 90 | |
| 91 /** @type {remoting.XmppLoginHandler} */ | |
| 92 this.loginHandler_ = | |
| 93 new remoting.XmppLoginHandler(xmppServer, username, authToken, | |
|
kelvinp
2014/08/29 01:37:10
Instead of binding private functions and handing t
Sergey Ulanov
2014/08/29 23:40:30
This would create circular dependency between Xmpp
| |
| 94 this.sendInternal_.bind(this), | |
| 95 this.startTls_.bind(this), | |
| 96 this.onHandshakeDone_.bind(this), | |
| 97 this.onIncomingStanzaCallback_, | |
| 98 this.onError_.bind(this)); | |
| 99 chrome.socket.create("tcp", {}, this.onSocketCreated_.bind(this)); | |
| 100 this.setState_(remoting.XmppConnection.State.CONNECTING); | |
| 101 } | |
| 102 | |
| 103 /** @param {string} message */ | |
| 104 remoting.XmppConnection.prototype.sendMessage = function(message) { | |
| 105 if (this.state_ != remoting.XmppConnection.State.CONNECTED) { | |
| 106 console.error( | |
| 107 'remoting.XmppConnection.prototype.sendMessage() is called when not ' + | |
| 108 'connected.'); | |
| 109 return; | |
| 110 } | |
| 111 | |
| 112 this.sendInternal_(message); | |
| 113 } | |
| 114 | |
| 115 /** @return {remoting.XmppConnection.State} Current state */ | |
| 116 remoting.XmppConnection.prototype.getState = function() { | |
| 117 return this.state_; | |
| 118 } | |
| 119 | |
| 120 /** @return {remoting.Error} Error when in FAILED state. */ | |
| 121 remoting.XmppConnection.prototype.getError = function() { | |
| 122 return this.error_; | |
| 123 } | |
| 124 | |
| 125 /** @return {string} Current JID when in CONNECTED state. */ | |
| 126 remoting.XmppConnection.prototype.getJid = function() { | |
| 127 return this.jid_; | |
| 128 } | |
| 129 | |
| 130 remoting.XmppConnection.prototype.dispose = function() { | |
| 131 this.closeSocket_(); | |
| 132 this.setState_(remoting.XmppConnection.State.CLOSED); | |
| 133 } | |
| 134 | |
| 135 /** @param {chrome.socket.CreateInfo} createInfo */ | |
| 136 remoting.XmppConnection.prototype.onSocketCreated_ = function(createInfo) { | |
| 137 // Check if connection was terminated. | |
| 138 if (this.state_ != remoting.XmppConnection.State.CONNECTING) { | |
| 139 chrome.socket.destroy(createInfo.socketId); | |
| 140 return; | |
| 141 } | |
| 142 | |
| 143 this.socketId_ = createInfo.socketId; | |
| 144 | |
| 145 chrome.socket.connect(this.socketId_, | |
| 146 this.server_, | |
| 147 this.port_, | |
| 148 this.onSocketConnected_.bind(this)); | |
| 149 } | |
| 150 | |
| 151 /** @param {number} result */ | |
| 152 remoting.XmppConnection.prototype.onSocketConnected_ = function(result) { | |
| 153 // Check if connection was terminated. | |
| 154 if (this.state_ != remoting.XmppConnection.State.CONNECTING) | |
| 155 return; | |
| 156 | |
| 157 if (result != 0) { | |
| 158 this.onError_(remoting.Error.NETWORK_FAILURE, | |
| 159 'Failed to connect to ' + this.server_ + ': ' + result); | |
| 160 return; | |
| 161 } | |
| 162 | |
| 163 this.setState_(remoting.XmppConnection.State.HANDSHAKE); | |
| 164 | |
| 165 this.tryRead_(); | |
| 166 this.loginHandler_.start(); | |
| 167 } | |
| 168 | |
| 169 remoting.XmppConnection.prototype.tryRead_ = function() { | |
|
kelvinp
2014/08/29 01:37:10
tryRead will cause onRead to call, which will call
Sergey Ulanov
2014/08/29 23:40:30
chrome.socket.read() never calls the callback sync
| |
| 170 base.debug.assert(!this.readPending_); | |
| 171 if ((this.state_ != remoting.XmppConnection.State.HANDSHAKE && | |
| 172 this.state_ != remoting.XmppConnection.State.CONNECTED) || | |
| 173 this.starttlsPending_) { | |
| 174 return; | |
| 175 } | |
| 176 | |
| 177 this.readPending_ = true; | |
| 178 chrome.socket.read(this.socketId_, this.onRead_.bind(this)); | |
| 179 } | |
| 180 | |
| 181 /** @param {chrome.socket.ReadInfo} readInfo */ | |
| 182 remoting.XmppConnection.prototype.onRead_ = function(readInfo) { | |
| 183 base.debug.assert(this.readPending_); | |
| 184 this.readPending_ = false; | |
| 185 | |
| 186 if (readInfo.resultCode < 0) { | |
| 187 this.onError_(remoting.Error.NETWORK_FAILURE, | |
| 188 'Failed to receive from XMPP socket: ' + readInfo.resultCode); | |
| 189 return; | |
| 190 } | |
| 191 | |
| 192 this.loginHandler_.onDataReceived(readInfo.data); | |
| 193 this.tryRead_(); | |
| 194 } | |
| 195 | |
| 196 /** @param {string} text */ | |
| 197 remoting.XmppConnection.prototype.sendInternal_ = function(text) { | |
| 198 this.sendQueue_.push(base.encodeUtf8(text)); | |
| 199 this.doSend_(); | |
| 200 } | |
| 201 | |
| 202 remoting.XmppConnection.prototype.doSend_ = function() { | |
| 203 if (this.sendPending_) | |
| 204 return; | |
| 205 if (this.sendQueue_.length == 0) | |
| 206 return; | |
| 207 | |
| 208 var data = this.sendQueue_[0] | |
| 209 this.sendPending_ = true; | |
| 210 chrome.socket.write(this.socketId_, data, this.onWrite_.bind(this)); | |
| 211 } | |
| 212 | |
| 213 /** @param {chrome.socket.WriteInfo} writeInfo */ | |
| 214 remoting.XmppConnection.prototype.onWrite_ = function(writeInfo) { | |
| 215 base.debug.assert(this.sendPending_); | |
| 216 this.sendPending_ = false; | |
| 217 | |
| 218 if (writeInfo.bytesWritten < 0) { | |
| 219 console.error('TCP write failed with error ' + writeInfo.bytesWritten); | |
| 220 this.setState_(remoting.XmppConnection.State.FAILED); | |
| 221 return; | |
| 222 } | |
| 223 | |
| 224 base.debug.assert(this.sendQueue_.length > 0); | |
| 225 | |
| 226 var data = this.sendQueue_[0] | |
| 227 base.debug.assert(writeInfo.bytesWritten <= data.byteLength); | |
|
kelvinp
2014/08/29 01:37:10
It is strange that we assert that bytesWritten <=
Sergey Ulanov
2014/08/29 23:40:29
replaced >= with == in the line below.
| |
| 228 if (writeInfo.bytesWritten >= data.byteLength) { | |
| 229 this.sendQueue_.shift(); | |
| 230 } else { | |
| 231 this.sendQueue_[0] = data.slice(data.byteLength - writeInfo.bytesWritten); | |
| 232 } | |
| 233 | |
| 234 this.doSend_(); | |
| 235 } | |
| 236 | |
| 237 remoting.XmppConnection.prototype.startTls_ = function() { | |
|
kelvinp
2014/08/29 01:37:10
This function is marked as private but it is not c
Sergey Ulanov
2014/08/29 23:40:30
No. It's internal detail of XmppConnection() and i
| |
| 238 base.debug.assert(!this.readPending_); | |
| 239 base.debug.assert(!this.starttlsPending_); | |
| 240 | |
| 241 this.starttlsPending_ = true; | |
| 242 chrome.socket.secure( | |
| 243 this.socketId_, {}, this.onTlsStarted_.bind(this)); | |
| 244 } | |
| 245 | |
| 246 /** @param {number} resultCode */ | |
| 247 remoting.XmppConnection.prototype.onTlsStarted_ = function(resultCode) { | |
| 248 base.debug.assert(this.starttlsPending_); | |
| 249 this.starttlsPending_ = false; | |
| 250 | |
| 251 if (resultCode < 0) { | |
| 252 this.onError_(remoting.Error.NETWORK_FAILURE, | |
| 253 'Failed to start TLS: ' + resultCode); | |
| 254 return; | |
| 255 } | |
| 256 | |
| 257 this.tryRead_(); | |
| 258 this.loginHandler_.onTlsStarted(); | |
| 259 } | |
| 260 | |
| 261 /** @param {string} jid */ | |
| 262 remoting.XmppConnection.prototype.onHandshakeDone_ = function(jid) { | |
| 263 this.jid_ = jid; | |
| 264 this.setState_(remoting.XmppConnection.State.CONNECTED); | |
| 265 } | |
| 266 | |
| 267 /** | |
| 268 * @param {remoting.Error} error | |
| 269 * @param {string} text | |
| 270 */ | |
| 271 remoting.XmppConnection.prototype.onError_ = function(error, text) { | |
| 272 console.error(text); | |
| 273 this.error_ = error; | |
| 274 this.closeSocket_(); | |
| 275 this.setState_(remoting.XmppConnection.State.FAILED); | |
| 276 } | |
| 277 | |
| 278 remoting.XmppConnection.prototype.closeSocket_ = function() { | |
| 279 if (this.socketId_ != -1) { | |
| 280 chrome.socket.destroy(this.socketId_); | |
| 281 this.socketId_ = -1; | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 /** @param {remoting.XmppConnection.State} newState */ | |
| 286 remoting.XmppConnection.prototype.setState_ = function(newState) { | |
| 287 if (this.state_ != newState) { | |
| 288 this.state_ = newState; | |
| 289 this.onStateChangedCallback_(this.state_); | |
| 290 } | |
| 291 } | |
| OLD | NEW |