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
parent c9f0a0e4
...@@ -84,6 +84,8 @@ class HostNPScriptObject : public HostStatusObserver { ...@@ -84,6 +84,8 @@ class HostNPScriptObject : public HostStatusObserver {
void PostLogDebugInfo(const std::string& message); void PostLogDebugInfo(const std::string& message);
private: private:
// These state values are duplicated in the JS code. Remember to update both
// copies when making changes.
enum State { enum State {
kDisconnected, kDisconnected,
kStarting, kStarting,
......
...@@ -97,12 +97,15 @@ ...@@ -97,12 +97,15 @@
'resources/icon_warning.png', 'resources/icon_warning.png',
'webapp/me2mom/choice.css', 'webapp/me2mom/choice.css',
'webapp/me2mom/choice.html', 'webapp/me2mom/choice.html',
'webapp/me2mom/client_screen.js',
'webapp/me2mom/client_session.js', 'webapp/me2mom/client_session.js',
'webapp/me2mom/cs_oauth2_trampoline.js', 'webapp/me2mom/cs_oauth2_trampoline.js',
'webapp/me2mom/debug_log.css', 'webapp/me2mom/debug_log.css',
'webapp/me2mom/debug_log.js', 'webapp/me2mom/debug_log.js',
'webapp/me2mom/dividerbottom.png', 'webapp/me2mom/dividerbottom.png',
'webapp/me2mom/dividertop.png', 'webapp/me2mom/dividertop.png',
'webapp/me2mom/host_screen.js',
'webapp/me2mom/host_session.js',
'webapp/me2mom/l10n.js', 'webapp/me2mom/l10n.js',
'webapp/me2mom/main.css', 'webapp/me2mom/main.css',
'webapp/me2mom/manifest.json', 'webapp/me2mom/manifest.json',
...@@ -113,6 +116,8 @@ ...@@ -113,6 +116,8 @@
'webapp/me2mom/scale-to-fit.png', 'webapp/me2mom/scale-to-fit.png',
'webapp/me2mom/spinner.gif', 'webapp/me2mom/spinner.gif',
'webapp/me2mom/toolbar.css', 'webapp/me2mom/toolbar.css',
'webapp/me2mom/ui_mode.js',
'webapp/me2mom/util.js',
'webapp/me2mom/wcs.js', 'webapp/me2mom/wcs.js',
'webapp/me2mom/wcs_loader.js', 'webapp/me2mom/wcs_loader.js',
'webapp/me2mom/xhr.js', 'webapp/me2mom/xhr.js',
...@@ -324,6 +329,8 @@ ...@@ -324,6 +329,8 @@
'webapp/me2mom/choice.html', 'webapp/me2mom/choice.html',
'webapp/me2mom/manifest.json', 'webapp/me2mom/manifest.json',
'webapp/me2mom/remoting.js', 'webapp/me2mom/remoting.js',
'webapp/me2mom/client_screen.js',
'webapp/me2mom/host_screen.js',
'host/plugin/host_script_object.cc', 'host/plugin/host_script_object.cc',
], ],
'outputs': [ 'outputs': [
...@@ -337,6 +344,8 @@ ...@@ -337,6 +344,8 @@
'webapp/me2mom/choice.html', 'webapp/me2mom/choice.html',
'webapp/me2mom/manifest.json', 'webapp/me2mom/manifest.json',
'webapp/me2mom/remoting.js', 'webapp/me2mom/remoting.js',
'webapp/me2mom/client_screen.js',
'webapp/me2mom/host_screen.js',
'host/plugin/host_script_object.cc', 'host/plugin/host_script_object.cc',
], ],
}, },
......
...@@ -15,12 +15,17 @@ found in the LICENSE file. ...@@ -15,12 +15,17 @@ found in the LICENSE file.
<link rel="stylesheet" href="main.css" /> <link rel="stylesheet" href="main.css" />
<link rel="stylesheet" href="choice.css" /> <link rel="stylesheet" href="choice.css" />
<link rel="stylesheet" href="toolbar.css" /> <link rel="stylesheet" href="toolbar.css" />
<script src="client_screen.js"></script>
<script src="client_session.js"></script> <script src="client_session.js"></script>
<script src="debug_log.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="l10n.js"></script>
<script src="oauth2.js"></script> <script src="oauth2.js"></script>
<script src="plugin_settings.js"></script> <script src="plugin_settings.js"></script>
<script src="remoting.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="xhr.js"></script>
<script src="wcs.js"></script> <script src="wcs.js"></script>
<script src="wcs_loader.js"></script> <script src="wcs_loader.js"></script>
......
This diff is collapsed.
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
/** /**
* @fileoverview * @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 * 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 * actual remoting work. There should be no UI code inside this class. It
......
...@@ -15,13 +15,17 @@ var remoting = remoting || {}; ...@@ -15,13 +15,17 @@ var remoting = remoting || {};
/** /**
* @constructor * @constructor
* @param {Element} logElement The HTML div to which to add log messages. * @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) { remoting.DebugLog = function(logElement, statsElement) {
this.debugLog = logElement; 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; remoting.DebugLog.prototype.MAX_DEBUG_LOG_SIZE = 1000;
/** /**
...@@ -31,18 +35,75 @@ 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) { remoting.DebugLog.prototype.log = function(message) {
// Remove lines from top if we've hit our max log size. // Remove lines from top if we've hit our max log size.
if (this.debugLog.childNodes.length == this.MAX_DEBUG_LOG_SIZE) { if (this.logElement.childNodes.length == this.MAX_DEBUG_LOG_SIZE) {
this.debugLog.removeChild(this.debugLog.firstChild); this.logElement.removeChild(this.logElement.firstChild);
} }
// Add the new <p> to the end of the debug log. // Add the new <p> to the end of the debug log.
var p = document.createElement('p'); var p = document.createElement('p');
p.appendChild(document.createTextNode(message)); p.appendChild(document.createTextNode(message));
this.debugLog.appendChild(p); this.logElement.appendChild(p);
// Scroll to bottom of div // 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} */ /** @type {remoting.DebugLog} */
remoting.debug = null; remoting.debug = null;
// 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;
}
}());
// 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);
};
...@@ -29,20 +29,26 @@ remoting.OAuth2 = function() { ...@@ -29,20 +29,26 @@ remoting.OAuth2 = function() {
remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token'; remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token';
/** @private */ /** @private */
remoting.OAuth2.prototype.KEY_ACCESS_TOKEN_ = 'oauth2-access-token'; 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. // Constants for parameters used in retrieving the OAuth2 credentials.
/** @private */ remoting.OAuth2.prototype.CLIENT_ID_ = /** @private */
remoting.OAuth2.prototype.CLIENT_ID_ =
'440925447803-2pi3v45bff6tp1rde2f7q6lgbor3o5uj.' + '440925447803-2pi3v45bff6tp1rde2f7q6lgbor3o5uj.' +
'apps.googleusercontent.com'; 'apps.googleusercontent.com';
/** @private */ /** @private */
remoting.OAuth2.prototype.CLIENT_SECRET_ = 'W2ieEsG-R1gIA4MMurGrgMc_'; 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/chromoting ' +
'https://www.googleapis.com/auth/googletalk ' + 'https://www.googleapis.com/auth/googletalk ' +
'https://www.googleapis.com/auth/userinfo#email'; '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'; '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'; 'https://accounts.google.com/o/oauth2/token';
/** @return {boolean} True if the app is already authenticated. */ /** @return {boolean} True if the app is already authenticated. */
...@@ -60,6 +66,7 @@ remoting.OAuth2.prototype.isAuthenticated = function() { ...@@ -60,6 +66,7 @@ remoting.OAuth2.prototype.isAuthenticated = function() {
*/ */
remoting.OAuth2.prototype.clear = function() { remoting.OAuth2.prototype.clear = function() {
window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_); window.localStorage.removeItem(this.KEY_REFRESH_TOKEN_);
window.localStorage.removeItem(this.KEY_EMAIL_);
this.clearAccessToken(); this.clearAccessToken();
}; };
...@@ -291,3 +298,49 @@ remoting.OAuth2.prototype.callWithToken = function(myfunc) { ...@@ -291,3 +298,49 @@ remoting.OAuth2.prototype.callWithToken = function(myfunc) {
myfunc(this.getAccessToken()); 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;
};
This diff is collapsed.
// 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];
};
// 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(' ', ' ');
}
...@@ -143,5 +143,5 @@ remoting.WcsLoader.prototype.constructWcs_ = function() { ...@@ -143,5 +143,5 @@ remoting.WcsLoader.prototype.constructWcs_ = function() {
remoting.WcsLoader.prototype.onWcsReady_ = function() { remoting.WcsLoader.prototype.onWcsReady_ = function() {
this.loadState_ = this.LoadState_.READY; this.loadState_ = this.LoadState_.READY;
this.onReady_(true); this.onReady_(true);
this.onReady_ = function() {}; this.onReady_ = function(success) {};
}; };
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