summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorweitaosu@chromium.org <weitaosu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-31 09:36:59 +0000
committerweitaosu@chromium.org <weitaosu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-31 09:36:59 +0000
commit0b833bd5a7fc48226a08adae38628c4bc289a473 (patch)
treec58624a3045392bd50793a8e2b271a9e4bc8ca6f /remoting
parent81829ec2b9a3b67af6f36a563c4293932952ab61 (diff)
downloadchromium_src-0b833bd5a7fc48226a08adae38628c4bc289a473.zip
chromium_src-0b833bd5a7fc48226a08adae38628c4bc289a473.tar.gz
chromium_src-0b833bd5a7fc48226a08adae38628c4bc289a473.tar.bz2
It2me native messaging host: webapp implementation.
The webapp implementation of the it2me native messaging is based on the me2me couterpart. But I got rid of the per-message onDone/onError callbacks because unlike me2me, all requests to the it2me host are asynchronous: direct responses to all requests are meaningless. Only the asynchronous callbacks on host state change and nat policy update need to be processed. I verified that this is fully functional on Linux. Windows and Mac verification haven't been done and are planned after the linux work is completed. BUG=309844 Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=248072 Review URL: https://codereview.chromium.org/138503009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@248157 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r--remoting/host/it2me/it2me_native_messaging_host_main.cc56
-rw-r--r--remoting/host/plugin/host_script_object.cc1
-rw-r--r--remoting/remoting_host.gypi21
-rw-r--r--remoting/remoting_webapp_files.gypi2
-rw-r--r--remoting/webapp/all_js_load.gtestjs2
-rw-r--r--remoting/webapp/host_controller.js4
-rw-r--r--remoting/webapp/host_it2me_dispatcher.js136
-rw-r--r--remoting/webapp/host_it2me_native_messaging.js349
-rw-r--r--remoting/webapp/host_screen.js69
-rw-r--r--remoting/webapp/host_session.js82
-rw-r--r--remoting/webapp/main.html2
-rw-r--r--remoting/webapp/remoting.js25
12 files changed, 681 insertions, 68 deletions
diff --git a/remoting/host/it2me/it2me_native_messaging_host_main.cc b/remoting/host/it2me/it2me_native_messaging_host_main.cc
index 2dca61f..87f3d6a 100644
--- a/remoting/host/it2me/it2me_native_messaging_host_main.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host_main.cc
@@ -4,16 +4,71 @@
#include "base/at_exit.h"
#include "base/command_line.h"
+#include "base/i18n/icu_util.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "media/base/media.h"
+#include "net/socket/ssl_server_socket.h"
+#include "remoting/base/resources.h"
#include "remoting/host/it2me/it2me_native_messaging_host.h"
#include "remoting/host/logging.h"
+#if defined(OS_LINUX)
+#include <gtk/gtk.h>
+#endif // defined(OS_LINUX)
+
+#if defined(OS_WIN)
+#include <commctrl.h>
+#endif // defined(OS_WIN)
+
namespace remoting {
// Creates a It2MeNativeMessagingHost instance, attaches it to stdin/stdout and
// runs the message loop until It2MeNativeMessagingHost signals shutdown.
int It2MeNativeMessagingHostMain() {
+#if defined(OS_MACOSX)
+ // Needed so we don't leak objects when threads are created.
+ base::mac::ScopedNSAutoreleasePool pool;
+#endif
+
+#if defined(REMOTING_ENABLE_BREAKPAD)
+ // Initialize Breakpad as early as possible. On Mac the command-line needs to
+ // be initialized first, so that the preference for crash-reporting can be
+ // looked up in the config file.
+ if (IsUsageStatsAllowed()) {
+ InitializeCrashReporting();
+ }
+#endif // defined(REMOTING_ENABLE_BREAKPAD)
+
+#if defined(OS_WIN)
+ // Register and initialize common controls.
+ INITCOMMONCONTROLSEX info;
+ info.dwSize = sizeof(info);
+ info.dwICC = ICC_STANDARD_CLASSES;
+ InitCommonControlsEx(&info);
+#endif // defined(OS_WIN)
+
+ // Required to find the ICU data file, used by some file_util routines.
+ base::i18n::InitializeICU();
+
+ remoting::LoadResources("");
+
+ // Cannot use TOOLKIT_GTK because it is not defined when aura is enabled.
+#if defined(OS_LINUX)
+ // Required for any calls into GTK functions, such as the Disconnect and
+ // Continue windows. Calling with NULL arguments because we don't have
+ // any command line arguments for gtk to consume.
+ gtk_init(NULL, NULL);
+#endif // OS_LINUX
+
+ // Enable support for SSL server sockets, which must be done while still
+ // single-threaded.
+ net::EnableSSLServerSockets();
+
+ // Ensures runtime specific CPU features are initialized.
+ media::InitializeCPUSpecificMediaFeatures();
+
#if defined(OS_WIN)
// GetStdHandle() returns pseudo-handles for stdin and stdout even if
// the hosting executable specifies "Windows" subsystem. However the returned
@@ -48,6 +103,7 @@ int It2MeNativeMessagingHostMain() {
// Run the loop until channel is alive.
run_loop.Run();
+
return kSuccessExitCode;
}
diff --git a/remoting/host/plugin/host_script_object.cc b/remoting/host/plugin/host_script_object.cc
index 2cbca57..456ca50 100644
--- a/remoting/host/plugin/host_script_object.cc
+++ b/remoting/host/plugin/host_script_object.cc
@@ -508,6 +508,7 @@ bool HostNPScriptObject::Disconnect(const NPVariant* args,
if (it2me_host_.get()) {
it2me_host_->Disconnect();
it2me_host_ = NULL;
+ host_context_.reset();
}
return true;
diff --git a/remoting/remoting_host.gypi b/remoting/remoting_host.gypi
index d5dfc4a..a6c73d7 100644
--- a/remoting/remoting_host.gypi
+++ b/remoting/remoting_host.gypi
@@ -570,11 +570,32 @@
'host/it2me/it2me_native_messaging_host_main.cc',
],
'conditions': [
+ ['OS=="linux"', {
+ 'dependencies': [
+ # Always use GTK on Linux, even for Aura builds.
+ '../build/linux/system.gyp:gtk',
+ ],
+ }],
['OS=="linux" and linux_use_tcmalloc==1', {
'dependencies': [
'../base/allocator/allocator.gyp:allocator',
],
}],
+ ['OS=="win"', {
+ 'msvs_settings': {
+ 'VCManifestTool': {
+ 'EmbedManifest': 'true',
+ 'AdditionalManifestFiles': [
+ 'host/win/common-controls.manifest',
+ ],
+ },
+ 'VCLinkerTool': {
+ 'AdditionalDependencies': [
+ 'comctl32.lib',
+ ],
+ },
+ },
+ }],
],
}, # end of target 'remoting_it2me_native_messaging_host'
diff --git a/remoting/remoting_webapp_files.gypi b/remoting/remoting_webapp_files.gypi
index 78d4998..e902740 100644
--- a/remoting/remoting_webapp_files.gypi
+++ b/remoting/remoting_webapp_files.gypi
@@ -59,6 +59,8 @@
'remoting_webapp_js_host_files': [
'webapp/host_controller.js',
'webapp/host_dispatcher.js',
+ 'webapp/host_it2me_dispatcher.js',
+ 'webapp/host_it2me_native_messaging.js',
'webapp/host_native_messaging.js',
'webapp/host_session.js',
],
diff --git a/remoting/webapp/all_js_load.gtestjs b/remoting/webapp/all_js_load.gtestjs
index 7c72946..0ab7cf8 100644
--- a/remoting/webapp/all_js_load.gtestjs
+++ b/remoting/webapp/all_js_load.gtestjs
@@ -35,6 +35,8 @@ AllJsLoadTest.prototype = {
'host.js',
'host_controller.js',
'host_dispatcher.js',
+ 'host_it2me_dispatcher.js',
+ 'host_it2me_native_messaging.js',
'host_list.js',
'host_native_messaging.js',
//'host_plugin_proto.js', // Only used by jscompiler
diff --git a/remoting/webapp/host_controller.js b/remoting/webapp/host_controller.js
index 96a0bb0..21485b1 100644
--- a/remoting/webapp/host_controller.js
+++ b/remoting/webapp/host_controller.js
@@ -10,7 +10,7 @@ var remoting = remoting || {};
/** @constructor */
remoting.HostController = function() {
/** @return {remoting.HostPlugin} */
- var createNpapiPlugin = function() {
+ var createPluginForMe2Me = function() {
var plugin = remoting.HostSession.createPlugin();
/** @type {HTMLElement} @private */
var container = document.getElementById('daemon-plugin-container');
@@ -19,7 +19,7 @@ remoting.HostController = function() {
};
/** @type {remoting.HostDispatcher} @private */
- this.hostDispatcher_ = new remoting.HostDispatcher(createNpapiPlugin);
+ this.hostDispatcher_ = new remoting.HostDispatcher(createPluginForMe2Me);
/** @param {string} version */
var printVersion = function(version) {
diff --git a/remoting/webapp/host_it2me_dispatcher.js b/remoting/webapp/host_it2me_dispatcher.js
new file mode 100644
index 0000000..b018db6
--- /dev/null
+++ b/remoting/webapp/host_it2me_dispatcher.js
@@ -0,0 +1,136 @@
+// Copyright 2014 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
+ * This class provides an interface between the HostSession and either the
+ * NativeMessaging Host or the Host NPAPI plugin, depending on whether or not
+ * NativeMessaging is supported. Since the test for NativeMessaging support is
+ * asynchronous, the connection is attemped on either the the NativeMessaging
+ * host or the NPAPI plugin once the test is complete.
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * @constructor
+ */
+remoting.HostIt2MeDispatcher = function() {
+ /** @type {remoting.HostIt2MeNativeMessaging} @private */
+ this.nativeMessagingHost_ = new remoting.HostIt2MeNativeMessaging();
+
+ /** @type {remoting.HostPlugin} @private */
+ this.npapiHost_ = null;
+};
+
+/**
+ * @param {function():remoting.HostPlugin} createPluginCallback Callback to
+ * instantiate the NPAPI plugin when NativeMessaging is determined to be
+ * unsupported.
+ * @param {string} email The user's email address.
+ * @param {string} authServiceWithToken Concatenation of the auth service
+ * (e.g. oauth2) and the access token.
+ * @param {function(remoting.HostSession.State):void} onStateChanged Callback to
+ * invoke when the host state changes.
+ * @param {function(boolean):void} onNatPolicyChanged Callback to invoke when
+ * the nat traversal policy changes.
+ * @param {string} xmppServerAddress XMPP server host name (or IP address) and
+ * port.
+ * @param {boolean} xmppServerUseTls Whether to use TLS on connections to the
+ * XMPP server
+ * @param {string} directoryBotJid XMPP JID for the remoting directory server
+ * bot.
+ * @param {function():void} onError Callback to invoke if neither the native
+ * messaging host nor the npapi plugin works.
+ */
+remoting.HostIt2MeDispatcher.prototype.initAndConnect =
+ function(createPluginCallback, email, authServiceWithToken, onStateChanged,
+ onNatPolicyChanged, xmppServerAddress, xmppServerUseTls,
+ directoryBotJid, onError) {
+ var that = this;
+ function onNativeMessagingStarted() {
+ console.log('Native Messaging supported.');
+ that.nativeMessagingHost_.connect(
+ email, authServiceWithToken, onStateChanged, onNatPolicyChanged,
+ xmppServerAddress, xmppServerUseTls, directoryBotJid);
+ }
+
+ function onNativeMessagingFailed() {
+ console.log('Native Messaging unsupported, falling back to NPAPI.');
+ that.npapiHost_ = createPluginCallback();
+ // TODO(weitaosu): is there a better way to check whether NPAPI plugin is
+ // supported?
+ if (that.npapiHost_.hasOwnProperty('REQUESTED_ACCESS_CODE')) {
+ that.npapiHost_.xmppServerAddress = xmppServerAddress;
+ that.npapiHost_.xmppServerUseTls = xmppServerUseTls;
+ that.npapiHost_.directoryBotJid = directoryBotJid;
+ that.npapiHost_.onStateChanged = onStateChanged;
+ that.npapiHost_.onNatTraversalPolicyChanged = onNatTraversalPolicyChanged;
+ that.npapiHost_.logDebugInfo = logDebugInfo;
+ that.npapiHost_.localize(chrome.i18n.getMessage);
+ that.npapiHost_.connect(email, authServiceWithToken);
+ } else {
+ onError();
+ }
+ }
+
+ this.nativeMessagingHost_.initialize(
+ onNativeMessagingStarted, onNativeMessagingFailed, onError);
+};
+
+/**
+ * @return {void}
+ */
+remoting.HostIt2MeDispatcher.prototype.disconnect = function() {
+ if (this.npapiHost_) {
+ this.npapiHost_.disconnect();
+ } else {
+ this.nativeMessagingHost_.disconnect();
+ }
+};
+
+/**
+ * @return {string} The access code generated by the it2me host.
+ */
+remoting.HostIt2MeDispatcher.prototype.getAccessCode = function() {
+ if (this.npapiHost_) {
+ return this.npapiHost_.accessCode;
+ } else {
+ return this.nativeMessagingHost_.getAccessCode();
+ }
+};
+
+/**
+ * @return {number} The access code lifetime, in seconds.
+ */
+remoting.HostIt2MeDispatcher.prototype.getAccessCodeLifetime = function() {
+ if (this.npapiHost_) {
+ return this.npapiHost_.accessCodeLifetime;
+ } else {
+ return this.nativeMessagingHost_.getAccessCodeLifetime();
+ }
+};
+
+/**
+ * @return {string} The client's email address.
+ */
+remoting.HostIt2MeDispatcher.prototype.getClient = function() {
+ if (this.npapiHost_) {
+ return this.npapiHost_.client;
+ } else {
+ return this.nativeMessagingHost_.getClient();
+ }
+};
+
+/**
+ * @return {void}
+ */
+remoting.HostIt2MeDispatcher.prototype.cleanup = function() {
+ if (this.npapiHost_) {
+ this.npapiHost_.parentNode.removeChild(this.npapiHost_);
+ }
+};
diff --git a/remoting/webapp/host_it2me_native_messaging.js b/remoting/webapp/host_it2me_native_messaging.js
new file mode 100644
index 0000000..567f2c5
--- /dev/null
+++ b/remoting/webapp/host_it2me_native_messaging.js
@@ -0,0 +1,349 @@
+// Copyright 2014 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
+ * Class to communicate with the It2me Host component via Native Messaging.
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * @constructor
+ * @param {function(remoting.HostSession.State):void} onStateChanged Callback to
+ * invoke when the host state changes.
+ * @param {function(boolean):void} onNatPolicyChanged Callback to invoke when
+ * the nat traversal policy changes.
+ */
+remoting.HostIt2MeNativeMessaging = function() {
+ /**
+ * @type {number}
+ * @private
+ */
+ this.nextId_ = 0;
+
+ /**
+ * @type {?chrome.extension.Port}
+ * @private
+ */
+ this.port_ = null;
+
+ /**
+ * @type {string}
+ * @private
+ */
+ this.accessCode_ = '';
+
+ /**
+ * @type {number}
+ * @private
+ */
+ this.accessCodeLifetime_ = 0;
+
+ /**
+ * @type {string}
+ * @private
+ */
+ this.clientId_ = '';
+
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.initialized_ = false;
+
+ /**
+ * @type {function():void}
+ * @private
+ */
+ this.onHostStarted_ = function() {};
+
+ /**
+ * @type {function():void} Called if Native Messaging is not supported or the
+ * It2Me Native Messaging host is not installed.
+ * @private
+ * */
+ this.onHostInitFailed_ = function() {};
+
+ /**
+ * @type {function():void} Called if the It2Me Native Messaging host sends a
+ * malformed message: missing required attributes, attributes with
+ * incorrect types, etc.
+ * @private
+ */
+ this.onError_ = function() {};
+
+ /**
+ * @type {function(remoting.HostSession.State):void}
+ * @private
+ */
+ this.onStateChanged_ = function() {};
+
+ /**
+ * @type {function(boolean):void}
+ * @private
+ */
+ this.onNatPolicyChanged_ = function() {};
+};
+
+/**
+ * Sets up connection to the Native Messaging host process and exchanges
+ * 'hello' messages. If Native Messaging is not supported or if the it2me
+ * native messaging host is not installed, onHostInitFailed is invoked.
+ * Otherwise, onHostStarted is invoked.
+ *
+ * @param {function():void} onHostStarted Called after successful
+ * initialization.
+ * @param {function():void} onHostInitFailed Called if cannot connect to host.
+ * @param {function():void} onError Called on host error after successfully
+ * connecting to the host.
+ * @return {void}
+ */
+remoting.HostIt2MeNativeMessaging.prototype.initialize =
+ function(onHostStarted, onHostInitFailed, onError) {
+ this.onHostStarted_ = onHostStarted;
+ this.onHostInitFailed_ = onHostInitFailed;
+ this.onError_ = onError;
+
+ try {
+ this.port_ = chrome.runtime.connectNative(
+ 'com.google.chrome.remote_assistance');
+ this.port_.onMessage.addListener(this.onIncomingMessage_.bind(this));
+ this.port_.onDisconnect.addListener(this.onHostDisconnect_.bind(this));
+ this.postMessage_({type: 'hello'});
+ } catch (err) {
+ console.log('Native Messaging initialization failed: ',
+ /** @type {*} */ (err));
+ onHostInitFailed();
+ return;
+ }
+};
+
+/**
+ * Attaches a new ID to the supplied message, and posts it to the Native
+ * Messaging port.
+ * |message| should have its 'type' field set, and any other fields set
+ * depending on the message type.
+ *
+ * @param {{type: string}} message The message to post.
+ * @return {void}
+ * @private
+ */
+remoting.HostIt2MeNativeMessaging.prototype.postMessage_ =
+ function(message) {
+ var id = this.nextId_++;
+ message['id'] = id;
+ this.port_.postMessage(message);
+};
+
+/**
+ * Handler for incoming Native Messages.
+ *
+ * @param {Object} message The received message.
+ * @return {void}
+ * @private
+ */
+remoting.HostIt2MeNativeMessaging.prototype.onIncomingMessage_ =
+ function(message) {
+ var type = message['type'];
+ if (!checkType_('type', type, 'string')) {
+ this.onError_();
+ return;
+ }
+
+ switch (type) {
+ case 'helloResponse':
+ /** @type {string} */
+ var version = message['version'];
+ if (checkType_('version', version, 'string')) {
+ console.log('Host version: ', version);
+ this.initialized_ = true;
+ // A "hello" request is sent immediately after the native messaging host
+ // is started. We can proceed to the next task once we receive the
+ // "helloReponse".
+ this.onHostStarted_();
+ } else {
+ this.onError_();
+ }
+ break;
+
+ case 'connectResponse':
+ console.log('connectResponse received');
+ // Response to the "connect" request. No action is needed until we
+ // receive the corresponding "hostStateChagned" message.
+ break;
+
+ case 'disconnectResponse':
+ console.log('disconnectResponse received');
+ // Response to the "disconnect" request. No action is needed until we
+ // receive the corresponding "hostStateChagned" message.
+ break;
+
+ case 'hostStateChanged':
+ /** @type {string} */
+ var stateString = message['state'];
+ if (!checkType_('stateString', stateString, 'string')) {
+ this.onError_();
+ break;
+ }
+
+ console.log('hostStateChanged received: ', stateString);
+
+ /** @type {remoting.HostSession.State} */
+ var state = remoting.HostSession.stateFromString(stateString);
+
+ switch (state) {
+ case remoting.HostSession.State.RECEIVED_ACCESS_CODE:
+ /** @type {string} */
+ var accessCode = message['accessCode'];
+ if (!checkType_('accessCode', accessCode, 'string')) {
+ this.onError_();
+ break;
+ }
+
+ /** @type {number} */
+ var accessCodeLifetime = message['accessCodeLifetime'];
+ if (!checkType_('accessCodeLifetime', accessCodeLifetime, 'number')) {
+ this.onError_();
+ break;
+ }
+
+ this.onReceivedAccessCode_(accessCode, accessCodeLifetime);
+ break;
+
+ case remoting.HostSession.State.CONNECTED:
+ /** @type {string} */
+ var client = message['client'];
+ if (checkType_('client', client, 'string')) {
+ this.onConnected_(client);
+ } else {
+ this.onError_();
+ }
+ break;
+ }
+ this.onStateChanged_(state);
+ break;
+
+ case 'natPolicyChanged':
+ /** @type {boolean} */
+ var natTraversalEnabled = message['natTraversalEnabled'];
+ if (checkType_('natTraversalEnabled', natTraversalEnabled, 'boolean')) {
+ this.onNatPolicyChanged_(natTraversalEnabled);
+ } else {
+ this.onError_();
+ }
+ break;
+
+ case 'error':
+ /** @type {string} */
+ var description = message['description'];
+ // Ignore the return value.
+ checkType_('description', description, 'string');
+ this.onError_();
+ break;
+
+ default:
+ console.error('Unexpected native message: ', message);
+ this.onError_();
+ }
+};
+
+/**
+ * @param {string} email The user's email address.
+ * @param {string} authServiceWithToken Concatenation of the auth service
+ * (e.g. oauth2) and the access token.
+ * @param {function(remoting.HostSession.State):void} onStateChanged Callback to
+ * invoke when the host state changes.
+ * @param {function(boolean):void} onNatPolicyChanged Callback to invoke when
+ * the nat traversal policy changes.
+ * @param {string} xmppServerAddress XMPP server host name (or IP address) and
+ * port.
+ * @param {boolean} xmppServerUseTls Whether to use TLS on connections to the
+ * XMPP server
+ * @param {string} directoryBotJid XMPP JID for the remoting directory server
+ * bot.
+ * @return {void}
+ */
+remoting.HostIt2MeNativeMessaging.prototype.connect =
+ function(email, authServiceWithToken, onStateChanged, onNatPolicyChanged,
+ xmppServerAddress, xmppServerUseTls, directoryBotJid) {
+ this.onStateChanged_ = onStateChanged;
+ this.onNatPolicyChanged_ = onNatPolicyChanged;
+ this.postMessage_({
+ type: 'connect',
+ userName: email,
+ authServiceWithToken: authServiceWithToken,
+ xmppServerAddress: xmppServerAddress,
+ xmppServerUseTls: xmppServerUseTls,
+ directoryBotJid: directoryBotJid});
+};
+
+/**
+ * @return {void}
+ */
+remoting.HostIt2MeNativeMessaging.prototype.disconnect =
+ function() {
+ this.postMessage_({
+ type: 'disconnect'});
+};
+
+/**
+ * @param {string} accessCode
+ * @param {number} accessCodeLifetime
+ * @return {void}
+ * @private
+ */
+remoting.HostIt2MeNativeMessaging.prototype.onReceivedAccessCode_ =
+ function(accessCode, accessCodeLifetime) {
+ this.accessCode_ = accessCode;
+ this.accessCodeLifetime_ = accessCodeLifetime;
+};
+
+/**
+ * @param {string} clientId
+ * @return {void}
+ * @private
+ */
+remoting.HostIt2MeNativeMessaging.prototype.onConnected_ =
+ function(clientId) {
+ this.clientId_ = clientId;
+};
+
+/**
+ * @return {void}
+ * @private
+ */
+remoting.HostIt2MeNativeMessaging.prototype.onHostDisconnect_ = function() {
+ if (!this.initialized_) {
+ console.error('Native Messaging initialization failed.');
+ this.onHostInitFailed_();
+ } else {
+ console.error('Native Messaging port disconnected.');
+ this.onError_();
+ }
+}
+
+/**
+ * @return {string}
+ */
+remoting.HostIt2MeNativeMessaging.prototype.getAccessCode = function() {
+ return this.accessCode_
+};
+
+/**
+ * @return {number}
+ */
+remoting.HostIt2MeNativeMessaging.prototype.getAccessCodeLifetime = function() {
+ return this.accessCodeLifetime_
+};
+
+/**
+ * @return {string}
+ */
+remoting.HostIt2MeNativeMessaging.prototype.getClient = function() {
+ return this.clientId_;
+};
diff --git a/remoting/webapp/host_screen.js b/remoting/webapp/host_screen.js
index df4008b..39eb0c4 100644
--- a/remoting/webapp/host_screen.js
+++ b/remoting/webapp/host_screen.js
@@ -14,8 +14,7 @@ var remoting = remoting || {};
/**
* @type {boolean} Whether or not the last share was cancelled by the user.
- * This controls what screen is shown when the host plugin signals
- * completion.
+ * This controls what screen is shown when the host signals completion.
* @private
*/
var lastShareWasCancelled_ = false;
@@ -42,31 +41,38 @@ remoting.tryShareWithToken_ = function(token) {
disableTimeoutCountdown_();
var div = document.getElementById('host-plugin-container');
+ // TODO(weitaosu): Unlike the npapi plugin, the it2me native messaging host
+ // can be long-lived and will create an it2me host on demand. We should also
+ // keep the HostSession and HostIt2MeDispatcher object alive to avoid repeated
+ // creation of the native messaging host.
remoting.hostSession = new remoting.HostSession();
- remoting.hostSession.createPluginAndConnect(
+ remoting.hostSession.createDispatcherAndConnect(
document.getElementById('host-plugin-container'),
/** @type {string} */(remoting.identity.getCachedEmail()),
token,
- onNatTraversalPolicyChanged_,
onHostStateChanged_,
- logDebugInfo_);
+ onNatTraversalPolicyChanged_,
+ logDebugInfo_,
+ it2meConnectFailed_);
};
/**
* Callback for the host plugin to notify the web app of state changes.
* @param {remoting.HostSession.State} state The new state of the plugin.
+ * @return {void} Nothing.
+ * @private
*/
function onHostStateChanged_(state) {
if (state == remoting.HostSession.State.STARTING) {
// Nothing to do here.
- console.log('Host plugin state: STARTING');
+ console.log('Host state: STARTING');
} else if (state == remoting.HostSession.State.REQUESTED_ACCESS_CODE) {
// Nothing to do here.
- console.log('Host plugin state: REQUESTED_ACCESS_CODE');
+ console.log('Host state: REQUESTED_ACCESS_CODE');
} else if (state == remoting.HostSession.State.RECEIVED_ACCESS_CODE) {
- console.log('Host plugin state: RECEIVED_ACCESS_CODE');
+ console.log('Host state: RECEIVED_ACCESS_CODE');
var accessCode = remoting.hostSession.getAccessCode();
var accessCodeDisplay = document.getElementById('access-code-display');
accessCodeDisplay.innerText = '';
@@ -80,8 +86,7 @@ function onHostStateChanged_(state) {
}
accessCodeExpiresIn_ = remoting.hostSession.getAccessCodeLifetime();
if (accessCodeExpiresIn_ > 0) { // Check it hasn't expired.
- accessCodeTimerId_ = setInterval(
- remoting.decrementAccessCodeTimeout_, 1000);
+ accessCodeTimerId_ = setInterval(decrementAccessCodeTimeout_, 1000);
timerRunning_ = true;
updateAccessCodeTimeoutElement_();
updateTimeoutStyles_();
@@ -94,7 +99,7 @@ function onHostStateChanged_(state) {
}
} else if (state == remoting.HostSession.State.CONNECTED) {
- console.log('Host plugin state: CONNECTED');
+ console.log('Host state: CONNECTED');
var element = document.getElementById('host-shared-message');
var client = remoting.hostSession.getClient();
l10n.localizeElement(element, client);
@@ -102,10 +107,10 @@ function onHostStateChanged_(state) {
disableTimeoutCountdown_();
} else if (state == remoting.HostSession.State.DISCONNECTING) {
- console.log('Host plugin state: DISCONNECTING');
+ console.log('Host state: DISCONNECTING');
} else if (state == remoting.HostSession.State.DISCONNECTED) {
- console.log('Host plugin state: DISCONNECTED');
+ console.log('Host state: DISCONNECTED');
if (remoting.currentMode != remoting.AppMode.HOST_SHARE_FAILED) {
// If an error is being displayed, then the plugin should not be able to
// hide it by setting the state. Errors must be dismissed by the user
@@ -116,13 +121,13 @@ function onHostStateChanged_(state) {
remoting.setMode(remoting.AppMode.HOST_SHARE_FINISHED);
}
}
- remoting.hostSession.removePlugin();
+ remoting.hostSession.cleanup();
} else if (state == remoting.HostSession.State.ERROR) {
- console.error('Host plugin state: ERROR');
+ console.error('Host state: ERROR');
showShareError_(remoting.Error.UNEXPECTED);
} else if (state == remoting.HostSession.State.INVALID_DOMAIN_ERROR) {
- console.error('Host plugin state: INVALID_DOMAIN_ERROR');
+ console.error('Host state: INVALID_DOMAIN_ERROR');
showShareError_(remoting.Error.INVALID_HOST_DOMAIN);
} else {
console.error('Unknown state -> ' + state);
@@ -133,6 +138,7 @@ function onHostStateChanged_(state) {
* This is the callback that the host plugin invokes to indicate that there
* is additional debug log info to display.
* @param {string} msg The message (which will not be localized) to be logged.
+ * @private
*/
function logDebugInfo_(msg) {
console.log('plugin: ' + msg);
@@ -143,6 +149,7 @@ function logDebugInfo_(msg) {
*
* @param {string} errorTag The error message to be localized and displayed.
* @return {void} Nothing.
+ * @private
*/
function showShareError_(errorTag) {
var errorDiv = document.getElementById('host-plugin-error');
@@ -152,7 +159,21 @@ function showShareError_(errorTag) {
}
/**
- * Cancel an active or pending share operation.
+ * Show a sharing error with error code UNEXPECTED .
+ *
+ * @return {void} Nothing.
+ * @private
+ */
+function it2meConnectFailed_() {
+ // TODO (weitaosu): Instruct the user to install the native messaging host.
+ // We probably want to add a new error code (with the corresponding error
+ // message for sharing error.
+ console.error('Cannot share desktop.');
+ showShareError_(remoting.Error.UNEXPECTED);
+}
+
+/**
+ * Cancel an active or pending it2me share operation.
*
* @return {void} Nothing.
*/
@@ -166,7 +187,7 @@ remoting.cancelShare = function() {
// Hack to force JSCompiler type-safety.
var errorTyped = /** @type {{description: string}} */ error;
console.error('Error disconnecting: ' + errorTyped.description +
- '. The host plugin probably crashed.');
+ '. The host probably crashed.');
// TODO(jamiewalch): Clean this up. We should have a class representing
// the host plugin, like we do for the client, which should handle crash
// reporting and it should use a more detailed error message than the
@@ -195,17 +216,19 @@ var accessCodeTimerId_ = 0;
var accessCodeExpiresIn_ = 0;
/**
- * The timer callback function, which needs to be visible from the global
- * namespace.
+ * The timer callback function
+ * @return {void} Nothing.
* @private
*/
-remoting.decrementAccessCodeTimeout_ = function() {
+function decrementAccessCodeTimeout_() {
--accessCodeExpiresIn_;
updateAccessCodeTimeoutElement_();
};
/**
* Stop the access code timeout countdown if it is running.
+ * @return {void} Nothing.
+ * @private
*/
function disableTimeoutCountdown_() {
if (timerRunning_) {
@@ -228,6 +251,7 @@ var ACCESS_CODE_RED_THRESHOLD_ = 10;
*
* @return {boolean} True if the timeout is in progress, false if it has
* expired.
+ * @private
*/
function updateTimeoutStyles_() {
if (timerRunning_) {
@@ -251,6 +275,8 @@ function updateTimeoutStyles_() {
/**
* Update the text and appearance of the access code timeout element to
* reflect the time remaining.
+ * @return {void} Nothing.
+ * @private
*/
function updateAccessCodeTimeoutElement_() {
var pad = (accessCodeExpiresIn_ < 10) ? '0:0' : '0:';
@@ -265,6 +291,7 @@ function updateAccessCodeTimeoutElement_() {
* Callback to show or hide the NAT traversal warning when the policy changes.
* @param {boolean} enabled True if NAT traversal is enabled.
* @return {void} Nothing.
+ * @private
*/
function onNatTraversalPolicyChanged_(enabled) {
var natBox = document.getElementById('nat-box');
diff --git a/remoting/webapp/host_session.js b/remoting/webapp/host_session.js
index 72007a3..a2524ee 100644
--- a/remoting/webapp/host_session.js
+++ b/remoting/webapp/host_session.js
@@ -20,11 +20,10 @@ var remoting = remoting || {};
* @constructor
*/
remoting.HostSession = function() {
+ /** @type {remoting.HostIt2MeDispatcher} @private */
+ this.hostDispatcher_ = null;
};
-/** @type {remoting.HostPlugin} */
-remoting.HostSession.prototype.plugin = null;
-
// Note that these values are copied directly from host_script_object.h and
// must be kept in sync.
/** @enum {number} */
@@ -41,6 +40,19 @@ remoting.HostSession.State = {
};
/**
+ * @param {string} stateString The string representation of the host state.
+ * @return {remoting.HostSession.State} The HostSession.State enum value
+ * corresponding to stateString.
+ */
+remoting.HostSession.stateFromString = function(stateString) {
+ if (!remoting.HostSession.State.hasOwnProperty(stateString)) {
+ console.error('NativeMessaging: unexpected state string: ', stateString);
+ return remoting.HostSession.State.UNKNOWN;
+ }
+ return remoting.HostSession.State[stateString];
+}
+
+/**
* Create an instance of the host plugin.
* @return {remoting.HostPlugin} The new plugin instance.
*/
@@ -54,48 +66,59 @@ remoting.HostSession.createPlugin = function() {
};
/**
- * Create the host plugin and initiate a connection.
+ * Create the host dispatcher and initiate a connection.
* @param {Element} container The parent element to which to add the plugin.
* @param {string} email The user's email address.
* @param {string} accessToken A valid OAuth2 access token.
- * @param {function(boolean):void} onNatTraversalPolicyChanged Callback
- * for notification of changes to the NAT traversal policy.
* @param {function(remoting.HostSession.State):void} onStateChanged
* Callback for notifications of changes to the host plugin's state.
+ * @param {function(boolean):void} onNatTraversalPolicyChanged Callback
+ * for notification of changes to the NAT traversal policy.
* @param {function(string):void} logDebugInfo Callback allowing the plugin
* to log messages to the debug log.
+ * @param {function():void} onError Callback to invoke if neither the native
+ * messaging host nor the npapi plugin works.
+ * @return {void} Nothing.
*/
-remoting.HostSession.prototype.createPluginAndConnect =
- function(container, email, accessToken,
- onNatTraversalPolicyChanged, onStateChanged, logDebugInfo) {
- this.plugin = remoting.HostSession.createPlugin();
- container.appendChild(this.plugin);
- this.plugin.onNatTraversalPolicyChanged = onNatTraversalPolicyChanged;
- this.plugin.onStateChanged = onStateChanged;
- this.plugin.logDebugInfo = logDebugInfo;
- this.plugin.localize(chrome.i18n.getMessage);
- this.plugin.xmppServerAddress = remoting.settings.XMPP_SERVER_ADDRESS;
- this.plugin.xmppServerUseTls = remoting.settings.XMPP_SERVER_USE_TLS;
- this.plugin.directoryBotJid = remoting.settings.DIRECTORY_BOT_JID;
- this.plugin.connect(email, 'oauth2:' + accessToken);
+remoting.HostSession.prototype.createDispatcherAndConnect =
+ function(container, email, accessToken, onStateChanged,
+ onNatTraversalPolicyChanged, logDebugInfo, onError) {
+ /** @type {remoting.HostIt2MeDispatcher} @private */
+ this.hostDispatcher_ = new remoting.HostIt2MeDispatcher();
+
+ /** @return {remoting.HostPlugin} */
+ var createPluginForIt2Me = function() {
+ var plugin = remoting.HostSession.createPlugin();
+ container.appendChild(plugin);
+ return plugin;
+ };
+
+ this.hostDispatcher_.initAndConnect(
+ createPluginForIt2Me,
+ email, 'oauth2:' + accessToken,
+ onStateChanged, onNatTraversalPolicyChanged,
+ remoting.settings.XMPP_SERVER_ADDRESS,
+ remoting.settings.XMPP_SERVER_USE_TLS,
+ remoting.settings.DIRECTORY_BOT_JID,
+ onError);
};
/**
- * Get the access code generated by the host plugin. Valid only after the
- * plugin state is RECEIVED_ACCESS_CODE.
+ * Get the access code generated by the it2me host. Valid only after the
+ * host state is RECEIVED_ACCESS_CODE.
* @return {string} The access code.
*/
remoting.HostSession.prototype.getAccessCode = function() {
- return this.plugin.accessCode;
+ return this.hostDispatcher_.getAccessCode();
};
/**
- * Get the lifetime for the access code. Valid only after the plugin state is
+ * Get the lifetime for the access code. Valid only after the host state is
* RECEIVED_ACCESS_CODE.
* @return {number} The access code lifetime, in seconds.
*/
remoting.HostSession.prototype.getAccessCodeLifetime = function() {
- return this.plugin.accessCodeLifetime;
+ return this.hostDispatcher_.getAccessCodeLifetime();
};
/**
@@ -104,22 +127,21 @@ remoting.HostSession.prototype.getAccessCodeLifetime = function() {
* @return {string} The client's email address.
*/
remoting.HostSession.prototype.getClient = function() {
- return this.plugin.client;
+ return this.hostDispatcher_.getClient();
};
/**
- * Disconnect the client.
+ * Disconnect the it2me session.
* @return {void} Nothing.
*/
remoting.HostSession.prototype.disconnect = function() {
- this.plugin.disconnect();
+ this.hostDispatcher_.disconnect();
};
/**
- * Remove the plugin element from the document.
* @return {void} Nothing.
*/
-remoting.HostSession.prototype.removePlugin = function() {
- this.plugin.parentNode.removeChild(this.plugin);
+remoting.HostSession.prototype.cleanup = function() {
+ this.hostDispatcher_.cleanup();
};
diff --git a/remoting/webapp/main.html b/remoting/webapp/main.html
index eedde24..4d2bb42 100644
--- a/remoting/webapp/main.html
+++ b/remoting/webapp/main.html
@@ -26,6 +26,8 @@ found in the LICENSE file.
<script src="host.js"></script>
<script src="host_controller.js"></script>
<script src="host_dispatcher.js"></script>
+ <script src="host_it2me_dispatcher.js"></script>
+ <script src="host_it2me_native_messaging.js"></script>
<script src="host_list.js"></script>
<script src="host_native_messaging.js"></script>
<script src="host_screen.js"></script>
diff --git a/remoting/webapp/remoting.js b/remoting/webapp/remoting.js
index c393ab1..b0519c2 100644
--- a/remoting/webapp/remoting.js
+++ b/remoting/webapp/remoting.js
@@ -141,6 +141,16 @@ remoting.init = function() {
};
/**
+ * Returns whether or not IT2Me is supported via the host NPAPI plugin.
+ *
+ * @return {boolean}
+ */
+function isIT2MeSupported_() {
+ // Currently, IT2Me on Chromebooks is not supported.
+ return !remoting.runningOnChromeOS();
+}
+
+/**
* Display the user's email address and allow access to the rest of the app,
* including parsing URL parameters.
*
@@ -154,21 +164,6 @@ remoting.onEmail = function(email) {
};
/**
- * Returns whether or not IT2Me is supported via the host NPAPI plugin.
- *
- * @return {boolean}
- */
-function isIT2MeSupported_() {
- var container = document.getElementById('host-plugin-container');
- /** @type {remoting.HostPlugin} */
- var plugin = remoting.HostSession.createPlugin();
- container.appendChild(plugin);
- var result = plugin.hasOwnProperty('REQUESTED_ACCESS_CODE');
- container.removeChild(plugin);
- return result;
-}
-
-/**
* initHomeScreenUi is called if the app is not starting up in session mode,
* and also if the user cancels pin entry or the connection in session mode.
*/