blob: 2f84f7fb2ad92567bb6f7e8516d383a6d835e69a [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/** @suppress {duplicate} */
var remoting = remoting || {};
/**
* A signal strategy encapsulating a primary and a back-up strategy. If the
* primary fails or times out, then the secondary is used. Information about
* which strategy was used, and why, is returned via |onProgressCallback|.
*
* @param {remoting.SignalStrategy} primary
* @param {remoting.SignalStrategy} secondary
*
* @implements {remoting.SignalStrategy}
* @constructor
*/
remoting.FallbackSignalStrategy = function(primary,
secondary) {
/** @private {remoting.SignalStrategy} */
this.primary_ = primary;
this.primary_.setStateChangedCallback(this.onPrimaryStateChanged_.bind(this));
/** @private {remoting.SignalStrategy} */
this.secondary_ = secondary;
this.secondary_.setStateChangedCallback(
this.onSecondaryStateChanged_.bind(this));
/** @private {?function(remoting.SignalStrategy.State)} */
this.onStateChangedCallback_ = null;
/** @private {?function(Element):void} */
this.onIncomingStanzaCallback_ = null;
/**
* @private {number}
* @const
*/
this.PRIMARY_CONNECT_TIMEOUT_MS_ = 25 * 1000;
/**
* @enum {string}
* @private
*/
this.State = {
NOT_CONNECTED: 'not-connected',
PRIMARY_PENDING: 'primary-pending',
PRIMARY_SUCCEEDED: 'primary-succeeded',
SECONDARY_PENDING: 'secondary-pending',
SECONDARY_SUCCEEDED: 'secondary-succeeded',
SECONDARY_FAILED: 'secondary-failed',
CLOSED: 'closed'
};
/** @private {string} */
this.state_ = this.State.NOT_CONNECTED;
/** @private {?remoting.SignalStrategy.State} */
this.externalState_ = null;
/** @private {string} */
this.server_ = '';
/** @private {string} */
this.username_ = '';
/** @private {string} */
this.authToken_ = '';
/** @private {number} */
this.primaryConnectTimerId_ = 0;
/** @private */
this.logger_ = new remoting.SessionLogger(
remoting.ChromotingEvent.Role.CLIENT,
remoting.TelemetryEventWriter.Client.write
);
/**
* @type {Array<{strategyType: remoting.SignalStrategy.Type,
progress: remoting.FallbackSignalStrategy.Progress}>}
*/
this.connectionSetupResults_ = [];
};
/**
* @enum {string}
*/
remoting.FallbackSignalStrategy.Progress = {
SUCCEEDED: 'succeeded',
FAILED: 'failed',
TIMED_OUT: 'timed-out',
SUCCEEDED_LATE: 'succeeded-late',
FAILED_LATE: 'failed-late',
};
remoting.FallbackSignalStrategy.prototype.dispose = function() {
this.primary_.dispose();
this.secondary_.dispose();
};
/**
* @param {function(remoting.SignalStrategy.State):void} onStateChangedCallback
* Callback to call on state change.
*/
remoting.FallbackSignalStrategy.prototype.setStateChangedCallback = function(
onStateChangedCallback) {
this.onStateChangedCallback_ = onStateChangedCallback;
};
/**
* @param {?function(Element):void} onIncomingStanzaCallback Callback to call on
* incoming messages.
*/
remoting.FallbackSignalStrategy.prototype.setIncomingStanzaCallback =
function(onIncomingStanzaCallback) {
this.onIncomingStanzaCallback_ = onIncomingStanzaCallback;
if (this.state_ == this.State.PRIMARY_PENDING ||
this.state_ == this.State.PRIMARY_SUCCEEDED) {
this.primary_.setIncomingStanzaCallback(onIncomingStanzaCallback);
} else if (this.state_ == this.State.SECONDARY_PENDING ||
this.state_ == this.State.SECONDARY_SUCCEEDED) {
this.secondary_.setIncomingStanzaCallback(onIncomingStanzaCallback);
}
};
/**
* @param {string} server
* @param {string} username
* @param {string} authToken
*/
remoting.FallbackSignalStrategy.prototype.connect =
function(server, username, authToken) {
console.assert(this.state_ == this.State.NOT_CONNECTED,
'connect() called in state ' + this.state_ + '.');
console.assert(this.onStateChangedCallback_ != null,
'No state change callback registered.');
this.server_ = server;
this.username_ = username;
this.authToken_ = authToken;
this.state_ = this.State.PRIMARY_PENDING;
this.primary_.setIncomingStanzaCallback(this.onIncomingStanzaCallback_);
this.primary_.connect(server, username, authToken);
this.primaryConnectTimerId_ =
window.setTimeout(this.onPrimaryTimeout_.bind(this),
this.PRIMARY_CONNECT_TIMEOUT_MS_);
};
/**
* Sends a message. Can be called only in CONNECTED state.
* @param {string} message
*/
remoting.FallbackSignalStrategy.prototype.sendMessage = function(message) {
this.getConnectedSignalStrategy_().sendMessage(message);
};
/** @return {remoting.SignalStrategy.State} Current state */
remoting.FallbackSignalStrategy.prototype.getState = function() {
return (this.externalState_ === null)
? remoting.SignalStrategy.State.NOT_CONNECTED
: this.externalState_;
};
/** @return {!remoting.Error} Error when in FAILED state. */
remoting.FallbackSignalStrategy.prototype.getError = function() {
console.assert(this.state_ == this.State.SECONDARY_FAILED,
'getError() called in state ' + this.state_ + '.');
console.assert(
this.secondary_.getState() == remoting.SignalStrategy.State.FAILED,
'getError() called with secondary state ' + this.secondary_.getState() +
'.');
return this.secondary_.getError();
};
/** @return {string} Current JID when in CONNECTED state. */
remoting.FallbackSignalStrategy.prototype.getJid = function() {
return this.getConnectedSignalStrategy_().getJid();
};
/** @return {remoting.SignalStrategy.Type} The signal strategy type. */
remoting.FallbackSignalStrategy.prototype.getType = function() {
return this.getConnectedSignalStrategy_().getType();
};
/**
* @return {remoting.SignalStrategy} The active signal strategy, if the
* connection has succeeded.
* @private
*/
remoting.FallbackSignalStrategy.prototype.getConnectedSignalStrategy_ =
function() {
if (this.state_ == this.State.PRIMARY_SUCCEEDED) {
console.assert(
this.primary_.getState() == remoting.SignalStrategy.State.CONNECTED,
'getConnectedSignalStrategy_() called with primary state ' +
this.primary_.getState() + '.');
return this.primary_;
} else if (this.state_ == this.State.SECONDARY_SUCCEEDED) {
console.assert(
this.secondary_.getState() == remoting.SignalStrategy.State.CONNECTED,
'getConnectedSignalStrategy_() called with secondary state ' +
this.secondary_.getState() + '.');
return this.secondary_;
} else {
console.assert(
false,
'getConnectedSignalStrategy() called in state ' + this.state_ + '.');
return null;
}
};
/**
* @param {remoting.SignalStrategy.State} state
* @private
*/
remoting.FallbackSignalStrategy.prototype.onPrimaryStateChanged_ =
function(state) {
switch (state) {
case remoting.SignalStrategy.State.CONNECTED:
if (this.state_ == this.State.PRIMARY_PENDING) {
window.clearTimeout(this.primaryConnectTimerId_);
this.updateProgress_(
this.primary_,
remoting.FallbackSignalStrategy.Progress.SUCCEEDED);
this.state_ = this.State.PRIMARY_SUCCEEDED;
} else {
this.updateProgress_(
this.primary_,
remoting.FallbackSignalStrategy.Progress.SUCCEEDED_LATE);
return; // Don't notify the external callback
}
break;
case remoting.SignalStrategy.State.FAILED:
if (this.state_ == this.State.PRIMARY_PENDING) {
window.clearTimeout(this.primaryConnectTimerId_);
this.updateProgress_(
this.primary_,
remoting.FallbackSignalStrategy.Progress.FAILED);
this.connectSecondary_();
} else {
this.updateProgress_(
this.primary_,
remoting.FallbackSignalStrategy.Progress.FAILED_LATE);
}
return; // Don't notify the external callback
case remoting.SignalStrategy.State.CLOSED:
this.state_ = this.State.CLOSED;
break;
}
this.notifyExternalCallback_(state);
};
/**
* @param {remoting.SignalStrategy.State} state
* @private
*/
remoting.FallbackSignalStrategy.prototype.onSecondaryStateChanged_ =
function(state) {
switch (state) {
case remoting.SignalStrategy.State.CONNECTED:
this.updateProgress_(
this.secondary_,
remoting.FallbackSignalStrategy.Progress.SUCCEEDED);
this.state_ = this.State.SECONDARY_SUCCEEDED;
break;
case remoting.SignalStrategy.State.FAILED:
this.updateProgress_(
this.secondary_,
remoting.FallbackSignalStrategy.Progress.FAILED);
this.state_ = this.State.SECONDARY_FAILED;
break;
case remoting.SignalStrategy.State.CLOSED:
this.state_ = this.State.CLOSED;
break;
}
this.notifyExternalCallback_(state);
};
/**
* Notify the external callback of a change in state if it's consistent with
* the allowed state transitions (ie, if it represents a later stage in the
* connection process). Suppress state transitions that would violate this,
* for example a CONNECTING -> NOT_CONNECTED transition when we switch from
* the primary to the secondary signal strategy.
*
* @param {remoting.SignalStrategy.State} state
* @private
*/
remoting.FallbackSignalStrategy.prototype.notifyExternalCallback_ =
function(state) {
if (this.externalState_ === null || state > this.externalState_) {
this.externalState_ = state;
this.onStateChangedCallback_(state);
}
};
/**
* @private
*/
remoting.FallbackSignalStrategy.prototype.connectSecondary_ = function() {
console.assert(this.state_ == this.State.PRIMARY_PENDING,
'connectSecondary_() called in state ' + this.state_ + '.');
console.assert(this.server_ != '', 'No server address set.');
console.assert(this.username_ != '', 'No username set.');
console.assert(this.authToken_ != '', 'No auth token set.');
this.state_ = this.State.SECONDARY_PENDING;
this.primary_.setIncomingStanzaCallback(null);
this.secondary_.setIncomingStanzaCallback(this.onIncomingStanzaCallback_);
this.secondary_.connect(this.server_, this.username_, this.authToken_);
};
/**
* @private
*/
remoting.FallbackSignalStrategy.prototype.onPrimaryTimeout_ = function() {
this.updateProgress_(
this.primary_,
remoting.FallbackSignalStrategy.Progress.TIMED_OUT);
this.connectSecondary_();
};
/**
* @param {remoting.SignalStrategy} strategy
* @param {remoting.FallbackSignalStrategy.Progress} progress
* @private
*/
remoting.FallbackSignalStrategy.prototype.updateProgress_ = function(
strategy, progress) {
console.log('FallbackSignalStrategy progress: ' + strategy.getType() + ' ' +
progress);
this.logger_.logSignalStrategyProgress(strategy.getType(), progress);
};