summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-28 01:29:01 +0000
committerjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-28 01:29:01 +0000
commit2fa3126208495ca01c7f9f1ed353d8b45a775087 (patch)
tree235a1c9a281ad8d988a656b37d600c8d7297f2f6 /remoting
parentc9f0a0e41e82a5dbd60213d6a894f47c757c5bef (diff)
downloadchromium_src-2fa3126208495ca01c7f9f1ed353d8b45a775087.zip
chromium_src-2fa3126208495ca01c7f9f1ed353d8b45a775087.tar.gz
chromium_src-2fa3126208495ca01c7f9f1ed353d8b45a775087.tar.bz2
Refactored web-app
BUG=None TEST=Everything still works! Review URL: http://codereview.chromium.org/8416007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@107673 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r--remoting/host/plugin/host_script_object.h2
-rw-r--r--remoting/remoting.gyp9
-rw-r--r--remoting/webapp/me2mom/choice.html5
-rw-r--r--remoting/webapp/me2mom/client_screen.js401
-rw-r--r--remoting/webapp/me2mom/client_session.js2
-rw-r--r--remoting/webapp/me2mom/debug_log.js77
-rw-r--r--remoting/webapp/me2mom/host_screen.js269
-rw-r--r--remoting/webapp/me2mom/host_session.js116
-rw-r--r--remoting/webapp/me2mom/oauth2.js61
-rw-r--r--remoting/webapp/me2mom/remoting.js822
-rw-r--r--remoting/webapp/me2mom/ui_mode.js78
-rw-r--r--remoting/webapp/me2mom/util.js40
-rw-r--r--remoting/webapp/me2mom/wcs_loader.js2
13 files changed, 1111 insertions, 773 deletions
diff --git a/remoting/host/plugin/host_script_object.h b/remoting/host/plugin/host_script_object.h
index bfcdf31..da387ce 100644
--- a/remoting/host/plugin/host_script_object.h
+++ b/remoting/host/plugin/host_script_object.h
@@ -84,6 +84,8 @@ class HostNPScriptObject : public HostStatusObserver {
void PostLogDebugInfo(const std::string& message);
private:
+ // These state values are duplicated in the JS code. Remember to update both
+ // copies when making changes.
enum State {
kDisconnected,
kStarting,
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index f6ad2ad..a454229 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -97,12 +97,15 @@
'resources/icon_warning.png',
'webapp/me2mom/choice.css',
'webapp/me2mom/choice.html',
+ 'webapp/me2mom/client_screen.js',
'webapp/me2mom/client_session.js',
'webapp/me2mom/cs_oauth2_trampoline.js',
'webapp/me2mom/debug_log.css',
'webapp/me2mom/debug_log.js',
'webapp/me2mom/dividerbottom.png',
'webapp/me2mom/dividertop.png',
+ 'webapp/me2mom/host_screen.js',
+ 'webapp/me2mom/host_session.js',
'webapp/me2mom/l10n.js',
'webapp/me2mom/main.css',
'webapp/me2mom/manifest.json',
@@ -113,6 +116,8 @@
'webapp/me2mom/scale-to-fit.png',
'webapp/me2mom/spinner.gif',
'webapp/me2mom/toolbar.css',
+ 'webapp/me2mom/ui_mode.js',
+ 'webapp/me2mom/util.js',
'webapp/me2mom/wcs.js',
'webapp/me2mom/wcs_loader.js',
'webapp/me2mom/xhr.js',
@@ -324,6 +329,8 @@
'webapp/me2mom/choice.html',
'webapp/me2mom/manifest.json',
'webapp/me2mom/remoting.js',
+ 'webapp/me2mom/client_screen.js',
+ 'webapp/me2mom/host_screen.js',
'host/plugin/host_script_object.cc',
],
'outputs': [
@@ -337,6 +344,8 @@
'webapp/me2mom/choice.html',
'webapp/me2mom/manifest.json',
'webapp/me2mom/remoting.js',
+ 'webapp/me2mom/client_screen.js',
+ 'webapp/me2mom/host_screen.js',
'host/plugin/host_script_object.cc',
],
},
diff --git a/remoting/webapp/me2mom/choice.html b/remoting/webapp/me2mom/choice.html
index c08b375..9f53088 100644
--- a/remoting/webapp/me2mom/choice.html
+++ b/remoting/webapp/me2mom/choice.html
@@ -15,12 +15,17 @@ found in the LICENSE file.
<link rel="stylesheet" href="main.css" />
<link rel="stylesheet" href="choice.css" />
<link rel="stylesheet" href="toolbar.css" />
+ <script src="client_screen.js"></script>
<script src="client_session.js"></script>
<script src="debug_log.js"></script>
+ <script src="host_screen.js"></script>
+ <script src="host_session.js"></script>
<script src="l10n.js"></script>
<script src="oauth2.js"></script>
<script src="plugin_settings.js"></script>
<script src="remoting.js"></script>
+ <script src="ui_mode.js"></script>
+ <script src="util.js"></script>
<script src="xhr.js"></script>
<script src="wcs.js"></script>
<script src="wcs_loader.js"></script>
diff --git a/remoting/webapp/me2mom/client_screen.js b/remoting/webapp/me2mom/client_screen.js
new file mode 100644
index 0000000..6065672
--- /dev/null
+++ b/remoting/webapp/me2mom/client_screen.js
@@ -0,0 +1,401 @@
+// Copyright (c) 2011 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.
+
+/**
+ * @fileoverview
+ * Functions related to the 'client screen' for Chromoting.
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/** @enum {string} */
+remoting.ClientError = {
+ NO_RESPONSE: /*i18n-content*/'ERROR_NO_RESPONSE',
+ INVALID_ACCESS_CODE: /*i18n-content*/'ERROR_INVALID_ACCESS_CODE',
+ MISSING_PLUGIN: /*i18n-content*/'ERROR_MISSING_PLUGIN',
+ OAUTH_FETCH_FAILED: /*i18n-content*/'ERROR_AUTHENTICATION_FAILED',
+ HOST_IS_OFFLINE: /*i18n-content*/'ERROR_HOST_IS_OFFLINE',
+ INCOMPATIBLE_PROTOCOL: /*i18n-content*/'ERROR_INCOMPATIBLE_PROTOCOL',
+ BAD_PLUGIN_VERSION: /*i18n-content*/'ERROR_BAD_PLUGIN_VERSION',
+ OTHER_ERROR: /*i18n-content*/'ERROR_GENERIC'
+};
+
+(function() {
+
+/**
+ * @type {boolean} Whether or not the plugin should scale itself.
+ */
+remoting.scaleToFit = false;
+
+/**
+ * @type {remoting.ClientSession} The client session object, set once the
+ * access code has been successfully verified.
+ */
+remoting.clientSession = null;
+
+/**
+ * @type {string} The normalized access code.
+ */
+remoting.accessCode = '';
+
+/**
+ * @type {string} The host's JID, returned by the server.
+ */
+remoting.hostJid = '';
+
+/**
+ * @type {string} The host's public key, returned by the server.
+ */
+remoting.hostPublicKey = '';
+
+/**
+ * @type {XMLHttpRequest} The XHR object corresponding to the current
+ * support-hosts request, if there is one outstanding.
+ */
+remoting.supportHostsXhr_ = null;
+
+/**
+ * Entry point for the 'connect' functionality. This function checks for the
+ * existence of an OAuth2 token, and either requests one asynchronously, or
+ * calls through directly to tryConnectWithAccessToken_.
+ */
+remoting.tryConnect = function() {
+ document.getElementById('cancel-button').disabled = false;
+ if (remoting.oauth2.needsNewAccessToken()) {
+ remoting.oauth2.refreshAccessToken(function(xhr) {
+ if (remoting.oauth2.needsNewAccessToken()) {
+ // Failed to get access token
+ remoting.debug.log('tryConnect: OAuth2 token fetch failed');
+ showConnectError_(remoting.ClientError.OAUTH_FETCH_FAILED);
+ return;
+ }
+ tryConnectWithAccessToken_();
+ });
+ } else {
+ tryConnectWithAccessToken_();
+ }
+}
+
+/**
+ * Cancel an incomplete connect operation.
+ *
+ * @return {void} Nothing.
+ */
+remoting.cancelConnect = function() {
+ if (remoting.supportHostsXhr_) {
+ remoting.supportHostsXhr_.abort();
+ remoting.supportHostsXhr_ = null;
+ }
+ if (remoting.clientSession) {
+ remoting.clientSession.removePlugin();
+ remoting.clientSession = null;
+ }
+ remoting.setMode(remoting.AppMode.HOME);
+}
+
+/**
+ * Enable or disable scale-to-fit.
+ *
+ * @param {Element} button The scale-to-fit button. The style of this button is
+ * updated to reflect the new scaling state.
+ * @return {void} Nothing.
+ */
+remoting.toggleScaleToFit = function(button) {
+ remoting.scaleToFit = !remoting.scaleToFit;
+ if (remoting.scaleToFit) {
+ addClass(button, 'toggle-button-active');
+ } else {
+ removeClass(button, 'toggle-button-active');
+ }
+ remoting.clientSession.updateDimensions();
+}
+
+/**
+ * Update the remoting client layout in response to a resize event.
+ *
+ * @return {void} Nothing.
+ */
+remoting.onResize = function() {
+ if (remoting.clientSession)
+ remoting.clientSession.onWindowSizeChanged();
+ recenterToolbar_();
+}
+
+/**
+ * Disconnect the remoting client.
+ *
+ * @return {void} Nothing.
+ */
+remoting.disconnect = function() {
+ if (remoting.clientSession) {
+ remoting.clientSession.disconnect();
+ remoting.clientSession = null;
+ remoting.debug.log('Disconnected.');
+ remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED);
+ }
+}
+
+/**
+ * Second stage of the 'connect' functionality. Once an access token is
+ * available, load the WCS widget asynchronously and call through to
+ * tryConnectWithWcs_ when ready.
+ */
+function tryConnectWithAccessToken_() {
+ if (!remoting.wcsLoader) {
+ remoting.wcsLoader = new remoting.WcsLoader();
+ }
+ /** @param {function(string):void} setToken The callback function. */
+ var callWithToken = function(setToken) {
+ remoting.oauth2.callWithToken(setToken);
+ };
+ remoting.wcsLoader.start(
+ remoting.oauth2.getAccessToken(),
+ callWithToken,
+ tryConnectWithWcs_);
+}
+
+/**
+ * Final stage of the 'connect' functionality, called when the wcs widget has
+ * been loaded, or on error.
+ *
+ * @param {boolean} success True if the script was loaded successfully.
+ */
+function tryConnectWithWcs_(success) {
+ if (success) {
+ var accessCode = document.getElementById('access-code-entry').value;
+ remoting.accessCode = normalizeAccessCode_(accessCode);
+ // At present, only 12-digit access codes are supported, of which the first
+ // 7 characters are the supportId.
+ var kSupportIdLen = 7;
+ var kHostSecretLen = 5;
+ var kAccessCodeLen = kSupportIdLen + kHostSecretLen;
+ if (remoting.accessCode.length != kAccessCodeLen) {
+ remoting.debug.log('Bad access code length');
+ showConnectError_(remoting.ClientError.INVALID_ACCESS_CODE);
+ } else {
+ var supportId = remoting.accessCode.substring(0, kSupportIdLen);
+ remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
+ resolveSupportId(supportId);
+ }
+ } else {
+ showConnectError_(remoting.ClientError.OAUTH_FETCH_FAILED);
+ }
+}
+
+/**
+ * Callback function called when the state of the client plugin changes. The
+ * current state is available via the |state| member variable.
+ *
+ * @param {number} oldState The previous state of the plugin.
+ */
+// TODO(jamiewalch): Make this pass both the current and old states to avoid
+// race conditions.
+function onClientStateChange_(oldState) {
+ if (!remoting.clientSession) {
+ // If the connection has been cancelled, then we no longer have a reference
+ // to the session object and should ignore any state changes.
+ return;
+ }
+ var state = remoting.clientSession.state;
+ if (state == remoting.ClientSession.State.CREATED) {
+ remoting.debug.log('Created plugin');
+
+ } else if (state == remoting.ClientSession.State.BAD_PLUGIN_VERSION) {
+ showConnectError_(remoting.ClientError.BAD_PLUGIN_VERSION);
+
+ } else if (state == remoting.ClientSession.State.CONNECTING) {
+ remoting.debug.log('Connecting as ' + remoting.oauth2.getCachedEmail());
+
+ } else if (state == remoting.ClientSession.State.INITIALIZING) {
+ remoting.debug.log('Initializing connection');
+
+ } else if (state == remoting.ClientSession.State.CONNECTED) {
+ if (remoting.clientSession) {
+ remoting.setMode(remoting.AppMode.IN_SESSION);
+ recenterToolbar_();
+ showToolbarPreview_();
+ updateStatistics_();
+ }
+
+ } else if (state == remoting.ClientSession.State.CLOSED) {
+ if (oldState == remoting.ClientSession.State.CONNECTED) {
+ remoting.clientSession.removePlugin();
+ remoting.clientSession = null;
+ remoting.debug.log('Connection closed by host');
+ remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED);
+ } else {
+ // The transition from CONNECTING to CLOSED state may happen
+ // only with older client plugins. Current version should go the
+ // FAILED state when connection fails.
+ showConnectError_(remoting.ClientError.INVALID_ACCESS_CODE);
+ }
+
+ } else if (state == remoting.ClientSession.State.CONNECTION_FAILED) {
+ remoting.debug.log('Client plugin reported connection failed: ' +
+ remoting.clientSession.error);
+ if (remoting.clientSession.error ==
+ remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE) {
+ showConnectError_(remoting.ClientError.HOST_IS_OFFLINE);
+ } else if (remoting.clientSession.error ==
+ remoting.ClientSession.ConnectionError.SESSION_REJECTED) {
+ showConnectError_(remoting.ClientError.INVALID_ACCESS_CODE);
+ } else if (remoting.clientSession.error ==
+ remoting.ClientSession.ConnectionError.INCOMPATIBLE_PROTOCOL) {
+ showConnectError_(remoting.ClientError.INCOMPATIBLE_PROTOCOL);
+ } else if (remoting.clientSession.error ==
+ remoting.ClientSession.ConnectionError.NETWORK_FAILURE) {
+ showConnectError_(remoting.ClientError.OTHER_ERROR);
+ } else {
+ showConnectError_(remoting.ClientError.OTHER_ERROR);
+ }
+
+ } else {
+ remoting.debug.log('Unexpected client plugin state: ' + state);
+ // This should only happen if the web-app and client plugin get out of
+ // sync, and even then the version check should allow compatibility.
+ showConnectError_(remoting.ClientError.MISSING_PLUGIN);
+ }
+}
+
+/**
+ * Create the client session object and initiate the connection.
+ *
+ * @return {void} Nothing.
+ */
+function startSession_() {
+ remoting.debug.log('Starting session...');
+ var accessCode = document.getElementById('access-code-entry');
+ accessCode.value = ''; // The code has been validated and won't work again.
+ remoting.clientSession =
+ new remoting.ClientSession(
+ remoting.hostJid, remoting.hostPublicKey,
+ remoting.accessCode,
+ /** @type {string} */ (remoting.oauth2.getCachedEmail()),
+ onClientStateChange_);
+ /** @param {string} token The auth token. */
+ var createPluginAndConnect = function(token) {
+ remoting.clientSession.createPluginAndConnect(
+ document.getElementById('session-mode'),
+ token);
+ };
+ remoting.oauth2.callWithToken(createPluginAndConnect);
+}
+
+/**
+ * Show a client-side error message.
+ *
+ * @param {remoting.ClientError} errorTag The error to be localized and
+ * displayed.
+ * @return {void} Nothing.
+ */
+function showConnectError_(errorTag) {
+ remoting.debug.log('Connection failed: ' + errorTag);
+ var errorDiv = document.getElementById('connect-error-message');
+ l10n.localizeElementFromTag(errorDiv, /** @type {string} */ (errorTag));
+ remoting.accessCode = '';
+ if (remoting.clientSession) {
+ remoting.clientSession.disconnect();
+ remoting.clientSession = null;
+ }
+ remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED);
+}
+
+/**
+ * Parse the response from the server to a request to resolve a support id.
+ *
+ * @param {XMLHttpRequest} xhr The XMLHttpRequest object.
+ * @return {void} Nothing.
+ */
+function parseServerResponse_(xhr) {
+ remoting.supportHostsXhr_ = null;
+ remoting.debug.log('parseServerResponse: status = ' + xhr.status);
+ if (xhr.status == 200) {
+ var host = /** @type {{data: {jabberId: string, publicKey: string}}} */
+ JSON.parse(xhr.responseText);
+ if (host.data && host.data.jabberId && host.data.publicKey) {
+ remoting.hostJid = host.data.jabberId;
+ remoting.hostPublicKey = host.data.publicKey;
+ var split = remoting.hostJid.split('/');
+ document.getElementById('connected-to').innerText = split[0];
+ startSession_();
+ return;
+ }
+ }
+ var errorMsg = remoting.ClientError.OTHER_ERROR;
+ if (xhr.status == 404) {
+ errorMsg = remoting.ClientError.INVALID_ACCESS_CODE;
+ } else if (xhr.status == 0) {
+ errorMsg = remoting.ClientError.NO_RESPONSE;
+ } else {
+ remoting.debug.log('The server responded: ' + xhr.responseText);
+ }
+ showConnectError_(errorMsg);
+}
+
+/**
+ * Normalize the access code entered by the user.
+ *
+ * @param {string} accessCode The access code, as entered by the user.
+ * @return {string} The normalized form of the code (whitespace removed).
+ */
+function normalizeAccessCode_(accessCode) {
+ // Trim whitespace.
+ // TODO(sergeyu): Do we need to do any other normalization here?
+ return accessCode.replace(/\s/g, '');
+}
+
+/**
+ * Initiate a request to the server to resolve a support ID.
+ *
+ * @param {string} supportId The canonicalized support ID.
+ */
+function resolveSupportId(supportId) {
+ var headers = {
+ 'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken()
+ };
+
+ remoting.supportHostsXhr_ = remoting.xhr.get(
+ 'https://www.googleapis.com/chromoting/v1/support-hosts/' +
+ encodeURIComponent(supportId),
+ parseServerResponse_,
+ '',
+ headers);
+}
+
+/**
+ * Timer callback to update the statistics panel.
+ */
+function updateStatistics_() {
+ if (!remoting.clientSession ||
+ remoting.clientSession.state != remoting.ClientSession.State.CONNECTED) {
+ return;
+ }
+ remoting.debug.updateStatistics(remoting.clientSession.stats());
+ // Update the stats once per second.
+ window.setTimeout(updateStatistics_, 1000);
+}
+
+/**
+ * Force-show the tool-bar for three seconds to aid discoverability.
+ */
+function showToolbarPreview_() {
+ var toolbar = document.getElementById('session-toolbar');
+ addClass(toolbar, 'toolbar-preview');
+ window.setTimeout(removeClass, 3000, toolbar, 'toolbar-preview');
+}
+
+/**
+ * Update the horizontal position of the tool-bar to center it.
+ */
+function recenterToolbar_() {
+ var toolbar = document.getElementById('session-toolbar');
+ var toolbarX = (window.innerWidth - toolbar.clientWidth) / 2;
+ toolbar.style['left'] = toolbarX + 'px';
+}
+
+
+}());
diff --git a/remoting/webapp/me2mom/client_session.js b/remoting/webapp/me2mom/client_session.js
index 5afa4d2..9ec8b4c 100644
--- a/remoting/webapp/me2mom/client_session.js
+++ b/remoting/webapp/me2mom/client_session.js
@@ -4,7 +4,7 @@
/**
* @fileoverview
- * Session class that handles creation and teardown of a remoting session.
+ * Class handling creation and teardown of a remoting client session.
*
* This abstracts a <embed> element and controls the plugin which does the
* actual remoting work. There should be no UI code inside this class. It
diff --git a/remoting/webapp/me2mom/debug_log.js b/remoting/webapp/me2mom/debug_log.js
index 3d79fdd..94cc6e5 100644
--- a/remoting/webapp/me2mom/debug_log.js
+++ b/remoting/webapp/me2mom/debug_log.js
@@ -15,13 +15,17 @@ var remoting = remoting || {};
/**
* @constructor
* @param {Element} logElement The HTML div to which to add log messages.
+ * @param {Element} statsElement The HTML div to which to update stats.
*/
-remoting.DebugLog = function(logElement) {
- this.debugLog = logElement;
+remoting.DebugLog = function(logElement, statsElement) {
+ this.logElement = logElement;
+ this.statsElement = statsElement;
};
-/** Maximum number of lines to record in the debug log. Only the most
- * recent <n> lines are displayed. */
+/**
+ * Maximum number of lines to record in the debug log. Only the most
+ * recent <n> lines are displayed.
+ */
remoting.DebugLog.prototype.MAX_DEBUG_LOG_SIZE = 1000;
/**
@@ -31,18 +35,75 @@ remoting.DebugLog.prototype.MAX_DEBUG_LOG_SIZE = 1000;
*/
remoting.DebugLog.prototype.log = function(message) {
// Remove lines from top if we've hit our max log size.
- if (this.debugLog.childNodes.length == this.MAX_DEBUG_LOG_SIZE) {
- this.debugLog.removeChild(this.debugLog.firstChild);
+ if (this.logElement.childNodes.length == this.MAX_DEBUG_LOG_SIZE) {
+ this.logElement.removeChild(this.logElement.firstChild);
}
// Add the new <p> to the end of the debug log.
var p = document.createElement('p');
p.appendChild(document.createTextNode(message));
- this.debugLog.appendChild(p);
+ this.logElement.appendChild(p);
// Scroll to bottom of div
- this.debugLog.scrollTop = this.debugLog.scrollHeight;
+ this.logElement.scrollTop = this.logElement.scrollHeight;
+};
+
+/**
+ * Show or hide the debug log.
+ */
+remoting.DebugLog.prototype.toggle = function() {
+ var debugLog = /** @type {Element} */ this.logElement.parentNode;
+ if (debugLog.hidden) {
+ debugLog.hidden = false;
+ } else {
+ debugLog.hidden = true;
+ }
+};
+
+/**
+ * Update the statistics panel.
+ * @param {Object.<string, number>} stats The connection statistics.
+ */
+remoting.DebugLog.prototype.updateStatistics = function(stats) {
+ var units = '';
+ var videoBandwidth = stats['video_bandwidth'];
+ if (videoBandwidth < 1024) {
+ units = 'Bps';
+ } else if (videoBandwidth < 1048576) {
+ units = 'KiBps';
+ videoBandwidth = videoBandwidth / 1024;
+ } else if (videoBandwidth < 1073741824) {
+ units = 'MiBps';
+ videoBandwidth = videoBandwidth / 1048576;
+ } else {
+ units = 'GiBps';
+ videoBandwidth = videoBandwidth / 1073741824;
+ }
+
+ var statistics = document.getElementById('statistics');
+ this.statsElement.innerText =
+ 'Bandwidth: ' + videoBandwidth.toFixed(2) + units +
+ ', Frame Rate: ' +
+ (stats['video_frame_rate'] ?
+ stats['video_frame_rate'].toFixed(2) + ' fps' : 'n/a') +
+ ', Capture: ' + stats['capture_latency'].toFixed(2) + 'ms' +
+ ', Encode: ' + stats['encode_latency'].toFixed(2) + 'ms' +
+ ', Decode: ' + stats['decode_latency'].toFixed(2) + 'ms' +
+ ', Render: ' + stats['render_latency'].toFixed(2) + 'ms' +
+ ', Latency: ' + stats['roundtrip_latency'].toFixed(2) + 'ms';
};
+/**
+ * Check for the debug toggle hot-key.
+ *
+ * @param {Event} event The keyboard event.
+ * @return {void} Nothing.
+ */
+remoting.DebugLog.onKeydown = function(event) {
+ if (String.fromCharCode(event.which) == 'D') {
+ remoting.debug.toggle();
+ }
+}
+
/** @type {remoting.DebugLog} */
remoting.debug = null;
diff --git a/remoting/webapp/me2mom/host_screen.js b/remoting/webapp/me2mom/host_screen.js
new file mode 100644
index 0000000..87f614a
--- /dev/null
+++ b/remoting/webapp/me2mom/host_screen.js
@@ -0,0 +1,269 @@
+// Copyright (c) 2011 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.
+
+/**
+ * @fileoverview
+ * Functions related to the 'host screen' for Chromoting.
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+(function() {
+
+/**
+ * @type {boolean} Whether or not the last share was cancelled by the user.
+ * This controls what screen is shown when the host plugin signals
+ * completion.
+ */
+var lastShareWasCancelled_ = false;
+
+/**
+ * Start a host session. This is the main entry point for the host screen,
+ * called directly from the onclick action of a button on the home screen.
+ */
+remoting.tryShare = function() {
+ remoting.debug.log('Attempting to share...');
+ lastShareWasCancelled_ = false;
+ if (remoting.oauth2.needsNewAccessToken()) {
+ remoting.debug.log('Refreshing token...');
+ remoting.oauth2.refreshAccessToken(function() {
+ if (remoting.oauth2.needsNewAccessToken()) {
+ // If we still need it, we're going to infinite loop.
+ showShareError_(/*i18n-content*/'ERROR_AUTHENTICATION_FAILED');
+ throw 'Unable to get access token';
+ }
+ remoting.tryShare();
+ });
+ return;
+ }
+
+ onNatTraversalPolicyChanged_(true); // Hide warning by default.
+ remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
+ document.getElementById('cancel-button').disabled = false;
+ disableTimeoutCountdown_();
+
+ var div = document.getElementById('host-plugin-container');
+ remoting.hostSession = new remoting.HostSession();
+ remoting.hostSession.createPluginAndConnect(
+ document.getElementById('host-plugin-container'),
+ /** @type {string} */(remoting.oauth2.getCachedEmail()),
+ remoting.oauth2.getAccessToken(),
+ onNatTraversalPolicyChanged_,
+ onHostStateChanged_,
+ logDebugInfo_);
+};
+
+/**
+ * Callback for the host plugin to notify the web app of state changes.
+ * @param {remoting.HostSession.State} state The new state of the plugin.
+ */
+function onHostStateChanged_(state) {
+ if (state == remoting.HostSession.State.STARTING) {
+ // Nothing to do here.
+ remoting.debug.log('Host plugin state: STARTING');
+
+ } else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
+ // Nothing to do here.
+ remoting.debug.log('Host plugin state: REQUESTED_ACCESS_CODE');
+
+ } else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
+ remoting.debug.log('Host plugin state: RECEIVED_ACCESS_CODE');
+ var accessCode = remoting.hostSession.getAccessCode();
+ var accessCodeDisplay = document.getElementById('access-code-display');
+ accessCodeDisplay.innerText = '';
+ // Display the access code in groups of four digits for readability.
+ var kDigitsPerGroup = 4;
+ for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
+ var nextFourDigits = document.createElement('span');
+ nextFourDigits.className = 'access-code-digit-group';
+ nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
+ accessCodeDisplay.appendChild(nextFourDigits);
+ }
+ accessCodeExpiresIn_ = remoting.hostSession.getAccessCodeLifetime();
+ if (accessCodeExpiresIn_ > 0) { // Check it hasn't expired.
+ accessCodeTimerId_ = setInterval(
+ 'remoting.decrementAccessCodeTimeout_()', 1000);
+ timerRunning_ = true;
+ updateAccessCodeTimeoutElement_();
+ updateTimeoutStyles_();
+ remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
+ } else {
+ // This can only happen if the cloud tells us that the code lifetime is
+ // <= 0s, which shouldn't happen so we don't care how clean this UX is.
+ remoting.debug.log('Access code already invalid on receipt!');
+ remoting.cancelShare();
+ }
+
+ } else if (state == remoting.HostSession.State.CONNECTED) {
+ remoting.debug.log('Host plugin state: CONNECTED');
+ var element = document.getElementById('host-shared-message');
+ var client = remoting.hostSession.getClient();
+ l10n.localizeElement(element, client);
+ remoting.setMode(remoting.AppMode.HOST_SHARED);
+ disableTimeoutCountdown_();
+
+ } else if (state == remoting.HostSession.State.DISCONNECTING) {
+ remoting.debug.log('Host plugin state: DISCONNECTING');
+
+ } else if (state == remoting.HostSession.State.DISCONNECTED) {
+ remoting.debug.log('Host plugin state: DISCONNECTED');
+ if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
+ // If an error is being displayed, then the plugin should not be able to
+ // hide it by setting the state. Errors must be dismissed by the user
+ // clicking OK, which puts the app into mode HOME.
+ if (lastShareWasCancelled_) {
+ remoting.setMode(remoting.AppMode.HOME);
+ } else {
+ remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
+ }
+ }
+ remoting.hostSession.removePlugin();
+
+ } else if (state == remoting.HostSession.State.ERROR) {
+ remoting.debug.log('Host plugin state: ERROR');
+ showShareError_(/*i18n-content*/'ERROR_GENERIC');
+ } else {
+ remoting.debug.log('Unknown state -> ' + state);
+ }
+}
+
+/**
+ * This is the callback that the host plugin invokes to indicate that there
+ * is additional debug log info to display.
+ * @param {string} msg The message (which will not be localized) to be logged.
+ */
+function logDebugInfo_(msg) {
+ remoting.debug.log('plugin: ' + msg);
+}
+
+/**
+ * Show a host-side error message.
+ *
+ * @param {string} errorTag The error message to be localized and displayed.
+ * @return {void} Nothing.
+ */
+function showShareError_(errorTag) {
+ var errorDiv = document.getElementById('host-plugin-error');
+ l10n.localizeElementFromTag(errorDiv, errorTag);
+ remoting.debug.log('Sharing error: ' + errorTag);
+ remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
+}
+
+/**
+ * Cancel an active or pending share operation.
+ *
+ * @return {void} Nothing.
+ */
+remoting.cancelShare = function() {
+ remoting.debug.log('Canceling share...');
+ remoting.lastShareWasCancelled = true;
+ try {
+ remoting.hostSession.disconnect();
+ } catch (error) {
+ // Hack to force JSCompiler type-safety.
+ var errorTyped = /** @type {{description: string}} */ error;
+ remoting.debug.log('Error disconnecting: ' + errorTyped.description +
+ '. The host plugin probably crashed.');
+ // TODO(jamiewalch): Clean this up. We should have a class representing
+ // the host plugin, like we do for the client, which should handle crash
+ // reporting and it should use a more detailed error message than the
+ // default 'generic' one. See crbug.com/94624
+ showShareError_(/*i18n-content*/'ERROR_GENERIC');
+ }
+ disableTimeoutCountdown_();
+};
+
+/**
+ * @type {boolean} Whether or not the access code timeout countdown is running.
+ */
+var timerRunning_ = false;
+
+/**
+ * @type {number} The id of the access code expiry countdown timer.
+ */
+var accessCodeTimerId_ = 0;
+
+/**
+ * @type {number} The number of seconds until the access code expires.
+ */
+var accessCodeExpiresIn_ = 0;
+
+/**
+ * The timer callback function, which needs to be visible from the global
+ * namespace.
+ */
+remoting.decrementAccessCodeTimeout_ = function() {
+ --accessCodeExpiresIn_;
+ updateAccessCodeTimeoutElement_();
+};
+
+/**
+ * Stop the access code timeout countdown if it is running.
+ */
+function disableTimeoutCountdown_() {
+ if (timerRunning_) {
+ clearInterval(accessCodeTimerId_);
+ timerRunning_ = false;
+ updateTimeoutStyles_();
+ }
+}
+
+/**
+ * Constants controlling the access code timer countdown display.
+ */
+var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_ = 30;
+var ACCESS_CODE_RED_THRESHOLD_ = 10;
+
+/**
+ * Show/hide or restyle various elements, depending on the remaining countdown
+ * and timer state.
+ *
+ * @return {boolean} True if the timeout is in progress, false if it has
+ * expired.
+ */
+function updateTimeoutStyles_() {
+ if (timerRunning_) {
+ if (accessCodeExpiresIn_ <= 0) {
+ remoting.cancelShare();
+ return false;
+ }
+ if (accessCodeExpiresIn_ <= ACCESS_CODE_RED_THRESHOLD_) {
+ addClass(document.getElementById('access-code-display'), 'expiring');
+ } else {
+ removeClass(document.getElementById('access-code-display'), 'expiring');
+ }
+ }
+ document.getElementById('access-code-countdown').hidden =
+ (accessCodeExpiresIn_ > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD_) ||
+ !timerRunning_;
+ return true;
+}
+
+/**
+ * Update the text and appearance of the access code timeout element to
+ * reflect the time remaining.
+ */
+function updateAccessCodeTimeoutElement_() {
+ var pad = (accessCodeExpiresIn_ < 10) ? '0:0' : '0:';
+ l10n.localizeElement(document.getElementById('seconds-remaining'),
+ pad + accessCodeExpiresIn_);
+ if (!updateTimeoutStyles_()) {
+ disableTimeoutCountdown_();
+ }
+}
+
+/**
+ * Callback to show or hide the NAT traversal warning when the policy changes.
+ * @param {boolean} enabled True if NAT traversal is enabled.
+ * @return {void} Nothing.
+ */
+function onNatTraversalPolicyChanged_(enabled) {
+ var container = document.getElementById('nat-box-container');
+ container.hidden = enabled;
+}
+
+}());
diff --git a/remoting/webapp/me2mom/host_session.js b/remoting/webapp/me2mom/host_session.js
new file mode 100644
index 0000000..7dfb95a
--- /dev/null
+++ b/remoting/webapp/me2mom/host_session.js
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 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.
+
+/**
+ * @fileoverview
+ * Class handling creation and teardown of a remoting host session.
+ *
+ * This abstracts a <embed> element and controls the plugin which does the
+ * actual remoting work. There should be no UI code inside this class. It
+ * should be purely thought of as a controller of sorts.
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * @constructor
+ */
+remoting.HostSession = function() {
+ /** @private */
+ this.HOST_PLUGIN_ID_ = 'host-plugin-id';
+};
+
+/** @type {remoting.HostPlugin} */
+remoting.HostSession.prototype.plugin = null;
+
+// Note that these values are copied directly from host_script_object.h and
+// must be kept in sync.
+/** @enum {number} */
+remoting.HostSession.State = {
+ UNKNOWN: -1,
+ DISCONNECTED: 0,
+ STARTING: 1,
+ REQUESTED_ACCESS_CODE: 2,
+ RECEIVED_ACCESS_CODE: 3,
+ CONNECTED: 4,
+ DISCONNECTING: 5,
+ ERROR: 6
+};
+
+/**
+ * Create the host plugin and initiate a connection.
+ * @param {Element} container The parent element to which to add the plugin.
+ * @param {string} email The user's email address.
+ * @param {string} accessToken A valid OAuth2 access token.
+ * @param {function(boolean):void} onNatTraversalPolicyChanged Callback
+ * for notification of changes to the NAT traversal policy.
+ * @param {function(remoting.HostSession.State):void} onStateChanged
+ * Callback for notifications of changes to the host plugin's state.
+ * @param {function(string):void} logDebugInfo Callback allowing the plugin
+ * to log messages to the debug log.
+ */
+remoting.HostSession.prototype.createPluginAndConnect =
+ function(container, email, accessToken,
+ onNatTraversalPolicyChanged, onStateChanged, logDebugInfo) {
+ this.plugin = /** @type {remoting.HostPlugin} */
+ document.createElement('embed');
+ this.plugin.type = remoting.PLUGIN_MIMETYPE;
+ this.plugin.id = this.HOST_PLUGIN_ID_;
+ // Hiding the plugin means it doesn't load, so make it size zero instead.
+ this.plugin.width = 0;
+ this.plugin.height = 0;
+ container.appendChild(this.plugin);
+ this.plugin.onNatTraversalPolicyChanged = onNatTraversalPolicyChanged;
+ this.plugin.onStateChanged = onStateChanged;
+ this.plugin.logDebugInfo = logDebugInfo;
+ this.plugin.localize(chrome.i18n.getMessage);
+ this.plugin.connect(email, 'oauth2:' + accessToken);
+};
+
+/**
+ * Get the access code generated by the host plugin. Valid only after the
+ * plugin state is RECEIVED_ACCESS_CODE.
+ * @return {string} The access code.
+ */
+remoting.HostSession.prototype.getAccessCode = function() {
+ return this.plugin.accessCode;
+};
+
+/**
+ * Get the lifetime for the access code. Valid only after the plugin state is
+ * RECEIVED_ACCESS_CODE.
+ * @return {number} The access code lifetime, in seconds.
+ */
+remoting.HostSession.prototype.getAccessCodeLifetime = function() {
+ return this.plugin.accessCodeLifetime;
+};
+
+/**
+ * Get the email address of the connected client. Valid only after the plugin
+ * state is CONNECTED.
+ * @return {string} The client's email address.
+ */
+remoting.HostSession.prototype.getClient = function() {
+ return this.plugin.client;
+};
+
+/**
+ * Disconnect the client.
+ * @return {void} Nothing.
+ */
+remoting.HostSession.prototype.disconnect = function() {
+ this.plugin.disconnect();
+};
+
+
+/**
+ * Remove the plugin element from the document.
+ * @return {void} Nothing.
+ */
+remoting.HostSession.prototype.removePlugin = function() {
+ this.plugin.parentNode.removeChild(this.plugin);
+};
diff --git a/remoting/webapp/me2mom/oauth2.js b/remoting/webapp/me2mom/oauth2.js
index ee7cc73..7010715 100644
--- a/remoting/webapp/me2mom/oauth2.js
+++ b/remoting/webapp/me2mom/oauth2.js
@@ -29,20 +29,26 @@ remoting.OAuth2 = function() {
remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token';
/** @private */
remoting.OAuth2.prototype.KEY_ACCESS_TOKEN_ = 'oauth2-access-token';
+/** @private */
+remoting.OAuth2.prototype.KEY_EMAIL_ = 'remoting-email';
// Constants for parameters used in retrieving the OAuth2 credentials.
-/** @private */ remoting.OAuth2.prototype.CLIENT_ID_ =
+/** @private */
+remoting.OAuth2.prototype.CLIENT_ID_ =
'440925447803-2pi3v45bff6tp1rde2f7q6lgbor3o5uj.' +
'apps.googleusercontent.com';
/** @private */
remoting.OAuth2.prototype.CLIENT_SECRET_ = 'W2ieEsG-R1gIA4MMurGrgMc_';
-/** @private */ remoting.OAuth2.prototype.SCOPE_ =
+/** @private */
+remoting.OAuth2.prototype.SCOPE_ =
'https://www.googleapis.com/auth/chromoting ' +
'https://www.googleapis.com/auth/googletalk ' +
'https://www.googleapis.com/auth/userinfo#email';
-/** @private */ remoting.OAuth2.prototype.REDIRECT_URI_ =
+/** @private */
+remoting.OAuth2.prototype.REDIRECT_URI_ =
'https://talkgadget.google.com/talkgadget/blank';
-/** @private */ remoting.OAuth2.prototype.OAUTH2_TOKEN_ENDPOINT_ =
+/** @private */
+remoting.OAuth2.prototype.OAUTH2_TOKEN_ENDPOINT_ =
'https://accounts.google.com/o/oauth2/token';
/** @return {boolean} True if the app is already authenticated. */
@@ -60,6 +66,7 @@ remoting.OAuth2.prototype.isAuthenticated = function() {
*/
remoting.OAuth2.prototype.clear = function() {
window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_);
+ window.localStorage.removeItem(this.KEY_EMAIL_);
this.clearAccessToken();
};
@@ -291,3 +298,49 @@ remoting.OAuth2.prototype.callWithToken = function(myfunc) {
myfunc(this.getAccessToken());
};
+
+/**
+ * Get the user's email address.
+ *
+ * @param {function(?string):void} setEmail Callback invoked when the email
+ * address is available, or on error.
+ * @return {void} Nothing.
+ */
+remoting.OAuth2.prototype.getEmail = function(setEmail) {
+ /** @type {remoting.OAuth2} */
+ var that = this;
+ /** @param {XMLHttpRequest} xhr The XHR response. */
+ var onResponse = function(xhr) {
+ that.email = null;
+ if (xhr.status == 200) {
+ // TODO(ajwong): See if we can't find a JSON endpoint.
+ that.email = xhr.responseText.split('&')[0].split('=')[1];
+ }
+ window.localStorage.setItem(that.KEY_EMAIL_, that.email);
+ setEmail(that.email);
+ };
+
+ /** @param {string} token The access token. */
+ var getEmailFromToken = function(token) {
+ var headers = { 'Authorization': 'OAuth ' + token };
+ // TODO(ajwong): Update to new v2 API.
+ remoting.xhr.get('https://www.googleapis.com/userinfo/email',
+ onResponse, '', headers);
+ };
+
+ this.callWithToken(getEmailFromToken);
+};
+
+/**
+ * If the user's email address is cached, return it, otherwise return null.
+ *
+ * @return {?string} The email address, if it has been cached by a previous call
+ * to getEmail, otherwise null.
+ */
+remoting.OAuth2.prototype.getCachedEmail = function() {
+ var value = window.localStorage.getItem(this.KEY_EMAIL_);
+ if (typeof value == 'string') {
+ return value;
+ }
+ return null;
+};
diff --git a/remoting/webapp/me2mom/remoting.js b/remoting/webapp/me2mom/remoting.js
index 2f4250b..3e86b05 100644
--- a/remoting/webapp/me2mom/remoting.js
+++ b/remoting/webapp/me2mom/remoting.js
@@ -7,202 +7,31 @@
/** @suppress {duplicate} */
var remoting = remoting || {};
-/**
- * Whether or not the plugin should scale itself.
- * @type {boolean}
- */
-remoting.scaleToFit = false;
-
-/** @type {remoting.ClientSession} */
-remoting.session = null;
-
-/** @type {string} */ remoting.accessCode = '';
-/** @type {number} */ remoting.accessCodeTimerId = 0;
-/** @type {number} */ remoting.accessCodeExpiresIn = 0;
-/** @type {remoting.AppMode} */ remoting.currentMode;
-/** @type {string} */ remoting.hostJid = '';
-/** @type {string} */ remoting.hostPublicKey = '';
-/** @type {boolean} */ remoting.lastShareWasCancelled = false;
-/** @type {boolean} */ remoting.timerRunning = false;
-/** @type {string} */ remoting.username = '';
-
-/** @enum {string} */
-remoting.AppMode = {
- HOME: 'home',
- UNAUTHENTICATED: 'auth',
- CLIENT: 'client',
- CLIENT_UNCONNECTED: 'client.unconnected',
- CLIENT_CONNECTING: 'client.connecting',
- CLIENT_CONNECT_FAILED: 'client.connect-failed',
- CLIENT_SESSION_FINISHED: 'client.session-finished',
- HOST: 'host',
- HOST_WAITING_FOR_CODE: 'host.waiting-for-code',
- HOST_WAITING_FOR_CONNECTION: 'host.waiting-for-connection',
- HOST_SHARED: 'host.shared',
- HOST_SHARE_FAILED: 'host.share-failed',
- HOST_SHARE_FINISHED: 'host.share-finished',
- IN_SESSION: 'in-session'
-};
+/** @type {remoting.HostSession} */ remoting.hostSession = null;
(function() {
-window.addEventListener('blur', pluginLostFocus_, false);
-
-function pluginLostFocus_() {
- // If the plug loses input focus, release all keys as a precaution against
- // leaving them 'stuck down' on the host.
- if (remoting.session && remoting.session.plugin) {
- remoting.session.plugin.releaseAllKeys();
- }
-}
-
-/** @type {string} */
-remoting.HOST_PLUGIN_ID = 'host-plugin-id';
-
-/** @enum {string} */
-remoting.ClientError = {
- NO_RESPONSE: /*i18n-content*/'ERROR_NO_RESPONSE',
- INVALID_ACCESS_CODE: /*i18n-content*/'ERROR_INVALID_ACCESS_CODE',
- MISSING_PLUGIN: /*i18n-content*/'ERROR_MISSING_PLUGIN',
- OAUTH_FETCH_FAILED: /*i18n-content*/'ERROR_AUTHENTICATION_FAILED',
- HOST_IS_OFFLINE: /*i18n-content*/'ERROR_HOST_IS_OFFLINE',
- INCOMPATIBLE_PROTOCOL: /*i18n-content*/'ERROR_INCOMPATIBLE_PROTOCOL',
- BAD_PLUGIN_VERSION: /*i18n-content*/'ERROR_BAD_PLUGIN_VERSION',
- OTHER_ERROR: /*i18n-content*/'ERROR_GENERIC'
-};
-
-// Constants representing keys used for storing persistent application state.
-var KEY_APP_MODE_ = 'remoting-app-mode';
-var KEY_EMAIL_ = 'remoting-email';
-var KEY_USE_P2P_API_ = 'remoting-use-p2p-api';
-
-// Some constants for pretty-printing the access code.
-/** @type {number} */ var kSupportIdLen = 7;
-/** @type {number} */ var kHostSecretLen = 5;
-/** @type {number} */ var kAccessCodeLen = kSupportIdLen + kHostSecretLen;
-/** @type {number} */ var kDigitsPerGroup = 4;
-
/**
- * @param {string} classes A space-separated list of classes.
- * @param {string} cls The class to check for.
- * @return {boolean} True if |cls| is found within |classes|.
+ * Entry point for app initialization.
*/
-function hasClass(classes, cls) {
- return classes.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) != null;
-}
-
-/**
- * @param {Element} element The element to which to add the class.
- * @param {string} cls The new class.
- * @return {void} Nothing.
- */
-function addClass(element, cls) {
- if (!hasClass(element.className, cls)) {
- var padded = element.className == '' ? '' : element.className + ' ';
- element.className = padded + cls;
- }
-}
-
-/**
- * @param {Element} element The element from which to remove the class.
- * @param {string} cls The new class.
- * @return {void} Nothing.
- */
-function removeClass(element, cls) {
- element.className =
- element.className.replace(new RegExp('\\b' + cls + '\\b', 'g'), '')
- .replace(' ', ' ');
-}
-
-function retrieveEmail_(access_token) {
- var headers = {
- 'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken()
- };
-
- /** @param {XMLHttpRequest} xhr The XHR response. */
- var onResponse = function(xhr) {
- if (xhr.status != 200) {
- // TODO(ajwong): Have a better way of showing an error.
- remoting.debug.log('Unable to get email');
- document.getElementById('current-email').innerText = '???';
- return;
- }
-
- // TODO(ajwong): See if we can't find a JSON endpoint.
- setEmail(xhr.responseText.split('&')[0].split('=')[1]);
- };
-
- // TODO(ajwong): Update to new v2 API.
- remoting.xhr.get('https://www.googleapis.com/userinfo/email',
- onResponse, '', headers);
-}
-
-function refreshEmail_() {
- if (!getEmail() && remoting.oauth2.isAuthenticated()) {
- remoting.oauth2.callWithToken(retrieveEmail_);
- }
-}
-
-/**
- * @param {string} value The email address to place in local storage.
- * @return {void} Nothing.
- */
-function setEmail(value) {
- window.localStorage.setItem(KEY_EMAIL_, value);
- document.getElementById('current-email').innerText = value;
-}
-
-/**
- * @return {?string} The email address associated with the auth credentials.
- */
-function getEmail() {
- var result = window.localStorage.getItem(KEY_EMAIL_);
- return typeof result == 'string' ? result : null;
-}
-
-function exchangedCodeForToken_() {
- if (!remoting.oauth2.isAuthenticated()) {
- alert('Your OAuth2 token was invalid. Please try again.');
- }
- /** @param {string} token The auth token. */
- var retrieveEmail = function(token) { retrieveEmail_(token); }
- remoting.oauth2.callWithToken(retrieveEmail);
-}
-
-remoting.clearOAuth2 = function() {
- remoting.oauth2.clear();
- window.localStorage.removeItem(KEY_EMAIL_);
- remoting.setMode(remoting.AppMode.UNAUTHENTICATED);
-}
-
-remoting.toggleDebugLog = function() {
- var debugLog = document.getElementById('debug-log');
- if (debugLog.hidden) {
- debugLog.hidden = false;
- } else {
- debugLog.hidden = true;
- }
-}
-
remoting.init = function() {
l10n.localize();
var button = document.getElementById('toggle-scaling');
button.title = chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_SCALING');
// Create global objects.
remoting.oauth2 = new remoting.OAuth2();
- remoting.debug =
- new remoting.DebugLog(document.getElementById('debug-messages'));
- /** @type {XMLHttpRequest} */
- remoting.supportHostsXhr = null;
+ remoting.debug = new remoting.DebugLog(
+ document.getElementById('debug-messages'),
+ document.getElementById('statistics'));
refreshEmail_();
- var email = getEmail();
+ var email = remoting.oauth2.getCachedEmail();
if (email) {
document.getElementById('current-email').innerText = email;
}
- remoting.setMode(getAppStartupMode());
- if (isHostModeSupported()) {
+ remoting.setMode(getAppStartupMode_());
+ if (isHostModeSupported_()) {
var unsupported = document.getElementById('client-footer-text-cros');
unsupported.parentNode.removeChild(unsupported);
} else {
@@ -211,553 +40,106 @@ remoting.init = function() {
document.getElementById('client-footer-text-cros').id =
'client-footer-text';
}
-}
-
-/**
- * Change the app's modal state to |mode|, which is considered to be a dotted
- * hierachy of modes. For example, setMode('host.shared') will show any modal
- * elements with an data-ui-mode attribute of 'host' or 'host.shared' and hide
- * all others.
- *
- * @param {remoting.AppMode} mode The new modal state, expressed as a dotted
- * hiearchy.
- */
-remoting.setMode = function(mode) {
- var modes = mode.split('.');
- for (var i = 1; i < modes.length; ++i)
- modes[i] = modes[i - 1] + '.' + modes[i];
- var elements = document.querySelectorAll('[data-ui-mode]');
- for (var i = 0; i < elements.length; ++i) {
- /** @type {Element} */ var element = elements[i];
- var hidden = true;
- for (var m = 0; m < modes.length; ++m) {
- if (hasClass(element.getAttribute('data-ui-mode'), modes[m])) {
- hidden = false;
- break;
- }
- }
- element.hidden = hidden;
- }
- remoting.debug.log('App mode: ' + mode);
- remoting.currentMode = mode;
- if (mode == remoting.AppMode.IN_SESSION) {
- document.removeEventListener('keydown', remoting.checkHotkeys, false);
- } else {
- document.addEventListener('keydown', remoting.checkHotkeys, false);
- }
-}
-
-/**
- * Get the major mode that the app is running in.
- * @return {string} The app's current major mode.
- */
-remoting.getMajorMode = function() {
- return remoting.currentMode.split('.')[0];
-}
-
-remoting.tryShare = function() {
- remoting.debug.log('Attempting to share...');
- remoting.lastShareWasCancelled = false;
- if (remoting.oauth2.needsNewAccessToken()) {
- remoting.debug.log('Refreshing token...');
- remoting.oauth2.refreshAccessToken(function() {
- if (remoting.oauth2.needsNewAccessToken()) {
- // If we still need it, we're going to infinite loop.
- showShareError_(/*i18n-content*/'ERROR_AUTHENTICATION_FAILED');
- throw 'Unable to get access token';
- }
- remoting.tryShare();
- });
- return;
- }
-
- remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CODE);
- document.getElementById('cancel-button').disabled = false;
- disableTimeoutCountdown_();
-
- var div = document.getElementById('host-plugin-container');
- var plugin = /** @type {remoting.HostPlugin} */
- document.createElement('embed');
- plugin.type = remoting.PLUGIN_MIMETYPE;
- plugin.id = remoting.HOST_PLUGIN_ID;
- // Hiding the plugin means it doesn't load, so make it size zero instead.
- plugin.width = 0;
- plugin.height = 0;
- div.appendChild(plugin);
- onNatTraversalPolicyChanged_(true); // Hide warning by default.
- plugin.onNatTraversalPolicyChanged = onNatTraversalPolicyChanged_;
- plugin.onStateChanged = onStateChanged_;
- plugin.logDebugInfo = debugInfoCallback_;
- plugin.localize(chrome.i18n.getMessage);
- plugin.connect(/** @type {string} */ (getEmail()),
- 'oauth2:' + remoting.oauth2.getAccessToken());
-}
-function disableTimeoutCountdown_() {
- if (remoting.timerRunning) {
- clearInterval(remoting.accessCodeTimerId);
- remoting.timerRunning = false;
- updateTimeoutStyles_();
- }
+ window.addEventListener('blur', pluginLostFocus_, false);
}
-var ACCESS_CODE_TIMER_DISPLAY_THRESHOLD = 30;
-var ACCESS_CODE_RED_THRESHOLD = 10;
-
-/**
- * Show/hide or restyle various elements, depending on the remaining countdown
- * and timer state.
- *
- * @return {boolean} True if the timeout is in progress, false if it has
- * expired.
- */
-function updateTimeoutStyles_() {
- if (remoting.timerRunning) {
- if (remoting.accessCodeExpiresIn <= 0) {
+remoting.cancelPendingOperation = function() {
+ document.getElementById('cancel-button').disabled = true;
+ switch (remoting.getMajorMode()) {
+ case remoting.AppMode.HOST:
remoting.cancelShare();
- return false;
- }
- if (remoting.accessCodeExpiresIn <= ACCESS_CODE_RED_THRESHOLD) {
- addClass(document.getElementById('access-code-display'), 'expiring');
- } else {
- removeClass(document.getElementById('access-code-display'), 'expiring');
- }
- }
- document.getElementById('access-code-countdown').hidden =
- (remoting.accessCodeExpiresIn > ACCESS_CODE_TIMER_DISPLAY_THRESHOLD) ||
- !remoting.timerRunning;
- return true;
-}
-
-remoting.decrementAccessCodeTimeout_ = function() {
- --remoting.accessCodeExpiresIn;
- remoting.updateAccessCodeTimeoutElement_();
-}
-
-remoting.updateAccessCodeTimeoutElement_ = function() {
- var pad = (remoting.accessCodeExpiresIn < 10) ? '0:0' : '0:';
- l10n.localizeElement(document.getElementById('seconds-remaining'),
- pad + remoting.accessCodeExpiresIn);
- if (!updateTimeoutStyles_()) {
- disableTimeoutCountdown_();
+ break;
+ case remoting.AppMode.CLIENT:
+ remoting.cancelConnect();
+ break;
}
}
/**
- * Callback to show or hide the NAT traversal warning when the policy changes.
- * @param {boolean} enabled True if NAT traversal is enabled.
- * @return {void} Nothing.
- */
-function onNatTraversalPolicyChanged_(enabled) {
- var container = document.getElementById('nat-box-container');
- container.hidden = enabled;
-}
-
-/**
- * Callback for the host plugin to notify the web app of state changes.
- * @param {number} state The new state of the plugin.
+ * If the client is connected, or the host is shared, prompt before closing.
+ *
+ * @return {?string} The prompt string if a connection is active.
*/
-function onStateChanged_(state) {
- var plugin = /** @type {remoting.HostPlugin} */
- document.getElementById(remoting.HOST_PLUGIN_ID);
- if (state == plugin.STARTING) {
- // Nothing to do here.
- remoting.debug.log('Host plugin state: STARTING');
- } else if (state == plugin.REQUESTED_ACCESS_CODE) {
- // Nothing to do here.
- remoting.debug.log('Host plugin state: REQUESTED_ACCESS_CODE');
- } else if (state == plugin.RECEIVED_ACCESS_CODE) {
- remoting.debug.log('Host plugin state: RECEIVED_ACCESS_CODE');
- var accessCode = plugin.accessCode;
- var accessCodeDisplay = document.getElementById('access-code-display');
- accessCodeDisplay.innerText = '';
- // Display the access code in groups of four digits for readability.
- for (var i = 0; i < accessCode.length; i += kDigitsPerGroup) {
- var nextFourDigits = document.createElement('span');
- nextFourDigits.className = 'access-code-digit-group';
- nextFourDigits.innerText = accessCode.substring(i, i + kDigitsPerGroup);
- accessCodeDisplay.appendChild(nextFourDigits);
- }
- remoting.accessCodeExpiresIn = plugin.accessCodeLifetime;
- if (remoting.accessCodeExpiresIn > 0) { // Check it hasn't expired.
- remoting.accessCodeTimerId = setInterval(
- 'remoting.decrementAccessCodeTimeout_()', 1000);
- remoting.timerRunning = true;
- remoting.updateAccessCodeTimeoutElement_();
- updateTimeoutStyles_();
- remoting.setMode(remoting.AppMode.HOST_WAITING_FOR_CONNECTION);
- } else {
- // This can only happen if the cloud tells us that the code lifetime is
- // <= 0s, which shouldn't happen so we don't care how clean this UX is.
- remoting.debug.log('Access code already invalid on receipt!');
- remoting.cancelShare();
- }
- } else if (state == plugin.CONNECTED) {
- remoting.debug.log('Host plugin state: CONNECTED');
- var element = document.getElementById('host-shared-message');
- var client = plugin.client;
- l10n.localizeElement(element, client);
- remoting.setMode(remoting.AppMode.HOST_SHARED);
- disableTimeoutCountdown_();
- } else if (state == plugin.DISCONNECTING) {
- remoting.debug.log('Host plugin state: DISCONNECTING');
- } else if (state == plugin.DISCONNECTED) {
- remoting.debug.log('Host plugin state: DISCONNECTED');
- if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
- // If an error is being displayed, then the plugin should not be able to
- // hide it by setting the state. Errors must be dismissed by the user
- // clicking OK, which puts the app into mode HOME.
- if (remoting.lastShareWasCancelled) {
- remoting.setMode(remoting.AppMode.HOME);
- } else {
- remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
- }
- }
- plugin.parentNode.removeChild(plugin);
- } else if (state == plugin.ERROR) {
- remoting.debug.log('Host plugin state: ERROR');
- showShareError_(/*i18n-content*/'ERROR_GENERIC');
- } else {
- remoting.debug.log('Unknown state -> ' + state);
+remoting.promptClose = function() {
+ switch (remoting.currentMode) {
+ case remoting.AppMode.CLIENT_CONNECTING:
+ case remoting.AppMode.HOST_WAITING_FOR_CODE:
+ case remoting.AppMode.HOST_WAITING_FOR_CONNECTION:
+ case remoting.AppMode.HOST_SHARED:
+ case remoting.AppMode.IN_SESSION:
+ var result = chrome.i18n.getMessage(/*i18n-content*/'CLOSE_PROMPT');
+ return result;
+ default:
+ return null;
}
}
/**
- * This is the callback that the host plugin invokes to indicate that there
- * is additional debug log info to display.
- * @param {string} msg The message (which will not be localized) to be logged.
- */
-function debugInfoCallback_(msg) {
- remoting.debug.log('plugin: ' + msg);
-}
-
-/**
- * Show a host-side error message.
- *
- * @param {string} errorTag The error message to be localized and displayed.
- * @return {void} Nothing.
+ * Sign the user out of Chromoting by clearing the OAuth refresh token.
*/
-function showShareError_(errorTag) {
- var errorDiv = document.getElementById('host-plugin-error');
- l10n.localizeElementFromTag(errorDiv, errorTag);
- remoting.debug.log('Sharing error: ' + errorTag);
- remoting.setMode(remoting.AppMode.HOST_SHARE_FAILED);
+remoting.clearOAuth2 = function() {
+ remoting.oauth2.clear();
+ window.localStorage.removeItem(KEY_EMAIL_);
+ remoting.setMode(remoting.AppMode.UNAUTHENTICATED);
}
/**
- * Cancel an active or pending share operation.
- *
- * @return {void} Nothing.
+ * Callback function called when the browser window loses focus. In this case,
+ * release all keys to prevent them becoming 'stuck down' on the host.
*/
-remoting.cancelShare = function() {
- remoting.debug.log('Canceling share...');
- remoting.lastShareWasCancelled = true;
- var plugin = /** @type {remoting.HostPlugin} */
- document.getElementById(remoting.HOST_PLUGIN_ID);
- try {
- plugin.disconnect();
- } catch (error) {
- // Hack to force JSCompiler type-safety.
- var errorTyped = /** @type {{description: string}} */ error;
- remoting.debug.log('Error disconnecting: ' + errorTyped.description +
- '. The host plugin probably crashed.');
- // TODO(jamiewalch): Clean this up. We should have a class representing
- // the host plugin, like we do for the client, which should handle crash
- // reporting and it should use a more detailed error message than the
- // default 'generic' one. See crbug.com/94624
- showShareError_(/*i18n-content*/'ERROR_GENERIC');
+function pluginLostFocus_() {
+ if (remoting.clientSession && remoting.clientSession.plugin) {
+ remoting.clientSession.plugin.releaseAllKeys();
}
- disableTimeoutCountdown_();
}
/**
- * Cancel an incomplete connect operation.
- *
- * @return {void} Nothing.
+ * If the user is authenticated, but there is no email address cached, get one.
*/
-remoting.cancelConnect = function() {
- if (remoting.supportHostsXhr) {
- remoting.supportHostsXhr.abort();
- remoting.supportHostsXhr = null;
- }
- if (remoting.session) {
- remoting.session.removePlugin();
- remoting.session = null;
- }
- remoting.setMode(remoting.AppMode.HOME);
-}
-
-function updateStatistics() {
- if (!remoting.session)
- return;
- if (remoting.session.state != remoting.ClientSession.State.CONNECTED)
- return;
- var stats = remoting.session.stats();
-
- var units = '';
- var videoBandwidth = stats['video_bandwidth'];
- if (videoBandwidth < 1024) {
- units = 'Bps';
- } else if (videoBandwidth < 1048576) {
- units = 'KiBps';
- videoBandwidth = videoBandwidth / 1024;
- } else if (videoBandwidth < 1073741824) {
- units = 'MiBps';
- videoBandwidth = videoBandwidth / 1048576;
- } else {
- units = 'GiBps';
- videoBandwidth = videoBandwidth / 1073741824;
- }
-
- var statistics = document.getElementById('statistics');
- statistics.innerText =
- 'Bandwidth: ' + videoBandwidth.toFixed(2) + units +
- ', Frame Rate: ' +
- (stats['video_frame_rate'] ?
- stats['video_frame_rate'].toFixed(2) + ' fps' : 'n/a') +
- ', Capture: ' + stats['capture_latency'].toFixed(2) + 'ms' +
- ', Encode: ' + stats['encode_latency'].toFixed(2) + 'ms' +
- ', Decode: ' + stats['decode_latency'].toFixed(2) + 'ms' +
- ', Render: ' + stats['render_latency'].toFixed(2) + 'ms' +
- ', Latency: ' + stats['roundtrip_latency'].toFixed(2) + 'ms';
-
- // Update the stats once per second.
- window.setTimeout(updateStatistics, 1000);
-}
-
-function showToolbarPreview_() {
- var toolbar = document.getElementById('session-toolbar');
- addClass(toolbar, 'toolbar-preview');
- window.setTimeout(removeClass, 3000, toolbar, 'toolbar-preview');
-}
-
-/** @param {number} oldState The previous state of the plugin. */
-function onClientStateChange_(oldState) {
- if (!remoting.session) {
- // If the connection has been cancelled, then we no longer have a reference
- // to the session object and should ignore any state changes.
- return;
- }
- var state = remoting.session.state;
- if (state == remoting.ClientSession.State.CREATED) {
- remoting.debug.log('Created plugin');
- } else if (state == remoting.ClientSession.State.BAD_PLUGIN_VERSION) {
- showConnectError_(remoting.ClientError.BAD_PLUGIN_VERSION);
- } else if (state == remoting.ClientSession.State.CONNECTING) {
- remoting.debug.log('Connecting as ' + remoting.username);
- } else if (state == remoting.ClientSession.State.INITIALIZING) {
- remoting.debug.log('Initializing connection');
- } else if (state == remoting.ClientSession.State.CONNECTED) {
- if (remoting.session) {
- remoting.setMode(remoting.AppMode.IN_SESSION);
- recenterToolbar_();
- showToolbarPreview_();
- updateStatistics();
- }
- } else if (state == remoting.ClientSession.State.CLOSED) {
- if (oldState == remoting.ClientSession.State.CONNECTED) {
- remoting.session.removePlugin();
- remoting.session = null;
- remoting.debug.log('Connection closed by host');
- remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED);
- } else {
- // The transition from CONNECTING to CLOSED state may happen
- // only with older client plugins. Current version should go the
- // FAILED state when connection fails.
- showConnectError_(remoting.ClientError.INVALID_ACCESS_CODE);
- }
- } else if (state == remoting.ClientSession.State.CONNECTION_FAILED) {
- remoting.debug.log('Client plugin reported connection failed: ' +
- remoting.session.error);
- if (remoting.session.error ==
- remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE) {
- showConnectError_(remoting.ClientError.HOST_IS_OFFLINE);
- } else if (remoting.session.error ==
- remoting.ClientSession.ConnectionError.SESSION_REJECTED) {
- showConnectError_(remoting.ClientError.INVALID_ACCESS_CODE);
- } else if (remoting.session.error ==
- remoting.ClientSession.ConnectionError.INCOMPATIBLE_PROTOCOL) {
- showConnectError_(remoting.ClientError.INCOMPATIBLE_PROTOCOL);
- } else if (remoting.session.error ==
- remoting.ClientSession.ConnectionError.NETWORK_FAILURE) {
- showConnectError_(remoting.ClientError.OTHER_ERROR);
- } else {
- showConnectError_(remoting.ClientError.OTHER_ERROR);
- }
- } else {
- remoting.debug.log('Unexpected client plugin state: ' + state);
- // This should only happen if the web-app and client plugin get out of
- // sync, and even then the version check should allow compatibility.
- showConnectError_(remoting.ClientError.MISSING_PLUGIN);
+function refreshEmail_() {
+ if (!getEmail_() && remoting.oauth2.isAuthenticated()) {
+ remoting.oauth2.getEmail(setEmail_);
}
}
-function startSession_() {
- remoting.debug.log('Starting session...');
- var accessCode = document.getElementById('access-code-entry');
- accessCode.value = ''; // The code has been validated and won't work again.
- remoting.username =
- /** @type {string} email must be non-NULL to get here */ getEmail();
- remoting.session =
- new remoting.ClientSession(remoting.hostJid, remoting.hostPublicKey,
- remoting.accessCode, remoting.username,
- onClientStateChange_);
- /** @param {string} token The auth token. */
- var createPluginAndConnect = function(token) {
- remoting.session.createPluginAndConnect(
- document.getElementById('session-mode'),
- token);
- };
- remoting.oauth2.callWithToken(createPluginAndConnect);
-}
+/** The key under which the email address is stored. */
+var KEY_EMAIL_ = 'remoting-email';
/**
- * Show a client-side error message.
+ * Save the user's email address in local storage.
*
- * @param {remoting.ClientError} errorTag The error to be localized and
- * displayed.
- * @return {void} Nothing.
- */
-function showConnectError_(errorTag) {
- remoting.debug.log('Connection failed: ' + errorTag);
- var errorDiv = document.getElementById('connect-error-message');
- l10n.localizeElementFromTag(errorDiv, /** @type {string} */ (errorTag));
- remoting.accessCode = '';
- if (remoting.session) {
- remoting.session.disconnect();
- remoting.session = null;
- }
- remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED);
-}
-
-/**
- * @param {XMLHttpRequest} xhr The XMLHttpRequest object.
+ * @param {?string} email The email address to place in local storage.
* @return {void} Nothing.
*/
-function parseServerResponse_(xhr) {
- remoting.supportHostsXhr = null;
- remoting.debug.log('parseServerResponse: status = ' + xhr.status);
- if (xhr.status == 200) {
- var host = /** @type {{data: {jabberId: string, publicKey: string}}} */
- JSON.parse(xhr.responseText);
- if (host.data && host.data.jabberId && host.data.publicKey) {
- remoting.hostJid = host.data.jabberId;
- remoting.hostPublicKey = host.data.publicKey;
- var split = remoting.hostJid.split('/');
- document.getElementById('connected-to').innerText = split[0];
- startSession_();
- return;
- }
- }
- var errorMsg = remoting.ClientError.OTHER_ERROR;
- if (xhr.status == 404) {
- errorMsg = remoting.ClientError.INVALID_ACCESS_CODE;
- } else if (xhr.status == 0) {
- errorMsg = remoting.ClientError.NO_RESPONSE;
- } else {
- remoting.debug.log('The server responded: ' + xhr.responseText);
- }
- showConnectError_(errorMsg);
-}
-
-/** @param {string} accessCode The access code, as entered by the user.
- * @return {string} The normalized form of the code (whitespace removed). */
-function normalizeAccessCode_(accessCode) {
- // Trim whitespace.
- // TODO(sergeyu): Do we need to do any other normalization here?
- return accessCode.replace(/\s/g, '');
-}
-
-/** @param {string} supportId The canonicalized support ID. */
-function resolveSupportId(supportId) {
- var headers = {
- 'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken()
- };
-
- remoting.supportHostsXhr = remoting.xhr.get(
- 'https://www.googleapis.com/chromoting/v1/support-hosts/' +
- encodeURIComponent(supportId),
- parseServerResponse_,
- '',
- headers);
-}
-
-remoting.tryConnect = function() {
- document.getElementById('cancel-button').disabled = false;
- if (remoting.oauth2.needsNewAccessToken()) {
- remoting.oauth2.refreshAccessToken(function(xhr) {
- if (remoting.oauth2.needsNewAccessToken()) {
- // Failed to get access token
- remoting.debug.log('tryConnect: OAuth2 token fetch failed');
- showConnectError_(remoting.ClientError.OAUTH_FETCH_FAILED);
- return;
- }
- remoting.tryConnectWithAccessToken();
- });
+function setEmail_(email) {
+ if (email) {
+ document.getElementById('current-email').innerText = email;
} else {
- remoting.tryConnectWithAccessToken();
+ // TODO(ajwong): Have a better way of showing an error.
+ document.getElementById('current-email').innerText = '???';
}
}
-remoting.tryConnectWithAccessToken = function() {
- if (!remoting.wcsLoader) {
- remoting.wcsLoader = new remoting.WcsLoader();
- }
- /** @param {function(string):void} setToken The callback function. */
- var callWithToken = function(setToken) {
- remoting.oauth2.callWithToken(setToken);
- };
- remoting.wcsLoader.start(
- remoting.oauth2.getAccessToken(),
- callWithToken,
- remoting.tryConnectWithWcs);
-}
-
/**
- * WcsLoader callback, called when the wcs script has been loaded, or on error.
- * @param {boolean} success True if the script was loaded successfully.
+ * Read the user's email address from local storage.
+ *
+ * @return {?string} The email address associated with the auth credentials.
*/
-remoting.tryConnectWithWcs = function(success) {
- if (success) {
- var accessCode = document.getElementById('access-code-entry').value;
- remoting.accessCode = normalizeAccessCode_(accessCode);
- // At present, only 12-digit access codes are supported, of which the first
- // 7 characters are the supportId.
- if (remoting.accessCode.length != kAccessCodeLen) {
- remoting.debug.log('Bad access code length');
- showConnectError_(remoting.ClientError.INVALID_ACCESS_CODE);
- } else {
- var supportId = remoting.accessCode.substring(0, kSupportIdLen);
- remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
- resolveSupportId(supportId);
- }
- } else {
- showConnectError_(remoting.ClientError.OAUTH_FETCH_FAILED);
- }
-}
-
-remoting.cancelPendingOperation = function() {
- document.getElementById('cancel-button').disabled = true;
- switch (remoting.getMajorMode()) {
- case remoting.AppMode.HOST:
- remoting.cancelShare();
- break;
- case remoting.AppMode.CLIENT:
- remoting.cancelConnect();
- break;
- }
+function getEmail_() {
+ var result = window.localStorage.getItem(KEY_EMAIL_);
+ return typeof result == 'string' ? result : null;
}
-
/**
* Gets the major-mode that this application should start up in.
*
* @return {remoting.AppMode} The mode to start in.
*/
-function getAppStartupMode() {
+function getAppStartupMode_() {
if (!remoting.oauth2.isAuthenticated()) {
return remoting.AppMode.UNAUTHENTICATED;
}
- if (isHostModeSupported()) {
+ if (isHostModeSupported_()) {
return remoting.AppMode.HOME;
} else {
return remoting.AppMode.CLIENT_UNCONNECTED;
@@ -769,86 +151,8 @@ function getAppStartupMode() {
*
* @return {boolean} True if Host mode is supported.
*/
-function isHostModeSupported() {
+function isHostModeSupported_() {
// Currently, sharing on Chromebooks is not supported.
return !navigator.userAgent.match(/\bCrOS\b/);
}
-
-/**
- * Enable or disable scale-to-fit.
- *
- * @param {Element} button The scale-to-fit button. The style of this button is
- * updated to reflect the new scaling state.
- * @return {void} Nothing.
- */
-remoting.toggleScaleToFit = function(button) {
- remoting.scaleToFit = !remoting.scaleToFit;
- if (remoting.scaleToFit) {
- addClass(button, 'toggle-button-active');
- } else {
- removeClass(button, 'toggle-button-active');
- }
- remoting.session.updateDimensions();
-}
-
-/**
- * Update the remoting client layout in response to a resize event.
- *
- * @return {void} Nothing.
- */
-remoting.onResize = function() {
- if (remoting.session)
- remoting.session.onWindowSizeChanged();
- recenterToolbar_();
-}
-
-/**
- * Disconnect the remoting client.
- *
- * @return {void} Nothing.
- */
-remoting.disconnect = function() {
- if (remoting.session) {
- remoting.session.disconnect();
- remoting.session = null;
- remoting.debug.log('Disconnected.');
- remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED);
- }
-}
-
-/**
- * If the client is connected, or the host is shared, prompt before closing.
- *
- * @return {?string} The prompt string if a connection is active.
- */
-remoting.promptClose = function() {
- switch (remoting.currentMode) {
- case remoting.AppMode.CLIENT_CONNECTING:
- case remoting.AppMode.HOST_WAITING_FOR_CODE:
- case remoting.AppMode.HOST_WAITING_FOR_CONNECTION:
- case remoting.AppMode.HOST_SHARED:
- case remoting.AppMode.IN_SESSION:
- var result = chrome.i18n.getMessage(/*i18n-content*/'CLOSE_PROMPT');
- return result;
- default:
- return null;
- }
-}
-
-/**
- * @param {Event} event The keyboard event.
- * @return {void} Nothing.
- */
-remoting.checkHotkeys = function(event) {
- if (String.fromCharCode(event.which) == 'D') {
- remoting.toggleDebugLog();
- }
-}
-
-function recenterToolbar_() {
- var toolbar = document.getElementById('session-toolbar');
- var toolbarX = (window.innerWidth - toolbar.clientWidth) / 2;
- toolbar.style['left'] = toolbarX + 'px';
-}
-
}());
diff --git a/remoting/webapp/me2mom/ui_mode.js b/remoting/webapp/me2mom/ui_mode.js
new file mode 100644
index 0000000..e3d54a6
--- /dev/null
+++ b/remoting/webapp/me2mom/ui_mode.js
@@ -0,0 +1,78 @@
+// Copyright (c) 2011 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.
+
+/**
+ * @fileoverview
+ * Functions related to controlling the modal ui state of the app.
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/** @enum {string} */
+remoting.AppMode = {
+ HOME: 'home',
+ UNAUTHENTICATED: 'auth',
+ CLIENT: 'client',
+ CLIENT_UNCONNECTED: 'client.unconnected',
+ CLIENT_CONNECTING: 'client.connecting',
+ CLIENT_CONNECT_FAILED: 'client.connect-failed',
+ CLIENT_SESSION_FINISHED: 'client.session-finished',
+ HOST: 'host',
+ HOST_WAITING_FOR_CODE: 'host.waiting-for-code',
+ HOST_WAITING_FOR_CONNECTION: 'host.waiting-for-connection',
+ HOST_SHARED: 'host.shared',
+ HOST_SHARE_FAILED: 'host.share-failed',
+ HOST_SHARE_FINISHED: 'host.share-finished',
+ IN_SESSION: 'in-session'
+};
+
+/**
+ * @type {remoting.AppMode} The current app mode
+ */
+remoting.currentMode;
+
+/**
+ * Change the app's modal state to |mode|, which is considered to be a dotted
+ * hierachy of modes. For example, setMode('host.shared') will show any modal
+ * elements with an data-ui-mode attribute of 'host' or 'host.shared' and hide
+ * all others.
+ *
+ * @param {remoting.AppMode} mode The new modal state, expressed as a dotted
+ * hiearchy.
+ */
+remoting.setMode = function(mode) {
+ var modes = mode.split('.');
+ for (var i = 1; i < modes.length; ++i)
+ modes[i] = modes[i - 1] + '.' + modes[i];
+ var elements = document.querySelectorAll('[data-ui-mode]');
+ for (var i = 0; i < elements.length; ++i) {
+ var element = /** @type {Element} */ elements[i];
+ var hidden = true;
+ for (var m = 0; m < modes.length; ++m) {
+ if (hasClass(element.getAttribute('data-ui-mode'), modes[m])) {
+ hidden = false;
+ break;
+ }
+ }
+ element.hidden = hidden;
+ }
+ remoting.debug.log('App mode: ' + mode);
+ remoting.currentMode = mode;
+ if (mode == remoting.AppMode.IN_SESSION) {
+ document.removeEventListener('keydown', remoting.DebugLog.onKeydown, false);
+ } else {
+ document.addEventListener('keydown', remoting.DebugLog.onKeydown, false);
+ }
+};
+
+/**
+ * Get the major mode that the app is running in.
+ * @return {string} The app's current major mode.
+ */
+remoting.getMajorMode = function() {
+ return remoting.currentMode.split('.')[0];
+};
diff --git a/remoting/webapp/me2mom/util.js b/remoting/webapp/me2mom/util.js
new file mode 100644
index 0000000..0c15c0c
--- /dev/null
+++ b/remoting/webapp/me2mom/util.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2011 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.
+
+/**
+ * @fileoverview
+ * Simple utility functions for Chromoting.
+ */
+
+/**
+ * @param {string} classes A space-separated list of classes.
+ * @param {string} cls The class to check for.
+ * @return {boolean} True if |cls| is found within |classes|.
+ */
+function hasClass(classes, cls) {
+ return classes.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) != null;
+}
+
+/**
+ * @param {Element} element The element to which to add the class.
+ * @param {string} cls The new class.
+ * @return {void} Nothing.
+ */
+function addClass(element, cls) {
+ if (!hasClass(element.className, cls)) {
+ var padded = element.className == '' ? '' : element.className + ' ';
+ element.className = padded + cls;
+ }
+}
+
+/**
+ * @param {Element} element The element from which to remove the class.
+ * @param {string} cls The new class.
+ * @return {void} Nothing.
+ */
+function removeClass(element, cls) {
+ element.className =
+ element.className.replace(new RegExp('\\b' + cls + '\\b', 'g'), '')
+ .replace(' ', ' ');
+}
diff --git a/remoting/webapp/me2mom/wcs_loader.js b/remoting/webapp/me2mom/wcs_loader.js
index 241e3e8..8b931f11 100644
--- a/remoting/webapp/me2mom/wcs_loader.js
+++ b/remoting/webapp/me2mom/wcs_loader.js
@@ -143,5 +143,5 @@ remoting.WcsLoader.prototype.constructWcs_ = function() {
remoting.WcsLoader.prototype.onWcsReady_ = function() {
this.loadState_ = this.LoadState_.READY;
this.onReady_(true);
- this.onReady_ = function() {};
+ this.onReady_ = function(success) {};
};