// Copyright (c) 2012 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 || {};
/**
* @type {remoting.SessionConnector} The connector object, set when a connection
* is initiated.
*/
remoting.connector = null;
/**
* @type {remoting.ClientSession} The client session object, set once the
* connector has invoked its onOk callback.
*/
remoting.clientSession = null;
/**
* Initiate an IT2Me connection.
*/
remoting.connectIT2Me = function() {
if (!remoting.connector) {
remoting.connector = new remoting.SessionConnector(
document.getElementById('session-mode'),
remoting.onConnected,
showConnectError_);
}
var accessCode = document.getElementById('access-code-entry').value;
remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
remoting.connector.connectIT2Me(accessCode);
};
/**
* Update the remoting client layout in response to a resize event.
*
* @return {void} Nothing.
*/
remoting.onResize = function() {
if (remoting.clientSession) {
remoting.clientSession.onResize();
}
};
/**
* Handle changes in the visibility of the window, for example by pausing video.
*
* @return {void} Nothing.
*/
remoting.onVisibilityChanged = function() {
if (remoting.clientSession) {
remoting.clientSession.pauseVideo(
('hidden' in document) ? document.hidden : document.webkitHidden);
}
}
/**
* Disconnect the remoting client.
*
* @return {void} Nothing.
*/
remoting.disconnect = function() {
if (!remoting.clientSession) {
return;
}
if (remoting.clientSession.getMode() == remoting.ClientSession.Mode.IT2ME) {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
} else {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
}
remoting.clientSession.disconnect(true);
remoting.clientSession = null;
console.log('Disconnected.');
};
/**
* Sends a Ctrl-Alt-Del sequence to the remoting client.
*
* @return {void} Nothing.
*/
remoting.sendCtrlAltDel = function() {
if (remoting.clientSession) {
console.log('Sending Ctrl-Alt-Del.');
remoting.clientSession.sendCtrlAltDel();
}
};
/**
* Sends a Print Screen keypress to the remoting client.
*
* @return {void} Nothing.
*/
remoting.sendPrintScreen = function() {
if (remoting.clientSession) {
console.log('Sending Print Screen.');
remoting.clientSession.sendPrintScreen();
}
};
/**
* 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.
* @param {number} newState The current state of the plugin.
*/
function onClientStateChange_(oldState, newState) {
switch (newState) {
case remoting.ClientSession.State.CLOSED:
console.log('Connection closed by host');
if (remoting.clientSession.getMode() ==
remoting.ClientSession.Mode.IT2ME) {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_IT2ME);
} else {
remoting.setMode(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
}
break;
case remoting.ClientSession.State.FAILED:
var error = remoting.clientSession.getError();
console.error('Client plugin reported connection failed: ' + error);
if (error == null) {
error = remoting.Error.UNEXPECTED;
}
showConnectError_(error);
break;
default:
console.error('Unexpected client plugin state: ' + newState);
// This should only happen if the web-app and client plugin get out of
// sync, so MISSING_PLUGIN is a suitable error.
showConnectError_(remoting.Error.MISSING_PLUGIN);
break;
}
remoting.clientSession.disconnect(false);
remoting.clientSession.removePlugin();
remoting.clientSession = null;
}
/**
* Show a client-side error message.
*
* @param {remoting.Error} errorTag The error to be localized and
* displayed.
* @return {void} Nothing.
*/
function showConnectError_(errorTag) {
console.error('Connection failed: ' + errorTag);
var errorDiv = document.getElementById('connect-error-message');
l10n.localizeElementFromTag(errorDiv, /** @type {string} */ (errorTag));
remoting.accessCode = '';
var mode = remoting.clientSession ? remoting.clientSession.getMode()
: remoting.connector.getConnectionMode();
if (mode == remoting.ClientSession.Mode.IT2ME) {
remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_IT2ME);
} else {
remoting.setMode(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME);
}
}
/**
* Set the text on the buttons shown under the error message so that they are
* easy to understand in the case where a successful connection failed, as
* opposed to the case where a connection never succeeded.
*/
function setConnectionInterruptedButtonsText_() {
var button1 = document.getElementById('client-reconnect-button');
l10n.localizeElementFromTag(button1, /*i18n-content*/'RECONNECT');
button1.removeAttribute('autofocus');
var button2 = document.getElementById('client-finished-me2me-button');
l10n.localizeElementFromTag(button2, /*i18n-content*/'OK');
button2.setAttribute('autofocus', 'autofocus');
}
/**
* Timer callback to update the statistics panel.
*/
function updateStatistics_() {
if (!remoting.clientSession ||
remoting.clientSession.getState() !=
remoting.ClientSession.State.CONNECTED) {
return;
}
var perfstats = remoting.clientSession.getPerfStats();
remoting.stats.update(perfstats);
remoting.clientSession.logStatistics(perfstats);
// Update the stats once per second.
window.setTimeout(updateStatistics_, 1000);
}
/**
* Entry-point for Me2Me connections, handling showing of the host-upgrade nag
* dialog if necessary.
*
* @param {string} hostId The unique id of the host.
* @return {void} Nothing.
*/
remoting.connectMe2Me = function(hostId) {
var host = remoting.hostList.getHostForId(hostId);
if (!host) {
showConnectError_(remoting.Error.HOST_IS_OFFLINE);
return;
}
var webappVersion = chrome.runtime.getManifest().version;
if (remoting.Host.needsUpdate(host, webappVersion)) {
var needsUpdateMessage =
document.getElementById('host-needs-update-message');
l10n.localizeElementFromTag(needsUpdateMessage,
/*i18n-content*/'HOST_NEEDS_UPDATE_TITLE',
host.hostName);
/** @type {Element} */
var connect = document.getElementById('host-needs-update-connect-button');
/** @type {Element} */
var cancel = document.getElementById('host-needs-update-cancel-button');
/** @param {Event} event */
var onClick = function(event) {
connect.removeEventListener('click', onClick, false);
cancel.removeEventListener('click', onClick, false);
if (event.target == connect) {
remoting.connectMe2MeHostVersionAcknowledged_(host);
} else {
remoting.setMode(remoting.AppMode.HOME);
}
}
connect.addEventListener('click', onClick, false);
cancel.addEventListener('click', onClick, false);
remoting.setMode(remoting.AppMode.CLIENT_HOST_NEEDS_UPGRADE);
} else {
remoting.connectMe2MeHostVersionAcknowledged_(host);
}
};
/**
* Shows PIN entry screen localized to include the host name, and registers
* a host-specific one-shot event handler for the form submission.
*
* @param {remoting.Host} host The Me2Me host to which to connect.
* @return {void} Nothing.
*/
remoting.connectMe2MeHostVersionAcknowledged_ = function(host) {
if (!remoting.connector) {
remoting.connector = new remoting.SessionConnector(
document.getElementById('session-mode'),
remoting.onConnected,
showConnectError_);
}
remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
/**
* @param {string} tokenUrl Token-issue URL received from the host.
* @param {string} scope OAuth scope to request the token for.
* @param {string} hostPublicKey Host public key (DER and Base64 encoded).
* @param {function(string, string):void} onThirdPartyTokenFetched Callback.
*/
var fetchThirdPartyToken = function(
tokenUrl, hostPublicKey, scope, onThirdPartyTokenFetched) {
var thirdPartyTokenFetcher = new remoting.ThirdPartyTokenFetcher(
tokenUrl, hostPublicKey, scope, host.tokenUrlPatterns,
onThirdPartyTokenFetched);
thirdPartyTokenFetcher.fetchToken();
};
/**
* @param {boolean} supportsPairing
* @param {function(string):void} onPinFetched
*/
var requestPin = function(supportsPairing, onPinFetched) {
/** @type {Element} */
var pinForm = document.getElementById('pin-form');
/** @type {Element} */
var pinCancel = document.getElementById('cancel-pin-entry-button');
/** @type {Element} */
var rememberPin = document.getElementById('remember-pin');
/** @type {Element} */
var rememberPinCheckbox = document.getElementById('remember-pin-checkbox');
/**
* Event handler for both the 'submit' and 'cancel' actions. Using
* a single handler for both greatly simplifies the task of making
* them one-shot. If separate handlers were used, each would have
* to unregister both itself and the other.
*
* @param {Event} event The click or submit event.
*/
var onSubmitOrCancel = function(event) {
pinForm.removeEventListener('submit', onSubmitOrCancel, false);
pinCancel.removeEventListener('click', onSubmitOrCancel, false);
var pinField = document.getElementById('pin-entry');
var pin = pinField.value;
pinField.value = '';
if (event.target == pinForm) {
event.preventDefault();
// Set the focus away from the password field. This has to be done
// before the password field gets hidden, to work around a Blink
// clipboard-handling bug - http://crbug.com/281523.
document.getElementById('pin-connect-button').focus();
remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
onPinFetched(pin);
if (/** @type {boolean} */(rememberPinCheckbox.checked)) {
remoting.connector.pairingRequested = true;
}
} else {
remoting.setMode(remoting.AppMode.HOME);
}
};
pinForm.addEventListener('submit', onSubmitOrCancel, false);
pinCancel.addEventListener('click', onSubmitOrCancel, false);
rememberPin.hidden = !supportsPairing;
rememberPinCheckbox.checked = false;
var message = document.getElementById('pin-message');
l10n.localizeElement(message, host.hostName);
remoting.setMode(remoting.AppMode.CLIENT_PIN_PROMPT);
};
/** @param {Object} settings */
var connectMe2MeHostSettingsRetrieved = function(settings) {
/** @type {string} */
var clientId = '';
/** @type {string} */
var sharedSecret = '';
var pairingInfo = /** @type {Object} */ (settings['pairingInfo']);
if (pairingInfo) {
clientId = /** @type {string} */ (pairingInfo['clientId']);
sharedSecret = /** @type {string} */ (pairingInfo['sharedSecret']);
}
remoting.connector.connectMe2Me(host, requestPin, fetchThirdPartyToken,
clientId, sharedSecret);
}
remoting.HostSettings.load(host.hostId, connectMe2MeHostSettingsRetrieved);
};
/** @param {remoting.ClientSession} clientSession */
remoting.onConnected = function(clientSession) {
remoting.clientSession = clientSession;
remoting.clientSession.setOnStateChange(onClientStateChange_);
setConnectionInterruptedButtonsText_();
var connectedTo = document.getElementById('connected-to');
connectedTo.innerText = remoting.connector.getHostDisplayName();
document.getElementById('access-code-entry').value = '';
remoting.setMode(remoting.AppMode.IN_SESSION);
remoting.toolbar.center();
remoting.toolbar.preview();
remoting.clipboard.startSession();
updateStatistics_();
if (remoting.connector.pairingRequested) {
/**
* @param {string} clientId
* @param {string} sharedSecret
*/
var onPairingComplete = function(clientId, sharedSecret) {
var pairingInfo = {
pairingInfo: {
clientId: clientId,
sharedSecret: sharedSecret
}
};
remoting.HostSettings.save(remoting.connector.getHostId(), pairingInfo);
remoting.connector.updatePairingInfo(clientId, sharedSecret);
};
// Use the platform name as a proxy for the local computer name.
// TODO(jamiewalch): Use a descriptive name for the local computer, for
// example, its Chrome Sync name.
var clientName = '';
if (navigator.platform.indexOf('Mac') != -1) {
clientName = 'Mac';
} else if (navigator.platform.indexOf('Win32') != -1) {
clientName = 'Windows';
} else if (navigator.userAgent.match(/\bCrOS\b/)) {
clientName = 'ChromeOS';
} else if (navigator.platform.indexOf('Linux') != -1) {
clientName = 'Linux';
} else {
console.log('Unrecognized client platform. Using navigator.platform.');
clientName = navigator.platform;
}
clientSession.requestPairing(clientName, onPairingComplete);
}
};