OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 signal strategy encapsulating a primary and a back-up strategy. If the |
| 12 * primary fails or times out, then the secondary is used. Information about |
| 13 * which strategy was used, and why, is returned via |onProgressCallback|. |
| 14 * |
| 15 * @param {function( |
| 16 * function(remoting.SignalStrategy.State) |
| 17 * ):remoting.SignalStrategy} primaryFactory |
| 18 * @param {function( |
| 19 * function(remoting.SignalStrategy.State) |
| 20 * ):remoting.SignalStrategy} secondaryFactory |
| 21 * @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback |
| 22 * @param {function(remoting.FallbackSignalStrategy.Progress)} |
| 23 * onProgressCallback |
| 24 * |
| 25 * @implements {remoting.SignalStrategy} |
| 26 * @constructor |
| 27 */ |
| 28 remoting.FallbackSignalStrategy = function( |
| 29 primaryFactory, secondaryFactory, |
| 30 onStateChangedCallback, onProgressCallback) { |
| 31 /** |
| 32 * @type {remoting.SignalStrategy} |
| 33 * @private |
| 34 */ |
| 35 this.primary_ = primaryFactory(this.onPrimaryStateChanged_.bind(this)); |
| 36 |
| 37 /** |
| 38 * @type {remoting.SignalStrategy} |
| 39 * @private |
| 40 */ |
| 41 this.secondary_ = secondaryFactory(this.onSecondaryStateChanged_.bind(this)); |
| 42 |
| 43 /** |
| 44 * @type {function(remoting.SignalStrategy.State)} |
| 45 * @private |
| 46 */ |
| 47 this.onStateChangedCallback_ = onStateChangedCallback; |
| 48 |
| 49 /** |
| 50 * @type {function(remoting.FallbackSignalStrategy.Progress)} |
| 51 * @private |
| 52 */ |
| 53 this.onProgressCallback_ = onProgressCallback; |
| 54 |
| 55 /** |
| 56 * @type {?function(Element):void} |
| 57 * @private |
| 58 */ |
| 59 this.onIncomingStanzaCallback_ = null; |
| 60 |
| 61 /** |
| 62 * @type {number} |
| 63 * @private |
| 64 * @const |
| 65 */ |
| 66 this.PRIMARY_CONNECT_TIMEOUT_MS_ = 10 * 1000; |
| 67 |
| 68 /** |
| 69 * @enum {string} |
| 70 * @private |
| 71 */ |
| 72 this.State = { |
| 73 NOT_CONNECTED: 'not-connected', |
| 74 PRIMARY_PENDING: 'primary-pending', |
| 75 PRIMARY_SUCCEEDED: 'primary-succeeded', |
| 76 SECONDARY_PENDING: 'secondary-pending', |
| 77 SECONDARY_SUCCEEDED: 'secondary-succeeded', |
| 78 SECONDARY_FAILED: 'secondary-failed', |
| 79 CLOSED: 'closed' |
| 80 }; |
| 81 |
| 82 /** |
| 83 * @type {string} |
| 84 * @private |
| 85 */ |
| 86 this.state_ = this.State.NOT_CONNECTED; |
| 87 |
| 88 /** |
| 89 * @type {?remoting.SignalStrategy.State} |
| 90 * @private |
| 91 */ |
| 92 this.externalState_ = null; |
| 93 |
| 94 /** |
| 95 * @type {string} |
| 96 * @private |
| 97 */ |
| 98 this.server_ = ''; |
| 99 |
| 100 /** |
| 101 * @type {string} |
| 102 * @private |
| 103 */ |
| 104 this.username_ = ''; |
| 105 |
| 106 /** |
| 107 * @type {string} |
| 108 * @private |
| 109 */ |
| 110 this.authToken_ = ''; |
| 111 |
| 112 /** |
| 113 * @type {number} |
| 114 * @private |
| 115 */ |
| 116 this.primaryConnectTimerId_ = 0; |
| 117 }; |
| 118 |
| 119 /** |
| 120 * @enum {string} |
| 121 */ |
| 122 remoting.FallbackSignalStrategy.Progress = { |
| 123 PRIMARY_SUCCEEDED: 'primary-succeeded', |
| 124 PRIMARY_FAILED: 'primary-failed', |
| 125 PRIMARY_TIMED_OUT: 'primary-timed-out', |
| 126 PRIMARY_SUCCEEDED_LATE: 'primary-succeeded-late', |
| 127 PRIMARY_FAILED_LATE: 'primary-failed-late', |
| 128 SECONDARY_SUCCEEDED: 'secondary-succeeded', |
| 129 SECONDARY_FAILED: 'secondary-failed' |
| 130 }; |
| 131 |
| 132 remoting.FallbackSignalStrategy.prototype.dispose = function() { |
| 133 this.primary_.dispose(); |
| 134 this.secondary_.dispose(); |
| 135 }; |
| 136 |
| 137 /** |
| 138 * @param {?function(Element):void} onIncomingStanzaCallback Callback to call on |
| 139 * incoming messages. |
| 140 */ |
| 141 remoting.FallbackSignalStrategy.prototype.setIncomingStanzaCallback = |
| 142 function(onIncomingStanzaCallback) { |
| 143 this.onIncomingStanzaCallback_ = onIncomingStanzaCallback; |
| 144 if (this.state_ == this.State.PRIMARY_PENDING || |
| 145 this.state_ == this.State.PRIMARY_SUCCEEDED) { |
| 146 this.primary_.setIncomingStanzaCallback(onIncomingStanzaCallback); |
| 147 } else if (this.state_ == this.State.SECONDARY_PENDING || |
| 148 this.state_ == this.State.SECONDARY_SUCCEEDED) { |
| 149 this.secondary_.setIncomingStanzaCallback(onIncomingStanzaCallback); |
| 150 } |
| 151 }; |
| 152 |
| 153 /** |
| 154 * @param {string} server |
| 155 * @param {string} username |
| 156 * @param {string} authToken |
| 157 */ |
| 158 remoting.FallbackSignalStrategy.prototype.connect = |
| 159 function(server, username, authToken) { |
| 160 base.debug.assert(this.state_ == this.State.NOT_CONNECTED); |
| 161 this.server_ = server; |
| 162 this.username_ = username; |
| 163 this.authToken_ = authToken; |
| 164 this.state_ = this.State.PRIMARY_PENDING; |
| 165 this.primary_.setIncomingStanzaCallback(this.onIncomingStanzaCallback_); |
| 166 this.primary_.connect(server, username, authToken); |
| 167 this.primaryConnectTimerId_ = |
| 168 window.setTimeout(this.onPrimaryTimeout_.bind(this), |
| 169 this.PRIMARY_CONNECT_TIMEOUT_MS_); |
| 170 }; |
| 171 |
| 172 /** |
| 173 * Sends a message. Can be called only in CONNECTED state. |
| 174 * @param {string} message |
| 175 */ |
| 176 remoting.FallbackSignalStrategy.prototype.sendMessage = function(message) { |
| 177 this.getConnectedSignalStrategy_().sendMessage(message); |
| 178 }; |
| 179 |
| 180 /** @return {remoting.SignalStrategy.State} Current state */ |
| 181 remoting.FallbackSignalStrategy.prototype.getState = function() { |
| 182 return (this.externalState_ === null) |
| 183 ? remoting.SignalStrategy.State.NOT_CONNECTED |
| 184 : this.externalState_; |
| 185 }; |
| 186 |
| 187 /** @return {remoting.Error} Error when in FAILED state. */ |
| 188 remoting.FallbackSignalStrategy.prototype.getError = function() { |
| 189 base.debug.assert(this.state_ == this.State.SECONDARY_FAILED); |
| 190 base.debug.assert( |
| 191 this.secondary_.getState() == remoting.SignalStrategy.State.FAILED); |
| 192 return this.secondary_.getError(); |
| 193 }; |
| 194 |
| 195 /** @return {string} Current JID when in CONNECTED state. */ |
| 196 remoting.FallbackSignalStrategy.prototype.getJid = function() { |
| 197 return this.getConnectedSignalStrategy_().getJid(); |
| 198 }; |
| 199 |
| 200 /** |
| 201 * @return {remoting.SignalStrategy} The active signal strategy, if the |
| 202 * connection has succeeded. |
| 203 * @private |
| 204 */ |
| 205 remoting.FallbackSignalStrategy.prototype.getConnectedSignalStrategy_ = |
| 206 function() { |
| 207 if (this.state_ == this.State.PRIMARY_SUCCEEDED) { |
| 208 base.debug.assert( |
| 209 this.primary_.getState() == remoting.SignalStrategy.State.CONNECTED); |
| 210 return this.primary_; |
| 211 } else if (this.state_ == this.State.SECONDARY_SUCCEEDED) { |
| 212 base.debug.assert( |
| 213 this.secondary_.getState() == remoting.SignalStrategy.State.CONNECTED); |
| 214 return this.secondary_; |
| 215 } else { |
| 216 base.debug.assert( |
| 217 false, |
| 218 'getConnectedSignalStrategy called in unconnected state'); |
| 219 return null; |
| 220 } |
| 221 }; |
| 222 |
| 223 /** |
| 224 * @param {remoting.SignalStrategy.State} state |
| 225 * @private |
| 226 */ |
| 227 remoting.FallbackSignalStrategy.prototype.onPrimaryStateChanged_ = |
| 228 function(state) { |
| 229 switch (state) { |
| 230 case remoting.SignalStrategy.State.CONNECTED: |
| 231 if (this.state_ == this.State.PRIMARY_PENDING) { |
| 232 window.clearTimeout(this.primaryConnectTimerId_); |
| 233 this.onProgressCallback_( |
| 234 remoting.FallbackSignalStrategy.Progress.PRIMARY_SUCCEEDED); |
| 235 this.state_ = this.State.PRIMARY_SUCCEEDED; |
| 236 } else { |
| 237 this.onProgressCallback_( |
| 238 remoting.FallbackSignalStrategy.Progress.PRIMARY_SUCCEEDED_LATE); |
| 239 } |
| 240 break; |
| 241 |
| 242 case remoting.SignalStrategy.State.FAILED: |
| 243 if (this.state_ == this.State.PRIMARY_PENDING) { |
| 244 window.clearTimeout(this.primaryConnectTimerId_); |
| 245 this.onProgressCallback_( |
| 246 remoting.FallbackSignalStrategy.Progress.PRIMARY_FAILED); |
| 247 this.connectSecondary_(); |
| 248 } else { |
| 249 this.onProgressCallback_( |
| 250 remoting.FallbackSignalStrategy.Progress.PRIMARY_FAILED_LATE); |
| 251 } |
| 252 return; // Don't notify the external callback |
| 253 |
| 254 case remoting.SignalStrategy.State.CLOSED: |
| 255 this.state_ = this.State.CLOSED; |
| 256 break; |
| 257 } |
| 258 |
| 259 this.notifyExternalCallback_(state); |
| 260 }; |
| 261 |
| 262 /** |
| 263 * @param {remoting.SignalStrategy.State} state |
| 264 * @private |
| 265 */ |
| 266 remoting.FallbackSignalStrategy.prototype.onSecondaryStateChanged_ = |
| 267 function(state) { |
| 268 switch (state) { |
| 269 case remoting.SignalStrategy.State.CONNECTED: |
| 270 this.onProgressCallback_( |
| 271 remoting.FallbackSignalStrategy.Progress.SECONDARY_SUCCEEDED); |
| 272 this.state_ = this.State.SECONDARY_SUCCEEDED; |
| 273 break; |
| 274 |
| 275 case remoting.SignalStrategy.State.FAILED: |
| 276 this.onProgressCallback_( |
| 277 remoting.FallbackSignalStrategy.Progress.SECONDARY_FAILED); |
| 278 this.state_ = this.State.SECONDARY_FAILED; |
| 279 break; |
| 280 |
| 281 case remoting.SignalStrategy.State.CLOSED: |
| 282 this.state_ = this.State.CLOSED; |
| 283 break; |
| 284 } |
| 285 |
| 286 this.notifyExternalCallback_(state); |
| 287 }; |
| 288 |
| 289 /** |
| 290 * Notify the external callback of a change in state if it's consistent with |
| 291 * the allowed state transitions (ie, if it represents a later stage in the |
| 292 * connection process). Suppress state transitions that would violate this, |
| 293 * for example a CONNECTING -> NOT_CONNECTED transition when we switch from |
| 294 * the primary to the secondary signal strategy. |
| 295 * |
| 296 * @param {remoting.SignalStrategy.State} state |
| 297 * @private |
| 298 */ |
| 299 remoting.FallbackSignalStrategy.prototype.notifyExternalCallback_ = |
| 300 function(state) { |
| 301 if (this.externalState_ === null || state > this.externalState_) { |
| 302 this.externalState_ = state; |
| 303 this.onStateChangedCallback_(state); |
| 304 } |
| 305 }; |
| 306 |
| 307 /** |
| 308 * @private |
| 309 */ |
| 310 remoting.FallbackSignalStrategy.prototype.connectSecondary_ = function() { |
| 311 base.debug.assert(this.state_ == this.State.PRIMARY_PENDING); |
| 312 base.debug.assert(this.server_ != ''); |
| 313 base.debug.assert(this.username_ != ''); |
| 314 base.debug.assert(this.authToken_ != ''); |
| 315 |
| 316 this.state_ = this.State.SECONDARY_PENDING; |
| 317 this.primary_.setIncomingStanzaCallback(null); |
| 318 this.secondary_.setIncomingStanzaCallback(this.onIncomingStanzaCallback_); |
| 319 this.secondary_.connect(this.server_, this.username_, this.authToken_); |
| 320 }; |
| 321 |
| 322 /** |
| 323 * @private |
| 324 */ |
| 325 remoting.FallbackSignalStrategy.prototype.onPrimaryTimeout_ = function() { |
| 326 this.onProgressCallback_( |
| 327 remoting.FallbackSignalStrategy.Progress.PRIMARY_TIMED_OUT); |
| 328 this.connectSecondary_(); |
| 329 }; |
OLD | NEW |