0

Improve web-app typing.

BUG=None
TEST=Manual

Review URL: http://codereview.chromium.org/8336004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@106072 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
jamiewalch@google.com
2011-10-18 16:43:21 +00:00
parent 4b2e0b7bdd
commit 0bc497a026
11 changed files with 242 additions and 145 deletions

@@ -16,7 +16,6 @@
/** @suppress {duplicate} */ /** @suppress {duplicate} */
var remoting = remoting || {}; var remoting = remoting || {};
(function() {
/** /**
* @param {string} hostJid The jid of the host to connect to. * @param {string} hostJid The jid of the host to connect to.
* @param {string} hostPublicKey The base64 encoded version of the host's * @param {string} hostPublicKey The base64 encoded version of the host's
@@ -38,6 +37,9 @@ remoting.ClientSession = function(hostJid, hostPublicKey, accessCode, email,
this.accessCode = accessCode; this.accessCode = accessCode;
this.email = email; this.email = email;
this.clientJid = ''; this.clientJid = '';
this.sessionId = '';
/** @type {remoting.ViewerPlugin} */ this.plugin = null;
this.onStateChange = onStateChange; this.onStateChange = onStateChange;
}; };
@@ -117,7 +119,7 @@ remoting.ClientSession.prototype.PLUGIN_ID = 'session-client-plugin';
/** /**
* Callback to invoke when the state is changed. * Callback to invoke when the state is changed.
* *
* @type {function(remoting.ClientSession.State):void} * @param {remoting.ClientSession.State} state The previous state.
*/ */
remoting.ClientSession.prototype.onStateChange = function(state) { }; remoting.ClientSession.prototype.onStateChange = function(state) { };
@@ -146,8 +148,10 @@ remoting.ClientSession.prototype.createPluginAndConnect =
return; return;
} }
var that = this; /** @type {remoting.ClientSession} */ var that = this;
/** @param {string} msg The IQ stanza to send. */
this.plugin.sendIq = function(msg) { that.sendIq_(msg); }; this.plugin.sendIq = function(msg) { that.sendIq_(msg); };
/** @param {string} msg The message to log. */
this.plugin.debugInfo = function(msg) { this.plugin.debugInfo = function(msg) {
remoting.debug.log('plugin: ' + msg); remoting.debug.log('plugin: ' + msg);
}; };
@@ -180,13 +184,12 @@ remoting.ClientSession.prototype.createPluginAndConnect =
* @return {void} Nothing. * @return {void} Nothing.
*/ */
remoting.ClientSession.prototype.removePlugin = function() { remoting.ClientSession.prototype.removePlugin = function() {
var plugin = document.getElementById(this.PLUGIN_ID); if (this.plugin) {
if (plugin) {
var parentNode = this.plugin.parentNode; var parentNode = this.plugin.parentNode;
parentNode.removeChild(plugin); parentNode.removeChild(this.plugin);
plugin = null; this.plugin = null;
} }
} };
/** /**
* Deletes the <embed> element from the container and disconnects. * Deletes the <embed> element from the container and disconnects.
@@ -266,11 +269,13 @@ remoting.ClientSession.prototype.connectPluginToWcs_ =
if (this.clientJid == '') { if (this.clientJid == '') {
remoting.debug.log('Tried to connect without a full JID.'); remoting.debug.log('Tried to connect without a full JID.');
} }
var that = this; /** @type {remoting.ClientSession} */ var that = this;
remoting.wcs.setOnIq(function(stanza) { /** @param {string} stanza The IQ stanza received. */
remoting.debug.log('Receiving Iq: ' + stanza); var onIq = function(stanza) {
that.plugin.onIq(stanza); remoting.debug.log('Receiving Iq: ' + stanza);
}); that.plugin.onIq(stanza);
}
remoting.wcs.setOnIq(onIq);
that.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid, that.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid,
this.accessCode); this.accessCode);
}; };
@@ -302,7 +307,7 @@ remoting.ClientSession.prototype.connectionInfoUpdateCallback = function() {
error = remoting.ClientSession.ConnectionError.SESSION_REJECTED; error = remoting.ClientSession.ConnectionError.SESSION_REJECTED;
} else if (error == this.plugin.ERROR_INCOMPATIBLE_PROTOCOL) { } else if (error == this.plugin.ERROR_INCOMPATIBLE_PROTOCOL) {
error = remoting.ClientSession.ConnectionError.INCOMPATIBLE_PROTOCOL; error = remoting.ClientSession.ConnectionError.INCOMPATIBLE_PROTOCOL;
} else if (error == this.plugin.NETWORK_FAILURE) { } else if (error == this.plugin.ERROR_NETWORK_FAILURE) {
error = remoting.ClientSession.ConnectionError.NETWORK_FAILURE; error = remoting.ClientSession.ConnectionError.NETWORK_FAILURE;
} else { } else {
error = remoting.ClientSession.ConnectionError.OTHER; error = remoting.ClientSession.ConnectionError.OTHER;
@@ -327,7 +332,6 @@ remoting.ClientSession.prototype.setState_ = function(state) {
/** /**
* This is a callback that gets called when the window is resized. * This is a callback that gets called when the window is resized.
* *
* @private
* @return {void} Nothing. * @return {void} Nothing.
*/ */
remoting.ClientSession.prototype.onWindowSizeChanged = function() { remoting.ClientSession.prototype.onWindowSizeChanged = function() {
@@ -382,11 +386,11 @@ remoting.ClientSession.prototype.updateDimensions = function() {
if (this.plugin.width < windowWidth) if (this.plugin.width < windowWidth)
parentNode.style.left = (windowWidth - this.plugin.width) / 2 + 'px'; parentNode.style.left = (windowWidth - this.plugin.width) / 2 + 'px';
else else
parentNode.style.left = 0; parentNode.style.left = '0';
if (this.plugin.height < windowHeight) if (this.plugin.height < windowHeight)
parentNode.style.top = (windowHeight - this.plugin.height) / 2 + 'px'; parentNode.style.top = (windowHeight - this.plugin.height) / 2 + 'px';
else else
parentNode.style.top = 0; parentNode.style.top = '0';
remoting.debug.log('plugin dimensions: ' + remoting.debug.log('plugin dimensions: ' +
parentNode.style.left + ',' + parentNode.style.left + ',' +
@@ -398,7 +402,7 @@ remoting.ClientSession.prototype.updateDimensions = function() {
/** /**
* Returns an associative array with a set of stats for this connection. * Returns an associative array with a set of stats for this connection.
* *
* @return {Object} The connection statistics. * @return {Object.<string, number>} The connection statistics.
*/ */
remoting.ClientSession.prototype.stats = function() { remoting.ClientSession.prototype.stats = function() {
return { return {
@@ -410,5 +414,3 @@ remoting.ClientSession.prototype.stats = function() {
'roundtrip_latency': this.plugin.roundTripLatency 'roundtrip_latency': this.plugin.roundTripLatency
}; };
}; };
}());

@@ -12,16 +12,17 @@
/** @suppress {duplicate} */ /** @suppress {duplicate} */
var remoting = remoting || {}; var remoting = remoting || {};
(function() { /**
* @constructor
/** @constructor */ * @param {Element} logElement The HTML div to which to add log messages.
*/
remoting.DebugLog = function(logElement) { remoting.DebugLog = function(logElement) {
this.debugLog = logElement; this.debugLog = logElement;
} };
// Maximum numer of lines to record in the debug log. /** Maximum number of lines to record in the debug log. Only the most
// Only the most recent <n> lines are displayed. * recent <n> lines are displayed. */
var MAX_DEBUG_LOG_SIZE = 1000; remoting.DebugLog.prototype.MAX_DEBUG_LOG_SIZE = 1000;
/** /**
* Add the given message to the debug log. * Add the given message to the debug log.
@@ -30,7 +31,7 @@ var 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 == MAX_DEBUG_LOG_SIZE) { if (this.debugLog.childNodes.length == this.MAX_DEBUG_LOG_SIZE) {
this.debugLog.removeChild(this.debugLog.firstChild); this.debugLog.removeChild(this.debugLog.firstChild);
} }
@@ -41,6 +42,7 @@ remoting.DebugLog.prototype.log = function(message) {
// Scroll to bottom of div // Scroll to bottom of div
this.debugLog.scrollTop = this.debugLog.scrollHeight; this.debugLog.scrollTop = this.debugLog.scrollHeight;
} };
}()); /** @type {remoting.DebugLog} */
remoting.debug = null;

@@ -35,6 +35,7 @@ remoting.HostPlugin.prototype.localize = function(callback) {};
/** @type {number} */ remoting.HostPlugin.prototype.DISCONNECTING; /** @type {number} */ remoting.HostPlugin.prototype.DISCONNECTING;
/** @type {number} */ remoting.HostPlugin.prototype.ERROR; /** @type {number} */ remoting.HostPlugin.prototype.ERROR;
/** @type {string} */ remoting.HostPlugin.prototype.accessCode;
/** @type {number} */ remoting.HostPlugin.prototype.accessCodeLifetime; /** @type {number} */ remoting.HostPlugin.prototype.accessCodeLifetime;
/** @type {string} */ remoting.HostPlugin.prototype.client; /** @type {string} */ remoting.HostPlugin.prototype.client;

@@ -44,7 +44,7 @@ l10n.localizeElement = function(element, opt_substitutions) {
l10n.localize = function() { l10n.localize = function() {
var elements = document.querySelectorAll('[i18n-content]'); var elements = document.querySelectorAll('[i18n-content]');
for (var i = 0; i < elements.length; ++i) { for (var i = 0; i < elements.length; ++i) {
var element = elements[i]; /** @type {Element} */ var element = elements[i];
var substitutions = null; var substitutions = null;
for (var j = 1; j < 9; ++j) { for (var j = 1; j < 9; ++j) {
var attr = 'i18n-value-' + j; var attr = 'i18n-value-' + j;

@@ -16,27 +16,33 @@
/** @suppress {duplicate} */ /** @suppress {duplicate} */
var remoting = remoting || {}; var remoting = remoting || {};
(function() { /** @type {remoting.OAuth2} */
remoting.oauth2 = null;
/** @constructor */ /** @constructor */
remoting.OAuth2 = function() { remoting.OAuth2 = function() {
} };
// Constants representing keys used for storing persistent state. // Constants representing keys used for storing persistent state.
/** @private */
remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token'; remoting.OAuth2.prototype.KEY_REFRESH_TOKEN_ = 'oauth2-refresh-token';
/** @private */
remoting.OAuth2.prototype.KEY_ACCESS_TOKEN_ = 'oauth2-access-token'; remoting.OAuth2.prototype.KEY_ACCESS_TOKEN_ = 'oauth2-access-token';
// Constants for parameters used in retrieving the OAuth2 credentials. // Constants for parameters used in retrieving the OAuth2 credentials.
remoting.OAuth2.prototype.CLIENT_ID_ = /** @private */ remoting.OAuth2.prototype.CLIENT_ID_ =
'440925447803-2pi3v45bff6tp1rde2f7q6lgbor3o5uj.' + '440925447803-2pi3v45bff6tp1rde2f7q6lgbor3o5uj.' +
'apps.googleusercontent.com'; 'apps.googleusercontent.com';
/** @private */
remoting.OAuth2.prototype.CLIENT_SECRET_ = 'W2ieEsG-R1gIA4MMurGrgMc_'; remoting.OAuth2.prototype.CLIENT_SECRET_ = 'W2ieEsG-R1gIA4MMurGrgMc_';
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';
remoting.OAuth2.prototype.REDIRECT_URI_ = /** @private */ remoting.OAuth2.prototype.REDIRECT_URI_ =
'https://talkgadget.google.com/talkgadget/blank'; 'https://talkgadget.google.com/talkgadget/blank';
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. */
@@ -45,7 +51,7 @@ remoting.OAuth2.prototype.isAuthenticated = function() {
return true; return true;
} }
return false; return false;
} };
/** /**
* Removes all storage, and effectively unauthenticates the user. * Removes all storage, and effectively unauthenticates the user.
@@ -55,7 +61,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_);
this.clearAccessToken(); this.clearAccessToken();
} };
/** /**
* @param {string} token The new refresh token. * @param {string} token The new refresh token.
@@ -64,7 +70,7 @@ remoting.OAuth2.prototype.clear = function() {
remoting.OAuth2.prototype.setRefreshToken = function(token) { remoting.OAuth2.prototype.setRefreshToken = function(token) {
window.localStorage.setItem(this.KEY_REFRESH_TOKEN_, escape(token)); window.localStorage.setItem(this.KEY_REFRESH_TOKEN_, escape(token));
this.clearAccessToken(); this.clearAccessToken();
} };
/** @return {?string} The refresh token, if authenticated, or NULL. */ /** @return {?string} The refresh token, if authenticated, or NULL. */
remoting.OAuth2.prototype.getRefreshToken = function() { remoting.OAuth2.prototype.getRefreshToken = function() {
@@ -73,7 +79,7 @@ remoting.OAuth2.prototype.getRefreshToken = function() {
return unescape(value); return unescape(value);
} }
return null; return null;
} };
/** /**
* @param {string} token The new access token. * @param {string} token The new access token.
@@ -84,12 +90,13 @@ remoting.OAuth2.prototype.setAccessToken = function(token, expiration) {
var access_token = {'token': token, 'expiration': expiration}; var access_token = {'token': token, 'expiration': expiration};
window.localStorage.setItem(this.KEY_ACCESS_TOKEN_, window.localStorage.setItem(this.KEY_ACCESS_TOKEN_,
JSON.stringify(access_token)); JSON.stringify(access_token));
} };
/** /**
* Returns the current access token, setting it to a invalid value if none * Returns the current access token, setting it to a invalid value if none
* existed before. * existed before.
* *
* @private
* @return {{token: string, expiration: number}} The current access token, or * @return {{token: string, expiration: number}} The current access token, or
* an invalid token if not authenticated. * an invalid token if not authenticated.
*/ */
@@ -107,7 +114,7 @@ remoting.OAuth2.prototype.getAccessTokenInternal_ = function() {
} }
console.log('Invalid access token stored.'); console.log('Invalid access token stored.');
return {'token': '', 'expiration': 0}; return {'token': '', 'expiration': 0};
} };
/** /**
* Returns true if the access token is expired, or otherwise invalid. * Returns true if the access token is expired, or otherwise invalid.
@@ -128,30 +135,31 @@ remoting.OAuth2.prototype.needsNewAccessToken = function() {
return true; return true;
} }
return false; return false;
} };
/** /**
* Returns the current access token. * Returns the current access token.
* *
* Will throw if !isAuthenticated() or needsNewAccessToken(). * Will throw if !isAuthenticated() or needsNewAccessToken().
* *
* @return {{token: string, expiration: number}} * @return {string} The access token.
*/ */
remoting.OAuth2.prototype.getAccessToken = function() { remoting.OAuth2.prototype.getAccessToken = function() {
if (this.needsNewAccessToken()) { if (this.needsNewAccessToken()) {
throw 'Access Token expired.'; throw 'Access Token expired.';
} }
return this.getAccessTokenInternal_()['token']; return this.getAccessTokenInternal_()['token'];
} };
/** @return {void} Nothing. */ /** @return {void} Nothing. */
remoting.OAuth2.prototype.clearAccessToken = function() { remoting.OAuth2.prototype.clearAccessToken = function() {
window.localStorage.removeItem(this.KEY_ACCESS_TOKEN_); window.localStorage.removeItem(this.KEY_ACCESS_TOKEN_);
} };
/** /**
* Update state based on token response from the OAuth2 /token endpoint. * Update state based on token response from the OAuth2 /token endpoint.
* *
* @private
* @param {XMLHttpRequest} xhr The XHR object for this request. * @param {XMLHttpRequest} xhr The XHR object for this request.
* @return {void} Nothing. * @return {void} Nothing.
*/ */
@@ -175,7 +183,7 @@ remoting.OAuth2.prototype.processTokenResponse_ = function(xhr) {
console.log('Failed to get tokens. Status: ' + xhr.status + console.log('Failed to get tokens. Status: ' + xhr.status +
' response: ' + xhr.responseText); ' response: ' + xhr.responseText);
} }
} };
/** /**
* Asynchronously retrieves a new access token from the server. * Asynchronously retrieves a new access token from the server.
@@ -198,14 +206,17 @@ remoting.OAuth2.prototype.refreshAccessToken = function(onDone) {
'grant_type': 'refresh_token' 'grant_type': 'refresh_token'
}; };
/** @type {remoting.OAuth2} */
var that = this; var that = this;
/** @param {XMLHttpRequest} xhr The XHR reply. */
var processTokenResponse = function(xhr) {
that.processTokenResponse_(xhr);
onDone(xhr);
};
remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_, remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_,
function(xhr) { processTokenResponse,
that.processTokenResponse_(xhr);
onDone(xhr);
},
parameters); parameters);
} };
/** /**
* Redirect page to get a new OAuth2 Refresh Token. * Redirect page to get a new OAuth2 Refresh Token.
@@ -221,7 +232,7 @@ remoting.OAuth2.prototype.doAuthRedirect = function() {
'response_type': 'code' 'response_type': 'code'
}); });
window.location.replace(GET_CODE_URL); window.location.replace(GET_CODE_URL);
} };
/** /**
* Asynchronously exchanges an authorization code for a refresh token. * Asynchronously exchanges an authorization code for a refresh token.
@@ -240,14 +251,17 @@ remoting.OAuth2.prototype.exchangeCodeForToken = function(code, onDone) {
'grant_type': 'authorization_code' 'grant_type': 'authorization_code'
}; };
/** @type {remoting.OAuth2} */
var that = this; var that = this;
/** @param {XMLHttpRequest} xhr The XHR reply. */
var processTokenResponse = function(xhr) {
that.processTokenResponse_(xhr);
onDone(xhr);
};
remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_, remoting.xhr.post(this.OAUTH2_TOKEN_ENDPOINT_,
function(xhr) { processTokenResponse,
that.processTokenResponse_(xhr);
onDone(xhr);
},
parameters); parameters);
} };
/** /**
* Call myfunc with an access token as the only parameter. * Call myfunc with an access token as the only parameter.
@@ -257,11 +271,12 @@ remoting.OAuth2.prototype.exchangeCodeForToken = function(code, onDone) {
* *
* The access token will remain valid for at least 2 minutes. * The access token will remain valid for at least 2 minutes.
* *
* @param {function({token: string, expiration: number}):void} myfunc * @param {function(string):void} myfunc
* Function to invoke with access token. * Function to invoke with access token.
* @return {void} Nothing. * @return {void} Nothing.
*/ */
remoting.OAuth2.prototype.callWithToken = function(myfunc) { remoting.OAuth2.prototype.callWithToken = function(myfunc) {
/** @type {remoting.OAuth2} */
var that = this; var that = this;
if (remoting.oauth2.needsNewAccessToken()) { if (remoting.oauth2.needsNewAccessToken()) {
remoting.oauth2.refreshAccessToken(function() { remoting.oauth2.refreshAccessToken(function() {
@@ -275,5 +290,4 @@ remoting.OAuth2.prototype.callWithToken = function(myfunc) {
} }
myfunc(this.getAccessToken()); myfunc(this.getAccessToken());
} };
}());

@@ -5,18 +5,26 @@
/** @suppress {duplicate} */ /** @suppress {duplicate} */
var remoting = remoting || {}; var remoting = remoting || {};
(function() {
'use strict'; 'use strict';
window.addEventListener('blur', pluginLostFocus_, false); /**
* Whether or not the plugin should scale itself.
* @type {boolean}
*/
remoting.scaleToFit = false;
function pluginLostFocus_() { /** @type {remoting.ClientSession} */
// If the plug loses input focus, release all keys as a precaution against remoting.session = null;
// leaving them 'stuck down' on the host.
if (remoting.session && remoting.session.plugin) { /** @type {string} */ remoting.accessCode = '';
remoting.session.plugin.releaseAllKeys(); /** @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} */ /** @enum {string} */
remoting.AppMode = { remoting.AppMode = {
@@ -36,7 +44,19 @@ remoting.AppMode = {
IN_SESSION: 'in-session' IN_SESSION: 'in-session'
}; };
remoting.EMAIL = 'email'; (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'; remoting.HOST_PLUGIN_ID = 'host-plugin-id';
/** @enum {string} */ /** @enum {string} */
@@ -51,27 +71,31 @@ remoting.ClientError = {
OTHER_ERROR: /*i18n-content*/'ERROR_GENERIC' OTHER_ERROR: /*i18n-content*/'ERROR_GENERIC'
}; };
/**
* Whether or not the plugin should scale itself.
* @type {boolean}
*/
remoting.scaleToFit = false;
// Constants representing keys used for storing persistent application state. // Constants representing keys used for storing persistent application state.
var KEY_APP_MODE_ = 'remoting-app-mode'; var KEY_APP_MODE_ = 'remoting-app-mode';
var KEY_EMAIL_ = 'remoting-email'; var KEY_EMAIL_ = 'remoting-email';
var KEY_USE_P2P_API_ = 'remoting-use-p2p-api'; var KEY_USE_P2P_API_ = 'remoting-use-p2p-api';
// Some constants for pretty-printing the access code. // Some constants for pretty-printing the access code.
var kSupportIdLen = 7; /** @type {number} */ var kSupportIdLen = 7;
var kHostSecretLen = 5; /** @type {number} */ var kHostSecretLen = 5;
var kAccessCodeLen = kSupportIdLen + kHostSecretLen; /** @type {number} */ var kAccessCodeLen = kSupportIdLen + kHostSecretLen;
var kDigitsPerGroup = 4; /** @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|.
*/
function hasClass(classes, cls) { function hasClass(classes, cls) {
return classes.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); 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) { function addClass(element, cls) {
if (!hasClass(element.className, cls)) { if (!hasClass(element.className, cls)) {
var padded = element.className == '' ? '' : element.className + ' '; var padded = element.className == '' ? '' : element.className + ' ';
@@ -79,6 +103,11 @@ function addClass(element, 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) { function removeClass(element, cls) {
element.className = element.className =
element.className.replace(new RegExp('\\b' + cls + '\\b', 'g'), '') element.className.replace(new RegExp('\\b' + cls + '\\b', 'g'), '')
@@ -90,6 +119,7 @@ function retrieveEmail_(access_token) {
'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken() 'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken()
}; };
/** @param {XMLHttpRequest} xhr The XHR response. */
var onResponse = function(xhr) { var onResponse = function(xhr) {
if (xhr.status != 200) { if (xhr.status != 200) {
// TODO(ajwong): Have a better way of showing an error. // TODO(ajwong): Have a better way of showing an error.
@@ -113,6 +143,10 @@ function refreshEmail_() {
} }
} }
/**
* @param {string} value The email address to place in local storage.
* @return {void} Nothing.
*/
function setEmail(value) { function setEmail(value) {
window.localStorage.setItem(KEY_EMAIL_, value); window.localStorage.setItem(KEY_EMAIL_, value);
document.getElementById('current-email').innerText = value; document.getElementById('current-email').innerText = value;
@@ -130,9 +164,9 @@ function exchangedCodeForToken_() {
if (!remoting.oauth2.isAuthenticated()) { if (!remoting.oauth2.isAuthenticated()) {
alert('Your OAuth2 token was invalid. Please try again.'); alert('Your OAuth2 token was invalid. Please try again.');
} }
remoting.oauth2.callWithToken(function(token) { /** @param {string} token The auth token. */
retrieveEmail_(token); var retrieveEmail = function(token) { retrieveEmail_(token); }
}); remoting.oauth2.callWithToken(retrieveEmail);
} }
remoting.clearOAuth2 = function() { remoting.clearOAuth2 = function() {
@@ -194,7 +228,7 @@ remoting.setMode = function(mode) {
modes[i] = modes[i - 1] + '.' + modes[i]; modes[i] = modes[i - 1] + '.' + modes[i];
var elements = document.querySelectorAll('[data-ui-mode]'); var elements = document.querySelectorAll('[data-ui-mode]');
for (var i = 0; i < elements.length; ++i) { for (var i = 0; i < elements.length; ++i) {
var element = elements[i]; /** @type {Element} */ var element = elements[i];
var hidden = true; var hidden = true;
for (var m = 0; m < modes.length; ++m) { for (var m = 0; m < modes.length; ++m) {
if (hasClass(element.getAttribute('data-ui-mode'), modes[m])) { if (hasClass(element.getAttribute('data-ui-mode'), modes[m])) {
@@ -215,7 +249,7 @@ remoting.setMode = function(mode) {
/** /**
* Get the major mode that the app is running in. * Get the major mode that the app is running in.
* @return {remoting.AppMode} The app's current major mode. * @return {string} The app's current major mode.
*/ */
remoting.getMajorMode = function() { remoting.getMajorMode = function() {
return remoting.currentMode.split('.')[0]; return remoting.currentMode.split('.')[0];
@@ -309,6 +343,11 @@ remoting.updateAccessCodeTimeoutElement_ = function() {
} }
} }
/**
* 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) { function onNatTraversalPolicyChanged_(enabled) {
var container = document.getElementById('nat-box-container'); var container = document.getElementById('nat-box-container');
container.hidden = enabled; container.hidden = enabled;
@@ -415,7 +454,9 @@ remoting.cancelShare = function() {
try { try {
plugin.disconnect(); plugin.disconnect();
} catch (error) { } catch (error) {
remoting.debug.log('Error disconnecting: ' + error.description + // Hack to force JSCompiler type-safety.
var errorTyped = /** @type {{description: string}} */ error;
remoting.debug.log('Error disconnecting: ' + errorTyped.description +
'. The host plugin probably crashed.'); '. The host plugin probably crashed.');
// TODO(jamiewalch): Clean this up. We should have a class representing // TODO(jamiewalch): Clean this up. We should have a class representing
// the host plugin, like we do for the client, which should handle crash // the host plugin, like we do for the client, which should handle crash
@@ -484,6 +525,7 @@ function showToolbarPreview_() {
window.setTimeout(removeClass, 3000, toolbar, 'toolbar-preview'); window.setTimeout(removeClass, 3000, toolbar, 'toolbar-preview');
} }
/** @param {number} oldState The previous state of the plugin. */
function onClientStateChange_(oldState) { function onClientStateChange_(oldState) {
if (!remoting.session) { if (!remoting.session) {
// If the connection has been cancelled, then we no longer have a reference // If the connection has been cancelled, then we no longer have a reference
@@ -553,11 +595,13 @@ function startSession_() {
new remoting.ClientSession(remoting.hostJid, remoting.hostPublicKey, new remoting.ClientSession(remoting.hostJid, remoting.hostPublicKey,
remoting.accessCode, remoting.username, remoting.accessCode, remoting.username,
onClientStateChange_); onClientStateChange_);
remoting.oauth2.callWithToken(function(token) { /** @param {string} token The auth token. */
var createPluginAndConnect = function(token) {
remoting.session.createPluginAndConnect( remoting.session.createPluginAndConnect(
document.getElementById('session-mode'), document.getElementById('session-mode'),
token); token);
}); };
remoting.oauth2.callWithToken(createPluginAndConnect);
} }
/** /**
@@ -587,8 +631,9 @@ function parseServerResponse_(xhr) {
remoting.supportHostsXhr = null; remoting.supportHostsXhr = null;
remoting.debug.log('parseServerResponse: status = ' + xhr.status); remoting.debug.log('parseServerResponse: status = ' + xhr.status);
if (xhr.status == 200) { if (xhr.status == 200) {
var host = JSON.parse(xhr.responseText); var host = /** @type {{data: {jabberId: string, publicKey: string}}} */
if (host.data && host.data.jabberId) { JSON.parse(xhr.responseText);
if (host.data && host.data.jabberId && host.data.publicKey) {
remoting.hostJid = host.data.jabberId; remoting.hostJid = host.data.jabberId;
remoting.hostPublicKey = host.data.publicKey; remoting.hostPublicKey = host.data.publicKey;
var split = remoting.hostJid.split('/'); var split = remoting.hostJid.split('/');
@@ -608,12 +653,15 @@ function parseServerResponse_(xhr) {
showConnectError_(errorMsg); 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) { function normalizeAccessCode_(accessCode) {
// Trim whitespace. // Trim whitespace.
// TODO(sergeyu): Do we need to do any other normalization here? // TODO(sergeyu): Do we need to do any other normalization here?
return accessCode.replace(/\s/g, ''); return accessCode.replace(/\s/g, '');
} }
/** @param {string} supportId The canonicalized support ID. */
function resolveSupportId(supportId) { function resolveSupportId(supportId) {
var headers = { var headers = {
'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken() 'Authorization': 'OAuth ' + remoting.oauth2.getAccessToken()
@@ -648,11 +696,13 @@ remoting.tryConnectWithAccessToken = function() {
if (!remoting.wcsLoader) { if (!remoting.wcsLoader) {
remoting.wcsLoader = new 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.wcsLoader.start(
remoting.oauth2.getAccessToken(), remoting.oauth2.getAccessToken(),
function(setToken) { callWithToken,
remoting.oauth2.callWithToken(setToken);
},
remoting.tryConnectWithWcs); remoting.tryConnectWithWcs);
} }
@@ -770,6 +820,10 @@ remoting.promptClose = function() {
} }
} }
/**
* @param {Event} event The keyboard event.
* @return {void} Nothing.
*/
remoting.checkHotkeys = function(event) { remoting.checkHotkeys = function(event) {
if (String.fromCharCode(event.which) == 'D') { if (String.fromCharCode(event.which) == 'D') {
remoting.toggleDebugLog(); remoting.toggleDebugLog();

@@ -14,33 +14,50 @@ var remoting = remoting || {};
remoting.ViewerPlugin = function() {}; remoting.ViewerPlugin = function() {};
/** @param {string} iq The Iq stanza received from the host. */ /** @param {string} iq The Iq stanza received from the host. */
remoting.ViewerPlugin.onIq = function(iq) {}; remoting.ViewerPlugin.prototype.onIq = function(iq) {};
/** @param {boolean} scale True to enable scaling, false to disable. */ /** @param {boolean} scale True to enable scaling, false to disable. */
remoting.ViewerPlugin.setScaleToFit = function(scale) {}; remoting.ViewerPlugin.prototype.setScaleToFit = function(scale) {};
/** Release all keys currently pressed by this client. */
remoting.ViewerPlugin.prototype.releaseAllKeys = function() {};
/**
* @param {string} hostJid The host's JID.
* @param {string} hostPublicKey The host's public key.
* @param {string} clientJid The client's JID.
* @param {string} accessCode The access code.
* @return {void} Nothing.
*/
remoting.ViewerPlugin.prototype.connect =
function(hostJid, hostPublicKey, clientJid, accessCode) {};
/** @type {number} */ remoting.ViewerPlugin.prototype.apiMinVersion; /** @type {number} */ remoting.ViewerPlugin.prototype.apiMinVersion;
/** @type {number} */ remoting.ViewerPlugin.prototype.apiVersion; /** @type {number} */ remoting.ViewerPlugin.prototype.apiVersion;
/** @type {number} */ remoting.ViewerPlugin.desktopHeight; /** @type {number} */ remoting.ViewerPlugin.prototype.desktopHeight;
/** @type {number} */ remoting.ViewerPlugin.desktopWidth; /** @type {number} */ remoting.ViewerPlugin.prototype.desktopWidth;
/** @type {number} */ remoting.ViewerPlugin.STATUS_UNKNOWN; /** @type {number} */ remoting.ViewerPlugin.prototype.status;
/** @type {number} */ remoting.ViewerPlugin.STATUS_CONNECTING; /** @type {number} */ remoting.ViewerPlugin.prototype.error;
/** @type {number} */ remoting.ViewerPlugin.STATUS_INITIALIZING;
/** @type {number} */ remoting.ViewerPlugin.STATUS_CONNECTED;
/** @type {number} */ remoting.ViewerPlugin.STATUS_CLOSED;
/** @type {number} */ remoting.ViewerPlugin.STATUS_FAILED;
/** @type {number} */ remoting.ViewerPlugin.ERROR_NONE; /** @type {number} */ remoting.ViewerPlugin.prototype.STATUS_UNKNOWN;
/** @type {number} */ remoting.ViewerPlugin.ERROR_HOST_IS_OFFLINE; /** @type {number} */ remoting.ViewerPlugin.prototype.STATUS_CONNECTING;
/** @type {number} */ remoting.ViewerPlugin.ERROR_SESSION_REJECTED; /** @type {number} */ remoting.ViewerPlugin.prototype.STATUS_INITIALIZING;
/** @type {number} */ remoting.ViewerPlugin.ERROR_INCOMPATIBLE_PROTOCOL; /** @type {number} */ remoting.ViewerPlugin.prototype.STATUS_CONNECTED;
/** @type {number} */ remoting.ViewerPlugin.ERROR_NETWORK_FAILURE; /** @type {number} */ remoting.ViewerPlugin.prototype.STATUS_CLOSED;
/** @type {number} */ remoting.ViewerPlugin.prototype.STATUS_FAILED;
/** @type {number} */ remoting.ViewerPlugin.videoBandwidth; /** @type {number} */ remoting.ViewerPlugin.prototype.ERROR_NONE;
/** @type {number} */ remoting.ViewerPlugin.videoCaptureLatency; /** @type {number} */ remoting.ViewerPlugin.prototype.ERROR_HOST_IS_OFFLINE;
/** @type {number} */ remoting.ViewerPlugin.videoEncodeLatency; /** @type {number} */ remoting.ViewerPlugin.prototype.ERROR_SESSION_REJECTED;
/** @type {number} */ remoting.ViewerPlugin.videoDecodeLatency; /** @type {number} */
/** @type {number} */ remoting.ViewerPlugin.videoRenderLatency; remoting.ViewerPlugin.prototype.ERROR_INCOMPATIBLE_PROTOCOL;
/** @type {number} */ remoting.ViewerPlugin.roundTripLatency; /** @type {number} */ remoting.ViewerPlugin.prototype.ERROR_NETWORK_FAILURE;
/** @type {number} */ remoting.ViewerPlugin.prototype.videoBandwidth;
/** @type {number} */ remoting.ViewerPlugin.prototype.videoCaptureLatency;
/** @type {number} */ remoting.ViewerPlugin.prototype.videoEncodeLatency;
/** @type {number} */ remoting.ViewerPlugin.prototype.videoDecodeLatency;
/** @type {number} */ remoting.ViewerPlugin.prototype.videoRenderLatency;
/** @type {number} */ remoting.ViewerPlugin.prototype.roundTripLatency;

@@ -13,7 +13,9 @@
/** @suppress {duplicate} */ /** @suppress {duplicate} */
var remoting = remoting || {}; var remoting = remoting || {};
(function() { /** @type {remoting.Wcs} */
remoting.wcs = null;
/** /**
* @constructor * @constructor
* *
@@ -55,6 +57,7 @@ remoting.Wcs = function(wcsIqClient, token, onReady, refreshToken) {
*/ */
this.clientFullJid_ = ''; this.clientFullJid_ = '';
/** @type {remoting.Wcs} */
var that = this; var that = this;
/** /**
* A timer that polls for an updated access token. * A timer that polls for an updated access token.
@@ -67,13 +70,15 @@ remoting.Wcs = function(wcsIqClient, token, onReady, refreshToken) {
/** /**
* A function called when an IQ stanza is received. * A function called when an IQ stanza is received.
* @type {function(string): void} * @param {string} stanza The IQ stanza.
* @private * @private
*/ */
this.onIq_ = function(stanza) {}; this.onIq_ = function(stanza) {};
// Handle messages from the WcsIqClient. // Handle messages from the WcsIqClient.
this.wcsIqClient_.setOnMessage(function(msg) { that.onMessage_(msg); }); /** @param {Array.<string>} msg An array of message strings. */
var onMessage = function(msg) { that.onMessage_(msg); };
this.wcsIqClient_.setOnMessage(onMessage);
// Start the WcsIqClient. // Start the WcsIqClient.
this.wcsIqClient_.connectChannel(); this.wcsIqClient_.connectChannel();
@@ -96,7 +101,7 @@ remoting.Wcs.prototype.setToken_ = function(tokenNew) {
/** /**
* Handles a message coming from the WcsIqClient. * Handles a message coming from the WcsIqClient.
* *
* @param {Array} msg The message. * @param {Array.<string>} msg The message.
* @return {void} Nothing. * @return {void} Nothing.
* @private * @private
*/ */
@@ -140,5 +145,3 @@ remoting.Wcs.prototype.sendIq = function(stanza) {
remoting.Wcs.prototype.setOnIq = function(onIq) { remoting.Wcs.prototype.setOnIq = function(onIq) {
this.onIq_ = onIq; this.onIq_ = onIq;
}; };
}());

@@ -12,8 +12,8 @@ var remoting = remoting || {};
*/ */
remoting.WcsIqClient = function() {}; remoting.WcsIqClient = function() {};
/** @param {function(string): void} onMsg The function called when a message /** @param {function(Array.<string>): void} onMsg The function called when a
* is received. * message is received.
* @return {void} Nothing. */ * @return {void} Nothing. */
remoting.WcsIqClient.prototype.setOnMessage = function(onMsg) {}; remoting.WcsIqClient.prototype.setOnMessage = function(onMsg) {};

@@ -14,7 +14,9 @@
/** @suppress {duplicate} */ /** @suppress {duplicate} */
var remoting = remoting || {}; var remoting = remoting || {};
(function() { /** @type {remoting.WcsLoader} */
remoting.wcsLoader = null;
/** /**
* @constructor * @constructor
*/ */
@@ -28,14 +30,14 @@ remoting.WcsLoader = function() {
/** /**
* A callback that gets an updated access token asynchronously. * A callback that gets an updated access token asynchronously.
* @type {function(function(string): void): void} * @param {function(string): void} setToken The function to call when the
* token is available.
* @private * @private
*/ */
this.refreshToken_ = function(setToken) {}; this.refreshToken_ = function(setToken) {};
/** /**
* The function called when WCS is ready. * The function called when WCS is ready.
* @type {function(): void}
* @private * @private
*/ */
this.onReady_ = function() {}; this.onReady_ = function() {};
@@ -59,7 +61,7 @@ remoting.WcsLoader = function() {
/** /**
* The WCS client that will be downloaded. * The WCS client that will be downloaded.
* @type {Object} * @type {remoting.WcsIqClient}
*/ */
this.wcsIqClient = null; this.wcsIqClient = null;
}; };
@@ -103,6 +105,7 @@ remoting.WcsLoader.prototype.start = function(token, refreshToken, onReady) {
var node = document.createElement('script'); var node = document.createElement('script');
node.src = this.TALK_GADGET_URL_ + 'iq?access_token=' + this.token_; node.src = this.TALK_GADGET_URL_ + 'iq?access_token=' + this.token_;
node.type = 'text/javascript'; node.type = 'text/javascript';
/** @type {remoting.WcsLoader} */
var that = this; var that = this;
node.onload = function() { that.constructWcs_(); }; node.onload = function() { that.constructWcs_(); };
document.body.insertBefore(node, document.body.firstChild); document.body.insertBefore(node, document.body.firstChild);
@@ -116,12 +119,16 @@ remoting.WcsLoader.prototype.start = function(token, refreshToken, onReady) {
* @private * @private
*/ */
remoting.WcsLoader.prototype.constructWcs_ = function() { remoting.WcsLoader.prototype.constructWcs_ = function() {
/** @type {remoting.WcsLoader} */
var that = this; var that = this;
/** @param {function(string): void} setToken The function to call when the
token is available. */
var refreshToken = function(setToken) { that.refreshToken_(setToken); };
remoting.wcs = new remoting.Wcs( remoting.wcs = new remoting.Wcs(
remoting.wcsLoader.wcsIqClient, remoting.wcsLoader.wcsIqClient,
this.token_, this.token_,
function() { that.onWcsReady_(); }, function() { that.onWcsReady_(); },
function(setToken) { that.refreshToken_(setToken); }); refreshToken);
}; };
/** /**
@@ -135,5 +142,3 @@ remoting.WcsLoader.prototype.onWcsReady_ = function() {
this.onReady_(); this.onReady_();
this.onReady_ = function() {}; this.onReady_ = function() {};
}; };
}());

@@ -13,10 +13,9 @@
var remoting = remoting || {}; var remoting = remoting || {};
/** Namespace for XHR functions */ /** Namespace for XHR functions */
/** @type {Object} */
remoting.xhr = remoting.xhr || {}; remoting.xhr = remoting.xhr || {};
(function() {
/** /**
* Takes an associative array of parameters and urlencodes it. * Takes an associative array of parameters and urlencodes it.
* *
@@ -33,7 +32,7 @@ remoting.xhr.urlencodeParamHash = function(paramHash) {
return paramArray.join('&'); return paramArray.join('&');
} }
return ''; return '';
} };
/** /**
* Execute an XHR GET asynchronously. * Execute an XHR GET asynchronously.
@@ -41,17 +40,18 @@ remoting.xhr.urlencodeParamHash = function(paramHash) {
* @param {string} url The base URL to GET, excluding parameters. * @param {string} url The base URL to GET, excluding parameters.
* @param {function(XMLHttpRequest):void} onDone The function to call on * @param {function(XMLHttpRequest):void} onDone The function to call on
* completion. * completion.
* @param {(string|Object.<string>)} opt_parameters The request parameters, * @param {(string|Object.<string>)=} opt_parameters The request parameters,
* either as an associative array, or a string. If it is a string, do * either as an associative array, or a string. If it is a string, do
* not include the ? and be sure it is correctly URLEncoded. * not include the ? and be sure it is correctly URLEncoded.
* @param {Object.<string>} opt_headers Additional headers to include on the * @param {Object.<string>=} opt_headers Additional headers to include on the
* request. * request.
* @param {boolean} opt_withCredentials Set the withCredentials flags in the * @param {boolean=} opt_withCredentials Set the withCredentials flags in the
* XHR. * XHR.
* @return {XMLHttpRequest} The request object. * @return {XMLHttpRequest} The request object.
*/ */
remoting.xhr.get = function(url, onDone, opt_parameters, opt_headers, remoting.xhr.get = function(url, onDone, opt_parameters, opt_headers,
opt_withCredentials) { opt_withCredentials) {
/** @type {XMLHttpRequest} */
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState != 4) { if (xhr.readyState != 4) {
@@ -95,7 +95,7 @@ remoting.xhr.get = function(url, onDone, opt_parameters, opt_headers,
xhr.send(null); xhr.send(null);
return xhr; return xhr;
} };
/** /**
* Execute an XHR POST asynchronously. * Execute an XHR POST asynchronously.
@@ -103,17 +103,18 @@ remoting.xhr.get = function(url, onDone, opt_parameters, opt_headers,
* @param {string} url The base URL to POST, excluding parameters. * @param {string} url The base URL to POST, excluding parameters.
* @param {function(XMLHttpRequest):void} onDone The function to call on * @param {function(XMLHttpRequest):void} onDone The function to call on
* completion. * completion.
* @param {(string|Object.<string>)} opt_parameters The request parameters, * @param {(string|Object.<string>)=} opt_parameters The request parameters,
* either as an associative array, or a string. If it is a string, be * either as an associative array, or a string. If it is a string, be
* sure it is correctly URLEncoded. * sure it is correctly URLEncoded.
* @param {Object.<string>} opt_headers Additional headers to include on the * @param {Object.<string>=} opt_headers Additional headers to include on the
* request. * request.
* @param {boolean} opt_withCredentials Set the withCredentials flags in the * @param {boolean=} opt_withCredentials Set the withCredentials flags in the
* XHR. * XHR.
* @return {void} Nothing. * @return {void} Nothing.
*/ */
remoting.xhr.post = function(url, onDone, opt_parameters, opt_headers, remoting.xhr.post = function(url, onDone, opt_parameters, opt_headers,
opt_withCredentials) { opt_withCredentials) {
/** @type {XMLHttpRequest} */
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState != 4) { if (xhr.readyState != 4) {
@@ -153,6 +154,4 @@ remoting.xhr.post = function(url, onDone, opt_parameters, opt_headers,
} }
xhr.send(postData); xhr.send(postData);
} };
}());