diff options
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/host/plugin/daemon_controller_linux.cc | 19 | ||||
-rw-r--r-- | remoting/host/plugin/host_script_object.cc | 43 | ||||
-rw-r--r-- | remoting/host/plugin/host_script_object.h | 8 | ||||
-rw-r--r-- | remoting/remoting.gyp | 3 | ||||
-rwxr-xr-x | remoting/tools/me2me_virtual_host.py | 67 | ||||
-rw-r--r-- | remoting/webapp/_locales/en/messages.json | 28 | ||||
-rw-r--r-- | remoting/webapp/ask_pin_dialog.js | 111 | ||||
-rw-r--r-- | remoting/webapp/daemon_plugin.js | 43 | ||||
-rw-r--r-- | remoting/webapp/event_handlers.js | 4 | ||||
-rw-r--r-- | remoting/webapp/host_plugin_proto.js | 16 | ||||
-rw-r--r-- | remoting/webapp/host_setup_dialog.js | 351 | ||||
-rw-r--r-- | remoting/webapp/main.css | 2 | ||||
-rw-r--r-- | remoting/webapp/main.html | 26 | ||||
-rw-r--r-- | remoting/webapp/remoting.js | 3 | ||||
-rw-r--r-- | remoting/webapp/ui_mode.js | 6 | ||||
-rw-r--r-- | remoting/webapp/xhr.js | 4 |
16 files changed, 549 insertions, 185 deletions
diff --git a/remoting/host/plugin/daemon_controller_linux.cc b/remoting/host/plugin/daemon_controller_linux.cc index 88d634e..74f6e97 100644 --- a/remoting/host/plugin/daemon_controller_linux.cc +++ b/remoting/host/plugin/daemon_controller_linux.cc @@ -11,9 +11,11 @@ #include "base/compiler_specific.h" #include "base/environment.h" #include "base/file_path.h" +#include "base/json/json_writer.h" #include "base/logging.h" #include "base/process_util.h" #include "base/string_split.h" +#include "base/values.h" namespace remoting { @@ -22,6 +24,15 @@ namespace { const char* kDaemonScript = "me2me_virtual_host.py"; const int64 kDaemonTimeoutMs = 5000; +// TODO(sergeyu): This is a very hacky implementation of +// DaemonController interface for linux. Current version works, but +// there are sevaral problems with it: +// * All calls are executed synchronously, even though this API is +// supposed to be asynchronous. +// * The host is configured by passing configuration data as CL +// argument - this is obviously not secure. +// Rewrite this code to solve these two problems. +// http://crbug.com/120950 . class DaemonControllerLinux : public remoting::DaemonController { public: DaemonControllerLinux(); @@ -113,10 +124,14 @@ void DaemonControllerLinux::GetConfig(const GetConfigCallback& callback) { void DaemonControllerLinux::SetConfigAndStart( scoped_ptr<base::DictionaryValue> config) { - // TODO(sergeyu): Save the |config|. + std::vector<std::string> args; + args.push_back("--explicit-config"); + std::string config_json; + base::JSONWriter::Write(config.get(), &config_json); + args.push_back(config_json); // TODO(sergeyu): Set state to START_FAILED if RunScript() fails. std::vector<std::string> no_args; - RunScript(no_args, NULL); + RunScript(args, NULL); } void DaemonControllerLinux::SetPin(const std::string& pin) { diff --git a/remoting/host/plugin/host_script_object.cc b/remoting/host/plugin/host_script_object.cc index fea43ea..9be73b4 100644 --- a/remoting/host/plugin/host_script_object.cc +++ b/remoting/host/plugin/host_script_object.cc @@ -3,7 +3,6 @@ // found in the LICENSE file. #include "remoting/host/plugin/host_script_object.h" -#include "remoting/host/plugin/daemon_controller.h" #include "base/bind.h" #include "base/json/json_reader.h" @@ -14,7 +13,7 @@ #include "base/threading/platform_thread.h" #include "base/threading/sequenced_worker_pool.h" #include "base/utf_string_conversions.h" -#include "remoting/jingle_glue/xmpp_signal_strategy.h" +#include "net/base/net_util.h" #include "remoting/base/auth_token_util.h" #include "remoting/host/chromoting_host.h" #include "remoting/host/chromoting_host_context.h" @@ -22,9 +21,11 @@ #include "remoting/host/host_key_pair.h" #include "remoting/host/host_secret.h" #include "remoting/host/it2me_host_user_interface.h" +#include "remoting/host/plugin/daemon_controller.h" #include "remoting/host/plugin/host_log_handler.h" #include "remoting/host/policy_hack/nat_policy.h" #include "remoting/host/register_support_host_request.h" +#include "remoting/jingle_glue/xmpp_signal_strategy.h" #include "remoting/protocol/it2me_host_authenticator_factory.h" namespace remoting { @@ -43,6 +44,7 @@ const char* kAttrNameOnStateChanged = "onStateChanged"; const char* kFuncNameConnect = "connect"; const char* kFuncNameDisconnect = "disconnect"; const char* kFuncNameLocalize = "localize"; +const char* kFuncNameGetHostName = "getHostName"; const char* kFuncNameGenerateKeyPair = "generateKeyPair"; const char* kFuncNameSetDaemonPin = "setDaemonPin"; const char* kFuncNameGetDaemonConfig = "getDaemonConfig"; @@ -151,6 +153,7 @@ bool HostNPScriptObject::HasMethod(const std::string& method_name) { return (method_name == kFuncNameConnect || method_name == kFuncNameDisconnect || method_name == kFuncNameLocalize || + method_name == kFuncNameGetHostName || method_name == kFuncNameGenerateKeyPair || method_name == kFuncNameSetDaemonPin || method_name == kFuncNameGetDaemonConfig || @@ -179,6 +182,8 @@ bool HostNPScriptObject::Invoke(const std::string& method_name, return Disconnect(args, arg_count, result); } else if (method_name == kFuncNameLocalize) { return Localize(args, arg_count, result); + } else if (method_name == kFuncNameGetHostName) { + return GetHostName(args, arg_count, result); } else if (method_name == kFuncNameGenerateKeyPair) { return GenerateKeyPair(args, arg_count, result); } else if (method_name == kFuncNameSetDaemonPin) { @@ -352,6 +357,7 @@ bool HostNPScriptObject::Enumerate(std::vector<std::string>* values) { kFuncNameConnect, kFuncNameDisconnect, kFuncNameLocalize, + kFuncNameGetHostName, kFuncNameGenerateKeyPair, kFuncNameSetDaemonPin, kFuncNameGetDaemonConfig, @@ -593,9 +599,21 @@ bool HostNPScriptObject::Localize(const NPVariant* args, } } +bool HostNPScriptObject::GetHostName(const NPVariant* args, + uint32_t arg_count, + NPVariant* result) { + if (arg_count != 0) { + SetException("getHostName: bad number of arguments"); + return false; + } + DCHECK(result); + *result = NPVariantFromString(net::GetHostName()); + return true; +} + bool HostNPScriptObject::GenerateKeyPair(const NPVariant* args, - uint32_t arg_count, - NPVariant* result) { + uint32_t arg_count, + NPVariant* result) { if (arg_count != 1) { SetException("generateKeyPair: bad number of arguments"); return false; @@ -936,23 +954,28 @@ void HostNPScriptObject::UpdateWebappNatPolicy(bool nat_traversal_enabled) { void HostNPScriptObject::DoGenerateKeyPair(NPObject* callback) { HostKeyPair key_pair; key_pair.Generate(); - InvokeGenerateKeyPairCallback(callback, key_pair.GetAsString()); + InvokeGenerateKeyPairCallback(callback, key_pair.GetAsString(), + key_pair.GetPublicKey()); } void HostNPScriptObject::InvokeGenerateKeyPairCallback( NPObject* callback, - const std::string& result) { + const std::string& private_key, + const std::string& public_key) { if (!plugin_message_loop_proxy_->BelongsToCurrentThread()) { plugin_message_loop_proxy_->PostTask( FROM_HERE, base::Bind( &HostNPScriptObject::InvokeGenerateKeyPairCallback, - base::Unretained(this), callback, result)); + base::Unretained(this), callback, private_key, public_key)); return; } - NPVariant result_val = NPVariantFromString(result); - InvokeAndIgnoreResult(callback, &result_val, 1); - g_npnetscape_funcs->releasevariantvalue(&result_val); + NPVariant params[2]; + params[0] = NPVariantFromString(private_key); + params[1] = NPVariantFromString(public_key); + InvokeAndIgnoreResult(callback, params, arraysize(params)); + g_npnetscape_funcs->releasevariantvalue(&(params[0])); + g_npnetscape_funcs->releasevariantvalue(&(params[1])); g_npnetscape_funcs->releaseobject(callback); } diff --git a/remoting/host/plugin/host_script_object.h b/remoting/host/plugin/host_script_object.h index f9d543f..37a56cb2 100644 --- a/remoting/host/plugin/host_script_object.h +++ b/remoting/host/plugin/host_script_object.h @@ -117,6 +117,11 @@ class HostNPScriptObject : public HostStatusObserver { ////////////////////////////////////////////////////////// // Plugin methods for Me2Me host. + // Returns host name. No arguments. + bool GetHostName(const NPVariant* args, + uint32_t arg_count, + NPVariant* result); + // Generates new key pair to use for the host. The specified // callback is called when when the key is generated. The key is // returned in format understood by the host (PublicKeyInfo @@ -201,7 +206,8 @@ class HostNPScriptObject : public HostStatusObserver { // Helpers for GenerateKeyPair(). void DoGenerateKeyPair(NPObject* callback); void InvokeGenerateKeyPairCallback(NPObject* callback, - const std::string& result); + const std::string& private_key, + const std::string& public_key); // Callback handler for DaemonController::GetConfig(). void InvokeGetDaemonConfigCallback(NPObject* callback, diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 02354b7..6137323 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -121,7 +121,6 @@ 'resources/icon_host.png', 'resources/icon_pencil.png', 'resources/icon_warning.png', - 'webapp/ask_pin_dialog.js', 'webapp/client_plugin.js', 'webapp/client_plugin_async.js', 'webapp/client_plugin_v1.js', @@ -139,6 +138,7 @@ 'webapp/host_list.js', 'webapp/host_screen.js', 'webapp/host_session.js', + 'webapp/host_setup_dialog.js', 'webapp/host_table_entry.js', 'webapp/l10n.js', 'webapp/log_to_server.js', @@ -597,6 +597,7 @@ 'webapp/client_screen.js', 'webapp/main.html', 'webapp/host_table_entry.js', + 'webapp/host_setup_dialog.js', 'webapp/manifest.json', 'webapp/remoting.js', 'host/plugin/host_script_object.cc', diff --git a/remoting/tools/me2me_virtual_host.py b/remoting/tools/me2me_virtual_host.py index 6881fb6..56ff78f 100755 --- a/remoting/tools/me2me_virtual_host.py +++ b/remoting/tools/me2me_virtual_host.py @@ -540,8 +540,10 @@ def main(): parser.add_option("", "--check-running", dest="check_running", default=False, action="store_true", help="return 0 if the daemon is running, or 1 otherwise") - parser.add_option("", "--explicit-pin", dest="explicit_pin", default=None, + parser.add_option("", "--explicit-pin", dest="explicit_pin", help="set or unset the pin on the command line") + parser.add_option("", "--explicit-config", dest="explicit_config", + help="explicitly specify content of the config") (options, args) = parser.parse_args() size_components = options.size.split("x") @@ -584,6 +586,12 @@ def main(): if not os.path.exists(CONFIG_DIR): os.makedirs(CONFIG_DIR, mode=0700) + if options.explicit_config: + for file_name in ["auth.json", "host#%s.json" % host_hash]: + settings_file = open(os.path.join(CONFIG_DIR, file_name), 'w') + settings_file.write(options.explicit_config) + settings_file.close() + auth = Authentication(os.path.join(CONFIG_DIR, "auth.json")) need_auth_tokens = not auth.load_config() @@ -610,36 +618,37 @@ def main(): print "The running instance has been updated with the new PIN." return 0 - # The loop is to deal with the case of registering a new Host with - # previously-saved auth tokens (from a previous run of this script), which - # may require re-prompting for username & password. - while True: - try: - if need_auth_tokens: - auth.generate_tokens() - auth.save_config() - need_auth_tokens = False - except Exception: - logging.error("Authentication failed") - return 1 - - try: - if register_host: - host.register(auth) - host.save_config() - except urllib2.HTTPError, err: - if err.getcode() == 401: - # Authentication failed - re-prompt for username & password. - need_auth_tokens = True - continue - else: - # Not an authentication error. - logging.error("Directory returned error: " + str(err)) - logging.error(err.read()) + if not options.explicit_config: + # The loop is to deal with the case of registering a new Host with + # previously-saved auth tokens (from a previous run of this script), which + # may require re-prompting for username & password. + while True: + try: + if need_auth_tokens: + auth.generate_tokens() + auth.save_config() + need_auth_tokens = False + except Exception: + logging.error("Authentication failed") return 1 - # |auth| and |host| are both set up, so break out of the loop. - break + try: + if register_host: + host.register(auth) + host.save_config() + except urllib2.HTTPError, err: + if err.getcode() == 401: + # Authentication failed - re-prompt for username & password. + need_auth_tokens = True + continue + else: + # Not an authentication error. + logging.error("Directory returned error: " + str(err)) + logging.error(err.read()) + return 1 + + # |auth| and |host| are both set up, so break out of the loop. + break global g_pidfile g_pidfile = PidFile(pid_filename) diff --git a/remoting/webapp/_locales/en/messages.json b/remoting/webapp/_locales/en/messages.json index ee8791b..a0a41c3 100644 --- a/remoting/webapp/_locales/en/messages.json +++ b/remoting/webapp/_locales/en/messages.json @@ -17,10 +17,6 @@ "message": "All connections", "description": "In the connection history dialog, clicking this button shows all recent connections unfiltered." }, - "ASK_PIN_DIALOG_DESCRIPTION": { - "message": "To protect access to this computer, please choose a PIN. This PIN will be required when connecting from another location.", - "description": "Explanatory text displayed when the user enables remote access or changes the PIN." - }, "ASK_PIN_DIALOG_LABEL": { "message": "PIN", "description": "Label next to the PIN entry edit box. The user must enter a PIN before enabling remote access to their computer." @@ -222,6 +218,30 @@ "message": "To securely access this computer from anywhere you sign in to Chromoting, you must first enable remote connections.", "description": "Message displayed when the current computer is not accepting remote connections, instructing the user how to enable them." }, + "HOST_SETUP_DIALOG_DESCRIPTION": { + "message": "To protect access to this computer, please choose a PIN. This PIN will be required when connecting from another location.", + "description": "Explanatory text displayed when the user enables remote access or changes the PIN." + }, + "HOST_SETUP_HOST_FAILED": { + "message": "Failed to start remote access service.", + "description": "Message shown when host service fails to start when enabling the host on local computer." + }, + "HOST_SETUP_REGISTRATION_FAILED": { + "message": "Failed to register this computer.", + "description": "Message shown when host registration fails when enabling the host on local computer." + }, + "HOST_SETUP_STARTED": { + "message": "Remote connections for this computer have been enabled.", + "description": "Message shown after access to local computer has been enabled successfully." + }, + "HOST_SETUP_STARTING": { + "message": "Enabling remote connections for this computer.", + "description": "Message shown when local machine is being registered in the directory and when starting the host." + }, + "HOST_SETUP_UPDATING_PIN": { + "message": "PIN for this computer is being updated.", + "description": "Message shown while changing PIN for the local computer." + }, "HOME_SHARE_BUTTON": { "message": "Share", "description": "Clicking this button starts the desktop sharing process." diff --git a/remoting/webapp/ask_pin_dialog.js b/remoting/webapp/ask_pin_dialog.js deleted file mode 100644 index 176fdf4..0000000 --- a/remoting/webapp/ask_pin_dialog.js +++ /dev/null @@ -1,111 +0,0 @@ -// 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. - -'use strict'; - -/** @suppress {duplicate} */ -var remoting = remoting || {}; - -/** - * @param {remoting.DaemonPlugin} daemon The parent daemon plugin instance. - * @constructor - */ -remoting.AskPinDialog = function(daemon) { - this.startDaemon_ = false; - this.daemon_ = daemon; - this.okButton_ = document.getElementById('daemon-pin-ok'); - this.spinner_ = document.getElementById('start-daemon-spinner'); - this.pinEntry_ = document.getElementById('daemon-pin-entry'); - this.pinConfirm_ = document.getElementById('daemon-pin-confirm'); - /** @type {remoting.AskPinDialog} */ - var that = this; - /** @param {Event} event The event. */ - var onSubmit = function(event) { - event.preventDefault(); - that.onSubmit_(); - }; - var form = document.getElementById('ask-pin-form'); - form.addEventListener('submit', onSubmit, false); -}; - -/** - * Show the dialog in order to get a PIN prior to starting the daemon. When the - * user clicks OK, the dialog shows a spinner until the daemon has started. - * - * @return {void} Nothing. - */ -remoting.AskPinDialog.prototype.showForStart = function() { - remoting.setMode(remoting.AppMode.ASK_PIN); - this.startDaemon_ = true; -}; - -/** - * Show the dialog in order to change the PIN associated with a running daemon. - * - * @return {void} Nothing. - */ -remoting.AskPinDialog.prototype.showForPin = function() { - remoting.setMode(remoting.AppMode.ASK_PIN); - this.startDaemon_ = false; -}; - -/** - * @return {void} Nothing. - */ -remoting.AskPinDialog.prototype.hide = function() { - remoting.setMode(remoting.AppMode.HOME); -}; - -/** @private */ -remoting.AskPinDialog.prototype.onSubmit_ = function() { - // TODO(jamiewalch): Add validation and error checks when we improve the UI. - var pin = this.pinEntry_.value; - this.daemon_.setPin(pin); - if (this.startDaemon_) { - this.daemon_.start(); - this.pollDaemonState_(); - } else { - this.hide(); - } -}; - -/** - * @return {void} Nothing. - * @private - */ -remoting.AskPinDialog.prototype.pollDaemonState_ = function() { - var state = this.daemon_.state(); - var retry = false; // Set to true if we haven't finished yet. - switch (state) { - case remoting.DaemonPlugin.State.STOPPED: - case remoting.DaemonPlugin.State.NOT_INSTALLED: - retry = true; - break; - case remoting.DaemonPlugin.State.STARTED: - this.hide(); - this.daemon_.updateDom(); - break; - case remoting.DaemonPlugin.State.START_FAILED: - // TODO(jamiewalch): Show an error message. - break; - default: - // TODO(jamiewalch): Show an error message. - console.error('Unexpected daemon state', state); - break; - } - if (retry) { - this.okButton_.hidden = true; - this.spinner_.hidden = false; - /** @type {remoting.AskPinDialog} */ - var that = this; - var pollDaemonState = function() { that.pollDaemonState_(); } - window.setTimeout(pollDaemonState, 1000); - } else { - this.okButton_.hidden = false; - this.spinner_.hidden = true; - } -}; - -/** @type {remoting.AskPinDialog} */ -remoting.askPinDialog = null; diff --git a/remoting/webapp/daemon_plugin.js b/remoting/webapp/daemon_plugin.js index cec5a8f..2f16fda 100644 --- a/remoting/webapp/daemon_plugin.js +++ b/remoting/webapp/daemon_plugin.js @@ -44,6 +44,8 @@ remoting.DaemonPlugin.prototype.state = function() { * @return {void} Nothing. */ remoting.DaemonPlugin.prototype.updateDom = function() { + // TODO(sergeyu): This code updates UI state. Does it belong here, + // or should it moved somewhere else? var match = ''; switch (this.state()) { case remoting.DaemonPlugin.State.STARTED: @@ -59,27 +61,54 @@ remoting.DaemonPlugin.prototype.updateDom = function() { }; /** + * Generates new host key pair. + * @param {function(string,string):void} callback Callback for the + * generated key pair. + * @return {void} Nothing. + */ +remoting.DaemonPlugin.prototype.generateKeyPair = function(callback) { + this.plugin_.generateKeyPair(callback); +}; + +/** + * @return {string} Local hostname + */ +remoting.DaemonPlugin.prototype.getHostName = function() { + return this.plugin_.getHostName(); +}; + +/** + * Read current host configuration. + * @param {function(string):void} callback Host config callback. + * @return {void} Nothing. + */ +remoting.DaemonPlugin.prototype.getConfig = function(callback) { + this.plugin_.getDaemonConfig(callback); +}; + +/** * Start the daemon process. - * @return {boolean} False if insufficient state has been set. + * @param {string} config Host config. + * @return {void} Nothing. */ -remoting.DaemonPlugin.prototype.start = function() { - return this.plugin_.startDaemon(); +remoting.DaemonPlugin.prototype.start = function(config) { + this.plugin_.startDaemon(config); }; /** * Stop the daemon process. - * @return {boolean} False if insufficient state has been set. + * @return {void} Nothing. */ remoting.DaemonPlugin.prototype.stop = function() { - return this.plugin_.stopDaemon(); + this.plugin_.stopDaemon(); }; /** * @param {string} pin The new PIN for the daemon process. - * @return {boolean} True if the PIN was set successfully. + * @return {void} Nothing. */ remoting.DaemonPlugin.prototype.setPin = function(pin) { - return this.plugin_.setDaemonPin(pin); + this.plugin_.setDaemonPin(pin); }; /** @type {remoting.DaemonPlugin} */ diff --git a/remoting/webapp/event_handlers.js b/remoting/webapp/event_handlers.js index a245908..c36787a 100644 --- a/remoting/webapp/event_handlers.js +++ b/remoting/webapp/event_handlers.js @@ -66,9 +66,9 @@ function onLoad() { { event: 'click', id: 'toolbar-stub', fn: function() { remoting.toolbar.toggle(); } }, { event: 'click', id: 'start-daemon', - fn: function() { remoting.askPinDialog.showForStart(); } }, + fn: function() { remoting.hostSetupDialog.showForStart(); } }, { event: 'click', id: 'change-daemon-pin', - fn: function() { remoting.askPinDialog.showForPin(); } }, + fn: function() { remoting.hostSetupDialog.showForPin(); } }, { event: 'click', id: 'stop-daemon', fn: stopDaemon }, { event: 'submit', id: 'access-code-form', fn: sendAccessCode }, { event: 'submit', id: 'pin-form', fn: connectHostWithPin }, diff --git a/remoting/webapp/host_plugin_proto.js b/remoting/webapp/host_plugin_proto.js index f555d10..6760fe4 100644 --- a/remoting/webapp/host_plugin_proto.js +++ b/remoting/webapp/host_plugin_proto.js @@ -25,11 +25,20 @@ remoting.HostPlugin.prototype.disconnect = function() {}; * @return {void} Nothing. */ remoting.HostPlugin.prototype.localize = function(callback) {}; +/** @return {string} Local hostname. */ +remoting.HostPlugin.prototype.getHostName = function() {}; + +/** @param {function(string, string):void} callback Callback to be called + * after new key is generated. + * @return {void} Nothing. */ +remoting.HostPlugin.prototype.generateKeyPair = function(callback) {}; + /** @param {string} pin The new PIN. * @return {void} Nothing. */ remoting.HostPlugin.prototype.setDaemonPin = function(pin) {}; -/** @param {string} callback Callback to be called for the config. +/** @param {function(string):void} callback Callback to be called for + * the config. * @return {void} Nothing. */ remoting.HostPlugin.prototype.getDaemonConfig = function(callback) {}; @@ -40,11 +49,6 @@ remoting.HostPlugin.prototype.startDaemon = function(config) {}; /** @return {void} Nothing. */ remoting.HostPlugin.prototype.stopDaemon = function() {}; -/** @param {function(string):void} callback Callback to be called - * after new key is generated. - * @return {void} Nothing. */ -remoting.HostPlugin.prototype.generateKeyPair = function(callback) {}; - /** @type {number} */ remoting.HostPlugin.prototype.state; /** @type {number} */ remoting.HostPlugin.prototype.STARTING; diff --git a/remoting/webapp/host_setup_dialog.js b/remoting/webapp/host_setup_dialog.js new file mode 100644 index 0000000..1e9e66c --- /dev/null +++ b/remoting/webapp/host_setup_dialog.js @@ -0,0 +1,351 @@ +// 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. + +'use strict'; + +/** @suppress {duplicate} */ +var remoting = remoting || {}; + +/** + * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of + * steps for the flow. + * @constructor + */ +remoting.HostSetupFlow = function(sequence) { + this.sequence_ = sequence; + this.currentStep_ = 0; + this.state_ = sequence[0]; + + this.pin = ''; + this.hostConfig = ''; +}; + +/** @enum {number} */ +remoting.HostSetupFlow.State = { + NONE: 0, + ASK_PIN: 1, + REGISTER_HOST: 2, + START_HOST: 3, + HOST_STARTED: 4, + UPDATE_PIN: 5, + REGISTRATION_FAILED: 6, + HOST_START_FAILED: 7 +}; + +/** @return {remoting.HostSetupFlow.State} Current state of the flow. */ +remoting.HostSetupFlow.prototype.getState = function() { + return this.state_; +}; + +/** + * @param {boolean} success + * @return {remoting.HostSetupFlow.State} New state. + */ +remoting.HostSetupFlow.prototype.switchToNextStep = function(success) { + if (this.state_ == remoting.HostSetupFlow.State.NONE) { + return this.state_; + } + if (success) { + // If the current step was successful then switch to the next + // step in the sequence. + if (this.currentStep_ < this.sequence_.length - 1) { + this.currentStep_ += 1; + this.state_ = this.sequence_[this.currentStep_]; + } else { + this.state_ = remoting.HostSetupFlow.State.NONE; + } + } else { + // Current step failed, so switch to corresponding error state. + if (this.state_ == remoting.HostSetupFlow.State.REGISTER_HOST) { + this.state_ = remoting.HostSetupFlow.State.REGISTRATION_FAILED; + } else { + // TODO(sergeyu): Add other error states and use them here. + this.state_ = remoting.HostSetupFlow.State.HOST_START_FAILED; + } + } + return this.state_; +}; + +/** + * @param {remoting.DaemonPlugin} daemon The parent daemon plugin instance. + * @constructor + */ +remoting.HostSetupDialog = function(daemon) { + this.daemon_ = daemon; + this.pinEntry_ = document.getElementById('daemon-pin-entry'); + this.pinConfirm_ = document.getElementById('daemon-pin-confirm'); + + /** @type {remoting.HostSetupFlow} */ + this.flow_ = new remoting.HostSetupFlow([remoting.HostSetupFlow.State.NONE]); + + /** @type {remoting.HostSetupDialog} */ + var that = this; + /** @param {Event} event The event. */ + var onPinSubmit = function(event) { + event.preventDefault(); + that.onPinSubmit_(); + }; + var form = document.getElementById('ask-pin-form'); + form.addEventListener('submit', onPinSubmit, false); +}; + +/** + * Show the dialog in order to get a PIN prior to starting the daemon. When the + * user clicks OK, the dialog shows a spinner until the daemon has started. + * + * @return {void} Nothing. + */ +remoting.HostSetupDialog.prototype.showForStart = function() { + this.startNewFlow_( + [remoting.HostSetupFlow.State.ASK_PIN, + remoting.HostSetupFlow.State.REGISTER_HOST, + remoting.HostSetupFlow.State.START_HOST, + remoting.HostSetupFlow.State.HOST_STARTED]); +}; + +/** + * Show the dialog in order to change the PIN associated with a running daemon. + * + * @return {void} Nothing. + */ +remoting.HostSetupDialog.prototype.showForPin = function() { + this.startNewFlow_( + [remoting.HostSetupFlow.State.ASK_PIN, + remoting.HostSetupFlow.State.UPDATE_PIN]); +}; + +/** + * @return {void} Nothing. + */ +remoting.HostSetupDialog.prototype.hide = function() { + remoting.setMode(remoting.AppMode.HOME); +}; + +/** + * Starts new flow with the specified sequence of steps. + * @param {Array.<remoting.HostSetupFlow.State>} sequence Sequence of steps. + * @private + */ +remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) { + this.flow_ = new remoting.HostSetupFlow(sequence); + this.pinEntry_.text = ''; + this.pinConfirm_.text = ''; + this.updateState_(); +}; + +/** + * Updates current UI mode according to the current state of the setup + * flow and start the action corresponding to the current step (if + * any). + * @private + */ +remoting.HostSetupDialog.prototype.updateState_ = function() { + /** @param {string} tag */ + function showProcessingMessage(tag) { + var errorDiv = document.getElementById('host-setup-processing-message'); + l10n.localizeElementFromTag(errorDiv, tag); + remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING); + } + /** @param {string} tag */ + function showDoneMessage(tag) { + var errorDiv = document.getElementById('host-setup-done-message'); + l10n.localizeElementFromTag(errorDiv, tag); + remoting.setMode(remoting.AppMode.HOST_SETUP_DONE); + } + /** @param {string} tag */ + function showErrorMessage(tag) { + var errorDiv = document.getElementById('host-setup-error-message'); + l10n.localizeElementFromTag(errorDiv, tag); + remoting.setMode(remoting.AppMode.HOST_SETUP_ERROR); + } + + var state = this.flow_.getState(); + if (state == remoting.HostSetupFlow.State.NONE) { + this.hide(); + } else if (state == remoting.HostSetupFlow.State.ASK_PIN) { + remoting.setMode(remoting.AppMode.HOST_SETUP_ASK_PIN); + } else if (state == remoting.HostSetupFlow.State.REGISTER_HOST) { + showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING'); + this.registerHost_(); + } else if (state == remoting.HostSetupFlow.State.START_HOST) { + showProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING'); + this.startHost_(); + } else if (state == remoting.HostSetupFlow.State.UPDATE_PIN) { + showProcessingMessage(/*i18n-content*/'HOST_SETUP_UPDATING_PIN'); + this.updatePin_(); + } else if (state == remoting.HostSetupFlow.State.HOST_STARTED) { + showDoneMessage(/*i18n-content*/'HOST_SETUP_STARTED'); + } else if (state == remoting.HostSetupFlow.State.REGISTRATION_FAILED) { + showErrorMessage(/*i18n-content*/'HOST_SETUP_REGISTRATION_FAILED'); + } else if (state == remoting.HostSetupFlow.State.HOST_START_FAILED) { + showErrorMessage(/*i18n-content*/'HOST_SETUP_HOST_FAILED'); + } +}; + +/** + * Registers new host. + */ +remoting.HostSetupDialog.prototype.registerHost_ = function() { + /** @type {remoting.HostSetupDialog} */ + var that = this; + /** @type {remoting.HostSetupFlow} */ + var flow = this.flow_; + + /** @return {string} */ + function generateUuid() { + var random = new Uint16Array(8); + window.crypto.getRandomValues(random); + /** @type {Array.<string>} */ + var e = new Array(); + for (var i = 0; i < 8; i++) { + e[i] = (/** @type {number} */random[i] + 0x10000). + toString(16).substring(1); + } + return e[0] + e[1] + '-' + e[2] + "-" + e[3] + '-' + + e[4] + '-' + e[5] + e[6] + e[7]; + } + + var newHostId = generateUuid(); + + /** @param {string} privateKey + * @param {XMLHttpRequest} xhr */ + function onRegistered(privateKey, xhr) { + if (flow !== that.flow_ || + flow.getState() != remoting.HostSetupFlow.State.REGISTER_HOST) { + console.error('Host setup was interrupted when registering the host'); + return; + } + + var success = (xhr.status == 200); + + if (success) { + // TODO(sergeyu): Calculate HMAC of the PIN instead of storing it + // in plaintext. + flow.hostConfig = JSON.stringify({ + xmpp_login: remoting.oauth2.getCachedEmail(), + oauth_refresh_token: remoting.oauth2.getRefreshToken(), + host_id: newHostId, + host_name: that.daemon_.getHostName(), + host_secret_hash: 'plain:' + flow.pin, + private_key: privateKey + }); + } else { + console.log('Failed to register the host. Status: ' + xhr.status + + ' response: ' + xhr.responseText); + } + + flow.switchToNextStep(success); + that.updateState_(); + } + + /** + * @param {string} privateKey + * @param {string} publicKey + * @param {string} oauthToken + */ + function doRegisterHost(privateKey, publicKey, oauthToken) { + if (flow !== that.flow_ || + flow.getState() != remoting.HostSetupFlow.State.REGISTER_HOST) { + console.error('Host setup was interrupted when generating key pair'); + return; + } + + var headers = { + 'Authorization': 'OAuth ' + oauthToken, + 'Content-type' : 'application/json; charset=UTF-8' + }; + + var newHostDetails = { data: { + hostId: newHostId, + hostName: that.daemon_.getHostName(), + publicKey: publicKey + } }; + remoting.xhr.post( + 'https://www.googleapis.com/chromoting/v1/@me/hosts/', + /** @param {XMLHttpRequest} xhr */ + function (xhr) { onRegistered(privateKey, xhr); }, + JSON.stringify(newHostDetails), + headers); + } + + this.daemon_.generateKeyPair( + /** @param {string} privateKey + * @param {string} publicKey */ + function(privateKey, publicKey) { + remoting.oauth2.callWithToken( + /** @param {string} oauthToken */ + function(oauthToken) { + doRegisterHost(privateKey, publicKey, oauthToken); + }); + }); +}; + +/** + * Starts the host process after it's registered. + */ +remoting.HostSetupDialog.prototype.startHost_ = function() { + this.daemon_.start(this.flow_.hostConfig); + this.pollDaemonState_(); +}; + +remoting.HostSetupDialog.prototype.updatePin_ = function() { + this.daemon_.setPin(this.flow_.pin); + this.pollDaemonState_(); +} + +/** @private */ +remoting.HostSetupDialog.prototype.onPinSubmit_ = function() { + if (this.flow_.getState() != remoting.HostSetupFlow.State.ASK_PIN) { + console.error('PIN submitted in an invalid state', this.flow_.getState()); + return; + } + // TODO(jamiewalch): Add validation and error checks when we improve the UI. + var pin = this.pinEntry_.value; + this.flow_.pin = pin; + this.flow_.switchToNextStep(true); + this.updateState_(); +}; + +/** + * @return {void} Nothing. + * @private + */ +remoting.HostSetupDialog.prototype.pollDaemonState_ = function() { + var state = this.daemon_.state(); + var retry = false; // Set to true if we haven't finished yet. + switch (state) { + case remoting.DaemonPlugin.State.STOPPED: + case remoting.DaemonPlugin.State.NOT_INSTALLED: + retry = true; + break; + case remoting.DaemonPlugin.State.STARTED: + if (this.flow_.getState() == remoting.HostSetupFlow.State.START_HOST || + this.flow_.getState() == remoting.HostSetupFlow.State.UPDATE_PIN) { + this.flow_.switchToNextStep(true); + this.updateState_(); + } + this.daemon_.updateDom(); + break; + case remoting.DaemonPlugin.State.START_FAILED: + if (this.flow_.getState() == remoting.HostSetupFlow.State.START_HOST || + this.flow_.getState() == remoting.HostSetupFlow.State.UPDATE_PIN) { + this.flow_.switchToNextStep(false); + this.updateState_(); + } + break; + default: + // TODO(jamiewalch): Show an error message. + console.error('Unexpected daemon state', state); + break; + } + if (retry) { + /** @type {remoting.HostSetupDialog} */ + var that = this; + var pollDaemonState = function() { that.pollDaemonState_(); } + window.setTimeout(pollDaemonState, 1000); + } +}; + +/** @type {remoting.HostSetupDialog} */ +remoting.hostSetupDialog = null; diff --git a/remoting/webapp/main.css b/remoting/webapp/main.css index 8c2b153..26f0132 100644 --- a/remoting/webapp/main.css +++ b/remoting/webapp/main.css @@ -438,7 +438,7 @@ button { margin-top: 24px; } -#ask-pin-dialog { +#host-setup-dialog { position: absolute; top: 50%; left: 50%; diff --git a/remoting/webapp/main.html b/remoting/webapp/main.html index 9a3826a..7722ff5 100644 --- a/remoting/webapp/main.html +++ b/remoting/webapp/main.html @@ -16,7 +16,7 @@ found in the LICENSE file. <link rel="stylesheet" href="main.css"> <link rel="stylesheet" href="menu_button.css"> <link rel="stylesheet" href="toolbar.css"> - <script src="ask_pin_dialog.js"></script> + <script src="host_setup_dialog.js"></script> <script src="client_plugin_async.js"></script> <script src="client_plugin_v1.js"></script> <script src="client_screen.js"></script> @@ -185,18 +185,19 @@ found in the LICENSE file. </div> <!-- home --> <div id="dialog-screen" - data-ui-mode="home.host home.client home.auth home.history home.confirm-host-delete home.ask-pin" + data-ui-mode="home.host home.client home.auth home.history home.confirm-host-delete home.host-setup" hidden></div> <div id="dialog-container" - data-ui-mode="home.host home.client home.auth home.history home.confirm-host-delete home.ask-pin" + data-ui-mode="home.host home.client home.auth home.history home.confirm-host-delete home.host-setup" hidden> <div class="box-spacer"></div> - <div id="ask-pin-dialog" data-ui-mode="home.ask-pin" hidden> - <p i18n-content="ASK_PIN_DIALOG_DESCRIPTION"></p> - <form id="ask-pin-form" action=""> + <div id="host-setup-dialog" data-ui-mode="home.host-setup" hidden> + <p i18n-content="HOST_SETUP_DIALOG_DESCRIPTION"></p> + <form id="ask-pin-form" data-ui-mode="home.host-setup.ask-pin" + action="" hidden> <label for="daemon-pin-input" i18n-content="ASK_PIN_DIALOG_LABEL"></label> <input id="daemon-pin-entry" type="password"> @@ -205,9 +206,18 @@ found in the LICENSE file. i18n-content="ASK_PIN_DIALOG_CONFIRM_LABEL"></label> <input id="daemon-pin-confirm" type="password"> <button id="daemon-pin-ok" type="submit" i18n-content="OK"></button> - <img id="start-daemon-spinner" src="spinner.gif" hidden> </form> - </div> <!-- ask-pin-dialog --> + <div data-ui-mode="home.host-setup.processing" hidden> + <img src="spinner.gif"> + <span id="host-setup-processing-message" class="message"></span> + </div> + <div data-ui-mode="home.host-setup.done" hidden> + <span id="host-setup-done-message" class="message"></span> + </div> + <div data-ui-mode="home.host-setup.error" hidden> + <span id="host-setup-error-message" class="error-state"></span> + </div> + </div> <!-- host-setup-dialog --> <div id="auth-dialog" data-ui-mode="home.auth" diff --git a/remoting/webapp/remoting.js b/remoting/webapp/remoting.js index 6ec9694..4266e0b 100644 --- a/remoting/webapp/remoting.js +++ b/remoting/webapp/remoting.js @@ -86,7 +86,8 @@ remoting.initDaemonUi = function () { remoting.daemonPlugin = new remoting.DaemonPlugin(); remoting.daemonPlugin.updateDom(); remoting.setMode(getAppStartupMode_()); - remoting.askPinDialog = new remoting.AskPinDialog(remoting.daemonPlugin); + remoting.hostSetupDialog = + new remoting.HostSetupDialog(remoting.daemonPlugin); }; /** diff --git a/remoting/webapp/ui_mode.js b/remoting/webapp/ui_mode.js index ecb5d57..f8ee589 100644 --- a/remoting/webapp/ui_mode.js +++ b/remoting/webapp/ui_mode.js @@ -38,7 +38,11 @@ remoting.AppMode = { CLIENT_SESSION_FINISHED_ME2ME: 'home.client.session-finished.me2me', HISTORY: 'home.history', CONFIRM_HOST_DELETE: 'home.confirm-host-delete', - ASK_PIN: 'home.ask-pin', + HOST_SETUP: 'home.host-setup', + HOST_SETUP_ASK_PIN: 'home.host-setup.ask-pin', + HOST_SETUP_PROCESSING: 'home.host-setup.processing', + HOST_SETUP_DONE: 'home.host-setup.done', + HOST_SETUP_ERROR: 'home.host-setup.error', IN_SESSION: 'in-session' }; diff --git a/remoting/webapp/xhr.js b/remoting/webapp/xhr.js index 2a48077..b0c2056 100644 --- a/remoting/webapp/xhr.js +++ b/remoting/webapp/xhr.js @@ -164,7 +164,9 @@ remoting.xhr.doMethod = function(methodName, url, onDone, } xhr.open(methodName, url, true); - if (methodName == 'POST') { + if (methodName == 'POST' && + (typeof opt_headers !== 'object' || + typeof opt_headers['Content-type'] !== 'string')) { xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); } // Add in request headers. |