| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 /** | |
| 6 * @fileoverview | |
| 7 * Class handling creation and teardown of a remoting client session. | |
| 8 * | |
| 9 * The ClientSession class controls lifetime of the client plugin | |
| 10 * object and provides the plugin with the functionality it needs to | |
| 11 * establish connection, e.g. delivers incoming/outgoing signaling | |
| 12 * messages. | |
| 13 * | |
| 14 * This class should not access the plugin directly, instead it should | |
| 15 * do it through ClientPlugin class which abstracts plugin version | |
| 16 * differences. | |
| 17 */ | |
| 18 | |
| 19 'use strict'; | |
| 20 | |
| 21 /** @suppress {duplicate} */ | |
| 22 var remoting = remoting || {}; | |
| 23 | |
| 24 /** | |
| 25 * @param {remoting.ClientPlugin} plugin | |
| 26 * @param {remoting.SignalStrategy} signalStrategy Signal strategy. | |
| 27 * @param {remoting.ClientSession.EventHandler} listener | |
| 28 * | |
| 29 * @constructor | |
| 30 * @extends {base.EventSourceImpl} | |
| 31 * @implements {base.Disposable} | |
| 32 * @implements {remoting.ClientPlugin.ConnectionEventHandler} | |
| 33 */ | |
| 34 remoting.ClientSession = function(plugin, signalStrategy, listener) { | |
| 35 base.inherits(this, base.EventSourceImpl); | |
| 36 | |
| 37 /** @private */ | |
| 38 this.state_ = remoting.ClientSession.State.INITIALIZING; | |
| 39 | |
| 40 /** @private {!remoting.Error} */ | |
| 41 this.error_ = remoting.Error.none(); | |
| 42 | |
| 43 /** @private {remoting.Host} */ | |
| 44 this.host_ = null; | |
| 45 | |
| 46 /** @private {remoting.CredentialsProvider} */ | |
| 47 this.credentialsProvider_ = null; | |
| 48 | |
| 49 /** @private */ | |
| 50 this.sessionId_ = ''; | |
| 51 | |
| 52 /** @private */ | |
| 53 this.listener_ = listener; | |
| 54 | |
| 55 /** @private */ | |
| 56 this.hasReceivedFrame_ = false; | |
| 57 | |
| 58 /** @private */ | |
| 59 this.logToServer_ = new remoting.LogToServer(signalStrategy); | |
| 60 | |
| 61 /** @private */ | |
| 62 this.signalStrategy_ = signalStrategy; | |
| 63 base.debug.assert(this.signalStrategy_.getState() == | |
| 64 remoting.SignalStrategy.State.CONNECTED); | |
| 65 this.signalStrategy_.setIncomingStanzaCallback( | |
| 66 this.onIncomingMessage_.bind(this)); | |
| 67 | |
| 68 /** @private {remoting.FormatIq} */ | |
| 69 this.iqFormatter_ = null; | |
| 70 | |
| 71 /** | |
| 72 * Allow host-offline error reporting to be suppressed in situations where it | |
| 73 * would not be useful, for example, when using a cached host JID. | |
| 74 * | |
| 75 * @type {boolean} @private | |
| 76 */ | |
| 77 this.logHostOfflineErrors_ = true; | |
| 78 | |
| 79 /** @private {remoting.ClientPlugin} */ | |
| 80 this.plugin_ = plugin; | |
| 81 plugin.setConnectionEventHandler(this); | |
| 82 | |
| 83 /** @private */ | |
| 84 this.connectedDisposables_ = new base.Disposables(); | |
| 85 | |
| 86 this.defineEvents(Object.keys(remoting.ClientSession.Events)); | |
| 87 }; | |
| 88 | |
| 89 /** @enum {string} */ | |
| 90 remoting.ClientSession.Events = { | |
| 91 videoChannelStateChanged: 'videoChannelStateChanged' | |
| 92 }; | |
| 93 | |
| 94 /** | |
| 95 * @interface | |
| 96 * [START]-------> [onConnected] ------> [onDisconnected] | |
| 97 * | | |
| 98 * |-----> [OnConnectionFailed] | |
| 99 * | |
| 100 */ | |
| 101 remoting.ClientSession.EventHandler = function() {}; | |
| 102 | |
| 103 /** | |
| 104 * Called when the connection failed before it is connected. | |
| 105 * | |
| 106 * @param {!remoting.Error} error | |
| 107 */ | |
| 108 remoting.ClientSession.EventHandler.prototype.onConnectionFailed = | |
| 109 function(error) {}; | |
| 110 | |
| 111 /** | |
| 112 * Called when a new session has been connected. The |connectionInfo| will be | |
| 113 * valid until onDisconnected() is called. | |
| 114 * | |
| 115 * @param {!remoting.ConnectionInfo} connectionInfo | |
| 116 */ | |
| 117 remoting.ClientSession.EventHandler.prototype.onConnected = | |
| 118 function(connectionInfo) {}; | |
| 119 | |
| 120 /** | |
| 121 * Called when the current session has been disconnected. | |
| 122 * | |
| 123 * @param {!remoting.Error} reason Reason that the session is disconnected. | |
| 124 * Set to remoting.Error.none() if there is no error. | |
| 125 */ | |
| 126 remoting.ClientSession.EventHandler.prototype.onDisconnected = | |
| 127 function(reason) {}; | |
| 128 | |
| 129 // Note that the positive values in both of these enums are copied directly | |
| 130 // from connection_to_host.h and must be kept in sync. Code in | |
| 131 // chromoting_instance.cc converts the C++ enums into strings that must match | |
| 132 // the names given here. | |
| 133 // The negative values represent state transitions that occur within the | |
| 134 // web-app that have no corresponding plugin state transition. | |
| 135 /** @enum {number} */ | |
| 136 remoting.ClientSession.State = { | |
| 137 CONNECTION_CANCELED: -3, // Connection closed (gracefully) before connecting. | |
| 138 CONNECTION_DROPPED: -2, // Succeeded, but subsequently closed with an error. | |
| 139 CREATED: -1, | |
| 140 UNKNOWN: 0, | |
| 141 INITIALIZING: 1, | |
| 142 CONNECTING: 2, | |
| 143 // We don't currently receive AUTHENTICATED from the host - it comes through | |
| 144 // as 'CONNECTING' instead. | |
| 145 // TODO(garykac) Update chromoting_instance.cc to send this once we've | |
| 146 // shipped a webapp release with support for AUTHENTICATED. | |
| 147 AUTHENTICATED: 3, | |
| 148 CONNECTED: 4, | |
| 149 CLOSED: 5, | |
| 150 FAILED: 6 | |
| 151 }; | |
| 152 | |
| 153 /** | |
| 154 * @param {string} state The state name. | |
| 155 * @return {remoting.ClientSession.State} The session state enum value. | |
| 156 */ | |
| 157 remoting.ClientSession.State.fromString = function(state) { | |
| 158 if (!remoting.ClientSession.State.hasOwnProperty(state)) { | |
| 159 throw "Invalid ClientSession.State: " + state; | |
| 160 } | |
| 161 return remoting.ClientSession.State[state]; | |
| 162 }; | |
| 163 | |
| 164 /** @enum {number} */ | |
| 165 remoting.ClientSession.ConnectionError = { | |
| 166 UNKNOWN: -1, | |
| 167 NONE: 0, | |
| 168 HOST_IS_OFFLINE: 1, | |
| 169 SESSION_REJECTED: 2, | |
| 170 INCOMPATIBLE_PROTOCOL: 3, | |
| 171 NETWORK_FAILURE: 4, | |
| 172 HOST_OVERLOAD: 5 | |
| 173 }; | |
| 174 | |
| 175 /** | |
| 176 * @param {string} error The connection error name. | |
| 177 * @return {remoting.ClientSession.ConnectionError} The connection error enum. | |
| 178 */ | |
| 179 remoting.ClientSession.ConnectionError.fromString = function(error) { | |
| 180 if (!remoting.ClientSession.ConnectionError.hasOwnProperty(error)) { | |
| 181 console.error('Unexpected ClientSession.ConnectionError string: ', error); | |
| 182 return remoting.ClientSession.ConnectionError.UNKNOWN; | |
| 183 } | |
| 184 return remoting.ClientSession.ConnectionError[error]; | |
| 185 } | |
| 186 | |
| 187 /** | |
| 188 * Type used for performance statistics collected by the plugin. | |
| 189 * @constructor | |
| 190 */ | |
| 191 remoting.ClientSession.PerfStats = function() {}; | |
| 192 /** @type {number} */ | |
| 193 remoting.ClientSession.PerfStats.prototype.videoBandwidth; | |
| 194 /** @type {number} */ | |
| 195 remoting.ClientSession.PerfStats.prototype.videoFrameRate; | |
| 196 /** @type {number} */ | |
| 197 remoting.ClientSession.PerfStats.prototype.captureLatency; | |
| 198 /** @type {number} */ | |
| 199 remoting.ClientSession.PerfStats.prototype.encodeLatency; | |
| 200 /** @type {number} */ | |
| 201 remoting.ClientSession.PerfStats.prototype.decodeLatency; | |
| 202 /** @type {number} */ | |
| 203 remoting.ClientSession.PerfStats.prototype.renderLatency; | |
| 204 /** @type {number} */ | |
| 205 remoting.ClientSession.PerfStats.prototype.roundtripLatency; | |
| 206 | |
| 207 // Keys for connection statistics. | |
| 208 remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH = 'videoBandwidth'; | |
| 209 remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE = 'videoFrameRate'; | |
| 210 remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY = 'captureLatency'; | |
| 211 remoting.ClientSession.STATS_KEY_ENCODE_LATENCY = 'encodeLatency'; | |
| 212 remoting.ClientSession.STATS_KEY_DECODE_LATENCY = 'decodeLatency'; | |
| 213 remoting.ClientSession.STATS_KEY_RENDER_LATENCY = 'renderLatency'; | |
| 214 remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY = 'roundtripLatency'; | |
| 215 | |
| 216 /** | |
| 217 * Set of capabilities for which hasCapability() can be used to test. | |
| 218 * | |
| 219 * @enum {string} | |
| 220 */ | |
| 221 remoting.ClientSession.Capability = { | |
| 222 // When enabled this capability causes the client to send its screen | |
| 223 // resolution to the host once connection has been established. See | |
| 224 // this.plugin_.notifyClientResolution(). | |
| 225 SEND_INITIAL_RESOLUTION: 'sendInitialResolution', | |
| 226 | |
| 227 // Let the host know that we're interested in knowing whether or not it | |
| 228 // rate limits desktop-resize requests. | |
| 229 // TODO(kelvinp): This has been supported since M-29. Currently we only have | |
| 230 // <1000 users on M-29 or below. Remove this and the capability on the host. | |
| 231 RATE_LIMIT_RESIZE_REQUESTS: 'rateLimitResizeRequests', | |
| 232 | |
| 233 // Indicates that host/client supports Google Drive integration, and that the | |
| 234 // client should send to the host the OAuth tokens to be used by Google Drive | |
| 235 // on the host. | |
| 236 GOOGLE_DRIVE: 'googleDrive', | |
| 237 | |
| 238 // Indicates that the client supports the video frame-recording extension. | |
| 239 VIDEO_RECORDER: 'videoRecorder', | |
| 240 | |
| 241 // Indicates that the client supports 'cast'ing the video stream to a | |
| 242 // cast-enabled device. | |
| 243 CAST: 'casting', | |
| 244 }; | |
| 245 | |
| 246 /** | |
| 247 * Connects to |host| using |credentialsProvider| as the credentails. | |
| 248 * | |
| 249 * @param {remoting.Host} host | |
| 250 * @param {remoting.CredentialsProvider} credentialsProvider | |
| 251 */ | |
| 252 remoting.ClientSession.prototype.connect = function(host, credentialsProvider) { | |
| 253 this.host_ = host; | |
| 254 this.credentialsProvider_ = credentialsProvider; | |
| 255 this.iqFormatter_ = | |
| 256 new remoting.FormatIq(this.signalStrategy_.getJid(), host.jabberId); | |
| 257 this.plugin_.connect(this.host_, this.signalStrategy_.getJid(), | |
| 258 credentialsProvider); | |
| 259 }; | |
| 260 | |
| 261 /** | |
| 262 * Disconnect the current session with a particular |error|. The session will | |
| 263 * raise a |stateChanged| event in response to it. The caller should then call | |
| 264 * dispose() to remove and destroy the <embed> element. | |
| 265 * | |
| 266 * @param {!remoting.Error} error The reason for the disconnection. Use | |
| 267 * remoting.Error.none() if there is no error. | |
| 268 * @return {void} Nothing. | |
| 269 */ | |
| 270 remoting.ClientSession.prototype.disconnect = function(error) { | |
| 271 if (this.isFinished()) { | |
| 272 // Do not send the session-terminate Iq if disconnect() is already called or | |
| 273 // if it is initiated by the host. | |
| 274 return; | |
| 275 } | |
| 276 | |
| 277 this.sendIq_( | |
| 278 '<cli:iq ' + | |
| 279 'to="' + this.host_.jabberId + '" ' + | |
| 280 'type="set" ' + | |
| 281 'id="session-terminate" ' + | |
| 282 'xmlns:cli="jabber:client">' + | |
| 283 '<jingle ' + | |
| 284 'xmlns="urn:xmpp:jingle:1" ' + | |
| 285 'action="session-terminate" ' + | |
| 286 'sid="' + this.sessionId_ + '">' + | |
| 287 '<reason><success/></reason>' + | |
| 288 '</jingle>' + | |
| 289 '</cli:iq>'); | |
| 290 | |
| 291 var state = error.isNone() ? | |
| 292 remoting.ClientSession.State.CLOSED : | |
| 293 remoting.ClientSession.State.FAILED; | |
| 294 this.error_ = error; | |
| 295 this.setState_(state); | |
| 296 }; | |
| 297 | |
| 298 /** | |
| 299 * Deletes the <embed> element from the container and disconnects. | |
| 300 * | |
| 301 * @return {void} Nothing. | |
| 302 */ | |
| 303 remoting.ClientSession.prototype.dispose = function() { | |
| 304 base.dispose(this.connectedDisposables_); | |
| 305 this.connectedDisposables_ = null; | |
| 306 base.dispose(this.plugin_); | |
| 307 this.plugin_ = null; | |
| 308 }; | |
| 309 | |
| 310 /** | |
| 311 * @return {remoting.ClientSession.State} The current state. | |
| 312 */ | |
| 313 remoting.ClientSession.prototype.getState = function() { | |
| 314 return this.state_; | |
| 315 }; | |
| 316 | |
| 317 /** | |
| 318 * @return {remoting.LogToServer}. | |
| 319 */ | |
| 320 remoting.ClientSession.prototype.getLogger = function() { | |
| 321 return this.logToServer_; | |
| 322 }; | |
| 323 | |
| 324 /** | |
| 325 * @return {!remoting.Error} The current error code. | |
| 326 */ | |
| 327 remoting.ClientSession.prototype.getError = function() { | |
| 328 return this.error_; | |
| 329 }; | |
| 330 | |
| 331 /** | |
| 332 * Called when the client receives its first frame. | |
| 333 * | |
| 334 * @return {void} Nothing. | |
| 335 */ | |
| 336 remoting.ClientSession.prototype.onFirstFrameReceived = function() { | |
| 337 this.hasReceivedFrame_ = true; | |
| 338 }; | |
| 339 | |
| 340 /** | |
| 341 * @return {boolean} Whether the client has received a video buffer. | |
| 342 */ | |
| 343 remoting.ClientSession.prototype.hasReceivedFrame = function() { | |
| 344 return this.hasReceivedFrame_; | |
| 345 }; | |
| 346 | |
| 347 /** | |
| 348 * Sends a signaling message. | |
| 349 * | |
| 350 * @param {string} message XML string of IQ stanza to send to server. | |
| 351 * @return {void} Nothing. | |
| 352 * @private | |
| 353 */ | |
| 354 remoting.ClientSession.prototype.sendIq_ = function(message) { | |
| 355 // Extract the session id, so we can close the session later. | |
| 356 var parser = new DOMParser(); | |
| 357 var iqNode = parser.parseFromString(message, 'text/xml').firstChild; | |
| 358 var jingleNode = iqNode.firstChild; | |
| 359 if (jingleNode) { | |
| 360 var action = jingleNode.getAttribute('action'); | |
| 361 if (jingleNode.nodeName == 'jingle' && action == 'session-initiate') { | |
| 362 this.sessionId_ = jingleNode.getAttribute('sid'); | |
| 363 } | |
| 364 } | |
| 365 | |
| 366 console.log(base.timestamp() + this.iqFormatter_.prettifySendIq(message)); | |
| 367 if (this.signalStrategy_.getState() != | |
| 368 remoting.SignalStrategy.State.CONNECTED) { | |
| 369 console.log("Message above is dropped because signaling is not connected."); | |
| 370 return; | |
| 371 } | |
| 372 | |
| 373 this.signalStrategy_.sendMessage(message); | |
| 374 }; | |
| 375 | |
| 376 /** | |
| 377 * @param {string} message XML string of IQ stanza to send to server. | |
| 378 */ | |
| 379 remoting.ClientSession.prototype.onOutgoingIq = function(message) { | |
| 380 this.sendIq_(message); | |
| 381 }; | |
| 382 | |
| 383 /** | |
| 384 * @param {string} msg | |
| 385 */ | |
| 386 remoting.ClientSession.prototype.onDebugMessage = function(msg) { | |
| 387 console.log('plugin: ' + msg.trimRight()); | |
| 388 }; | |
| 389 | |
| 390 /** | |
| 391 * @param {Element} message | |
| 392 * @private | |
| 393 */ | |
| 394 remoting.ClientSession.prototype.onIncomingMessage_ = function(message) { | |
| 395 if (!this.plugin_) { | |
| 396 return; | |
| 397 } | |
| 398 var formatted = new XMLSerializer().serializeToString(message); | |
| 399 console.log(base.timestamp() + | |
| 400 this.iqFormatter_.prettifyReceiveIq(formatted)); | |
| 401 this.plugin_.onIncomingIq(formatted); | |
| 402 }; | |
| 403 | |
| 404 /** | |
| 405 * Callback that the plugin invokes to indicate that the connection | |
| 406 * status has changed. | |
| 407 * | |
| 408 * @param {remoting.ClientSession.State} status The plugin's status. | |
| 409 * @param {remoting.ClientSession.ConnectionError} error The plugin's error | |
| 410 * state, if any. | |
| 411 */ | |
| 412 remoting.ClientSession.prototype.onConnectionStatusUpdate = | |
| 413 function(status, error) { | |
| 414 if (status == remoting.ClientSession.State.FAILED) { | |
| 415 switch (error) { | |
| 416 case remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE: | |
| 417 this.error_ = new remoting.Error( | |
| 418 remoting.Error.Tag.HOST_IS_OFFLINE); | |
| 419 break; | |
| 420 case remoting.ClientSession.ConnectionError.SESSION_REJECTED: | |
| 421 this.error_ = new remoting.Error( | |
| 422 remoting.Error.Tag.INVALID_ACCESS_CODE); | |
| 423 break; | |
| 424 case remoting.ClientSession.ConnectionError.INCOMPATIBLE_PROTOCOL: | |
| 425 this.error_ = new remoting.Error( | |
| 426 remoting.Error.Tag.INCOMPATIBLE_PROTOCOL); | |
| 427 break; | |
| 428 case remoting.ClientSession.ConnectionError.NETWORK_FAILURE: | |
| 429 this.error_ = new remoting.Error( | |
| 430 remoting.Error.Tag.P2P_FAILURE); | |
| 431 break; | |
| 432 case remoting.ClientSession.ConnectionError.HOST_OVERLOAD: | |
| 433 this.error_ = new remoting.Error( | |
| 434 remoting.Error.Tag.HOST_OVERLOAD); | |
| 435 break; | |
| 436 default: | |
| 437 this.error_ = remoting.Error.unexpected(); | |
| 438 } | |
| 439 } | |
| 440 this.setState_(status); | |
| 441 }; | |
| 442 | |
| 443 /** | |
| 444 * Callback that the plugin invokes to indicate that the connection type for | |
| 445 * a channel has changed. | |
| 446 * | |
| 447 * @param {string} channel The channel name. | |
| 448 * @param {string} connectionType The new connection type. | |
| 449 * @private | |
| 450 */ | |
| 451 remoting.ClientSession.prototype.onRouteChanged = | |
| 452 function(channel, connectionType) { | |
| 453 console.log('plugin: Channel ' + channel + ' using ' + | |
| 454 connectionType + ' connection.'); | |
| 455 this.logToServer_.setConnectionType(connectionType); | |
| 456 }; | |
| 457 | |
| 458 /** | |
| 459 * Callback that the plugin invokes to indicate when the connection is | |
| 460 * ready. | |
| 461 * | |
| 462 * @param {boolean} ready True if the connection is ready. | |
| 463 */ | |
| 464 remoting.ClientSession.prototype.onConnectionReady = function(ready) { | |
| 465 // TODO(jamiewalch): Currently, the logic for determining whether or not the | |
| 466 // connection is available is based solely on whether or not any video frames | |
| 467 // have been received recently. which leads to poor UX on slow connections. | |
| 468 // Re-enable this once crbug.com/435315 has been fixed. | |
| 469 var ignoreVideoChannelState = true; | |
| 470 if (ignoreVideoChannelState) { | |
| 471 console.log('Video channel ' + (ready ? '' : 'not ') + 'ready.'); | |
| 472 return; | |
| 473 } | |
| 474 | |
| 475 this.raiseEvent(remoting.ClientSession.Events.videoChannelStateChanged, | |
| 476 ready); | |
| 477 }; | |
| 478 | |
| 479 /** @return {boolean} */ | |
| 480 remoting.ClientSession.prototype.isFinished = function() { | |
| 481 var finishedStates = [ | |
| 482 remoting.ClientSession.State.CLOSED, | |
| 483 remoting.ClientSession.State.FAILED, | |
| 484 remoting.ClientSession.State.CONNECTION_CANCELED, | |
| 485 remoting.ClientSession.State.CONNECTION_DROPPED | |
| 486 ]; | |
| 487 return finishedStates.indexOf(this.getState()) !== -1; | |
| 488 }; | |
| 489 /** | |
| 490 * @param {remoting.ClientSession.State} newState The new state for the session. | |
| 491 * @return {void} Nothing. | |
| 492 * @private | |
| 493 */ | |
| 494 remoting.ClientSession.prototype.setState_ = function(newState) { | |
| 495 var oldState = this.state_; | |
| 496 this.state_ = this.translateState_(oldState, newState); | |
| 497 | |
| 498 if (newState == remoting.ClientSession.State.CONNECTED) { | |
| 499 this.connectedDisposables_.add( | |
| 500 new base.RepeatingTimer(this.reportStatistics.bind(this), 1000)); | |
| 501 } else if (this.isFinished()) { | |
| 502 base.dispose(this.connectedDisposables_); | |
| 503 this.connectedDisposables_ = null; | |
| 504 } | |
| 505 | |
| 506 this.notifyStateChanges_(oldState, this.state_); | |
| 507 this.logToServer_.logClientSessionStateChange(this.state_, this.error_); | |
| 508 }; | |
| 509 | |
| 510 /** | |
| 511 * @param {remoting.ClientSession.State} oldState The new state for the session. | |
| 512 * @param {remoting.ClientSession.State} newState The new state for the session. | |
| 513 * @private | |
| 514 */ | |
| 515 remoting.ClientSession.prototype.notifyStateChanges_ = | |
| 516 function(oldState, newState) { | |
| 517 /** @type {remoting.Error} */ | |
| 518 var error; | |
| 519 switch (this.state_) { | |
| 520 case remoting.ClientSession.State.CONNECTED: | |
| 521 console.log('Connection established.'); | |
| 522 var connectionInfo = new remoting.ConnectionInfo( | |
| 523 this.host_, this.credentialsProvider_, this, this.plugin_); | |
| 524 this.listener_.onConnected(connectionInfo); | |
| 525 break; | |
| 526 | |
| 527 case remoting.ClientSession.State.CONNECTING: | |
| 528 remoting.identity.getEmail().then(function(/** string */ email) { | |
| 529 console.log('Connecting as ' + email); | |
| 530 }); | |
| 531 break; | |
| 532 | |
| 533 case remoting.ClientSession.State.AUTHENTICATED: | |
| 534 console.log('Connection authenticated.'); | |
| 535 break; | |
| 536 | |
| 537 case remoting.ClientSession.State.INITIALIZING: | |
| 538 console.log('Connection initializing .'); | |
| 539 break; | |
| 540 | |
| 541 case remoting.ClientSession.State.CLOSED: | |
| 542 console.log('Connection closed.'); | |
| 543 this.listener_.onDisconnected(remoting.Error.none()); | |
| 544 break; | |
| 545 | |
| 546 case remoting.ClientSession.State.CONNECTION_CANCELED: | |
| 547 case remoting.ClientSession.State.FAILED: | |
| 548 error = this.getError(); | |
| 549 if (!error.isNone()) { | |
| 550 console.error('Connection failed: ' + error.toString()); | |
| 551 } | |
| 552 this.listener_.onConnectionFailed(error); | |
| 553 break; | |
| 554 | |
| 555 case remoting.ClientSession.State.CONNECTION_DROPPED: | |
| 556 error = this.getError(); | |
| 557 console.error('Connection dropped: ' + error.toString()); | |
| 558 this.listener_.onDisconnected(error); | |
| 559 break; | |
| 560 | |
| 561 default: | |
| 562 console.error('Unexpected client plugin state: ' + newState); | |
| 563 } | |
| 564 }; | |
| 565 | |
| 566 /** | |
| 567 * @param {remoting.ClientSession.State} previous | |
| 568 * @param {remoting.ClientSession.State} current | |
| 569 * @return {remoting.ClientSession.State} | |
| 570 * @private | |
| 571 */ | |
| 572 remoting.ClientSession.prototype.translateState_ = function(previous, current) { | |
| 573 var State = remoting.ClientSession.State; | |
| 574 if (previous == State.CONNECTING || previous == State.AUTHENTICATED) { | |
| 575 if (current == State.CLOSED) { | |
| 576 return remoting.ClientSession.State.CONNECTION_CANCELED; | |
| 577 } else if (current == State.FAILED && | |
| 578 this.error_.hasTag(remoting.Error.Tag.HOST_IS_OFFLINE) && | |
| 579 !this.logHostOfflineErrors_) { | |
| 580 // The application requested host-offline errors to be suppressed, for | |
| 581 // example, because this connection attempt is using a cached host JID. | |
| 582 console.log('Suppressing host-offline error.'); | |
| 583 return State.CONNECTION_CANCELED; | |
| 584 } | |
| 585 } else if (previous == State.CONNECTED && current == State.FAILED) { | |
| 586 return State.CONNECTION_DROPPED; | |
| 587 } | |
| 588 return current; | |
| 589 }; | |
| 590 | |
| 591 /** @private */ | |
| 592 remoting.ClientSession.prototype.reportStatistics = function() { | |
| 593 this.logToServer_.logStatistics(this.plugin_.getPerfStats()); | |
| 594 }; | |
| 595 | |
| 596 /** | |
| 597 * Enable or disable logging of connection errors due to a host being offline. | |
| 598 * For example, if attempting a connection using a cached JID, host-offline | |
| 599 * errors should not be logged because the JID will be refreshed and the | |
| 600 * connection retried. | |
| 601 * | |
| 602 * @param {boolean} enable True to log host-offline errors; false to suppress. | |
| 603 */ | |
| 604 remoting.ClientSession.prototype.logHostOfflineErrors = function(enable) { | |
| 605 this.logHostOfflineErrors_ = enable; | |
| 606 }; | |
| 607 | |
| OLD | NEW |