| Index: remoting/webapp/me2mom/client_screen.js
|
| diff --git a/remoting/webapp/me2mom/client_screen.js b/remoting/webapp/me2mom/client_screen.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6065672b9e34e6a95b4974e480b33f5ae55e87b7
|
| --- /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';
|
| +}
|
| +
|
| +
|
| +}());
|
|
|