diff options
author | weitaosu@chromium.org <weitaosu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-31 20:35:09 +0000 |
---|---|---|
committer | weitaosu@chromium.org <weitaosu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-31 20:35:09 +0000 |
commit | ef3e70fb37a3533fda28dd5f15249fd59e6bb1bf (patch) | |
tree | 2f396c2d0d2782d84f14c875c1341bd55f40427f /remoting | |
parent | 4441f2a5b8ce9a09a6b1996aac78e7814b71f5c4 (diff) | |
download | chromium_src-ef3e70fb37a3533fda28dd5f15249fd59e6bb1bf.zip chromium_src-ef3e70fb37a3533fda28dd5f15249fd59e6bb1bf.tar.gz chromium_src-ef3e70fb37a3533fda28dd5f15249fd59e6bb1bf.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
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=248157
Review URL: https://codereview.chromium.org/138503009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@248255 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/host/it2me/it2me_native_messaging_host_main.cc | 61 | ||||
-rw-r--r-- | remoting/host/plugin/host_script_object.cc | 1 | ||||
-rw-r--r-- | remoting/remoting_host.gypi | 21 | ||||
-rw-r--r-- | remoting/remoting_webapp_files.gypi | 2 | ||||
-rw-r--r-- | remoting/webapp/all_js_load.gtestjs | 2 | ||||
-rw-r--r-- | remoting/webapp/host_controller.js | 4 | ||||
-rw-r--r-- | remoting/webapp/host_it2me_dispatcher.js | 136 | ||||
-rw-r--r-- | remoting/webapp/host_it2me_native_messaging.js | 349 | ||||
-rw-r--r-- | remoting/webapp/host_screen.js | 69 | ||||
-rw-r--r-- | remoting/webapp/host_session.js | 82 | ||||
-rw-r--r-- | remoting/webapp/main.html | 2 | ||||
-rw-r--r-- | remoting/webapp/remoting.js | 25 |
12 files changed, 686 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..e052516 100644 --- a/remoting/host/it2me/it2me_native_messaging_host_main.cc +++ b/remoting/host/it2me/it2me_native_messaging_host_main.cc @@ -4,16 +4,76 @@ #include "base/at_exit.h" #include "base/command_line.h" +#include "base/i18n/icu_util.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/breakpad.h" +#include "remoting/base/resources.h" #include "remoting/host/it2me/it2me_native_messaging_host.h" #include "remoting/host/logging.h" +#include "remoting/host/usage_stats_consent.h" + +#if defined(OS_LINUX) +#include <gtk/gtk.h> +#endif // defined(OS_LINUX) + +#if defined(OS_MACOSX) +#include "base/mac/scoped_nsautorelease_pool.h" +#endif // defined(OS_MACOSX) + +#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 // defined(OS_MACOSX) + +#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 +108,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. */ |