Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / webapp / base / js / fallback_signal_strategy.js
blob0596640ac7c60613d0bdca1cdd70c96e862e94bc
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.
5 'use strict';
7 /** @suppress {duplicate} */
8 var remoting = remoting || {};
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 {remoting.SignalStrategy} primary
16  * @param {remoting.SignalStrategy} secondary
17  *
18  * @implements {remoting.SignalStrategy}
19  * @constructor
20  */
21 remoting.FallbackSignalStrategy = function(primary,
22                                            secondary) {
23   /** @private {remoting.SignalStrategy} */
24   this.primary_ = primary;
25   this.primary_.setStateChangedCallback(this.onPrimaryStateChanged_.bind(this));
27   /** @private {remoting.SignalStrategy} */
28   this.secondary_ = secondary;
29   this.secondary_.setStateChangedCallback(
30       this.onSecondaryStateChanged_.bind(this));
32   /** @private {?function(remoting.SignalStrategy.State)} */
33   this.onStateChangedCallback_ = null;
35   /** @private {?function(Element):void} */
36   this.onIncomingStanzaCallback_ = null;
38   /**
39    * @private {number}
40    * @const
41    */
42   this.PRIMARY_CONNECT_TIMEOUT_MS_ = 10 * 1000;
44   /**
45    * @enum {string}
46    * @private
47    */
48   this.State = {
49     NOT_CONNECTED: 'not-connected',
50     PRIMARY_PENDING: 'primary-pending',
51     PRIMARY_SUCCEEDED: 'primary-succeeded',
52     SECONDARY_PENDING: 'secondary-pending',
53     SECONDARY_SUCCEEDED: 'secondary-succeeded',
54     SECONDARY_FAILED: 'secondary-failed',
55     CLOSED: 'closed'
56   };
58   /** @private {string} */
59   this.state_ = this.State.NOT_CONNECTED;
61   /** @private {?remoting.SignalStrategy.State} */
62   this.externalState_ = null;
64   /** @private {string} */
65   this.server_ = '';
67   /** @private {string} */
68   this.username_ = '';
70   /** @private {string} */
71   this.authToken_ = '';
73   /** @private {number} */
74   this.primaryConnectTimerId_ = 0;
76   /** @private {remoting.Logger} */
77   this.logger_ = new remoting.SessionLogger(
78       remoting.ChromotingEvent.Role.CLIENT,
79       remoting.TelemetryEventWriter.Client.write
80   );
82   /**
83    * @type {Array<{strategyType: remoting.SignalStrategy.Type,
84                     progress: remoting.FallbackSignalStrategy.Progress}>}
85    */
86   this.connectionSetupResults_ = [];
90 /**
91  * @enum {string}
92  */
93 remoting.FallbackSignalStrategy.Progress = {
94   SUCCEEDED: 'succeeded',
95   FAILED: 'failed',
96   TIMED_OUT: 'timed-out',
97   SUCCEEDED_LATE: 'succeeded-late',
98   FAILED_LATE: 'failed-late',
101 remoting.FallbackSignalStrategy.prototype.dispose = function() {
102   this.primary_.dispose();
103   this.secondary_.dispose();
107  * @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback
108  *   Callback to call on state change.
109  */
110 remoting.FallbackSignalStrategy.prototype.setStateChangedCallback = function(
111     onStateChangedCallback) {
112   this.onStateChangedCallback_ = onStateChangedCallback;
116  * @param {?function(Element):void} onIncomingStanzaCallback Callback to call on
117  *     incoming messages.
118  */
119 remoting.FallbackSignalStrategy.prototype.setIncomingStanzaCallback =
120     function(onIncomingStanzaCallback) {
121   this.onIncomingStanzaCallback_ = onIncomingStanzaCallback;
122   if (this.state_ == this.State.PRIMARY_PENDING ||
123       this.state_ == this.State.PRIMARY_SUCCEEDED) {
124     this.primary_.setIncomingStanzaCallback(onIncomingStanzaCallback);
125   } else if (this.state_ == this.State.SECONDARY_PENDING ||
126              this.state_ == this.State.SECONDARY_SUCCEEDED) {
127     this.secondary_.setIncomingStanzaCallback(onIncomingStanzaCallback);
128   }
132  * @param {string} server
133  * @param {string} username
134  * @param {string} authToken
135  */
136 remoting.FallbackSignalStrategy.prototype.connect =
137     function(server, username, authToken) {
138   console.assert(this.state_ == this.State.NOT_CONNECTED,
139                 'connect() called in state ' + this.state_ + '.');
140   console.assert(this.onStateChangedCallback_ != null,
141                  'No state change callback registered.');
142   this.server_ = server;
143   this.username_ = username;
144   this.authToken_ = authToken;
145   this.state_ = this.State.PRIMARY_PENDING;
146   this.primary_.setIncomingStanzaCallback(this.onIncomingStanzaCallback_);
147   this.primary_.connect(server, username, authToken);
148   this.primaryConnectTimerId_ =
149       window.setTimeout(this.onPrimaryTimeout_.bind(this),
150                         this.PRIMARY_CONNECT_TIMEOUT_MS_);
154  * Sends a message. Can be called only in CONNECTED state.
155  * @param {string} message
156  */
157 remoting.FallbackSignalStrategy.prototype.sendMessage = function(message) {
158   this.getConnectedSignalStrategy_().sendMessage(message);
161 /** @return {remoting.SignalStrategy.State} Current state */
162 remoting.FallbackSignalStrategy.prototype.getState = function() {
163   return (this.externalState_ === null)
164       ? remoting.SignalStrategy.State.NOT_CONNECTED
165       : this.externalState_;
168 /** @return {!remoting.Error} Error when in FAILED state. */
169 remoting.FallbackSignalStrategy.prototype.getError = function() {
170   console.assert(this.state_ == this.State.SECONDARY_FAILED,
171                 'getError() called in state ' + this.state_ + '.');
172   console.assert(
173       this.secondary_.getState() == remoting.SignalStrategy.State.FAILED,
174       'getError() called with secondary state ' + this.secondary_.getState() +
175       '.');
176   return this.secondary_.getError();
179 /** @return {string} Current JID when in CONNECTED state. */
180 remoting.FallbackSignalStrategy.prototype.getJid = function() {
181   return this.getConnectedSignalStrategy_().getJid();
184 /** @return {remoting.SignalStrategy.Type} The signal strategy type. */
185 remoting.FallbackSignalStrategy.prototype.getType = function() {
186   return this.getConnectedSignalStrategy_().getType();
190  * @return {remoting.SignalStrategy} The active signal strategy, if the
191  *     connection has succeeded.
192  * @private
193  */
194 remoting.FallbackSignalStrategy.prototype.getConnectedSignalStrategy_ =
195     function() {
196   if (this.state_ == this.State.PRIMARY_SUCCEEDED) {
197     console.assert(
198         this.primary_.getState() == remoting.SignalStrategy.State.CONNECTED,
199         'getConnectedSignalStrategy_() called with primary state ' +
200         this.primary_.getState() + '.');
201     return this.primary_;
202   } else if (this.state_ == this.State.SECONDARY_SUCCEEDED) {
203     console.assert(
204         this.secondary_.getState() == remoting.SignalStrategy.State.CONNECTED,
205         'getConnectedSignalStrategy_() called with secondary state ' +
206         this.secondary_.getState() + '.');
207     return this.secondary_;
208   } else {
209     console.assert(
210         false,
211         'getConnectedSignalStrategy() called in state ' + this.state_ + '.');
212     return null;
213   }
217  * @param {remoting.SignalStrategy.State} state
218  * @private
219  */
220 remoting.FallbackSignalStrategy.prototype.onPrimaryStateChanged_ =
221     function(state) {
222   switch (state) {
223     case remoting.SignalStrategy.State.CONNECTED:
224       if (this.state_ == this.State.PRIMARY_PENDING) {
225         window.clearTimeout(this.primaryConnectTimerId_);
226         this.updateProgress_(
227             this.primary_,
228             remoting.FallbackSignalStrategy.Progress.SUCCEEDED);
229         this.state_ = this.State.PRIMARY_SUCCEEDED;
230       } else {
231         this.updateProgress_(
232             this.primary_,
233             remoting.FallbackSignalStrategy.Progress.SUCCEEDED_LATE);
234       }
235       break;
237     case remoting.SignalStrategy.State.FAILED:
238       if (this.state_ == this.State.PRIMARY_PENDING) {
239         window.clearTimeout(this.primaryConnectTimerId_);
240         this.updateProgress_(
241             this.primary_,
242             remoting.FallbackSignalStrategy.Progress.FAILED);
243         this.connectSecondary_();
244       } else {
245         this.updateProgress_(
246             this.primary_,
247             remoting.FallbackSignalStrategy.Progress.FAILED_LATE);
248       }
249       return;  // Don't notify the external callback
251     case remoting.SignalStrategy.State.CLOSED:
252       this.state_ = this.State.CLOSED;
253       break;
254   }
256   this.notifyExternalCallback_(state);
260  * @param {remoting.SignalStrategy.State} state
261  * @private
262  */
263 remoting.FallbackSignalStrategy.prototype.onSecondaryStateChanged_ =
264     function(state) {
265   switch (state) {
266     case remoting.SignalStrategy.State.CONNECTED:
267       this.updateProgress_(
268           this.secondary_,
269           remoting.FallbackSignalStrategy.Progress.SUCCEEDED);
270       this.state_ = this.State.SECONDARY_SUCCEEDED;
271       break;
273     case remoting.SignalStrategy.State.FAILED:
274       this.updateProgress_(
275           this.secondary_,
276           remoting.FallbackSignalStrategy.Progress.FAILED);
277       this.state_ = this.State.SECONDARY_FAILED;
278       break;
280     case remoting.SignalStrategy.State.CLOSED:
281       this.state_ = this.State.CLOSED;
282       break;
283   }
285   this.notifyExternalCallback_(state);
289  * Notify the external callback of a change in state if it's consistent with
290  * the allowed state transitions (ie, if it represents a later stage in the
291  * connection process). Suppress state transitions that would violate this,
292  * for example a CONNECTING -> NOT_CONNECTED transition when we switch from
293  * the primary to the secondary signal strategy.
295  * @param {remoting.SignalStrategy.State} state
296  * @private
297  */
298 remoting.FallbackSignalStrategy.prototype.notifyExternalCallback_ =
299     function(state) {
300   if (this.externalState_ === null || state > this.externalState_) {
301     this.externalState_ = state;
302     this.onStateChangedCallback_(state);
303   }
307  * @private
308  */
309 remoting.FallbackSignalStrategy.prototype.connectSecondary_ = function() {
310   console.assert(this.state_ == this.State.PRIMARY_PENDING,
311                 'connectSecondary_() called in state ' + this.state_ + '.');
312   console.assert(this.server_ != '', 'No server address set.');
313   console.assert(this.username_ != '', 'No username set.');
314   console.assert(this.authToken_ != '', 'No auth token set.');
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_);
323  * @private
324  */
325 remoting.FallbackSignalStrategy.prototype.onPrimaryTimeout_ = function() {
326   this.updateProgress_(
327       this.primary_,
328       remoting.FallbackSignalStrategy.Progress.TIMED_OUT);
329   this.connectSecondary_();
333  * @param {remoting.SignalStrategy} strategy
334  * @param {remoting.FallbackSignalStrategy.Progress} progress
335  * @private
336  */
337 remoting.FallbackSignalStrategy.prototype.updateProgress_ = function(
338     strategy, progress) {
339   console.log('FallbackSignalStrategy progress: ' + strategy.getType() + ' ' +
340       progress);
341   this.logger_.logSignalStrategyProgress(strategy.getType(), progress);