Commit 3b3e16c4 authored by kelvinp's avatar kelvinp Committed by Commit bot

[Webapp Refactor] Implements remoting.Activity.

This CL abstracts Me2Me and It2Me under one common interface
remoting.Activity.

Instead of littering desktop_remoting.js with if statements based on the
mode, all Me2Me specific logic can be moved into
remoting.Me2MeActivity and likewise for It2Me.

BUG=466406
NOTRY=true
TEST=All integration tests passed on chromoting bots.
https://chromium-swarm.appspot.com/user/task/26aed85d041d9d10

Review URL: https://codereview.chromium.org/1054273002

Cr-Commit-Position: refs/heads/master@{#324156}
parent bc374788
......@@ -282,9 +282,10 @@
'webapp/crd/js/crd_event_handlers.js',
'webapp/crd/js/crd_experimental.js',
'webapp/crd/js/crd_main.js',
'webapp/crd/js/activity.js',
'webapp/crd/js/desktop_remoting.js',
'webapp/crd/js/it2me_connect_flow.js',
'webapp/crd/js/me2me_connect_flow.js',
'webapp/crd/js/it2me_activity.js',
'webapp/crd/js/me2me_activity.js',
],
# These template files are used to construct main.html.
......
......@@ -53,6 +53,11 @@ remoting.InputDialog.prototype.show = function() {
return this.deferred_.promise();
};
/** @return {HTMLElement} */
remoting.InputDialog.prototype.inputField = function() {
return this.inputField_;
}
/** @private */
remoting.InputDialog.prototype.onSubmit_ = function() {
this.deferred_.resolve(this.inputField_.value);
......
......@@ -20,11 +20,11 @@ found in the LICENSE file.
class="button-row">
<button id="client-reconnect-button"
type="button"
i18n-content="RETRY"
autofocus="autofocus">
i18n-content="RECONNECT">
</button>
<button id="client-finished-me2me-button"
type="button"
i18n-content="CANCEL">
autofocus="autofocus"
i18n-content="OK">
</button>
</div> <!-- connect-failed.me2me session-finished.me2me -->
// 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.
/** @suppress {duplicate} */
var remoting = remoting || {};
(function() {
'use strict';
/**
* An Activity is a responsible for
* 1. Showing the appropriate UX to establish a connection with the host and
* create a remoting.ClientSession.
* 2. Handling connection failures and retrying if necessary.
* 3. Responding to session state changes and showing UX if necessary.
*
* @interface
* @extends {base.Disposable}
* @extends {remoting.ClientSession.EventHandler}
*/
remoting.Activity = function() {};
/**
* Starts a new connection.
*
* @return {void}
*/
remoting.Activity.prototype.start = function() {};
})();
\ No newline at end of file
......@@ -92,10 +92,48 @@ remoting.ClientSession = function(plugin, host, signalStrategy,
/** @enum {string} */
remoting.ClientSession.Events = {
stateChanged: 'stateChanged',
stateChanged: 'stateChanged', // deprecated.
videoChannelStateChanged: 'videoChannelStateChanged',
};
/**
* @interface
* [START]-------> [onConnected] ------> [onDisconnected]
* | |
* |-----> [OnConnectionFailed] |----> [onError]
*
* TODO(kelvinp): Route session state changes through this interface.
*/
remoting.ClientSession.EventHandler = function() {};
/**
* Called when the connection failed before it is connected.
*
* @param {!remoting.Error} error
*/
remoting.ClientSession.EventHandler.prototype.onConnectionFailed =
function(error) {};
/**
* Called when a new session has been connected. The |connectionInfo| will be
* valid until onDisconnected() or onError() is called.
*
* @param {!remoting.ConnectionInfo} connectionInfo
*/
remoting.ClientSession.EventHandler.prototype.onConnected =
function(connectionInfo) {};
/**
* Called when the current session has been disconnected.
*/
remoting.ClientSession.EventHandler.prototype.onDisconnected = function() {};
/**
* Called when an error needs to be displayed to the user.
* @param {!remoting.Error} error
*/
remoting.ClientSession.EventHandler.prototype.onError = function(error) {};
// Note that the positive values in both of these enums are copied directly
// from connection_to_host.h and must be kept in sync. Code in
// chromoting_instance.cc converts the C++ enums into strings that must match
......@@ -543,19 +581,6 @@ remoting.ClientSession.prototype.logHostOfflineErrors = function(enable) {
this.logHostOfflineErrors_ = enable;
};
/**
* Request pairing with the host for PIN-less authentication.
*
* @param {string} clientName The human-readable name of the client.
* @param {function(string, string):void} onDone Callback to receive the
* client id and shared secret when they are available.
*/
remoting.ClientSession.prototype.requestPairing = function(clientName, onDone) {
if (this.plugin_) {
this.plugin_.requestPairing(clientName, onDone);
}
};
/**
* Sends a clipboard item to the host.
*
......
......@@ -22,16 +22,17 @@ var remoting = remoting || {};
remoting.DesktopRemoting = function(appCapabilities) {
base.inherits(this, remoting.Application, appCapabilities);
/**
* Whether to refresh the JID and retry the connection if the current JID
* is offline.
*
* @private {boolean}
*/
this.refreshHostJidIfOffline_ = true;
/** @private {remoting.DesktopConnectedView} */
this.connectedView_ = null;
/** @private {remoting.Activity} */
this.activity_ = null;
};
/** @private */
remoting.DesktopRemoting.prototype.reset_ = function() {
base.dispose(this.connectedView_);
this.connectedView_ = null;
};
/**
......@@ -152,22 +153,6 @@ remoting.DesktopRemoting.prototype.exitApplication_ = function() {
remoting.DesktopRemoting.prototype.onConnected_ = function(connectionInfo) {
this.initSession_(connectionInfo);
// Set the text on the buttons shown under the error message so that they are
// easy to understand in the case where a successful connection failed, as
// opposed to the case where a connection never succeeded.
// TODO(garykac): Investigate to see if these need to be reverted to their
// original values in the onDisconnected_ method.
var button1 = document.getElementById('client-reconnect-button');
l10n.localizeElementFromTag(button1, /*i18n-content*/'RECONNECT');
button1.removeAttribute('autofocus');
var button2 = document.getElementById('client-finished-me2me-button');
l10n.localizeElementFromTag(button2, /*i18n-content*/'OK');
button2.setAttribute('autofocus', 'autofocus');
// Reset the refresh flag so that the next connection will retry if needed.
this.refreshHostJidIfOffline_ = true;
document.getElementById('access-code-entry').value = '';
remoting.setMode(remoting.AppMode.IN_SESSION);
if (!base.isAppsV2()) {
remoting.toolbar.center();
......@@ -183,14 +168,6 @@ remoting.DesktopRemoting.prototype.onConnected_ = function(connectionInfo) {
connectionInfo.plugin().setRemapKeys('0x0700e4>0x0700e7');
}
if (remoting.app.getConnectionMode() === remoting.Application.Mode.ME2ME) {
if (remoting.app.hasCapability(remoting.ClientSession.Capability.CAST)) {
this.sessionConnector_.registerProtocolExtension(
new remoting.CastExtensionHandler());
}
this.sessionConnector_.registerProtocolExtension(
new remoting.GnubbyAuthHandler());
}
if (connectionInfo.session().hasCapability(
remoting.ClientSession.Capability.VIDEO_RECORDER)) {
var recorder = new remoting.VideoFrameRecorder();
......@@ -198,52 +175,15 @@ remoting.DesktopRemoting.prototype.onConnected_ = function(connectionInfo) {
this.connectedView_.setVideoFrameRecorder(recorder);
}
if (remoting.pairingRequested) {
var that = this;
/**
* @param {string} clientId
* @param {string} sharedSecret
*/
var onPairingComplete = function(clientId, sharedSecret) {
var connector = that.sessionConnector_;
var host = remoting.hostList.getHostForId(connector.getHostId());
host.options.pairingInfo.clientId = clientId;
host.options.pairingInfo.sharedSecret = sharedSecret;
host.options.save();
connector.updatePairingInfo(clientId, sharedSecret);
};
// Use the platform name as a proxy for the local computer name.
// TODO(jamiewalch): Use a descriptive name for the local computer, for
// example, its Chrome Sync name.
var clientName = '';
if (remoting.platformIsMac()) {
clientName = 'Mac';
} else if (remoting.platformIsWindows()) {
clientName = 'Windows';
} else if (remoting.platformIsChromeOS()) {
clientName = 'ChromeOS';
} else if (remoting.platformIsLinux()) {
clientName = 'Linux';
} else {
console.log('Unrecognized client platform. Using navigator.platform.');
clientName = navigator.platform;
}
connectionInfo.session().requestPairing(clientName, onPairingComplete);
}
this.activity_.onConnected(connectionInfo);
};
/**
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.onDisconnected_ = function() {
var mode = this.getConnectionMode();
if (mode === remoting.Application.Mode.IT2ME) {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
} else {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
}
base.dispose(this.connectedView_);
this.connectedView_ = null;
this.activity_.onDisconnected();
this.reset_();
};
/**
......@@ -251,30 +191,7 @@ remoting.DesktopRemoting.prototype.onDisconnected_ = function() {
* @override {remoting.ApplicationInterface}
*/
remoting.DesktopRemoting.prototype.onConnectionFailed_ = function(error) {
var that = this;
var onHostListRefresh = function(/** boolean */ success) {
if (success) {
var connector = that.sessionConnector_;
var host = remoting.hostList.getHostForId(connector.getHostId());
if (host) {
connector.retryConnectMe2Me(host);
return;
}
}
that.onError_(error);
};
var mode = this.getConnectionMode();
if (error.hasTag(remoting.Error.Tag.HOST_IS_OFFLINE) &&
mode === remoting.Application.Mode.ME2ME &&
this.refreshHostJidIfOffline_) {
this.refreshHostJidIfOffline_ = false;
// The plugin will be re-created when the host finished refreshing
remoting.hostList.refresh(onHostListRefresh);
} else {
this.onError_(error);
}
this.activity_.onConnectionFailed(error);
};
/**
......@@ -283,9 +200,6 @@ remoting.DesktopRemoting.prototype.onConnectionFailed_ = function(error) {
*/
remoting.DesktopRemoting.prototype.onError_ = function(error) {
console.error('Connection failed: ' + error.toString());
var mode = this.getConnectionMode();
base.dispose(this.connectedView_);
this.connectedView_ = null;
if (error.hasTag(remoting.Error.Tag.AUTHENTICATION_FAILED)) {
remoting.setMode(remoting.AppMode.HOME);
......@@ -293,17 +207,8 @@ remoting.DesktopRemoting.prototype.onError_ = function(error) {
return;
}
// Reset the refresh flag so that the next connection will retry if needed.
this.refreshHostJidIfOffline_ = true;
var errorDiv = document.getElementById('connect-error-message');
l10n.localizeElementFromTag(errorDiv, error.getTag());
if (mode == remoting.Application.Mode.IT2ME) {
remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_IT2ME);
} else {
remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME);
}
this.activity_.onError(error);
this.reset_();
};
/**
......@@ -367,8 +272,15 @@ remoting.DesktopRemoting.prototype.getConnectedViewForTesting = function() {
*/
remoting.DesktopRemoting.prototype.connectMe2Me_ = function(hostId) {
var host = remoting.hostList.getHostForId(hostId);
var flow = new remoting.Me2MeConnectFlow(this.sessionConnector_, host);
flow.start();
// The Me2MeActivity triggers a reconnect underneath the hood on connection
// failure, but it won't notify the DesktopRemoting upon successful
// re-connection. Therefore, we can't dispose the activity on connection
// failure. Instead, the activity is only disposed when a new one is
// created. This would be fixed once |sessionConnector| is moved out of the
// application.
base.dispose(this.activity_);
this.activity_ = new remoting.Me2MeActivity(this.sessionConnector_, host);
this.activity_.start();
};
/**
......@@ -377,6 +289,7 @@ remoting.DesktopRemoting.prototype.connectMe2Me_ = function(hostId) {
* @private
*/
remoting.DesktopRemoting.prototype.connectIt2Me_ = function() {
var flow = new remoting.It2MeConnectFlow(this.sessionConnector_);
flow.start();
base.dispose(this.activity_);
this.activity_ = new remoting.It2MeActivity(this.sessionConnector_);
this.activity_.start();
};
......@@ -17,8 +17,9 @@ var ACCESS_CODE_LENGTH = SUPPORT_ID_LENGTH + HOST_SECRET_LENGTH;
/**
* @param {remoting.SessionConnector} sessionConnector
* @constructor
* @implements {remoting.Activity}
*/
remoting.It2MeConnectFlow = function(sessionConnector) {
remoting.It2MeActivity = function(sessionConnector) {
/** @private */
this.sessionConnector_ = sessionConnector;
/** @private */
......@@ -35,8 +36,9 @@ remoting.It2MeConnectFlow = function(sessionConnector) {
form.querySelector('#cancel-access-code-button'));
};
remoting.It2MeActivity.prototype.dispose = function() {};
remoting.It2MeConnectFlow.prototype.start = function() {
remoting.It2MeActivity.prototype.start = function() {
var that = this;
this.accessCodeDialog_.show().then(function(/** string */ accessCode) {
......@@ -64,12 +66,40 @@ remoting.It2MeConnectFlow.prototype.start = function() {
});
};
/**
* @param {!remoting.Error} error
*/
remoting.It2MeActivity.prototype.onConnectionFailed = function(error) {
this.onError(error);
};
/**
* @param {!remoting.ConnectionInfo} connectionInfo
*/
remoting.It2MeActivity.prototype.onConnected = function(connectionInfo) {
this.accessCodeDialog_.inputField().value = '';
};
remoting.It2MeActivity.prototype.onDisconnected = function() {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
};
/**
* @param {!remoting.Error} error
*/
remoting.It2MeActivity.prototype.onError = function(error) {
var errorDiv = document.getElementById('connect-error-message');
l10n.localizeElementFromTag(errorDiv, error.getTag());
remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_IT2ME);
};
/**
* @param {string} accessCode
* @return {Promise} Promise that resolves if the access code is valid.
* @private
*/
remoting.It2MeConnectFlow.prototype.verifyAccessCode_ = function(accessCode) {
remoting.It2MeActivity.prototype.verifyAccessCode_ = function(accessCode) {
var normalizedAccessCode = accessCode.replace(/\s/g, '');
if (normalizedAccessCode.length !== ACCESS_CODE_LENGTH) {
return Promise.reject(new remoting.Error(
......@@ -89,7 +119,7 @@ remoting.It2MeConnectFlow.prototype.verifyAccessCode_ = function(accessCode) {
* @return {Promise<!remoting.Xhr.Response>}
* @private
*/
remoting.It2MeConnectFlow.prototype.getHostInfo_ = function(token) {
remoting.It2MeActivity.prototype.getHostInfo_ = function(token) {
var that = this;
return new remoting.Xhr({
method: 'GET',
......@@ -107,7 +137,7 @@ remoting.It2MeConnectFlow.prototype.getHostInfo_ = function(token) {
* @return {!Promise<!remoting.Host>} Rejects on error.
* @private
*/
remoting.It2MeConnectFlow.prototype.onHostInfo_ = function(xhrResponse) {
remoting.It2MeActivity.prototype.onHostInfo_ = function(xhrResponse) {
if (xhrResponse.status == 200) {
var response = /** @type {{data: {jabberId: string, publicKey: string}}} */
(base.jsonParseSafe(xhrResponse.getText()));
......
......@@ -14,21 +14,30 @@ var remoting = remoting || {};
* @param {remoting.Host} host
*
* @constructor
* @implements {remoting.Activity}
*/
remoting.Me2MeConnectFlow = function(sessionConnector, host) {
remoting.Me2MeActivity = function(sessionConnector, host) {
/** @private */
this.host_ = host;
/** @private */
this.connector_ = sessionConnector;
/** @private */
this.pinDialog_ =
new remoting.PinDialog(document.getElementById('pin-dialog'), host);
/** @private */
this.hostUpdateDialog_ = new remoting.HostNeedsUpdateDialog(
document.getElementById('host-needs-update-dialog'), this.host_);
/** @private */
this.retryOnHostOffline_ = true;
};
remoting.Me2MeConnectFlow.prototype.start = function() {
remoting.Me2MeActivity.prototype.dispose = function() {};
remoting.Me2MeActivity.prototype.start = function() {
var webappVersion = chrome.runtime.getManifest().version;
var needsUpdateDialog = new remoting.HostNeedsUpdateDialog(
document.getElementById('host-needs-update-dialog'), this.host_);
var that = this;
needsUpdateDialog.showIfNecessary(webappVersion).then(function() {
this.hostUpdateDialog_.showIfNecessary(webappVersion).then(function() {
return that.host_.options.load();
}).then(function() {
that.connect_();
......@@ -40,7 +49,7 @@ remoting.Me2MeConnectFlow.prototype.start = function() {
};
/** @private */
remoting.Me2MeConnectFlow.prototype.connect_ = function() {
remoting.Me2MeActivity.prototype.connect_ = function() {
remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
var host = this.host_;
......@@ -58,18 +67,15 @@ remoting.Me2MeConnectFlow.prototype.connect_ = function() {
thirdPartyTokenFetcher.fetchToken();
};
var that = this;
/**
* @param {boolean} supportsPairing
* @param {function(string):void} onPinFetched
*/
var requestPin = function(supportsPairing, onPinFetched) {
var pinDialog =
new remoting.PinDialog(document.getElementById('pin-dialog'), host);
pinDialog.show(supportsPairing).then(function(/** string */ pin) {
that.pinDialog_.show(supportsPairing).then(function(/** string */ pin) {
remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
onPinFetched(pin);
/** @type {boolean} */
remoting.pairingRequested = pinDialog.pairingRequested();
}).catch(function(/** remoting.Error */ error) {
base.debug.assert(error.hasTag(remoting.Error.Tag.CANCELLED));
remoting.setMode(remoting.AppMode.HOME);
......@@ -81,6 +87,61 @@ remoting.Me2MeConnectFlow.prototype.connect_ = function() {
pairingInfo.clientId, pairingInfo.sharedSecret);
};
/**
* @param {!remoting.Error} error
*/
remoting.Me2MeActivity.prototype.onConnectionFailed = function(error) {
var that = this;
var onHostListRefresh = function(/** boolean */ success) {
if (success) {
// Get the host from the hostList for the refreshed JID.
var host = remoting.hostList.getHostForId(that.host_.hostId);
that.connector_.retryConnectMe2Me(host);
return;
}
that.onError(error);
};
if (error.hasTag(remoting.Error.Tag.HOST_IS_OFFLINE) &&
this.retryOnHostOffline_) {
this.retryOnHostOffline_ = false;
// The plugin will be re-created when the host finished refreshing
remoting.hostList.refresh(onHostListRefresh);
} else {
this.onError(error);
}
};
/**
* @param {!remoting.ConnectionInfo} connectionInfo
*/
remoting.Me2MeActivity.prototype.onConnected = function(connectionInfo) {
// Reset the refresh flag so that the next connection will retry if needed.
this.retryOnHostOffline_ = true;
if (remoting.app.hasCapability(remoting.ClientSession.Capability.CAST)) {
this.connector_.registerProtocolExtension(
new remoting.CastExtensionHandler());
}
this.connector_.registerProtocolExtension(new remoting.GnubbyAuthHandler());
this.pinDialog_.requestPairingIfNecessary(connectionInfo.plugin(),
this.connector_);
};
remoting.Me2MeActivity.prototype.onDisconnected = function() {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
};
/**
* @param {!remoting.Error} error
*/
remoting.Me2MeActivity.prototype.onError = function(error) {
this.retryOnHostOffline_ = true;
var errorDiv = document.getElementById('connect-error-message');
l10n.localizeElementFromTag(errorDiv, error.getTag());
remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME);
};
/**
* @param {HTMLElement} rootElement
......@@ -189,9 +250,43 @@ remoting.PinDialog.prototype.show = function(supportsPairing) {
return this.dialog_.show();
};
/** @return {boolean} */
remoting.PinDialog.prototype.pairingRequested = function() {
return this.pairingCheckbox_.checked;
/**
* @param {remoting.ClientPlugin} plugin
* @param {remoting.SessionConnector} connector
*/
remoting.PinDialog.prototype.requestPairingIfNecessary =
function(plugin, connector) {
if (this.pairingCheckbox_.checked) {
var that = this;
/**
* @param {string} clientId
* @param {string} sharedSecret
*/
var onPairingComplete = function(clientId, sharedSecret) {
that.host_.options.pairingInfo.clientId = clientId;
that.host_.options.pairingInfo.sharedSecret = sharedSecret;
that.host_.options.save();
connector.updatePairingInfo(clientId, sharedSecret);
};
// Use the platform name as a proxy for the local computer name.
// TODO(jamiewalch): Use a descriptive name for the local computer, for
// example, its Chrome Sync name.
var clientName = '';
if (remoting.platformIsMac()) {
clientName = 'Mac';
} else if (remoting.platformIsWindows()) {
clientName = 'Windows';
} else if (remoting.platformIsChromeOS()) {
clientName = 'ChromeOS';
} else if (remoting.platformIsLinux()) {
clientName = 'Linux';
} else {
console.log('Unrecognized client platform. Using navigator.platform.');
clientName = navigator.platform;
}
plugin.requestPairing(clientName, onPairingComplete);
}
};
})();
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment