summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
Diffstat (limited to 'remoting')
-rw-r--r--remoting/host/plugin/daemon_controller_linux.cc19
-rw-r--r--remoting/host/plugin/host_script_object.cc43
-rw-r--r--remoting/host/plugin/host_script_object.h8
-rw-r--r--remoting/remoting.gyp3
-rwxr-xr-xremoting/tools/me2me_virtual_host.py67
-rw-r--r--remoting/webapp/_locales/en/messages.json28
-rw-r--r--remoting/webapp/ask_pin_dialog.js111
-rw-r--r--remoting/webapp/daemon_plugin.js43
-rw-r--r--remoting/webapp/event_handlers.js4
-rw-r--r--remoting/webapp/host_plugin_proto.js16
-rw-r--r--remoting/webapp/host_setup_dialog.js351
-rw-r--r--remoting/webapp/main.css2
-rw-r--r--remoting/webapp/main.html26
-rw-r--r--remoting/webapp/remoting.js3
-rw-r--r--remoting/webapp/ui_mode.js6
-rw-r--r--remoting/webapp/xhr.js4
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.